@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
@@ -2,9 +2,11 @@ use crate::{
2
2
  ByteArray, ByteArrayRef, ByteArrayRefArray, UserDataHandle, client::Client, runtime::Runtime,
3
3
  };
4
4
  use anyhow::{Context, bail};
5
+ use crossbeam_utils::atomic::AtomicCell;
5
6
  use prost::Message;
6
7
  use std::{
7
8
  collections::{HashMap, HashSet},
9
+ num::NonZero,
8
10
  sync::Arc,
9
11
  time::Duration,
10
12
  };
@@ -22,14 +24,14 @@ use temporal_sdk_core_api::{
22
24
  };
23
25
  use temporal_sdk_core_protos::{
24
26
  coresdk::{
25
- ActivityHeartbeat, ActivityTaskCompletion,
27
+ ActivityHeartbeat, ActivityTaskCompletion, nexus::NexusTaskCompletion,
26
28
  workflow_completion::WorkflowActivationCompletion,
27
29
  },
28
30
  temporal::api::history::v1::History,
29
31
  };
30
32
  use tokio::sync::{
33
+ Notify,
31
34
  mpsc::{Sender, channel},
32
- oneshot,
33
35
  };
34
36
  use tokio_stream::wrappers::ReceiverStream;
35
37
 
@@ -51,6 +53,7 @@ pub struct WorkerOptions {
51
53
  pub workflow_task_poller_behavior: PollerBehavior,
52
54
  pub nonsticky_to_sticky_poll_ratio: f32,
53
55
  pub activity_task_poller_behavior: PollerBehavior,
56
+ pub nexus_task_poller_behavior: PollerBehavior,
54
57
  pub nondeterminism_as_workflow_fail: bool,
55
58
  pub nondeterminism_as_workflow_fail_for_types: ByteArrayRefArray,
56
59
  }
@@ -130,6 +133,7 @@ pub struct TunerHolder {
130
133
  pub workflow_slot_supplier: SlotSupplier,
131
134
  pub activity_slot_supplier: SlotSupplier,
132
135
  pub local_activity_slot_supplier: SlotSupplier,
136
+ pub nexus_task_slot_supplier: SlotSupplier,
133
137
  }
134
138
 
135
139
  #[repr(C)]
