@temporalio/core-bridge 1.13.0 → 1.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/Cargo.lock +239 -382
  2. package/Cargo.toml +11 -11
  3. package/lib/native.d.ts +10 -3
  4. package/package.json +3 -3
  5. package/releases/aarch64-apple-darwin/index.node +0 -0
  6. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  7. package/releases/x86_64-apple-darwin/index.node +0 -0
  8. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  9. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  10. package/sdk-core/.cargo/config.toml +71 -11
  11. package/sdk-core/.clippy.toml +1 -0
  12. package/sdk-core/.github/workflows/heavy.yml +2 -0
  13. package/sdk-core/.github/workflows/per-pr.yml +50 -18
  14. package/sdk-core/ARCHITECTURE.md +44 -48
  15. package/sdk-core/Cargo.toml +26 -7
  16. package/sdk-core/README.md +4 -0
  17. package/sdk-core/arch_docs/diagrams/TimerMachine_Coverage.puml +14 -0
  18. package/sdk-core/arch_docs/diagrams/initial_event_history.png +0 -0
  19. package/sdk-core/arch_docs/sdks_intro.md +299 -0
  20. package/sdk-core/client/Cargo.toml +8 -7
  21. package/sdk-core/client/src/callback_based.rs +1 -2
  22. package/sdk-core/client/src/lib.rs +485 -299
  23. package/sdk-core/client/src/metrics.rs +32 -8
  24. package/sdk-core/client/src/proxy.rs +124 -5
  25. package/sdk-core/client/src/raw.rs +598 -307
  26. package/sdk-core/client/src/replaceable.rs +253 -0
  27. package/sdk-core/client/src/retry.rs +9 -6
  28. package/sdk-core/client/src/worker_registry/mod.rs +19 -3
  29. package/sdk-core/client/src/workflow_handle/mod.rs +20 -17
  30. package/sdk-core/core/Cargo.toml +100 -31
  31. package/sdk-core/core/src/core_tests/activity_tasks.rs +55 -225
  32. package/sdk-core/core/src/core_tests/mod.rs +2 -8
  33. package/sdk-core/core/src/core_tests/queries.rs +3 -5
  34. package/sdk-core/core/src/core_tests/replay_flag.rs +3 -62
  35. package/sdk-core/core/src/core_tests/updates.rs +4 -5
  36. package/sdk-core/core/src/core_tests/workers.rs +4 -3
  37. package/sdk-core/core/src/core_tests/workflow_cancels.rs +10 -7
  38. package/sdk-core/core/src/core_tests/workflow_tasks.rs +28 -291
  39. package/sdk-core/core/src/ephemeral_server/mod.rs +15 -3
  40. package/sdk-core/core/src/internal_flags.rs +11 -1
  41. package/sdk-core/core/src/lib.rs +50 -36
  42. package/sdk-core/core/src/pollers/mod.rs +5 -5
  43. package/sdk-core/core/src/pollers/poll_buffer.rs +2 -2
  44. package/sdk-core/core/src/protosext/mod.rs +13 -5
  45. package/sdk-core/core/src/protosext/protocol_messages.rs +4 -11
  46. package/sdk-core/core/src/retry_logic.rs +256 -108
  47. package/sdk-core/core/src/telemetry/metrics.rs +1 -0
  48. package/sdk-core/core/src/telemetry/mod.rs +8 -2
  49. package/sdk-core/core/src/telemetry/prometheus_meter.rs +2 -2
  50. package/sdk-core/core/src/test_help/integ_helpers.rs +971 -0
  51. package/sdk-core/core/src/test_help/mod.rs +10 -1100
  52. package/sdk-core/core/src/test_help/unit_helpers.rs +218 -0
  53. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +42 -6
  54. package/sdk-core/core/src/worker/activities/local_activities.rs +19 -19
  55. package/sdk-core/core/src/worker/activities.rs +10 -3
  56. package/sdk-core/core/src/worker/client/mocks.rs +3 -3
  57. package/sdk-core/core/src/worker/client.rs +130 -93
  58. package/sdk-core/core/src/worker/heartbeat.rs +12 -13
  59. package/sdk-core/core/src/worker/mod.rs +31 -21
  60. package/sdk-core/core/src/worker/nexus.rs +14 -3
  61. package/sdk-core/core/src/worker/slot_provider.rs +9 -0
  62. package/sdk-core/core/src/worker/tuner.rs +159 -0
  63. package/sdk-core/core/src/worker/workflow/history_update.rs +3 -265
  64. package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +1 -54
  65. package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +0 -82
  66. package/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +0 -67
  67. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +1 -192
  68. package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +0 -43
  69. package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +6 -554
  70. package/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +0 -71
  71. package/sdk-core/core/src/worker/workflow/machines/nexus_operation_state_machine.rs +102 -3
  72. package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +10 -539
  73. package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +0 -139
  74. package/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +1 -119
  75. package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +6 -63
  76. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +9 -4
  77. package/sdk-core/core/src/worker/workflow/mod.rs +5 -1
  78. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +8 -3
  79. package/sdk-core/core-api/Cargo.toml +4 -4
  80. package/sdk-core/core-api/src/envconfig.rs +153 -54
  81. package/sdk-core/core-api/src/lib.rs +68 -0
  82. package/sdk-core/core-api/src/telemetry/metrics.rs +2 -1
  83. package/sdk-core/core-api/src/telemetry.rs +13 -0
  84. package/sdk-core/core-c-bridge/Cargo.toml +13 -8
  85. package/sdk-core/core-c-bridge/include/temporal-sdk-core-c-bridge.h +184 -22
  86. package/sdk-core/core-c-bridge/src/client.rs +462 -184
  87. package/sdk-core/core-c-bridge/src/envconfig.rs +314 -0
  88. package/sdk-core/core-c-bridge/src/lib.rs +1 -0
  89. package/sdk-core/core-c-bridge/src/random.rs +4 -4
  90. package/sdk-core/core-c-bridge/src/runtime.rs +22 -23
  91. package/sdk-core/core-c-bridge/src/testing.rs +1 -4
  92. package/sdk-core/core-c-bridge/src/tests/context.rs +31 -31
  93. package/sdk-core/core-c-bridge/src/tests/mod.rs +32 -28
  94. package/sdk-core/core-c-bridge/src/tests/utils.rs +7 -7
  95. package/sdk-core/core-c-bridge/src/worker.rs +319 -66
  96. package/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +6 -1
  97. package/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/dupe_transitions_fail.stderr +5 -5
  98. package/sdk-core/sdk/Cargo.toml +8 -2
  99. package/sdk-core/sdk/src/activity_context.rs +1 -1
  100. package/sdk-core/sdk/src/app_data.rs +1 -1
  101. package/sdk-core/sdk/src/interceptors.rs +1 -4
  102. package/sdk-core/sdk/src/lib.rs +1 -5
  103. package/sdk-core/sdk/src/workflow_context/options.rs +10 -1
  104. package/sdk-core/sdk/src/workflow_future.rs +1 -1
  105. package/sdk-core/sdk-core-protos/Cargo.toml +6 -6
  106. package/sdk-core/sdk-core-protos/build.rs +10 -23
  107. package/sdk-core/sdk-core-protos/protos/api_upstream/.github/workflows/create-release.yml +9 -1
  108. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +254 -5
  109. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +234 -5
  110. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +1 -1
  111. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/deployment/v1/message.proto +6 -0
  112. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/namespace/v1/message.proto +6 -2
  113. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +60 -2
  114. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +30 -6
  115. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +2 -0
  116. package/sdk-core/{test-utils → sdk-core-protos}/src/canned_histories.rs +5 -5
  117. package/sdk-core/sdk-core-protos/src/history_builder.rs +2 -2
  118. package/sdk-core/sdk-core-protos/src/lib.rs +25 -9
  119. package/sdk-core/sdk-core-protos/src/test_utils.rs +89 -0
  120. package/sdk-core/sdk-core-protos/src/utilities.rs +14 -5
  121. package/sdk-core/tests/c_bridge_smoke_test.c +10 -0
  122. package/sdk-core/tests/cloud_tests.rs +10 -8
  123. package/sdk-core/tests/common/http_proxy.rs +134 -0
  124. package/sdk-core/{test-utils/src/lib.rs → tests/common/mod.rs} +214 -281
  125. package/sdk-core/{test-utils/src → tests/common}/workflows.rs +4 -3
  126. package/sdk-core/tests/fuzzy_workflow.rs +1 -1
  127. package/sdk-core/tests/global_metric_tests.rs +8 -7
  128. package/sdk-core/tests/heavy_tests.rs +7 -3
  129. package/sdk-core/tests/integ_tests/client_tests.rs +111 -24
  130. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +14 -9
  131. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +4 -4
  132. package/sdk-core/tests/integ_tests/metrics_tests.rs +114 -14
  133. package/sdk-core/tests/integ_tests/pagination_tests.rs +273 -0
  134. package/sdk-core/tests/integ_tests/polling_tests.rs +311 -93
  135. package/sdk-core/tests/integ_tests/queries_tests.rs +4 -4
  136. package/sdk-core/tests/integ_tests/update_tests.rs +13 -7
  137. package/sdk-core/tests/integ_tests/visibility_tests.rs +26 -9
  138. package/sdk-core/tests/integ_tests/worker_tests.rs +668 -13
  139. package/sdk-core/tests/integ_tests/worker_versioning_tests.rs +40 -24
  140. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +244 -11
  141. package/sdk-core/tests/integ_tests/workflow_tests/appdata_propagation.rs +1 -1
  142. package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +78 -2
  143. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +61 -2
  144. package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +465 -7
  145. package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +41 -2
  146. package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +315 -3
  147. package/sdk-core/tests/integ_tests/workflow_tests/eager.rs +1 -1
  148. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +1990 -14
  149. package/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +65 -2
  150. package/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +123 -23
  151. package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +525 -3
  152. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +65 -16
  153. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +32 -23
  154. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +126 -5
  155. package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +1 -2
  156. package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +124 -8
  157. package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +62 -2
  158. package/sdk-core/tests/integ_tests/workflow_tests.rs +67 -8
  159. package/sdk-core/tests/main.rs +26 -17
  160. package/sdk-core/tests/manual_tests.rs +5 -1
  161. package/sdk-core/tests/runner.rs +22 -40
  162. package/sdk-core/tests/shared_tests/mod.rs +1 -1
  163. package/sdk-core/tests/shared_tests/priority.rs +1 -1
  164. package/sdk-core/{core/benches/workflow_replay.rs → tests/workflow_replay_bench.rs} +10 -5
  165. package/src/client.rs +97 -20
  166. package/src/helpers/callbacks.rs +4 -4
  167. package/src/helpers/errors.rs +7 -1
  168. package/src/helpers/handles.rs +1 -0
  169. package/src/helpers/try_from_js.rs +4 -3
  170. package/src/lib.rs +3 -2
  171. package/src/metrics.rs +3 -0
  172. package/src/runtime.rs +5 -2
  173. package/src/worker.rs +9 -12
  174. package/ts/native.ts +13 -3
  175. package/sdk-core/arch_docs/diagrams/workflow_internals.svg +0 -1
  176. package/sdk-core/core/src/core_tests/child_workflows.rs +0 -281
  177. package/sdk-core/core/src/core_tests/determinism.rs +0 -318
  178. package/sdk-core/core/src/core_tests/local_activities.rs +0 -1442
  179. package/sdk-core/test-utils/Cargo.toml +0 -38
  180. package/sdk-core/test-utils/src/histfetch.rs +0 -28
  181. package/sdk-core/test-utils/src/interceptors.rs +0 -46
