You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/develop/dotnet/core-application.mdx
+54Lines changed: 54 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -84,6 +84,60 @@ This means there are several things Workflows cannot do such as:
84
84
Some calls in .NET do unsuspecting non-deterministic things and are easy to accidentally use.
85
85
This is especially true with `Task`s.
86
86
Temporal requires that the deterministic `TaskScheduler.Current` is used, but many .NET async calls will use `TaskScheduler.Default` implicitly (and some analyzers even encourage this).
87
+
88
+
The following sections cover replay-safe APIs, followed by .NET-specific `Task` gotchas.
89
+
90
+
#### Logging
91
+
92
+
Use [`Workflow.Logger`](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html#Temporalio_Workflows_Workflow_Logger), which is an instance of .NET's `ILogger`. The SDK logger automatically suppresses log messages during replay to avoid duplicates:
93
+
94
+
```csharp
95
+
Workflow.Logger.LogInformation("Starting workflow for {Name}", name);
96
+
```
97
+
98
+
For logger configuration, see [Observability: Logging](/develop/dotnet/observability#logging).
99
+
100
+
#### Random numbers and UUIDs
101
+
102
+
Use [`Workflow.Random`](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html#Temporalio_Workflows_Workflow_Random) to get a deterministic `Random` instance. For UUIDs, use [`Workflow.NewGuid()`](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html#Temporalio_Workflows_Workflow_NewGuid). Never use `System.Random` or `Guid.NewGuid()` directly:
103
+
104
+
```csharp
105
+
// Good - deterministic across replays
106
+
varvalue=Workflow.Random.Next(1, 100);
107
+
varuniqueId=Workflow.NewGuid();
108
+
109
+
// Bad - different result on every replay
110
+
varvalue=newRandom().Next(1, 100);
111
+
```
112
+
113
+
#### Current time
114
+
115
+
Use [`Workflow.UtcNow`](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html#Temporalio_Workflows_Workflow_UtcNow) instead of `DateTime.UtcNow`. The SDK returns the time of the last Workflow Task, which is consistent across replays:
116
+
117
+
```csharp
118
+
varcurrentTime=Workflow.UtcNow;
119
+
```
120
+
121
+
#### Detecting replay (advanced)
122
+
123
+
Use [`Workflow.Unsafe.IsReplaying`](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.Unsafe.html#Temporalio_Workflows_Workflow_Unsafe_IsReplaying) to guard code that should only run on the first execution, such as emitting metrics or sending external notifications from an Interceptor.
124
+
:::caution
125
+
126
+
Never use this to affect Workflow business logic — branching on replay status breaks determinism.
127
+
128
+
:::
129
+
130
+
```csharp
131
+
if (!Workflow.Unsafe.IsReplaying)
132
+
{
133
+
EmitMetric("workflow_started", 1);
134
+
}
135
+
```
136
+
137
+
If your goal is to always take action when something new is happening, check that `Workflow.Unsafe.IsReplayingHistoryEvents` is false instead. This will be false during read-only operations like queries and update validators. This is what the SDK's built-in logger and metric meter use internally.
138
+
139
+
#### .NET Task gotchas
140
+
87
141
Here are some known gotchas to avoid with .NET tasks inside of Workflows:
88
142
89
143
- Do not use `Task.Run` - this uses the default scheduler and puts work on the thread pool.
Copy file name to clipboardExpand all lines: docs/develop/go/core-application.mdx
+50Lines changed: 50 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -403,6 +403,56 @@ The Temporal Go SDK has APIs to handle equivalent Go constructs:
403
403
-`workflow.Context` This is a replacement for `context.Context`.
404
404
See [Tracing](/develop/go/observability#tracing) for more information about context propagation.
405
405
406
+
The following sections describe the most common replay-safe patterns.
407
+
408
+
#### Logging
409
+
410
+
Use [`workflow.GetLogger(ctx)`](https://pkg.go.dev/go.temporal.io/sdk/workflow#GetLogger) instead of the standard `log` package. The SDK logger automatically suppresses log messages during replay to avoid duplicates:
411
+
412
+
```go
413
+
logger:= workflow.GetLogger(ctx)
414
+
logger.Info("Starting workflow", "name", name)
415
+
```
416
+
417
+
For logger configuration, see [Observability: Logging](/develop/go/observability#logging).
418
+
419
+
#### Random numbers and UUIDs
420
+
421
+
Use [`workflow.SideEffect`](https://pkg.go.dev/go.temporal.io/sdk/workflow#SideEffect) to capture non-deterministic values like random numbers. The result is stored in the Event History and reused on replay instead of re-executing:
For more details, see [Side Effects](/develop/go/side-effects).
432
+
433
+
#### Current time
434
+
435
+
Use [`workflow.Now(ctx)`](https://pkg.go.dev/go.temporal.io/sdk/workflow#Now) instead of `time.Now()`. The SDK returns the time of the last Workflow Task, which is consistent across replays:
436
+
437
+
```go
438
+
currentTime:= workflow.Now(ctx)
439
+
```
440
+
441
+
#### Detecting replay (advanced)
442
+
443
+
Use [`workflow.IsReplaying(ctx)`](https://pkg.go.dev/go.temporal.io/sdk/workflow#IsReplaying) to guard code that should only run on the first execution, such as emitting metrics or sending external notifications from an Interceptor.
444
+
:::caution
445
+
446
+
Never use this to affect Workflow business logic — branching on replay status breaks determinism.
447
+
448
+
:::
449
+
450
+
```go
451
+
if !workflow.IsReplaying(ctx) {
452
+
emitMetric("workflow_started", 1)
453
+
}
454
+
```
455
+
406
456
## How to develop an Activity Definition in Go {#activity-definition}
407
457
408
458
In the Temporal Go SDK programming model, an Activity Definition is an exportable function or a `struct` method.
Copy file name to clipboardExpand all lines: docs/develop/java/core-application.mdx
+53Lines changed: 53 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -269,6 +269,59 @@ The following constraints apply when writing Workflow Definitions:
269
269
A single implementation can implement a Workflow Type which by definition is dynamically loaded from some external source.
270
270
All standard `WorkflowOptions` and determinism rules apply to Dynamic Workflow implementations.
271
271
272
+
The following sections describe the most common replay-safe patterns.
273
+
274
+
#### Logging
275
+
276
+
Use [`Workflow.getLogger()`](https://www.javadoc.io/doc/io.temporal/temporal-sdk/latest/io/temporal/workflow/Workflow.html) instead of SLF4J loggers directly. The SDK logger automatically omits log messages during replay:
For logger configuration, see [Observability: Logging](/develop/java/observability#logging).
286
+
287
+
#### Random numbers and UUIDs
288
+
289
+
Use [`Workflow.newRandom()`](https://javadoc.io/doc/io.temporal/temporal-sdk/latest/io/temporal/workflow/Workflow.html#newRandom) for deterministic random numbers, and [`Workflow.randomUUID()`](https://www.javadoc.io/static/io.temporal/temporal-sdk/latest/io/temporal/workflow/Workflow.html#randomUUID()) for deterministic UUIDs. Never use `java.util.Random` or `UUID.randomUUID()` directly:
For other non-deterministic values, use [`Workflow.sideEffect()`](https://www.javadoc.io/doc/io.temporal/temporal-sdk/latest/io/temporal/workflow/Workflow.html#sideEffect(java.lang.Class,io.temporal.workflow.Functions.Func)). See [Side Effects](/develop/java/side-effects) for details.
301
+
302
+
#### Current time
303
+
304
+
Use [`Workflow.currentTimeMillis()`](https://www.javadoc.io/doc/io.temporal/temporal-sdk/latest/io/temporal/workflow/Workflow.html) instead of `System.currentTimeMillis()` or `Instant.now()`:
305
+
306
+
```java
307
+
long currentTime =Workflow.currentTimeMillis();
308
+
```
309
+
310
+
#### Detecting replay (advanced)
311
+
312
+
Use [`WorkflowUnsafe.isReplaying()`](https://www.javadoc.io/doc/io.temporal/temporal-sdk/latest/io/temporal/workflow/WorkflowUnsafe.html) to guard code that should only run on the first execution, such as emitting metrics or sending external notifications from an Interceptor.
313
+
:::caution
314
+
315
+
Never use this to affect Workflow business logic — branching on replay status breaks determinism.
Copy file name to clipboardExpand all lines: docs/develop/python/core-application.mdx
+58-1Lines changed: 58 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -201,7 +201,7 @@ Workflow logic is constrained by [deterministic execution requirements](/workflo
201
201
Therefore, each language is limited to the use of certain idiomatic techniques. However, each Temporal SDK provides a
202
202
set of APIs that can be used inside your Workflow to interact with external (to the Workflow) application code.
203
203
204
-
Workflow code must be deterministic. This means:
204
+
Workflow code must be deterministic because the Temporal Server may [replay](/develop/python/testing-suite#replay) your Workflow to reconstruct its state. This means:
205
205
206
206
- no threading
207
207
- no randomness
@@ -214,6 +214,63 @@ All API safe for Workflows used in the [`temporalio.workflow`](https://python.te
214
214
run in the implicit [`asyncio` event loop](https://docs.python.org/3/library/asyncio-eventloop.html) and be
215
215
_deterministic_.
216
216
217
+
The SDK provides replay-safe alternatives for common needs:
218
+
219
+
#### Logging
220
+
221
+
Use [`workflow.logger`](https://python.temporal.io/temporalio.workflow.html#logger) instead of `print()` or the standard `logging` module.
222
+
The SDK logger automatically suppresses log messages during replay to avoid duplicates:
223
+
224
+
```python
225
+
@workflow.defn
226
+
classMyWorkflow:
227
+
@workflow.run
228
+
asyncdefrun(self, name: str) -> str:
229
+
workflow.logger.info("Starting workflow", name)
230
+
# ...
231
+
```
232
+
233
+
For logger configuration, see [Observability: Log from a Workflow](/develop/python/observability#logging).
234
+
235
+
#### Random numbers and UUIDs
236
+
237
+
Use [`workflow.random()`](https://python.temporal.io/temporalio.workflow.html#random) to get a deterministic `random.Random` instance seeded per Workflow Execution. Never use `random.random()` or other `random` module functions directly.
238
+
For UUIDs, use [`workflow.uuid4()`](https://python.temporal.io/temporalio.workflow.html#uuid4) instead of `uuid.uuid4()`:
239
+
240
+
```python
241
+
# Good - deterministic across replays
242
+
value = workflow.random().randint(1, 100)
243
+
unique_id = workflow.uuid4()
244
+
245
+
# Bad - different result on every replay
246
+
import random
247
+
value = random.randint(1, 100)
248
+
```
249
+
250
+
#### Current time
251
+
252
+
Use [`workflow.now()`](https://python.temporal.io/temporalio.workflow.html#now) instead of `datetime.now()` or `time.time()`. The SDK returns the time of the last Workflow Task, which is consistent across replays:
253
+
254
+
```python
255
+
current_time = workflow.now()
256
+
```
257
+
258
+
#### Detecting replay (advanced)
259
+
260
+
Use [`workflow.unsafe.is_replaying`](https://python.temporal.io/temporalio.workflow.html#is_replaying) to guard code that should only run on the first execution, such as emitting metrics or sending external notifications from an [Interceptor](/develop/python/interceptors).
261
+
:::caution
262
+
263
+
Never use this to affect Workflow business logic — branching on replay status breaks determinism.
264
+
265
+
:::
266
+
267
+
```python
268
+
ifnot workflow.unsafe.is_replaying():
269
+
emit_metric("workflow_started", 1)
270
+
```
271
+
272
+
If your goal is to always take action when something new is happening, check that `workflow.unsafe.is_replaying_history_events()` is false instead. This will be false during read-only operations like queries and update validators. This is what the SDK's built-in logger and tracing interceptors use internally.
273
+
217
274
## Develop a basic Activity {#develop-activities}
218
275
219
276
**How to develop a basic Activity using the Temporal Python SDK.**
|**Description**| Wraps calls from your application to the Temporal Client to start a Workflow or send [Messages](/encyclopedia/workflow-message-passing/) to it | Wraps calls arriving into a [Workflow Execution](/workflow-execution), such as executing the Workflow, handling [Messages](/encyclopedia/workflow-message-passing/)| Wraps calls a [Workflow](/workflow-definition) makes to the SDK, such as scheduling [Activities](/activities), starting [Child Workflows](/child-workflows), and invoking [Nexus Operations](/nexus)| Wraps calls arriving into an [Activity Execution](/activity-execution)| Wraps calls an [Activity](/activities) makes to the SDK, such as sending [Heartbeats](/encyclopedia/detecting-activity-failures#activity-heartbeat) and reading Activity info |
[Python SDK methods](https://python.temporal.io/temporalio.client.OutboundInterceptor.html) for details.
37
+
These are not exhaustive lists; refer to the linked API docs for each category.
37
38
38
-
The first of these categories is a Client call, and the remaining 4 are Worker calls.
39
+
:::warning Workflow interceptors and replay
40
+
41
+
Workflow inbound and outbound interceptor methods also execute during [replay](/develop/python/testing-suite#replay). Use replay-safe APIs for logging, randomness, and time in these interceptors.
42
+
See [Develop Workflow logic](/develop/python/core-application#workflow-logic-requirements) for details.
43
+
44
+
If you want to write generic code shared by all inbound Workflow call handlers but want to skip read-only operations, check `workflow.unsafe.is_read_only()`.
45
+
46
+
Activity and Client interceptors are not affected by replay.
47
+
48
+
:::
49
+
50
+
## Register an Interceptor {#register}
51
+
52
+
Registering an interceptor means supplying an interceptor instance to the SDK so Temporal can invoke it when matching
53
+
Client or Worker calls occur. Once registered, the interceptor runs as part of the call path and can observe or modify
54
+
request and response data.
55
+
56
+
### Register on the Client
57
+
58
+
Pass interceptors in the `interceptors` argument of `Client.connect()`. Client interceptors modify outbound calls such
59
+
as starting and signaling Workflows.
60
+
61
+
```python
62
+
client =await Client.connect(
63
+
"localhost:7233",
64
+
interceptors=[TracingInterceptor()],
65
+
)
66
+
```
67
+
68
+
The `interceptors` list can contain multiple interceptors.
69
+
In this case they form a chain: a method implemented on an interceptor instance in the list can perform side effects, and modify the data, before passing it on to the corresponding method on the next interceptor in the list.
70
+
71
+
### Register via a Plugin
72
+
73
+
If you're building a reusable library or want to bundle interceptors with other primitives, you can register them through a [Plugin](/develop/plugins-guide#interceptors).
74
+
75
+
### Register on the Worker only
76
+
77
+
If your interceptor doesn't affect the Client, you can pass interceptors in the `interceptors` argument of `Worker()`.
78
+
Worker interceptors modify inbound and outbound Workflow and Activity calls.
79
+
80
+
```python
81
+
worker = Worker(
82
+
client,
83
+
task_queue="my-task-queue",
84
+
interceptors=[SomeWorkerInterceptor()],
85
+
# ...
86
+
)
87
+
```
88
+
89
+
:::note
90
+
91
+
If your interceptor class inherits from both `client.Interceptor` and `worker.Interceptor`, pass it to
92
+
`Client.connect()` rather than the `Worker()` constructor. The Worker will use interceptors from its underlying Client
93
+
automatically.
94
+
95
+
:::
39
96
40
97
## Register an Interceptor {#register}
41
98
@@ -153,11 +210,10 @@ and `worker.Interceptor` as above, since their method sets do not overlap.
153
210
154
211
You can then [register](#register) this interceptor in your client/starter code.
155
212
156
-
Your interceptor classes need not implement every method; the default implementation is always to pass the data on to
157
-
the next method in the interceptor chain. During execution, when the SDK encounters an Inbound Activity call, it will
158
-
look to the first Interceptor instance, get hold of the appropriate intercepted method, and call it. The intercepted
159
-
method will perform its function then call the same method on the next Interceptor in the chain. At the end of the chain
160
-
the SDK will call the "real" SDK method.
213
+
Your interceptor classes need not implement every method; the default implementation is always to pass the data on to the next method in the interceptor chain.
214
+
During execution, when the SDK encounters an Inbound Activity call, it will look to the first Interceptor instance, get hold of the appropriate intercepted method, and call it.
215
+
The intercepted method will perform its function then call the same method on the next Interceptor in the chain.
216
+
At the end of the chain the SDK will call the "real" SDK method.
0 commit comments