@@ -163,14 +167,24 @@ struct CustomSlotSupplier<SK> {
163
167
  unsafe impl<SK> Send for CustomSlotSupplier<SK> {}
164
168
  unsafe impl<SK> Sync for CustomSlotSupplier<SK> {}
165
169
 
166
- pub type CustomReserveSlotCallback =
167
- unsafe extern "C" fn(ctx: *const SlotReserveCtx, sender: *mut libc::c_void);
168
- pub type CustomCancelReserveCallback = unsafe extern "C" fn(token_source: *mut libc::c_void);
169
- /// Must return C#-tracked id for the permit. A zero value means no permit was reserved.
170
- pub type CustomTryReserveSlotCallback = unsafe extern "C" fn(ctx: *const SlotReserveCtx) -> usize;
171
- pub type CustomMarkSlotUsedCallback = unsafe extern "C" fn(ctx: *const SlotMarkUsedCtx);
172
- pub type CustomReleaseSlotCallback = unsafe extern "C" fn(ctx: *const SlotReleaseCtx);
173
- pub type CustomSlotImplFreeCallback =
170
+ pub type CustomSlotSupplierReserveCallback = unsafe extern "C" fn(
171
+ ctx: *const SlotReserveCtx,
172
+ completion_ctx: *const SlotReserveCompletionCtx,
173
+ user_data: *mut libc::c_void,
174
+ );
175
+ pub type CustomSlotSupplierCancelReserveCallback = unsafe extern "C" fn(
176
+ completion_ctx: *const SlotReserveCompletionCtx,
177
+ user_data: *mut libc::c_void,
178
+ );
179
+ pub type CustomSlotSupplierTryReserveCallback =
180
+ unsafe extern "C" fn(ctx: *const SlotReserveCtx, user_data: *mut libc::c_void) -> usize;
181
+ pub type CustomSlotSupplierMarkUsedCallback =
182
+ unsafe extern "C" fn(ctx: *const SlotMarkUsedCtx, user_data: *mut libc::c_void);
183
+ pub type CustomSlotSupplierReleaseCallback =
184
+ unsafe extern "C" fn(ctx: *const SlotReleaseCtx, user_data: *mut libc::c_void);
185
+ pub type CustomSlotSupplierAvailableSlotsCallback =
186
+ Option<unsafe extern "C" fn(available_slots: *mut usize, user_data: *mut libc::c_void) -> bool>;
187
+ pub type CustomSlotSupplierFreeCallback =
174
188
  unsafe extern "C" fn(userimpl: *const CustomSlotSupplierCallbacks);
175
189
 
176
190
  #[repr(C)]
@@ -179,12 +193,46 @@ pub struct CustomSlotSupplierCallbacksImpl(pub *const CustomSlotSupplierCallback
179
193
 
180
194
  #[repr(C)]
181
195
  pub struct CustomSlotSupplierCallbacks {
182
- pub reserve: CustomReserveSlotCallback,
183
- pub cancel_reserve: CustomCancelReserveCallback,
184
- pub try_reserve: CustomTryReserveSlotCallback,
185
- pub mark_used: CustomMarkSlotUsedCallback,
186
- pub release: CustomReleaseSlotCallback,
187
- pub free: CustomSlotImplFreeCallback,
196
+ /// Called to initiate asynchronous slot reservation. `ctx` contains information about
197
+ /// reservation request. The pointer is only valid for the duration of the function call; the
198
+ /// implementation should copy the data out of it for later use, and return as soon as possible.
199
+ ///
200
+ /// When slot is reserved, the implementation should call [`temporal_core_complete_async_reserve`]
201
+ /// with the same `completion_ctx` as passed to this function. Reservation cannot be cancelled
202
+ /// by Lang, but it can be cancelled by Core through [`cancel_reserve`](Self::cancel_reserve)
203
+ /// callback. If reservation was cancelled, [`temporal_core_complete_async_cancel_reserve`]
204
+ /// should be called instead.
205
+ ///
206
+ /// Slot reservation cannot error. The implementation should recover from errors and keep trying
207
+ /// to reserve a slot until it eventually succeeds, or until reservation is cancelled by Core.
208
+ pub reserve: CustomSlotSupplierReserveCallback,
209
+ /// Called to cancel slot reservation. `completion_ctx` specifies which reservation is being
210
+ /// cancelled; the matching [`reserve`](Self::reserve) call was made with the same `completion_ctx`.
211
+ /// After cancellation, the implementation should call [`temporal_core_complete_async_cancel_reserve`]
212
+ /// with the same `completion_ctx`. Calling [`temporal_core_complete_async_reserve`] is not
213
+ /// needed after cancellation.
214
+ pub cancel_reserve: CustomSlotSupplierCancelReserveCallback,
215
+ /// Called to try an immediate slot reservation. The callback should return 0 if immediate
216
+ /// reservation is not currently possible, or permit ID if reservation was successful. Permit ID
217
+ /// is arbitrary, but must be unique among live reservations as it's later used for [`mark_used`](Self::mark_used)
218
+ /// and [`release`](Self::release) callbacks.
219
+ pub try_reserve: CustomSlotSupplierTryReserveCallback,
220
+ /// Called after successful reservation to mark slot as used. See [`SlotSupplier`](temporal_sdk_core_api::worker::SlotSupplier)
221
+ /// trait for details.
222
+ pub mark_used: CustomSlotSupplierMarkUsedCallback,
223
+ /// Called to free a previously reserved slot.
224
+ pub release: CustomSlotSupplierReleaseCallback,
225
+ /// Called to retrieve the number of available slots if known. If the implementation knows how
226
+ /// many slots are available at the moment, it should set the value behind the `available_slots`
227
+ /// pointer and return true. If that number is unknown, it should return false.
228
+ ///
229
+ /// This function pointer can be set to null. It will be treated as if the number of available
230
+ /// slots is never known.
231
+ pub available_slots: CustomSlotSupplierAvailableSlotsCallback,
232
+ /// Called when the slot supplier is being dropped. All resources should be freed.
233
+ pub free: CustomSlotSupplierFreeCallback,
234
+ /// Passed as an extra argument to the callbacks.
235
+ pub user_data: *mut libc::c_void,
188
236
  }
189
237
 
190
238
  impl CustomSlotSupplierCallbacksImpl {
@@ -221,8 +269,6 @@ pub struct SlotReserveCtx {
221
269
  pub worker_identity: ByteArrayRef,
222
270
  pub worker_build_id: ByteArrayRef,
223
271
  pub is_sticky: bool,
224
- // The C# side will store a pointer here to the cancellation token source
225
- pub token_src: *mut libc::c_void,
226
272
  }
227
273
  unsafe impl Send for SlotReserveCtx {}
228
274
 
@@ -247,31 +293,67 @@ pub enum SlotInfo {
247
293
  #[repr(C)]
248
294
  pub struct SlotMarkUsedCtx {
249
295
  pub slot_info: SlotInfo,
250
- /// C# id for the slot permit.
296
+ /// Lang-issued permit ID.
251
297
  pub slot_permit: usize,
252
298
  }
253
299
 
254
300
  #[repr(C)]
255
301
  pub struct SlotReleaseCtx {
256
302
  pub slot_info: *const SlotInfo,
257
- /// C# id for the slot permit.
303
+ /// Lang-issued permit ID.
258
304
  pub slot_permit: usize,
259
305
  }
260
306
 
261
- struct CancelReserveGuard {
262
- token_src: *mut libc::c_void,
263
- callback: CustomCancelReserveCallback,
307
+ pub struct SlotReserveCompletionCtx {
308
+ state: AtomicCell<SlotReserveOperationState>,
309
+ notify: Notify,
264
310
  }
265
- impl Drop for CancelReserveGuard {
311
+
312
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
313
+ enum SlotReserveOperationState {
314
+ Pending,
315
+ Cancelled,
316
+ Completed(NonZero<usize>),
317
+ }
318
+
319
+ struct CancelReserveGuard<'a, SK: SlotKind + Send + Sync> {
320
+ slot_supplier: &'a CustomSlotSupplier<SK>,
321
+ completion_ctx: Arc<SlotReserveCompletionCtx>,
322
+ completed: bool,
323
+ }
324
+
325
+ impl<'a, SK: SlotKind + Send + Sync> Drop for CancelReserveGuard<'a, SK> {
266
326
  fn drop(&mut self) {
267
- if !self.token_src.is_null() {
327
+ // do not cancel if already completed
328
+ if !self.completed {
329
+ let state = self
330
+ .completion_ctx
331
+ .state
332
+ .swap(SlotReserveOperationState::Cancelled);
268
333
  unsafe {
269
- (self.callback)(self.token_src);
334
+ let inner = &*self.slot_supplier.inner.0;
335
+ match state {
336
+ SlotReserveOperationState::Cancelled => {
337
+ // This situation should never happen, but on the other hand, it doesn't
338
+ // result in any unsafety, deadlock or leak. It's safe to ignore it, but in
339
+ // debug builds we'd like to know it happened.
340
+ debug_assert!(false, "slot reservation cancelled twice")
341
+ }
342
+ SlotReserveOperationState::Pending => {
343
+ (inner.cancel_reserve)(Arc::as_ptr(&self.completion_ctx), inner.user_data)
344
+ }
345
+ SlotReserveOperationState::Completed(slot_permit) => (inner.release)(
346
+ &SlotReleaseCtx {
347
+ slot_info: std::ptr::null(),
348
+ slot_permit: slot_permit.into(),
349
+ },
350
+ inner.user_data,
351
+ ),
352
+ }
270
353
  }
271
354
  }
272
355
  }
273
356
  }
274
- unsafe impl Send for CancelReserveGuard {}
275
357
 
276
358
  #[async_trait::async_trait]
277
359
  impl<SK: SlotKind + Send + Sync> temporal_sdk_core_api::worker::SlotSupplier
@@ -280,36 +362,53 @@ impl<SK: SlotKind + Send + Sync> temporal_sdk_core_api::worker::SlotSupplier
280
362
  type SlotKind = SK;
281
363
 
282
364
  async fn reserve_slot(&self, ctx: &dyn SlotReservationContext) -> SlotSupplierPermit {
283
- let (tx, rx) = oneshot::channel();
284
365
  let ctx = Self::convert_reserve_ctx(ctx);
285
- let tx = Box::into_raw(Box::new(tx)) as *mut libc::c_void;
366
+ let completion_ctx = Arc::new(SlotReserveCompletionCtx {
367
+ state: AtomicCell::new(SlotReserveOperationState::Pending),
368
+ notify: Notify::new(),
369
+ });
286
370
  unsafe {
287
- let _drop_guard = CancelReserveGuard {
288
- token_src: ctx.token_src,
289
- callback: (*self.inner.0).cancel_reserve,
290
- };
291
- ((*self.inner.0).reserve)(&ctx, tx);
292
- rx.await.expect("reserve channel is not closed")
371
+ let inner = &*self.inner.0;
372
+ (inner.reserve)(&ctx, Arc::into_raw(completion_ctx.clone()), inner.user_data);
373
+ }
374
+ let mut guard = CancelReserveGuard {
375
+ slot_supplier: self,
376
+ completion_ctx,
377
+ completed: false,
378
+ };
379
+ // if the future is dropped before this await resolves, the guard is dropped which triggers cancellation
380
+ guard.completion_ctx.notify.notified().await;
381
+ guard.completed = true;
382
+ match guard.completion_ctx.state.load() {
383
+ SlotReserveOperationState::Completed(permit_id) => {
384
+ SlotSupplierPermit::with_user_data::<usize>(permit_id.get())
385
+ }
386
+ other => panic!("Unexpected slot reservation state: expected Completed, got {other:?}"),
293
387
  }
294
388
  }
295
389
 
296
390
  fn try_reserve_slot(&self, ctx: &dyn SlotReservationContext) -> Option<SlotSupplierPermit> {
297
391
  let ctx = Self::convert_reserve_ctx(ctx);
298
- let permit_id = unsafe { ((*self.inner.0).try_reserve)(&ctx) };
392
+ let permit_id = unsafe { ((*self.inner.0).try_reserve)(&ctx, (*self.inner.0).user_data) };
299
393
  if permit_id == 0 {
300
394
  None
301
395
  } else {
302
- Some(SlotSupplierPermit::with_user_data(permit_id))
396
+ Some(SlotSupplierPermit::with_user_data::<usize>(permit_id))
303
397
  }
304
398
  }
305
399
 
306
400
  fn mark_slot_used(&self, ctx: &dyn SlotMarkUsedContext<SlotKind = Self::SlotKind>) {
307
401
  let ctx = SlotMarkUsedCtx {
308
402
  slot_info: Self::convert_slot_info(ctx.info().downcast()),
309
- slot_permit: ctx.permit().user_data::<usize>().copied().unwrap_or(0),
403
+ slot_permit: ctx
404
+ .permit()
405
+ .user_data::<usize>()
406
+ .copied()
407
+ .expect("permit user data should be usize"),
310
408
  };
311
409
  unsafe {
312
- ((*self.inner.0).mark_used)(&ctx);
410
+ let inner = &*self.inner.0;
411
+ (inner.mark_used)(&ctx, inner.user_data);
313
412
  }
314
413
  }
315
414
 
@@ -321,15 +420,26 @@ impl<SK: SlotKind + Send + Sync> temporal_sdk_core_api::worker::SlotSupplier
321
420
  }
322
421
  let ctx = SlotReleaseCtx {
323
422
  slot_info: info_ptr,
324
- slot_permit: ctx.permit().user_data::<usize>().copied().unwrap_or(0),
423
+ slot_permit: ctx
424
+ .permit()
425
+ .user_data::<usize>()
426
+ .copied()
427
+ .expect("permit user data should be usize"),
325
428
  };
326
429
  unsafe {
327
- ((*self.inner.0).release)(&ctx);
430
+ let inner = &*self.inner.0;
431
+ (inner.release)(&ctx, inner.user_data);
328
432
  }
329
433
  }
330
434
 
331
435
  fn available_slots(&self) -> Option<usize> {
332
- None
436
+ unsafe {
437
+ let inner = &*self.inner.0;
438
+ inner.available_slots.and_then(|f| {
439
+ let mut available_slots = 0;
440
+ f(&mut available_slots, inner.user_data).then_some(available_slots)
441
+ })
442
+ }
333
443
  }
334
444
  }
335
445
 
@@ -358,7 +468,6 @@ impl<SK: SlotKind + Send + Sync> CustomSlotSupplier<SK> {
358
468
  ByteArrayRef::empty()
359
469
  },
360
470
  is_sticky: ctx.is_sticky(),
361
- token_src: std::ptr::null_mut(),
362
471
  }
363
472
  }
364
473
 
@@ -378,7 +487,7 @@ impl<SK: SlotKind + Send + Sync> CustomSlotSupplier<SK> {
378
487
  }
379
488
  temporal_sdk_core_api::worker::SlotInfo::Nexus(n) => SlotInfo::NexusSlotInfo {
380
489
  operation: n.operation.as_str().into(),
381
- service: n.operation.as_str().into(),
490
+ service: n.service.as_str().into(),
382
491
  },
383
492
  }