@@ -0,0 +1,299 @@
1
+ # Intro to Temporal SDKs
2
+
3
+ This document is primarily meant as an onboarding guide for new Temporal SDK developers, to be
4
+ accompanied by a live presentation. Hence, content does not have maximal context. If reading this
5
+ independently, you will also want to refer to our documentation [site](https://docs.temporal.io).
6
+
7
+ ## Primary SDK Concepts
8
+
9
+ See also our
10
+ [official docs on basic concepts](https://docs.temporal.io/evaluate/understanding-temporal).
11
+
12
+ ### Workflows
13
+
14
+ Workflows are durable programs. Their state is materialized from event history and they can be
15
+ replayed zero or more times. Workflow code must be deterministic and must use SDK Workflow-specific
16
+ APIs. Side effects and non-deterministic APIs should be placed in Activities.
17
+
18
+ **Example Workflow Code (Python):**
19
+
20
+ ```python
21
+ from datetime import timedelta
22
+ from temporalio import workflow
23
+
24
+ @workflow.defn
25
+ class SayHello:
26
+ @workflow.run
27
+ async def run(self, name: str) -> str:
28
+ return await workflow.execute_activity(
29
+ say_hello, name, schedule_to_close_timeout=timedelta(seconds=5)
30
+ )
31
+ ```
32
+
33
+ ### Activities
34
+
35
+ Activities are invoked from a Workflow and can run arbitrary code. They can be retried, are subject
36
+ to timeouts, and should be idempotent/reentrant. Activities can be short or long-running and can
37
+ heartbeat.
38
+
39
+ **Example Activity Code (Python):**
40
+
41
+ ```python
42
+ from temporalio import activity
43
+
44
+ @activity.defn
45
+ async def say_hello(name: str) -> str:
46
+ return f"Hello, {name}!"
47
+ ```
48
+
49
+ ### Clients
50
+
51
+ Clients serve as a driver for interaction with the Temporal server. They provide a high-level API
52
+ for a subset of gRPC calls, including Workflow "CRUD" operations, Activity completion, and
53
+ Schedules.
54
+
55
+ **Example Client Code (Python):**
56
+
57
+ ``` python
58
+ from temporalio.client import Client
59
+
60
+ # Import the workflow from the previous code
61
+ from .workflows import SayHello
62
+
63
+ async def main():
64
+ client = await Client.connect("localhost:7233")
65
+
66
+ result = await client.execute_workflow(
67
+ SayHello.run, "my name", id="my-workflow-id", task_queue="my-task-queue")
68
+
69
+ print(f"Result: {result}")
70
+ ```
71
+
72
+ ### Workers
73
+
74
+ Workers are stateless entities that poll the server for Workflow, Activity, and Nexus tasks. They
75
+ run user-defined Workflows, Activities, and Nexus Operations, manage concurrency and rate limits,
76
+ and provide the language runtime for workflows.
77
+
78
+ **Example Worker Code (Python):**
79
+
80
+ ``` python
81
+ from temporalio.client import Client
82
+ from temporalio.worker import Worker
83
+
84
+ from .activities import say_hello
85
+ from .workflows import SayHello
86
+
87
+ async def main():
88
+ client = await Client.connect("localhost:7233")
89
+
90
+ worker = Worker(
91
+ client,
92
+ task_queue="my-task-queue",
93
+ workflows=[SayHello],
94
+ activities=[say_hello])
95
+ await worker.run()
96
+ ```
97
+
98
+ ## SDK \<-\> Server Interaction
99
+
100
+ ### Event History and it's construction
101
+
102
+ Event history is a record of all events that have occurred in a Workflow. Workflow tasks are the
103
+ fundamental unit of progress for Workflows. Any time your Workflow code makes progress, it does so
104
+ because your worker processed one of these Workflow tasks. Workflow tasks contain some or all of the
105
+ Workflow’s history, along with possibly queries and other bits of information the worker might care
106
+ about.
107
+
108
+ The first few events in a workflow's history always look like this:
109
+
110
+ ![initial_event_history.png](diagrams/initial_event_history.png)
111
+
112
+ The first Workflow task for a Workflow always includes the initial events. When the SDK receives a
113
+ task, it processes the history, runs your Workflow code, and eventually responds with commands to
114
+ the server. Commands are actions like "start a timer" or "run an Activity."
115
+
116
+ A sequence diagram of this process is shown below. Not everything may make sense yet, but we can
117
+ refer back to this diagram during later explanations.
118
+
119
+ ```mermaid
120
+ %%{ init : { "theme" : "default", "themeVariables" : { "background" : "#fff" }}}%%
121
+ sequenceDiagram
122
+ box rgb(255, 255, 255)
123
+ participant Client
124
+ participant Server
125
+ participant SDK
126
+ participant UserCode as "User Code"
127
+ end
128
+
129
+ Client ->> Server: StartWorkflowExecution RPC
130
+ Server ->> Server: Generate Workflow Task & persist
131
+ Server -->> Client: runID
132
+
133
+ loop Workflow Task Polling
134
+ rect rgb(200, 230, 200)
135
+ SDK ->> Server: PollWorkflowTaskQueueRequest
136
+ opt Something triggers new Workflow Task
137
+ Server ->> Server: Generate Workflow Task & persist
138
+ end
139
+ Server -->> SDK: PollWorkflowTaskQueueResponse
140
+ end
141
+
142
+ rect rgb(220, 200, 220)
143
+ loop Processing one Workflow Task
144
+ SDK ->> SDK: Apply workflow history to state
145
+ SDK ->> UserCode: Activate & Unblock Awaitables
146
+ UserCode -->> SDK: Blocked on some awaitable or complete
147
+ end
148
+
149
+ SDK ->> Server: RespondWorkflowTaskCompletedRequest
150
+ Server ->> Server: Persist & possibly reply with new Workflow Task
151
+ Server -->> SDK: RespondWorkflowTaskCompletedResponse
152
+ end
153
+ end
154
+ ```
155
+
156
+ **How History is Constructed:**
157
+
158
+ Workflow tasks contain history and are responded to with commands. Every command is turned into a
159
+ corresponding event (referred to as "dual" or "command-events"). The Temporal server takes action on
160
+ received commands, and the consequences of those actions also become events (e.g., `TimerFired`,
161
+ `ActivityTaskCompleted`). When events that trigger a new Workflow task occur, the Temporal server
162
+ generates a new Workflow task because a worker needs to run the Workflow code to process this new
163
+ information.
164
+
165
+ This cycle continues until the Workflow is complete: new Workflow Tasks are created, processed by
166
+ the SDK, responded to with commands, until a new Workflow task begins, and so on.
167
+
168
+ We will use the following example Workflow to illustrate this process:
169
+
170
+ ```python
171
+ @activity.defn
172
+ async def say_hello(name: str) -> str:
173
+ return f"Hello, {name}!"
174
+
175
+ @workflow.defn
176
+ class ReplayExampleWorkflow:
177
+ @workflow.run
178
+ async def run(self, name: str) -> str:
179
+ my_hello = workflow.execute_activity(
180
+ say_hello, name, schedule_to_close_timeout=timedelta(seconds=5)
181
+ )
182
+ timer = workflow.sleep(10)
183
+ (activity_result, _) = await asyncio.gather(my_hello, timer)
184
+ return activity_result
185
+ ```
186
+
187
+ **Example Workflow History Analysis:**
188
+
189
+ 1. The initial Workflow task is processed. The `execute_activity` call creates a command for
190
+ scheduling an Activity, and `workflow.sleep` creates a command for a timer.
191
+ 2. The `asyncio.gather` line indicates a wait for both the activity and timer to resolve.
192
+ 3. The SDK knows when to respond to the task because the Workflow cannot make any more progress. All
193
+ buffered commands are sent to the server (activity and timer commands, in that order). This
194
+ completes the first Workflow task.
195
+ 4. The server receives these commands, which are converted into `ActivityTaskScheduled` and
196
+ `TimerStarted` events (duals of the commands).
197
+ 5. When the Activity completes, an `ActivityTaskCompleted` event is added to the history. This
198
+ triggers a new Workflow task.
199
+ 6. The worker processes this new task, and the future `my_hello` is resolved. The Workflow code,
200
+ however, is still blocked on `gather` because the timer hasn't fired. The SDK replies with an
201
+ empty list of commands, indicating no new actions.
202
+ 7. The timer fires, resulting in a `TimerFired` event. This generates another Workflow task.
203
+ 8. When this task is processed, the `gather` call resolves. The Workflow function returns, producing
204
+ a `CompleteWorkflowExecution` command.
205
+ 9. This command is converted into a `WorkflowExecutionCompleted` event, and the Workflow finishes.
206
+
207
+ The complete history at the end of all this is the following:
208
+
209
+ 1. Workflow Execution Started
210
+ 2. Workflow Task Scheduled
211
+ 3. Workflow Task Started
212
+ 4. Workflow Task Completed
213
+ 5. Activity Task Scheduled `say_hello`
214
+ 6. Timer Started `1 (1s)`
215
+ 7. Activity Task Started `say_hello`
216
+ 8. Activity Task Completed `say_hello`
217
+ 9. Workflow Task Scheduled
218
+ 10. Workflow Task Started
219
+ 11. Workflow Task Completed `Notice: No commands sent`
220
+ 12. Timer Fired `1 (1s)`
221
+ 13. Workflow Task Scheduled
222
+ 14. Workflow Task Started
223
+ 15. Workflow Task Completed
224
+ 16. Workflow Execution Completed
225
+
226
+ This repetitive nature of processing Workflow tasks, where the SDK polls for tasks, processes
227
+ history, unblocks user code, and sends new commands, continues until Workflow completion.
228
+
229
+ ### How Replay Works
230
+
231
+ Above we described the sequence of events as if the Python objects remained in memory between
232
+ workflow tasks, with asyncio futures "waiting" to be resolved. That is indeed typically the case,
233
+ but technically it's a performance optimization (we refer to it as the "sticky" optimization), where
234
+ history is delivered in incremental pieces rather than in its entirety. When a worker doesn't have
235
+ the workflow already in-cache, it "replays" the workflow code from the beginning (this is why we
236
+ said workers are stateless), making use of the supplied history to deterministically reach precisely
237
+ the state it had reached previously.
238
+
239
+ Replay is fundamental to Temporal's durability and determinism, and so we'll now look at it in a bit
240
+ more detail.
241
+
242
+ **History Processing:**
243
+
244
+ SDKs process events from history serially, one at a time. Each event is fed to a specific instance
245
+ of a state machine. There is a type of state machine for every command, as well as some non-command
246
+ events/concepts. The state of a particular Workflow run consists of the combination of the states of
247
+ these state machines, and importantly, the order in which they are created.
248
+
249
+ **Timer State Machine in Core (Example):**
250
+
251
+ We can use the Timer state machine to illustrate how determinism is enforced:
252
+
253
+ <img src="https://uml.planttext.com/plantuml/svg/fL912iCW4Bpx2g6tWX_eeGI4Fg3s5d8mkj206h3w_oOR8q9jfFJaBhCpiuvsho1zYDQG_ZnGngwKUv01D4adPVrfD1661HBhB9-jbKud-4A5UeAE1aW5RP9JZzXZik1_KRc3chrUPP2AqB9uGu5Bfy2WELQa9baIRfFF7bWt6Pim4Zukl7b-dTYUu1_-qHe2NCYX5t15Rnqjbia9x2tPYyDiCQzAc0dkmEdbxxq1" width="500" alt="Timer FSM Diagram"/>
254
+
255
+ When we first create the timer, we immediately schedule it, which produces the `StartTimer` command,
256
+ which becomes the `TimerStarted` event. The `Created` state isn’t really necessary here, but the
257
+ schedule transition serves as a convenient way to produce the start timer command.
258
+
259
+ When we see a `TimerStarted` event on history, we know that the command we issued exists in Workflow
260
+ history, and we move to the StartCommandRecorded state and wait to see what happens - it can either
261
+ fire or be canceled.
262
+
263
+ In our example, there’s a `TimerFired` event, so when the SDK processes that event, we’d take that
264
+ transition, and the machine ends up in the final fired state
265
+
266
+ At this point, you might be wondering - why does that `StartCommandRecorded` state matter? Why have
267
+ a transition for `TimerStarted` at all? We know we sent the command to the server, so why not just
268
+ wait to see if the timer fires or is canceled from the `StartCommandCreated` state? The answer is
269
+ that's a big part of the secret sauce for how we enforce determinism.
270
+
271
+ **Enforcing Determinism:**
272
+
273
+ A Workflow must always emit the same commands in the same sequence. Temporal can detect violations
274
+ of this rule because of the "dual" events that each command produces. The `TimerStarted` transition
275
+ ensures that when replaying a Workflow, the current code attempting to start a timer does so at the
276
+ same point and in the same way as what exists in history. The order in which state machines are
277
+ created by Workflow code also matters to ensure the same sequence of commands is produced. This
278
+ prevents scenarios where changed code might run a command that should have been mutually exclusive
279
+ with a command already in history.
280
+
281
+ **Example of Nondeterminism:**
282
+
283
+ If the order of arguments to `asyncio.gather` is swapped in the previous example Workflow, replaying
284
+ the Workflow will result in a nondeterminism error (often referred to as an "NDE"):
285
+
286
+ ```
287
+ temporalio.workflow.NondeterminismError: Workflow activation completion failed: Failure { failure:
288
+ Some(Failure { message: "[TMPRL1100] Nondeterminism error: Timer machine does not handle this event:
289
+ HistoryEvent(id: 5, ActivityTaskScheduled)", source: "", stack_trace: "", encoded_attributes: None,
290
+ cause: None, failure_info: Some(ApplicationFailureInfo(ApplicationFailureInfo { r#type: "",
291
+ non_retryable: false, details: None, next_retry_delay: None })) }), force_cause:
292
+ NonDeterministicError }
293
+ ```
294
+
295
+ This error occurs because the "dual" events in the history are not in the same order as the commands
296
+ produced by the modified Workflow code. The timer machine, expecting a `TimerStarted` event,
297
+ encounters an `ActivityTaskScheduled` event instead, leading to a nondeterminism error. In other
298
+ words, we did not produce the same commands in the same order. This mechanism ensures Workflow state
299
+ consistency.
@@ -20,23 +20,24 @@ backoff = "0.4"
20
20
  base64 = "0.22"
21
21
  derive_builder = { workspace = true }
22
22
  derive_more = { workspace = true }
23
+ dyn-clone = "1.0"
23
24
  bytes = "1.10"
24
25
  futures-util = { version = "0.3", default-features = false }
25
26
  futures-retry = "0.6.0"
26
- http = "1.1"
27
+ http = "1.3"
27
28
  http-body-util = "0.1"
28
- hyper = { version = "1.4.1" }
29
- hyper-util = "0.1.6"
29
+ hyper = { version = "1.7.0" }
30
+ hyper-util = "0.1.16"
30
31
  opentelemetry = { workspace = true, features = ["metrics"], optional = true }
31
32
  parking_lot = "0.12"
32
33
  slotmap = "1.0"
33
34
  thiserror = { workspace = true }
34
- tokio = "1.1"
35
+ tokio = "1.47"
35
36
  tonic = { workspace = true, features = ["tls-ring", "tls-native-roots"] }
36
37
  tower = { version = "0.5", features = ["util"] }
37
38
  tracing = "0.1"
38
- url = "2.2"
39
- uuid = { version = "1.1", features = ["v4"] }
39
+ url = "2.5"
40
+ uuid = { version = "1.18", features = ["v4"] }
40
41
 
41
42
  [dependencies.temporal-sdk-core-protos]
42
43
  path = "../sdk-core-protos"
@@ -47,7 +48,7 @@ path = "../core-api"
47
48
  [dev-dependencies]
48
49
  assert_matches = "1"
49
50
  mockall = "0.13"
50
- rstest = "0.25"
51
+ rstest = "0.26"
51
52
 
52
53
  [lints]
53
54
  workspace = true
@@ -3,8 +3,7 @@
3
3
 
4
4
  use anyhow::anyhow;
5
5
  use bytes::{BufMut, BytesMut};
6
- use futures_util::future::BoxFuture;
7
- use futures_util::stream;
6
+ use futures_util::{future::BoxFuture, stream};
8
7
  use http::{HeaderMap, Request, Response};
9
8
  use http_body_util::{BodyExt, StreamBody, combinators::BoxBody};
10
9
  use hyper::body::{Bytes, Frame};