384
493
  }
@@ -558,7 +667,7 @@ pub extern "C" fn temporal_core_worker_poll_workflow_activation(
558
667
  worker
559
668
  .runtime
560
669
  .clone()
561
- .alloc_utf8(&format!("Poll failure: {err}"))
670
+ .alloc_utf8(&format!("Workflow polling failure: {err}"))
562
671
  .into_raw()
563
672
  .cast_const(),
564
673
  ),
@@ -592,7 +701,41 @@ pub extern "C" fn temporal_core_worker_poll_activity_task(
592
701
  worker
593
702
  .runtime
594
703
  .clone()
595
- .alloc_utf8(&format!("Poll failure: {err}"))
704
+ .alloc_utf8(&format!("Activity polling failure: {err}"))
705
+ .into_raw()
706
+ .cast_const(),
707
+ ),
708
+ };
709
+ unsafe {
710
+ callback(user_data.into(), success, fail);
711
+ }
712
+ });
713
+ }
714
+
715
+ #[unsafe(no_mangle)]
716
+ pub extern "C" fn temporal_core_worker_poll_nexus_task(
717
+ worker: *mut Worker,
718
+ user_data: *mut libc::c_void,
719
+ callback: WorkerPollCallback,
720
+ ) {
721
+ let worker = unsafe { &*worker };
722
+ let user_data = UserDataHandle(user_data);
723
+ let core_worker = worker.worker.as_ref().unwrap().clone();
724
+ worker.runtime.core.tokio_handle().spawn(async move {
725
+ let (success, fail) = match core_worker.poll_nexus_task().await {
726
+ Ok(task) => (
727
+ ByteArray::from_vec(task.encode_to_vec())
728
+ .into_raw()
729
+ .cast_const(),
730
+ std::ptr::null(),
731
+ ),
732
+ Err(PollError::ShutDown) => (std::ptr::null(), std::ptr::null()),
733
+ Err(err) => (
734
+ std::ptr::null(),
735
+ worker
736
+ .runtime
737
+ .clone()
738
+ .alloc_utf8(&format!("Nexus polling failure: {err}"))
596
739
  .into_raw()
597
740
  .cast_const(),
598
741
  ),
@@ -620,7 +763,7 @@ pub extern "C" fn temporal_core_worker_complete_workflow_activation(
620
763
  worker
621
764
  .runtime
622
765
  .clone()
623
- .alloc_utf8(&format!("Decode failure: {err}"))
766
+ .alloc_utf8(&format!("Workflow task decode failure: {err}"))
624
767
  .into_raw(),
625
768
  );
626
769
  }
@@ -635,7 +778,7 @@ pub extern "C" fn temporal_core_worker_complete_workflow_activation(
635
778
  Err(err) => worker
636
779
  .runtime
637
780
  .clone()
638
- .alloc_utf8(&format!("Completion failure: {err}"))
781
+ .alloc_utf8(&format!("Workflow completion failure: {err}"))
639
782
  .into_raw()
640
783
  .cast_const(),
641
784
  };
@@ -662,7 +805,7 @@ pub extern "C" fn temporal_core_worker_complete_activity_task(
662
805
  worker
663
806
  .runtime
664
807
  .clone()
665
- .alloc_utf8(&format!("Decode failure: {err}"))
808
+ .alloc_utf8(&format!("Activity task decode failure: {err}"))
666
809
  .into_raw(),
667
810
  );
668
811
  }
@@ -677,7 +820,49 @@ pub extern "C" fn temporal_core_worker_complete_activity_task(
677
820
  Err(err) => worker
678
821
  .runtime
679
822
  .clone()
680
- .alloc_utf8(&format!("Completion failure: {err}"))
823
+ .alloc_utf8(&format!("Activity completion failure: {err}"))
824
+ .into_raw()
825
+ .cast_const(),
826
+ };
827
+ unsafe {
828
+ callback(user_data.into(), fail);
829
+ }
830
+ });
831
+ }
832
+
833
+ #[unsafe(no_mangle)]
834
+ pub extern "C" fn temporal_core_worker_complete_nexus_task(
835
+ worker: *mut Worker,
836
+ completion: ByteArrayRef,
837
+ user_data: *mut libc::c_void,
838
+ callback: WorkerCallback,
839
+ ) {
840
+ let worker = unsafe { &*worker };
841
+ let completion = match NexusTaskCompletion::decode(completion.to_slice()) {
842
+ Ok(completion) => completion,
843
+ Err(err) => {
844
+ unsafe {
845
+ callback(
846
+ user_data,
847
+ worker
848
+ .runtime
849
+ .clone()
850
+ .alloc_utf8(&format!("Nexus task decode failure: {err}"))
851
+ .into_raw(),
852
+ );
853
+ }
854
+ return;
855
+ }
856
+ };
857
+ let user_data = UserDataHandle(user_data);
858
+ let core_worker = worker.worker.as_ref().unwrap().clone();
859
+ worker.runtime.core.tokio_handle().spawn(async move {
860
+ let fail = match core_worker.complete_nexus_task(completion).await {
861
+ Ok(_) => std::ptr::null(),
862
+ Err(err) => worker
863
+ .runtime
864
+ .clone()
865
+ .alloc_utf8(&format!("Nexus completion failure: {err}"))
681
866
  .into_raw()
682
867
  .cast_const(),
683
868
  };
@@ -707,7 +892,7 @@ pub extern "C" fn temporal_core_worker_record_activity_heartbeat(
707
892
  Err(err) => worker
708
893
  .runtime
709
894
  .clone()
710
- .alloc_utf8(&format!("Decode failure: {err}"))
895
+ .alloc_utf8(&format!("Activity heartbeat decode failure: {err}"))
711
896
  .into_raw(),
712
897
  }
713
898
  }
@@ -861,29 +1046,88 @@ pub extern "C" fn temporal_core_worker_replay_push(
861
1046
  }
862
1047
  }
863
1048
 
1049
+ /// Completes asynchronous slot reservation started by a call to [`CustomSlotSupplierCallbacks::reserve`].
1050
+ ///
1051
+ /// `completion_ctx` must be the same as the one passed to the matching [`reserve`](CustomSlotSupplierCallbacks::reserve)
1052
+ /// call. `permit_id` is arbitrary, but must be unique among live reservations as it's later used
1053
+ /// for [`mark_used`](CustomSlotSupplierCallbacks::mark_used) and [`release`](CustomSlotSupplierCallbacks::release)
1054
+ /// callbacks.
1055
+ ///
1056
+ /// This function returns true if the reservation was completed successfully, or false if the
1057
+ /// reservation was cancelled before completion. If this function returns false, the implementation
1058
+ /// should call [`temporal_core_complete_async_cancel_reserve`] with the same `completion_ctx`.
1059
+ ///
1060
+ /// **Caution:** if this function returns true, `completion_ctx` gets freed. Afterwards, calling
1061
+ /// either [`temporal_core_complete_async_reserve`] or [`temporal_core_complete_async_cancel_reserve`]
1062
+ /// with the same `completion_ctx` will cause **memory corruption!**
864
1063
  #[unsafe(no_mangle)]
865
1064
  pub extern "C" fn temporal_core_complete_async_reserve(
866
- sender: *mut libc::c_void,
1065
+ completion_ctx: *const SlotReserveCompletionCtx,
867
1066
  permit_id: usize,
868
- ) {
869
- if !sender.is_null() {
870
- unsafe {
871
- let sender = Box::from_raw(sender as *mut oneshot::Sender<SlotSupplierPermit>);
872
- let permit = SlotSupplierPermit::with_user_data(permit_id);
873
- let _ = sender.send(permit);
1067
+ ) -> bool {
1068
+ if completion_ctx.is_null() {
1069
+ panic!("completion_ctx is null");
1070
+ }
1071
+ let permit_id =
1072
+ NonZero::new(permit_id).expect("permit_id cannot be 0 on successful reservation");
1073
+ let prev_state = unsafe {
1074
+ // Not turning completion_ctx into Arc yet as we only want to deallocate it on success
1075
+ (*completion_ctx).state.compare_exchange(
1076
+ SlotReserveOperationState::Pending,
1077
+ SlotReserveOperationState::Completed(permit_id),
1078
+ )
1079
+ };
1080
+ match prev_state {
1081
+ Ok(_) => {
1082
+ let completion_ctx = unsafe { Arc::from_raw(completion_ctx) };
1083
+ completion_ctx.notify.notify_one();
1084
+ true
874
1085
  }
875
- } else {
876
- panic!("ReserveSlot sender must not be null!");
1086
+ Err(SlotReserveOperationState::Cancelled) => false,
1087
+ Err(SlotReserveOperationState::Completed(prev_permit_id)) => {
1088
+ panic!(
1089
+ "temporal_core_complete_async_reserve called twice for the same reservation - first permit ID {prev_permit_id}, second permit ID {permit_id}"
1090
+ )
1091
+ }
1092
+ Err(SlotReserveOperationState::Pending) => unreachable!(),
877
1093
  }
878
1094
  }
879
1095
 
1096
+ /// Completes cancellation of asynchronous slot reservation.
1097
+ ///
1098
+ /// Cancellation can only be initiated by Core. It's done by calling [`CustomSlotSupplierCallbacks::cancel_reserve`]
1099
+ /// after an earlier call to [`CustomSlotSupplierCallbacks::reserve`].
1100
+ ///
1101
+ /// `completion_ctx` must be the same as the one passed to the matching [`cancel_reserve`](CustomSlotSupplierCallbacks::cancel_reserve)
1102
+ /// call.
1103
+ ///
1104
+ /// This function returns true on successful cancellation, or false if cancellation was not
1105
+ /// requested for the given `completion_ctx`. A false value indicates there's likely a logic bug in
1106
+ /// the implementation where it doesn't correctly wait for [`cancel_reserve`](CustomSlotSupplierCallbacks::cancel_reserve)
1107
+ /// callback to be called.
1108
+ ///
1109
+ /// **Caution:** if this function returns true, `completion_ctx` gets freed. Afterwards, calling
1110
+ /// either [`temporal_core_complete_async_reserve`] or [`temporal_core_complete_async_cancel_reserve`]
1111
+ /// with the same `completion_ctx` will cause **memory corruption!**
880
1112
  #[unsafe(no_mangle)]
881
- pub extern "C" fn temporal_core_set_reserve_cancel_target(
882
- ctx: *mut SlotReserveCtx,
883
- token_ptr: *mut libc::c_void,
884
- ) {
885
- if let Some(ctx) = unsafe { ctx.as_mut() } {
886
- ctx.token_src = token_ptr;
1113
+ pub extern "C" fn temporal_core_complete_async_cancel_reserve(
1114
+ completion_ctx: *const SlotReserveCompletionCtx,
1115
+ ) -> bool {
1116
+ if completion_ctx.is_null() {
1117
+ panic!("completion_ctx is null");
1118
+ }
1119
+ let state = unsafe { (*completion_ctx).state.load() };
1120
+ match state {
1121
+ SlotReserveOperationState::Cancelled => {
1122
+ drop(unsafe { Arc::from_raw(completion_ctx) });
1123
+ true
1124
+ }
1125
+ SlotReserveOperationState::Pending => false,
1126
+ SlotReserveOperationState::Completed(permit_id) => {
1127
+ panic!(
1128
+ "temporal_core_complete_async_cancel_reserve called on completed reservation - permit ID {permit_id}"
1129
+ )
1130
+ }
887
1131
  }
888
1132
  }
889
1133
 
@@ -958,6 +1202,7 @@ impl TryFrom<&WorkerOptions> for temporal_sdk_core::WorkerConfig {
958
1202
  .workflow_task_poller_behavior(temporal_sdk_core_api::worker::PollerBehavior::try_from(&opt.workflow_task_poller_behavior)?)
959
1203
  .nonsticky_to_sticky_poll_ratio(opt.nonsticky_to_sticky_poll_ratio)
960
1204
  .activity_task_poller_behavior(temporal_sdk_core_api::worker::PollerBehavior::try_from(&opt.activity_task_poller_behavior)?)
1205
+ .nexus_task_poller_behavior(temporal_sdk_core_api::worker::PollerBehavior::try_from(&opt.nexus_task_poller_behavior)?)
961
1206
  .workflow_failure_errors(if opt.nondeterminism_as_workflow_fail {
962
1207
  HashSet::from([WorkflowErrorType::Nondeterminism])
963
1208
  } else {
@@ -1003,10 +1248,17 @@ impl TryFrom<&TunerHolder> for temporal_sdk_core::TunerHolder {
1003
1248
  } else {
1004
1249
  None
1005
1250
  };
1251
+ let maybe_nexus_resource_opts =
1252
+ if let SlotSupplier::ResourceBased(ref ss) = holder.nexus_task_slot_supplier {
1253
+ Some(&ss.tuner_options)
1254
+ } else {
1255
+ None
1256
+ };
1006
1257
  let all_resource_opts = [
1007
1258
  maybe_wf_resource_opts,
1008
1259
  maybe_act_resource_opts,
1009
1260
  maybe_local_act_resource_opts,
1261
+ maybe_nexus_resource_opts,
1010
1262
  ];
1011
1263
  let mut set_resource_opts = all_resource_opts.iter().flatten();
1012
1264
  let first = set_resource_opts.next();
@@ -1033,6 +1285,7 @@ impl TryFrom<&TunerHolder> for temporal_sdk_core::TunerHolder {
1033
1285
  .workflow_slot_options(holder.workflow_slot_supplier.try_into()?)
1034
1286
  .activity_slot_options(holder.activity_slot_supplier.try_into()?)
1035
1287
  .local_activity_slot_options(holder.local_activity_slot_supplier.try_into()?)
1288
+ .nexus_slot_options(holder.nexus_task_slot_supplier.try_into()?)
1036
1289
  .build()
1037
1290
  .context("Invalid tuner holder options")?
1038
1291
  .build_tuner_holder()
@@ -434,6 +434,7 @@ impl StateMachineDefinition {
434
434
  }
435
435
  };
436
436
  let mut multi_dest_enums = vec![];
437
+ let mut multi_dest_enum_names = HashSet::new();
437
438
  let state_branches: Vec<_> = statemap.into_iter().map(|(from, transitions)| {
438
439
  let occupied_current_state = quote! { Some(#state_enum_name::#from(state_data)) };
439
440
  // Merge transition dest states with the same handler
@@ -468,7 +469,11 @@ impl StateMachineDefinition {
468
469
  }
469
470
  }
470
471
  };
471
- multi_dest_enums.push(multi_dest_enum);
472
+ // Deduplicate; two different events may each result in a transition
473
+ // set with the same set of dest states
474
+ if multi_dest_enum_names.insert(enum_ident.clone()) {
475
+ multi_dest_enums.push(multi_dest_enum);
476
+ }
472
477
  quote! {
473
478
  #transition_result_name<#enum_ident>
474
479
  }
@@ -1,11 +1,11 @@
1
1
  error: Duplicate transitions are not allowed!
2
2
  --> $DIR/dupe_transitions_fail.rs:5:1
3
3
  |
4
- 5 | / fsm! {
5
- 6 | | name SimpleMachine; command SimpleMachineCommand; error Infallible;
6
- 7 | |
7
- 8 | | One --(A)--> Two;
8
- 9 | | One --(A)--> Two;
4
+ 5 | / fsm! {
5
+ 6 | | name SimpleMachine; command SimpleMachineCommand; error Infallible;
6
+ 7 | |
7
+ 8 | | One --(A)--> Two;
8
+ 9 | | One --(A)--> Two;
9
9
  10 | | }
10
10
  | |_^
11
11
  |
@@ -16,9 +16,15 @@ anyhow = "1.0"
16
16
  derive_more = { workspace = true }
17
17
  futures-util = { version = "0.3", default-features = false }
18
18
  parking_lot = { version = "0.12", features = ["send_guard"] }
19
- prost-types = { version = "0.6", package = "prost-wkt-types" }
19
+ prost-types = { workspace = true }
20
20
  serde = "1.0"
21
- tokio = { version = "1.26", features = ["rt", "rt-multi-thread", "parking_lot", "time", "fs"] }
21
+ tokio = { version = "1.47", features = [
22
+ "rt",
23
+ "rt-multi-thread",
24
+ "parking_lot",
25
+ "time",
26
+ "fs",
27
+ ] }
22
28
  tokio-util = { version = "0.7" }
23
29
  tokio-stream = "0.1"
24
30
  tracing = "0.1"
@@ -56,7 +56,7 @@ pub struct ActivityInfo {
56
56
  impl ActContext {
57
57
  /// Construct new Activity Context, returning the context and the first argument to the activity
58
58
  /// (which may be a default [Payload]).
59
- pub(crate) fn new(
59
+ pub fn new(
60
60
  worker: Arc<dyn Worker>,
61
61
  app_data: Arc<AppData>,
62
62
  cancellation_token: CancellationToken,