@temporalio/core-bridge 1.15.0 → 1.16.1

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 (209) hide show
  1. package/Cargo.lock +172 -70
  2. package/lib/native.d.ts +1 -1
  3. package/package.json +2 -2
  4. package/releases/aarch64-apple-darwin/index.node +0 -0
  5. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  6. package/releases/x86_64-apple-darwin/index.node +0 -0
  7. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  8. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  9. package/sdk-core/.github/workflows/per-pr.yml +6 -6
  10. package/sdk-core/AGENTS.md +41 -30
  11. package/sdk-core/Cargo.toml +3 -0
  12. package/sdk-core/README.md +15 -9
  13. package/sdk-core/crates/client/Cargo.toml +4 -0
  14. package/sdk-core/crates/client/README.md +139 -0
  15. package/sdk-core/crates/client/src/async_activity_handle.rs +297 -0
  16. package/sdk-core/crates/client/src/callback_based.rs +7 -0
  17. package/sdk-core/crates/client/src/errors.rs +294 -0
  18. package/sdk-core/crates/client/src/{raw.rs → grpc.rs} +280 -159
  19. package/sdk-core/crates/client/src/lib.rs +920 -1326
  20. package/sdk-core/crates/client/src/metrics.rs +24 -33
  21. package/sdk-core/crates/client/src/options_structs.rs +457 -0
  22. package/sdk-core/crates/client/src/replaceable.rs +5 -4
  23. package/sdk-core/crates/client/src/request_extensions.rs +8 -9
  24. package/sdk-core/crates/client/src/retry.rs +99 -54
  25. package/sdk-core/crates/client/src/{worker/mod.rs → worker.rs} +1 -1
  26. package/sdk-core/crates/client/src/workflow_handle.rs +826 -0
  27. package/sdk-core/crates/common/Cargo.toml +61 -2
  28. package/sdk-core/crates/common/build.rs +742 -12
  29. package/sdk-core/crates/common/protos/api_upstream/.github/workflows/ci.yml +2 -0
  30. package/sdk-core/crates/common/protos/api_upstream/Makefile +2 -1
  31. package/sdk-core/crates/common/protos/api_upstream/buf.yaml +0 -3
  32. package/sdk-core/crates/common/protos/api_upstream/cmd/check-path-conflicts/main.go +137 -0
  33. package/sdk-core/crates/common/protos/api_upstream/openapi/openapiv2.json +1166 -770
  34. package/sdk-core/crates/common/protos/api_upstream/openapi/openapiv3.yaml +1243 -750
  35. package/sdk-core/crates/common/protos/api_upstream/temporal/api/deployment/v1/message.proto +2 -2
  36. package/sdk-core/crates/common/protos/api_upstream/temporal/api/enums/v1/workflow.proto +4 -3
  37. package/sdk-core/crates/common/protos/api_upstream/temporal/api/failure/v1/message.proto +1 -0
  38. package/sdk-core/crates/common/protos/api_upstream/temporal/api/history/v1/message.proto +4 -0
  39. package/sdk-core/crates/common/protos/api_upstream/temporal/api/namespace/v1/message.proto +6 -0
  40. package/sdk-core/crates/common/protos/api_upstream/temporal/api/nexus/v1/message.proto +16 -1
  41. package/sdk-core/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +64 -6
  42. package/sdk-core/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +88 -33
  43. package/sdk-core/crates/common/protos/local/temporal/sdk/core/nexus/nexus.proto +4 -2
  44. package/sdk-core/crates/common/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +4 -0
  45. package/sdk-core/crates/common/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +5 -5
  46. package/sdk-core/crates/common/src/activity_definition.rs +20 -0
  47. package/sdk-core/crates/common/src/data_converters.rs +770 -0
  48. package/sdk-core/crates/common/src/envconfig.rs +5 -0
  49. package/sdk-core/crates/common/src/lib.rs +15 -211
  50. package/sdk-core/crates/common/src/payload_visitor.rs +648 -0
  51. package/sdk-core/crates/common/src/priority.rs +110 -0
  52. package/sdk-core/crates/common/src/protos/canned_histories.rs +3 -0
  53. package/sdk-core/crates/common/src/protos/history_builder.rs +45 -0
  54. package/sdk-core/crates/common/src/protos/history_info.rs +2 -0
  55. package/sdk-core/crates/common/src/protos/mod.rs +122 -27
  56. package/sdk-core/crates/common/src/protos/task_token.rs +3 -3
  57. package/sdk-core/crates/common/src/protos/utilities.rs +11 -0
  58. package/sdk-core/crates/{sdk-core → common}/src/telemetry/log_export.rs +5 -7
  59. package/sdk-core/crates/common/src/telemetry/metrics/core.rs +125 -0
  60. package/sdk-core/crates/common/src/telemetry/metrics.rs +268 -223
  61. package/sdk-core/crates/{sdk-core → common}/src/telemetry/otel.rs +8 -13
  62. package/sdk-core/crates/{sdk-core → common}/src/telemetry/prometheus_meter.rs +49 -50
  63. package/sdk-core/crates/{sdk-core → common}/src/telemetry/prometheus_server.rs +2 -3
  64. package/sdk-core/crates/common/src/telemetry.rs +264 -4
  65. package/sdk-core/crates/common/src/worker.rs +68 -603
  66. package/sdk-core/crates/common/src/workflow_definition.rs +60 -0
  67. package/sdk-core/crates/macros/Cargo.toml +5 -1
  68. package/sdk-core/crates/macros/src/activities_definitions.rs +585 -0
  69. package/sdk-core/crates/macros/src/fsm_impl.rs +507 -0
  70. package/sdk-core/crates/macros/src/lib.rs +138 -512
  71. package/sdk-core/crates/macros/src/macro_utils.rs +106 -0
  72. package/sdk-core/crates/macros/src/workflow_definitions.rs +1224 -0
  73. package/sdk-core/crates/sdk/Cargo.toml +19 -6
  74. package/sdk-core/crates/sdk/README.md +415 -0
  75. package/sdk-core/crates/sdk/src/activities.rs +417 -0
  76. package/sdk-core/crates/sdk/src/interceptors.rs +1 -1
  77. package/sdk-core/crates/sdk/src/lib.rs +757 -442
  78. package/sdk-core/crates/sdk/src/workflow_context/options.rs +45 -35
  79. package/sdk-core/crates/sdk/src/workflow_context.rs +1033 -289
  80. package/sdk-core/crates/sdk/src/workflow_future.rs +277 -213
  81. package/sdk-core/crates/sdk/src/workflows.rs +711 -0
  82. package/sdk-core/crates/sdk-core/Cargo.toml +57 -64
  83. package/sdk-core/crates/sdk-core/benches/workflow_replay_bench.rs +41 -35
  84. package/sdk-core/crates/sdk-core/machine_coverage/ActivityMachine_Coverage.puml +1 -1
  85. package/sdk-core/crates/sdk-core/src/abstractions.rs +6 -10
  86. package/sdk-core/crates/sdk-core/src/core_tests/activity_tasks.rs +6 -5
  87. package/sdk-core/crates/sdk-core/src/core_tests/mod.rs +13 -15
  88. package/sdk-core/crates/sdk-core/src/core_tests/queries.rs +21 -25
  89. package/sdk-core/crates/sdk-core/src/core_tests/replay_flag.rs +7 -10
  90. package/sdk-core/crates/sdk-core/src/core_tests/updates.rs +14 -17
  91. package/sdk-core/crates/sdk-core/src/core_tests/workers.rs +493 -26
  92. package/sdk-core/crates/sdk-core/src/core_tests/workflow_tasks.rs +4 -8
  93. package/sdk-core/crates/sdk-core/src/ephemeral_server/mod.rs +7 -7
  94. package/sdk-core/crates/sdk-core/src/histfetch.rs +20 -10
  95. package/sdk-core/crates/sdk-core/src/lib.rs +41 -111
  96. package/sdk-core/crates/sdk-core/src/pollers/mod.rs +4 -9
  97. package/sdk-core/crates/sdk-core/src/pollers/poll_buffer.rs +118 -19
  98. package/sdk-core/crates/sdk-core/src/protosext/mod.rs +2 -2
  99. package/sdk-core/crates/sdk-core/src/replay/mod.rs +14 -5
  100. package/sdk-core/crates/sdk-core/src/telemetry/metrics.rs +179 -196
  101. package/sdk-core/crates/sdk-core/src/telemetry/mod.rs +3 -280
  102. package/sdk-core/crates/sdk-core/src/test_help/integ_helpers.rs +6 -9
  103. package/sdk-core/crates/sdk-core/src/test_help/unit_helpers.rs +3 -6
  104. package/sdk-core/crates/sdk-core/src/worker/activities/local_activities.rs +11 -14
  105. package/sdk-core/crates/sdk-core/src/worker/activities.rs +16 -19
  106. package/sdk-core/crates/sdk-core/src/worker/client/mocks.rs +9 -5
  107. package/sdk-core/crates/sdk-core/src/worker/client.rs +103 -81
  108. package/sdk-core/crates/sdk-core/src/worker/heartbeat.rs +7 -11
  109. package/sdk-core/crates/sdk-core/src/worker/mod.rs +1124 -229
  110. package/sdk-core/crates/sdk-core/src/worker/nexus.rs +145 -23
  111. package/sdk-core/crates/sdk-core/src/worker/slot_provider.rs +2 -2
  112. package/sdk-core/crates/sdk-core/src/worker/tuner/fixed_size.rs +2 -2
  113. package/sdk-core/crates/sdk-core/src/worker/tuner/resource_based.rs +13 -13
  114. package/sdk-core/crates/sdk-core/src/worker/tuner.rs +28 -8
  115. package/sdk-core/crates/sdk-core/src/worker/workflow/driven_workflow.rs +9 -3
  116. package/sdk-core/crates/sdk-core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +21 -22
  117. package/sdk-core/crates/sdk-core/src/worker/workflow/machines/workflow_machines.rs +19 -4
  118. package/sdk-core/crates/sdk-core/src/worker/workflow/managed_run.rs +14 -18
  119. package/sdk-core/crates/sdk-core/src/worker/workflow/mod.rs +4 -6
  120. package/sdk-core/crates/sdk-core/src/worker/workflow/run_cache.rs +4 -7
  121. package/sdk-core/crates/sdk-core/src/worker/workflow/wft_extraction.rs +2 -4
  122. package/sdk-core/crates/sdk-core/src/worker/workflow/wft_poller.rs +8 -9
  123. package/sdk-core/crates/sdk-core/src/worker/workflow/workflow_stream.rs +1 -3
  124. package/sdk-core/crates/sdk-core/tests/activities_procmacro.rs +6 -0
  125. package/sdk-core/crates/sdk-core/tests/activities_trybuild/basic_pass.rs +54 -0
  126. package/sdk-core/crates/sdk-core/tests/activities_trybuild/invalid_self_type_fail.rs +18 -0
  127. package/sdk-core/crates/sdk-core/tests/activities_trybuild/invalid_self_type_fail.stderr +5 -0
  128. package/sdk-core/crates/sdk-core/tests/activities_trybuild/missing_context_fail.rs +14 -0
  129. package/sdk-core/crates/sdk-core/tests/activities_trybuild/missing_context_fail.stderr +5 -0
  130. package/sdk-core/crates/sdk-core/tests/activities_trybuild/multi_arg_pass.rs +48 -0
  131. package/sdk-core/crates/sdk-core/tests/activities_trybuild/no_input_pass.rs +14 -0
  132. package/sdk-core/crates/sdk-core/tests/activities_trybuild/no_return_type_pass.rs +19 -0
  133. package/sdk-core/crates/sdk-core/tests/cloud_tests.rs +14 -5
  134. package/sdk-core/crates/sdk-core/tests/common/activity_functions.rs +55 -0
  135. package/sdk-core/crates/sdk-core/tests/common/mod.rs +241 -196
  136. package/sdk-core/crates/sdk-core/tests/common/workflows.rs +41 -28
  137. package/sdk-core/crates/sdk-core/tests/global_metric_tests.rs +3 -5
  138. package/sdk-core/crates/sdk-core/tests/heavy_tests/fuzzy_workflow.rs +73 -64
  139. package/sdk-core/crates/sdk-core/tests/heavy_tests.rs +298 -252
  140. package/sdk-core/crates/sdk-core/tests/integ_tests/async_activity_client_tests.rs +230 -0
  141. package/sdk-core/crates/sdk-core/tests/integ_tests/client_tests.rs +94 -57
  142. package/sdk-core/crates/sdk-core/tests/integ_tests/data_converter_tests.rs +381 -0
  143. package/sdk-core/crates/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +16 -12
  144. package/sdk-core/crates/sdk-core/tests/integ_tests/heartbeat_tests.rs +48 -40
  145. package/sdk-core/crates/sdk-core/tests/integ_tests/metrics_tests.rs +327 -255
  146. package/sdk-core/crates/sdk-core/tests/integ_tests/pagination_tests.rs +50 -45
  147. package/sdk-core/crates/sdk-core/tests/integ_tests/polling_tests.rs +147 -126
  148. package/sdk-core/crates/sdk-core/tests/integ_tests/queries_tests.rs +103 -89
  149. package/sdk-core/crates/sdk-core/tests/integ_tests/update_tests.rs +609 -453
  150. package/sdk-core/crates/sdk-core/tests/integ_tests/visibility_tests.rs +80 -62
  151. package/sdk-core/crates/sdk-core/tests/integ_tests/worker_heartbeat_tests.rs +360 -231
  152. package/sdk-core/crates/sdk-core/tests/integ_tests/worker_tests.rs +248 -185
  153. package/sdk-core/crates/sdk-core/tests/integ_tests/worker_versioning_tests.rs +52 -43
  154. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_client_tests.rs +180 -0
  155. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/activities.rs +428 -315
  156. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +82 -56
  157. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +56 -28
  158. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +364 -243
  159. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/client_interactions.rs +552 -0
  160. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +101 -42
  161. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +243 -147
  162. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/eager.rs +98 -28
  163. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +1475 -1036
  164. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +73 -41
  165. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +397 -238
  166. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/patches.rs +414 -189
  167. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/queries.rs +415 -0
  168. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/replay.rs +96 -36
  169. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/resets.rs +154 -137
  170. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/signals.rs +183 -105
  171. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +85 -38
  172. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/timers.rs +142 -40
  173. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +73 -54
  174. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests.rs +363 -226
  175. package/sdk-core/crates/sdk-core/tests/main.rs +17 -15
  176. package/sdk-core/crates/sdk-core/tests/manual_tests.rs +207 -152
  177. package/sdk-core/crates/sdk-core/tests/shared_tests/mod.rs +65 -34
  178. package/sdk-core/crates/sdk-core/tests/shared_tests/priority.rs +107 -84
  179. package/sdk-core/crates/sdk-core/tests/workflows_procmacro.rs +6 -0
  180. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/async_query_fail.rs +26 -0
  181. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/async_query_fail.stderr +5 -0
  182. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/basic_pass.rs +49 -0
  183. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/minimal_pass.rs +21 -0
  184. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/mut_query_fail.rs +26 -0
  185. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/mut_query_fail.stderr +5 -0
  186. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/sync_run_fail.rs +21 -0
  187. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/sync_run_fail.stderr +5 -0
  188. package/sdk-core/crates/sdk-core-c-bridge/Cargo.toml +7 -1
  189. package/sdk-core/crates/sdk-core-c-bridge/include/temporal-sdk-core-c-bridge.h +14 -14
  190. package/sdk-core/crates/sdk-core-c-bridge/src/client.rs +83 -74
  191. package/sdk-core/crates/sdk-core-c-bridge/src/metric.rs +9 -14
  192. package/sdk-core/crates/sdk-core-c-bridge/src/runtime.rs +1 -2
  193. package/sdk-core/crates/sdk-core-c-bridge/src/tests/context.rs +13 -13
  194. package/sdk-core/crates/sdk-core-c-bridge/src/tests/mod.rs +6 -6
  195. package/sdk-core/crates/sdk-core-c-bridge/src/tests/utils.rs +3 -4
  196. package/sdk-core/crates/sdk-core-c-bridge/src/worker.rs +62 -75
  197. package/sdk-core/rustfmt.toml +2 -1
  198. package/src/client.rs +205 -318
  199. package/src/metrics.rs +22 -30
  200. package/src/runtime.rs +4 -5
  201. package/src/worker.rs +16 -19
  202. package/ts/native.ts +1 -1
  203. package/sdk-core/crates/client/src/workflow_handle/mod.rs +0 -212
  204. package/sdk-core/crates/common/src/errors.rs +0 -85
  205. package/sdk-core/crates/common/tests/worker_task_types_test.rs +0 -129
  206. package/sdk-core/crates/sdk/src/activity_context.rs +0 -238
  207. package/sdk-core/crates/sdk/src/app_data.rs +0 -37
  208. package/sdk-core/crates/sdk-core/tests/integ_tests/activity_functions.rs +0 -5
  209. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/appdata_propagation.rs +0 -61
@@ -7,28 +7,36 @@ pub use options::{
7
7
 
8
8
  use crate::{
9
9
  CancelExternalWfResult, CancellableID, CancellableIDWithReason, CommandCreateRequest,
10
- CommandSubscribeChildWorkflowCompletion, IntoUpdateHandlerFunc, IntoUpdateValidatorFunc,
11
- NexusStartResult, RustWfCmd, SignalExternalWfResult, SupportsCancelReason, TimerResult,
12
- UnblockEvent, Unblockable, UpdateFunctions, workflow_context::options::IntoWorkflowCommand,
10
+ CommandSubscribeChildWorkflowCompletion, NexusStartResult, RustWfCmd, SignalExternalWfResult,
11
+ SupportsCancelReason, TimerResult, UnblockEvent, Unblockable,
12
+ workflow_context::options::IntoWorkflowCommand,
13
+ };
14
+ use futures_util::{
15
+ FutureExt,
16
+ future::{FusedFuture, Shared},
17
+ task::Context,
13
18
  };
14
- use futures_util::{FutureExt, Stream, StreamExt, future::Shared, task::Context};
15
- use parking_lot::{RwLock, RwLockReadGuard};
16
19
  use std::{
20
+ cell::{Ref, RefCell},
17
21
  collections::HashMap,
18
- future,
19
- future::Future,
22
+ future::{self, Future},
20
23
  marker::PhantomData,
21
- ops::Deref,
24
+ ops::{Deref, DerefMut},
22
25
  pin::Pin,
26
+ rc::Rc,
23
27
  sync::{
24
- Arc,
25
28
  atomic::{AtomicBool, Ordering},
26
29
  mpsc::{Receiver, Sender},
27
30
  },
28
- task::Poll,
31
+ task::{Poll, Waker},
29
32
  time::{Duration, SystemTime},
30
33
  };
31
34
  use temporalio_common::{
35
+ ActivityDefinition,
36
+ data_converters::{
37
+ GenericPayloadConverter, PayloadConversionError, PayloadConverter, SerializationContext,
38
+ SerializationContextData, TemporalDeserializable,
39
+ },
32
40
  protos::{
33
41
  coresdk::{
34
42
  activity_result::{ActivityResolution, activity_resolution},
@@ -48,144 +56,315 @@ use temporalio_common::{
48
56
  },
49
57
  temporal::api::{
50
58
  common::v1::{Memo, Payload, SearchAttributes},
59
+ failure::v1::Failure,
51
60
  sdk::v1::UserMetadata,
52
61
  },
53
62
  },
54
63
  worker::WorkerDeploymentVersion,
55
64
  };
56
- use tokio::sync::{mpsc, oneshot, watch};
57
- use tokio_stream::wrappers::UnboundedReceiverStream;
65
+ use tokio::sync::{oneshot, watch};
58
66
 
59
- /// Used within workflows to issue commands, get info, etc.
67
+ /// Non-generic base context containing all workflow execution infrastructure.
68
+ ///
69
+ /// This is used internally by futures and commands that don't need typed workflow state.
60
70
  #[derive(Clone)]
61
- pub struct WfContext {
71
+ pub struct BaseWorkflowContext {
72
+ inner: Rc<WorkflowContextInner>,
73
+ }
74
+ impl BaseWorkflowContext {
75
+ pub(crate) fn shared_mut(&self) -> impl DerefMut<Target = WorkflowContextSharedData> {
76
+ self.inner.shared.borrow_mut()
77
+ }
78
+
79
+ /// Create a read-only view of this context.
80
+ pub(crate) fn view(&self) -> WorkflowContextView {
81
+ WorkflowContextView::new(
82
+ self.inner.namespace.clone(),
83
+ self.inner.task_queue.clone(),
84
+ self.inner.run_id.clone(),
85
+ &self.inner.inital_information,
86
+ )
87
+ }
88
+ }
89
+
90
+ struct WorkflowContextInner {
62
91
  namespace: String,
63
92
  task_queue: String,
64
- inital_information: Arc<InitializeWorkflow>,
65
-
93
+ run_id: String,
94
+ inital_information: InitializeWorkflow,
66
95
  chan: Sender<RustWfCmd>,
67
96
  am_cancelled: watch::Receiver<Option<String>>,
68
- pub(crate) shared: Arc<RwLock<WfContextSharedData>>,
97
+ shared: RefCell<WorkflowContextSharedData>,
98
+ seq_nums: RefCell<WfCtxProtectedDat>,
99
+ payload_converter: PayloadConverter,
100
+ }
69
101
 
70
- seq_nums: Arc<RwLock<WfCtxProtectedDat>>,
102
+ /// Context provided to synchronous signal and update handlers.
103
+ ///
104
+ /// This type provides all workflow context capabilities except `state()`, `state_mut()`,
105
+ /// and `wait_condition()`. Those methods are not applicable in sync handler contexts.
106
+ ///
107
+ /// Sync handlers receive `&mut self` directly, so they can reference and mutate workflow state without
108
+ /// needing `state()`/`state_mut()`.
109
+ pub struct SyncWorkflowContext<W> {
110
+ base: BaseWorkflowContext,
111
+ /// Headers from the current handler invocation (signal, update, etc.)
112
+ headers: Rc<HashMap<String, Payload>>,
113
+ _phantom: PhantomData<W>,
71
114
  }
72
115
 
73
- // TODO: Dataconverter type interface to replace Payloads here. Possibly just use serde
74
- // traits.
75
- impl WfContext {
76
- /// Create a new wf context, returning the context itself and a receiver which outputs commands
77
- /// sent from the workflow.
78
- pub(super) fn new(
79
- namespace: String,
80
- task_queue: String,
81
- init_workflow_job: InitializeWorkflow,
82
- am_cancelled: watch::Receiver<Option<String>>,
83
- ) -> (Self, Receiver<RustWfCmd>) {
84
- // The receiving side is non-async
85
- let (chan, rx) = std::sync::mpsc::channel();
86
- (
87
- Self {
88
- namespace,
89
- task_queue,
90
- shared: Arc::new(RwLock::new(WfContextSharedData {
91
- random_seed: init_workflow_job.randomness_seed,
92
- search_attributes: init_workflow_job
93
- .search_attributes
94
- .clone()
95
- .unwrap_or_default(),
96
- ..Default::default()
97
- })),
98
- inital_information: Arc::new(init_workflow_job),
99
- chan,
100
- am_cancelled,
101
- seq_nums: Arc::new(RwLock::new(WfCtxProtectedDat {
102
- next_timer_sequence_number: 1,
103
- next_activity_sequence_number: 1,
104
- next_child_workflow_sequence_number: 1,
105
- next_cancel_external_wf_sequence_number: 1,
106
- next_signal_external_wf_sequence_number: 1,
107
- next_nexus_op_sequence_number: 1,
108
- })),
109
- },
110
- rx,
111
- )
116
+ impl<W> Clone for SyncWorkflowContext<W> {
117
+ fn clone(&self) -> Self {
118
+ Self {
119
+ base: self.base.clone(),
120
+ headers: self.headers.clone(),
121
+ _phantom: PhantomData,
122
+ }
112
123
  }
124
+ }
113
125
 
114
- /// Return the namespace the workflow is executing in
115
- pub fn namespace(&self) -> &str {
116
- &self.namespace
117
- }
126
+ /// Used within workflows to issue commands, get info, etc.
127
+ ///
128
+ /// The type parameter `W` represents the workflow type. This enables type-safe
129
+ /// access to workflow state via `state_mut()` for mutations.
130
+ pub struct WorkflowContext<W> {
131
+ sync: SyncWorkflowContext<W>,
132
+ /// The workflow instance
133
+ workflow_state: Rc<RefCell<W>>,
134
+ /// Wakers registered by `wait_condition` futures. Drained and woken on
135
+ /// every `state_mut` call so that waker-based combinators (e.g.
136
+ /// `FuturesOrdered`) re-poll the condition after state changes.
137
+ condition_wakers: Rc<RefCell<Vec<Waker>>>,
138
+ }
118
139
 
119
- /// Return the task queue the workflow is executing in
120
- pub fn task_queue(&self) -> &str {
121
- &self.task_queue
140
+ impl<W> Clone for WorkflowContext<W> {
141
+ fn clone(&self) -> Self {
142
+ Self {
143
+ sync: self.sync.clone(),
144
+ workflow_state: self.workflow_state.clone(),
145
+ condition_wakers: self.condition_wakers.clone(),
146
+ }
122
147
  }
148
+ }
123
149
 
124
- /// Get the arguments provided to the workflow upon execution start
125
- pub fn get_args(&self) -> &[Payload] {
126
- self.inital_information.arguments.as_slice()
127
- }
150
+ /// Read-only view of workflow context for use in init and query handlers.
151
+ ///
152
+ /// This provides access to workflow information but cannot issue commands.
153
+ #[derive(Clone, Debug)]
154
+ #[non_exhaustive]
155
+ pub struct WorkflowContextView {
156
+ /// The workflow's unique identifier
157
+ pub workflow_id: String,
158
+ /// The run id of this workflow execution
159
+ pub run_id: String,
160
+ /// The workflow type name
161
+ pub workflow_type: String,
162
+ /// The task queue this workflow is executing on
163
+ pub task_queue: String,
164
+ /// The namespace this workflow is executing in
165
+ pub namespace: String,
166
+
167
+ /// The current attempt number (starting from 1)
168
+ pub attempt: u32,
169
+ /// The run id of the very first execution in the chain
170
+ pub first_execution_run_id: String,
171
+ /// The run id of the previous execution if this is a continuation
172
+ pub continued_from_run_id: Option<String>,
173
+
174
+ /// When the workflow execution started
175
+ pub start_time: Option<SystemTime>,
176
+ /// Total workflow execution timeout including retries and continue as new
177
+ pub execution_timeout: Option<Duration>,
178
+ /// Timeout of a single workflow run
179
+ pub run_timeout: Option<Duration>,
180
+ /// Timeout of a single workflow task
181
+ pub task_timeout: Option<Duration>,
182
+
183
+ /// Information about the parent workflow, if this is a child workflow
184
+ pub parent: Option<ParentWorkflowInfo>,
185
+ /// Information about the root workflow in the execution chain
186
+ pub root: Option<RootWorkflowInfo>,
187
+
188
+ /// The workflow's retry policy
189
+ pub retry_policy: Option<temporalio_common::protos::temporal::api::common::v1::RetryPolicy>,
190
+ /// If this workflow runs on a cron schedule
191
+ pub cron_schedule: Option<String>,
192
+ /// User-defined memo
193
+ pub memo: Option<Memo>,
194
+ /// Initial search attributes
195
+ pub search_attributes: Option<SearchAttributes>,
196
+ }
128
197
 
129
- /// Return the current time according to the workflow (which is not wall-clock time).
130
- pub fn workflow_time(&self) -> Option<SystemTime> {
131
- self.shared.read().wf_time
132
- }
198
+ /// Information about a parent workflow.
199
+ #[derive(Clone, Debug)]
200
+ #[non_exhaustive]
201
+ pub struct ParentWorkflowInfo {
202
+ /// The parent workflow's unique identifier
203
+ pub workflow_id: String,
204
+ /// The parent workflow's run id
205
+ pub run_id: String,
206
+ /// The parent workflow's namespace
207
+ pub namespace: String,
208
+ }
133
209
 
134
- /// Return the length of history so far at this point in the workflow
135
- pub fn history_length(&self) -> u32 {
136
- self.shared.read().history_length
137
- }
210
+ /// Information about the root workflow in an execution chain.
211
+ #[derive(Clone, Debug)]
212
+ #[non_exhaustive]
213
+ pub struct RootWorkflowInfo {
214
+ /// The root workflow's unique identifier
215
+ pub workflow_id: String,
216
+ /// The root workflow's run id
217
+ pub run_id: String,
218
+ }
138
219
 
139
- /// Return the deployment version, if any, as it was when this point in the workflow was first
140
- /// reached. If this code is being executed for the first time, return this Worker's deployment
141
- /// version if it has one.
142
- pub fn current_deployment_version(&self) -> Option<WorkerDeploymentVersion> {
143
- self.shared.read().current_deployment_version.clone()
144
- }
220
+ impl WorkflowContextView {
221
+ /// Create a new view from workflow initialization data.
222
+ pub(crate) fn new(
223
+ namespace: String,
224
+ task_queue: String,
225
+ run_id: String,
226
+ init: &InitializeWorkflow,
227
+ ) -> Self {
228
+ let parent = init
229
+ .parent_workflow_info
230
+ .as_ref()
231
+ .map(|p| ParentWorkflowInfo {
232
+ workflow_id: p.workflow_id.clone(),
233
+ run_id: p.run_id.clone(),
234
+ namespace: p.namespace.clone(),
235
+ });
145
236
 
146
- /// Return current values for workflow search attributes
147
- pub fn search_attributes(&self) -> impl Deref<Target = SearchAttributes> + '_ {
148
- RwLockReadGuard::map(self.shared.read(), |s| &s.search_attributes)
237
+ let root = init.root_workflow.as_ref().map(|r| RootWorkflowInfo {
238
+ workflow_id: r.workflow_id.clone(),
239
+ run_id: r.run_id.clone(),
240
+ });
241
+
242
+ let continued_from_run_id = if init.continued_from_execution_run_id.is_empty() {
243
+ None
244
+ } else {
245
+ Some(init.continued_from_execution_run_id.clone())
246
+ };
247
+
248
+ let cron_schedule = if init.cron_schedule.is_empty() {
249
+ None
250
+ } else {
251
+ Some(init.cron_schedule.clone())
252
+ };
253
+
254
+ Self {
255
+ workflow_id: init.workflow_id.clone(),
256
+ run_id,
257
+ workflow_type: init.workflow_type.clone(),
258
+ task_queue,
259
+ namespace,
260
+ attempt: init.attempt as u32,
261
+ first_execution_run_id: init.first_execution_run_id.clone(),
262
+ continued_from_run_id,
263
+ start_time: init.start_time.and_then(|t| t.try_into().ok()),
264
+ execution_timeout: init
265
+ .workflow_execution_timeout
266
+ .and_then(|d| d.try_into().ok()),
267
+ run_timeout: init.workflow_run_timeout.and_then(|d| d.try_into().ok()),
268
+ task_timeout: init.workflow_task_timeout.and_then(|d| d.try_into().ok()),
269
+ parent,
270
+ root,
271
+ retry_policy: init.retry_policy.clone(),
272
+ cron_schedule,
273
+ memo: init.memo.clone(),
274
+ search_attributes: init.search_attributes.clone(),
275
+ }
149
276
  }
277
+ }
150
278
 
151
- /// Return the workflow's randomness seed
152
- pub fn random_seed(&self) -> u64 {
153
- self.shared.read().random_seed
279
+ /// Error type for activity execution outcomes.
280
+ #[derive(Debug, thiserror::Error)]
281
+ pub enum ActivityExecutionError {
282
+ /// The activity failed with the given failure details.
283
+ #[error("Activity failed: {}", .0.message)]
284
+ Failed(Box<Failure>),
285
+ /// The activity was cancelled.
286
+ #[error("Activity cancelled: {}", .0.message)]
287
+ Cancelled(Box<Failure>),
288
+ // TODO: Timed out variant
289
+ /// Failed to serialize input or deserialize result payload.
290
+ #[error("Payload conversion failed: {0}")]
291
+ Serialization(#[from] PayloadConversionError),
292
+ }
293
+
294
+ impl ActivityExecutionError {
295
+ /// Returns true if this error represents a timeout.
296
+ pub fn is_timeout(&self) -> bool {
297
+ match self {
298
+ ActivityExecutionError::Failed(f) => f.is_timeout().is_some(),
299
+ _ => false,
300
+ }
154
301
  }
302
+ }
155
303
 
156
- /// Returns true if the current workflow task is happening under replay
157
- pub fn is_replaying(&self) -> bool {
158
- self.shared.read().is_replaying
304
+ impl BaseWorkflowContext {
305
+ /// Create a new base context, returning the context itself and a receiver which outputs commands
306
+ /// sent from the workflow.
307
+ pub(crate) fn new(
308
+ namespace: String,
309
+ task_queue: String,
310
+ run_id: String,
311
+ init_workflow_job: InitializeWorkflow,
312
+ am_cancelled: watch::Receiver<Option<String>>,
313
+ payload_converter: PayloadConverter,
314
+ ) -> (Self, Receiver<RustWfCmd>) {
315
+ // The receiving side is non-async
316
+ let (chan, rx) = std::sync::mpsc::channel();
317
+ (
318
+ Self {
319
+ inner: Rc::new(WorkflowContextInner {
320
+ namespace,
321
+ task_queue,
322
+ run_id,
323
+ shared: RefCell::new(WorkflowContextSharedData {
324
+ random_seed: init_workflow_job.randomness_seed,
325
+ search_attributes: init_workflow_job
326
+ .search_attributes
327
+ .clone()
328
+ .unwrap_or_default(),
329
+ ..Default::default()
330
+ }),
331
+ inital_information: init_workflow_job,
332
+ chan,
333
+ am_cancelled,
334
+ seq_nums: RefCell::new(WfCtxProtectedDat {
335
+ next_timer_sequence_number: 1,
336
+ next_activity_sequence_number: 1,
337
+ next_child_workflow_sequence_number: 1,
338
+ next_cancel_external_wf_sequence_number: 1,
339
+ next_signal_external_wf_sequence_number: 1,
340
+ next_nexus_op_sequence_number: 1,
341
+ }),
342
+ payload_converter,
343
+ }),
344
+ },
345
+ rx,
346
+ )
159
347
  }
160
348
 
161
- /// Return various information that the workflow was initialized with. Will eventually become
162
- /// a proper non-proto workflow info struct.
163
- pub fn workflow_initial_info(&self) -> &InitializeWorkflow {
164
- &self.inital_information
349
+ /// Buffer a command to be sent in the activation reply
350
+ pub(crate) fn send(&self, c: RustWfCmd) {
351
+ self.inner.chan.send(c).expect("command channel intact");
165
352
  }
166
353
 
167
- /// A future that resolves if/when the workflow is cancelled, with the user provided cause
168
- pub async fn cancelled(&self) -> String {
169
- if let Some(s) = self.am_cancelled.borrow().as_ref() {
170
- return s.clone();
171
- }
172
- self.am_cancelled
173
- .clone()
174
- .changed()
175
- .await
176
- .expect("Cancelled send half not dropped");
177
- self.am_cancelled
178
- .borrow()
179
- .as_ref()
180
- .cloned()
181
- .unwrap_or_default()
354
+ /// Cancel any cancellable operation by ID
355
+ fn cancel(&self, cancellable_id: CancellableID) {
356
+ self.send(RustWfCmd::Cancel(cancellable_id));
182
357
  }
183
358
 
184
359
  /// Request to create a timer
185
- pub fn timer<T: Into<TimerOptions>>(&self, opts: T) -> impl CancellableFuture<TimerResult> {
360
+ pub fn timer<T: Into<TimerOptions>>(
361
+ &self,
362
+ opts: T,
363
+ ) -> impl CancellableFuture<TimerResult> + use<T> {
186
364
  let opts: TimerOptions = opts.into();
187
- let seq = self.seq_nums.write().next_timer_seq();
188
- let (cmd, unblocker) = CancellableWFCommandFut::new(CancellableID::Timer(seq));
365
+ let seq = self.inner.seq_nums.borrow_mut().next_timer_seq();
366
+ let (cmd, unblocker) =
367
+ CancellableWFCommandFut::new(CancellableID::Timer(seq), self.clone());
189
368
  self.send(
190
369
  CommandCreateRequest {
191
370
  cmd: WorkflowCommand {
@@ -213,53 +392,263 @@ impl WfContext {
213
392
  }
214
393
 
215
394
  /// Request to run an activity
216
- pub fn activity(
395
+ pub fn start_activity<AD: ActivityDefinition>(
217
396
  &self,
397
+ _activity: AD,
398
+ input: impl Into<AD::Input>,
218
399
  mut opts: ActivityOptions,
219
- ) -> impl CancellableFuture<ActivityResolution> {
400
+ ) -> impl CancellableFuture<Result<AD::Output, ActivityExecutionError>>
401
+ where
402
+ AD::Output: TemporalDeserializable,
403
+ {
404
+ let input = input.into();
405
+ let ctx = SerializationContext {
406
+ data: &SerializationContextData::Workflow,
407
+ converter: &self.inner.payload_converter,
408
+ };
409
+ let payloads = match self.inner.payload_converter.to_payloads(&ctx, &input) {
410
+ Ok(p) => p,
411
+ Err(e) => {
412
+ return ActivityFut::eager(e.into());
413
+ }
414
+ };
415
+ let seq = self.inner.seq_nums.borrow_mut().next_activity_seq();
416
+ let (cmd, unblocker) =
417
+ CancellableWFCommandFut::new(CancellableID::Activity(seq), self.clone());
220
418
  if opts.task_queue.is_none() {
221
- opts.task_queue = Some(self.task_queue.clone());
419
+ opts.task_queue = Some(self.inner.task_queue.clone());
222
420
  }
223
- let seq = self.seq_nums.write().next_activity_seq();
224
- let (cmd, unblocker) = CancellableWFCommandFut::new(CancellableID::Activity(seq));
225
421
  self.send(
226
422
  CommandCreateRequest {
227
- cmd: opts.into_command(seq),
423
+ cmd: opts.into_command(AD::name().to_string(), payloads, seq),
228
424
  unblocker,
229
425
  }
230
426
  .into(),
231
427
  );
232
- cmd
428
+ ActivityFut::running(cmd, self.inner.payload_converter.clone())
233
429
  }
234
430
 
235
431
  /// Request to run a local activity
236
- pub fn local_activity(
432
+ pub fn start_local_activity<AD: ActivityDefinition>(
237
433
  &self,
434
+ _activity: AD,
435
+ input: impl Into<AD::Input>,
238
436
  opts: LocalActivityOptions,
239
- ) -> impl CancellableFuture<ActivityResolution> + '_ {
240
- LATimerBackoffFut::new(opts, self)
437
+ ) -> impl CancellableFuture<Result<AD::Output, ActivityExecutionError>>
438
+ where
439
+ AD::Output: TemporalDeserializable,
440
+ {
441
+ let input = input.into();
442
+ let ctx = SerializationContext {
443
+ data: &SerializationContextData::Workflow,
444
+ converter: &self.inner.payload_converter,
445
+ };
446
+ let payloads = match self.inner.payload_converter.to_payloads(&ctx, &input) {
447
+ Ok(p) => p,
448
+ Err(e) => {
449
+ return ActivityFut::eager(e.into());
450
+ }
451
+ };
452
+ ActivityFut::running(
453
+ LATimerBackoffFut::new(AD::name().to_string(), payloads, opts, self.clone()),
454
+ self.inner.payload_converter.clone(),
455
+ )
241
456
  }
242
457
 
243
458
  /// Request to run a local activity with no implementation of timer-backoff based retrying.
244
459
  fn local_activity_no_timer_retry(
245
- &self,
460
+ self,
461
+ activity_type: String,
462
+ arguments: Vec<Payload>,
246
463
  opts: LocalActivityOptions,
247
464
  ) -> impl CancellableFuture<ActivityResolution> {
248
- let seq = self.seq_nums.write().next_activity_seq();
249
- let (cmd, unblocker) = CancellableWFCommandFut::new(CancellableID::LocalActivity(seq));
465
+ let seq = self.inner.seq_nums.borrow_mut().next_activity_seq();
466
+ let (cmd, unblocker) =
467
+ CancellableWFCommandFut::new(CancellableID::LocalActivity(seq), self.clone());
468
+ self.inner
469
+ .chan
470
+ .send(
471
+ CommandCreateRequest {
472
+ cmd: opts.into_command(activity_type, arguments, seq),
473
+ unblocker,
474
+ }
475
+ .into(),
476
+ )
477
+ .expect("command channel intact");
478
+ cmd
479
+ }
480
+
481
+ fn send_signal_wf(
482
+ self,
483
+ target: sig_we::Target,
484
+ signal: Signal,
485
+ ) -> impl CancellableFuture<SignalExternalWfResult> {
486
+ let seq = self
487
+ .inner
488
+ .seq_nums
489
+ .borrow_mut()
490
+ .next_signal_external_wf_seq();
491
+ let (cmd, unblocker) =
492
+ CancellableWFCommandFut::new(CancellableID::SignalExternalWorkflow(seq), self.clone());
250
493
  self.send(
251
494
  CommandCreateRequest {
252
- cmd: opts.into_command(seq),
495
+ cmd: WorkflowCommand {
496
+ variant: Some(
497
+ SignalExternalWorkflowExecution {
498
+ seq,
499
+ signal_name: signal.signal_name,
500
+ args: signal.data.input,
501
+ target: Some(target),
502
+ headers: signal.data.headers,
503
+ }
504
+ .into(),
505
+ ),
506
+ user_metadata: None,
507
+ },
253
508
  unblocker,
254
509
  }
255
510
  .into(),
256
511
  );
257
512
  cmd
258
513
  }
514
+ }
515
+
516
+ impl<W> SyncWorkflowContext<W> {
517
+ /// Return the workflow's unique identifier
518
+ pub fn workflow_id(&self) -> &str {
519
+ &self.base.inner.inital_information.workflow_id
520
+ }
521
+
522
+ /// Return the run id of this workflow execution
523
+ pub fn run_id(&self) -> &str {
524
+ &self.base.inner.run_id
525
+ }
526
+
527
+ /// Return the namespace the workflow is executing in
528
+ pub fn namespace(&self) -> &str {
529
+ &self.base.inner.namespace
530
+ }
531
+
532
+ /// Return the task queue the workflow is executing in
533
+ pub fn task_queue(&self) -> &str {
534
+ &self.base.inner.task_queue
535
+ }
536
+
537
+ /// Return the current time according to the workflow (which is not wall-clock time).
538
+ pub fn workflow_time(&self) -> Option<SystemTime> {
539
+ self.base.inner.shared.borrow().wf_time
540
+ }
541
+
542
+ /// Return the length of history so far at this point in the workflow
543
+ pub fn history_length(&self) -> u32 {
544
+ self.base.inner.shared.borrow().history_length
545
+ }
546
+
547
+ /// Return the deployment version, if any, as it was when this point in the workflow was first
548
+ /// reached. If this code is being executed for the first time, return this Worker's deployment
549
+ /// version if it has one.
550
+ pub fn current_deployment_version(&self) -> Option<WorkerDeploymentVersion> {
551
+ self.base
552
+ .inner
553
+ .shared
554
+ .borrow()
555
+ .current_deployment_version
556
+ .clone()
557
+ }
558
+
559
+ /// Return current values for workflow search attributes
560
+ pub fn search_attributes(&self) -> impl Deref<Target = SearchAttributes> + '_ {
561
+ Ref::map(self.base.inner.shared.borrow(), |s| &s.search_attributes)
562
+ }
563
+
564
+ /// Return the workflow's randomness seed
565
+ pub fn random_seed(&self) -> u64 {
566
+ self.base.inner.shared.borrow().random_seed
567
+ }
568
+
569
+ /// Returns true if the current workflow task is happening under replay
570
+ pub fn is_replaying(&self) -> bool {
571
+ self.base.inner.shared.borrow().is_replaying
572
+ }
573
+
574
+ /// Returns true if the server suggests this workflow should continue-as-new
575
+ pub fn continue_as_new_suggested(&self) -> bool {
576
+ self.base.inner.shared.borrow().continue_as_new_suggested
577
+ }
578
+
579
+ /// Returns the headers for the current handler invocation (signal, update, query, etc.).
580
+ ///
581
+ /// When called from within a signal handler, returns the headers that were sent with that
582
+ /// signal. When called from the main workflow run method, returns an empty map.
583
+ pub fn headers(&self) -> &HashMap<String, Payload> {
584
+ &self.headers
585
+ }
586
+
587
+ /// Returns the [PayloadConverter] currently used by the worker running this workflow.
588
+ pub fn payload_converter(&self) -> &PayloadConverter {
589
+ &self.base.inner.payload_converter
590
+ }
591
+
592
+ /// Return various information that the workflow was initialized with. Will eventually become
593
+ /// a proper non-proto workflow info struct.
594
+ pub fn workflow_initial_info(&self) -> &InitializeWorkflow {
595
+ &self.base.inner.inital_information
596
+ }
597
+
598
+ /// A future that resolves if/when the workflow is cancelled, with the user provided cause
599
+ pub fn cancelled(&self) -> impl FusedFuture<Output = String> + '_ {
600
+ let am_cancelled = self.base.inner.am_cancelled.clone();
601
+ async move {
602
+ if let Some(s) = am_cancelled.borrow().as_ref() {
603
+ return s.clone();
604
+ }
605
+ am_cancelled
606
+ .clone()
607
+ .changed()
608
+ .await
609
+ .expect("Cancelled send half not dropped");
610
+ am_cancelled.borrow().as_ref().cloned().unwrap_or_default()
611
+ }
612
+ .fuse()
613
+ }
614
+
615
+ /// Request to create a timer
616
+ pub fn timer<T: Into<TimerOptions>>(&self, opts: T) -> impl CancellableFuture<TimerResult> {
617
+ self.base.timer(opts)
618
+ }
619
+
620
+ /// Request to run an activity
621
+ pub fn start_activity<AD: ActivityDefinition>(
622
+ &self,
623
+ activity: AD,
624
+ input: impl Into<AD::Input>,
625
+ opts: ActivityOptions,
626
+ ) -> impl CancellableFuture<Result<AD::Output, ActivityExecutionError>>
627
+ where
628
+ AD::Output: TemporalDeserializable,
629
+ {
630
+ self.base.start_activity(activity, input, opts)
631
+ }
632
+
633
+ /// Request to run a local activity
634
+ pub fn start_local_activity<AD: ActivityDefinition>(
635
+ &self,
636
+ activity: AD,
637
+ input: impl Into<AD::Input>,
638
+ opts: LocalActivityOptions,
639
+ ) -> impl CancellableFuture<Result<AD::Output, ActivityExecutionError>>
640
+ where
641
+ AD::Output: TemporalDeserializable,
642
+ {
643
+ self.base.start_local_activity(activity, input, opts)
644
+ }
259
645
 
260
646
  /// Creates a child workflow stub with the provided options
261
647
  pub fn child_workflow(&self, opts: ChildWorkflowOptions) -> ChildWorkflow {
262
- ChildWorkflow { opts }
648
+ ChildWorkflow {
649
+ opts,
650
+ base_ctx: self.base.clone(),
651
+ }
263
652
  }
264
653
 
265
654
  /// Check (or record) that this workflow history was created with the provided patch
@@ -274,7 +663,7 @@ impl WfContext {
274
663
  }
275
664
 
276
665
  fn patch_impl(&self, patch_id: &str, deprecated: bool) -> bool {
277
- self.send(
666
+ self.base.send(
278
667
  workflow_command::Variant::SetPatchMarker(SetPatchMarker {
279
668
  patch_id: patch_id.to_string(),
280
669
  deprecated,
@@ -282,16 +671,18 @@ impl WfContext {
282
671
  .into(),
283
672
  );
284
673
  // See if we already know about the status of this change
285
- if let Some(present) = self.shared.read().changes.get(patch_id) {
674
+ if let Some(present) = self.base.inner.shared.borrow().changes.get(patch_id) {
286
675
  return *present;
287
676
  }
288
677
 
289
678
  // If we don't already know about the change, that means there is no marker in history,
290
679
  // and we should return false if we are replaying
291
- let res = !self.shared.read().is_replaying;
680
+ let res = !self.base.inner.shared.borrow().is_replaying;
292
681
 
293
- self.shared
294
- .write()
682
+ self.base
683
+ .inner
684
+ .shared
685
+ .borrow_mut()
295
686
  .changes
296
687
  .insert(patch_id.to_string(), res);
297
688
 
@@ -306,19 +697,21 @@ impl WfContext {
306
697
  ) -> impl CancellableFuture<SignalExternalWfResult> {
307
698
  let options: SignalWorkflowOptions = opts.into();
308
699
  let target = sig_we::Target::WorkflowExecution(NamespacedWorkflowExecution {
309
- namespace: self.namespace.clone(),
700
+ namespace: self.base.inner.namespace.clone(),
310
701
  workflow_id: options.workflow_id,
311
702
  run_id: options.run_id.unwrap_or_default(),
312
703
  });
313
- self.send_signal_wf(target, options.signal)
704
+ self.base.clone().send_signal_wf(target, options.signal)
314
705
  }
315
706
 
316
707
  /// Add or create a set of search attributes
317
708
  pub fn upsert_search_attributes(&self, attr_iter: impl IntoIterator<Item = (String, Payload)>) {
318
- self.send(RustWfCmd::NewNonblockingCmd(
709
+ self.base.send(RustWfCmd::NewNonblockingCmd(
319
710
  workflow_command::Variant::UpsertWorkflowSearchAttributes(
320
711
  UpsertWorkflowSearchAttributes {
321
- search_attributes: HashMap::from_iter(attr_iter),
712
+ search_attributes: Some(SearchAttributes {
713
+ indexed_fields: HashMap::from_iter(attr_iter),
714
+ }),
322
715
  },
323
716
  ),
324
717
  ))
@@ -326,7 +719,7 @@ impl WfContext {
326
719
 
327
720
  /// Add or create a set of search attributes
328
721
  pub fn upsert_memo(&self, attr_iter: impl IntoIterator<Item = (String, Payload)>) {
329
- self.send(RustWfCmd::NewNonblockingCmd(
722
+ self.base.send(RustWfCmd::NewNonblockingCmd(
330
723
  workflow_command::Variant::ModifyWorkflowProperties(ModifyWorkflowProperties {
331
724
  upserted_memo: Some(Memo {
332
725
  fields: HashMap::from_iter(attr_iter),
@@ -335,16 +728,9 @@ impl WfContext {
335
728
  ))
336
729
  }
337
730
 
338
- /// Return a stream that produces values when the named signal is sent to this workflow
339
- pub fn make_signal_channel(&self, signal_name: impl Into<String>) -> DrainableSignalStream {
340
- let (tx, rx) = mpsc::unbounded_channel();
341
- self.send(RustWfCmd::SubscribeSignal(signal_name.into(), tx));
342
- DrainableSignalStream(UnboundedReceiverStream::new(rx))
343
- }
344
-
345
731
  /// Force a workflow task failure (EX: in order to retry on non-sticky queue)
346
732
  pub fn force_task_fail(&self, with: anyhow::Error) {
347
- self.send(with.into());
733
+ self.base.send(with.into());
348
734
  }
349
735
 
350
736
  /// Request the cancellation of an external workflow. May resolve as a failure if the workflow
@@ -353,10 +739,15 @@ impl WfContext {
353
739
  &self,
354
740
  target: NamespacedWorkflowExecution,
355
741
  reason: String,
356
- ) -> impl Future<Output = CancelExternalWfResult> {
357
- let seq = self.seq_nums.write().next_cancel_external_wf_seq();
742
+ ) -> impl FusedFuture<Output = CancelExternalWfResult> {
743
+ let seq = self
744
+ .base
745
+ .inner
746
+ .seq_nums
747
+ .borrow_mut()
748
+ .next_cancel_external_wf_seq();
358
749
  let (cmd, unblocker) = WFCommandFut::new();
359
- self.send(
750
+ self.base.send(
360
751
  CommandCreateRequest {
361
752
  cmd: WorkflowCommand {
362
753
  variant: Some(
@@ -376,40 +767,25 @@ impl WfContext {
376
767
  cmd
377
768
  }
378
769
 
379
- /// Register an update handler by providing the handler name, a validator function, and an
380
- /// update handler. The validator must not mutate workflow state and is synchronous. The handler
381
- /// may mutate workflow state (though, that's annoying right now in the prototype) and is async.
382
- ///
383
- /// Note that if you want a validator that always passes, you will likely need to provide type
384
- /// annotations to make the compiler happy, like: `|_: &_, _: T| Ok(())`
385
- pub fn update_handler<Arg, Res>(
386
- &self,
387
- name: impl Into<String>,
388
- validator: impl IntoUpdateValidatorFunc<Arg>,
389
- handler: impl IntoUpdateHandlerFunc<Arg, Res>,
390
- ) {
391
- self.send(RustWfCmd::RegisterUpdate(
392
- name.into(),
393
- UpdateFunctions::new(validator, handler),
394
- ))
395
- }
396
-
397
770
  /// Start a nexus operation
398
771
  pub fn start_nexus_operation(
399
772
  &self,
400
773
  opts: NexusOperationOptions,
401
774
  ) -> impl CancellableFuture<NexusStartResult> {
402
- let seq = self.seq_nums.write().next_nexus_op_seq();
775
+ let seq = self.base.inner.seq_nums.borrow_mut().next_nexus_op_seq();
403
776
  let (result_future, unblocker) = WFCommandFut::new();
404
- self.send(RustWfCmd::SubscribeNexusOperationCompletion { seq, unblocker });
777
+ self.base
778
+ .send(RustWfCmd::SubscribeNexusOperationCompletion { seq, unblocker });
405
779
  let (cmd, unblocker) = CancellableWFCommandFut::new_with_dat(
406
780
  CancellableID::NexusOp(seq),
407
781
  NexusUnblockData {
408
782
  result_future: result_future.shared(),
409
783
  schedule_seq: seq,
784
+ base_ctx: self.base.clone(),
410
785
  },
786
+ self.base.clone(),
411
787
  );
412
- self.send(
788
+ self.base.send(
413
789
  CommandCreateRequest {
414
790
  cmd: opts.into_command(seq),
415
791
  unblocker,
@@ -419,55 +795,255 @@ impl WfContext {
419
795
  cmd
420
796
  }
421
797
 
422
- /// Wait for some condition to become true, yielding the workflow if it is not.
423
- pub fn wait_condition(&self, mut condition: impl FnMut() -> bool) -> impl Future<Output = ()> {
424
- future::poll_fn(move |_cx: &mut Context<'_>| {
425
- if condition() {
426
- Poll::Ready(())
427
- } else {
428
- Poll::Pending
429
- }
430
- })
798
+ /// Create a read-only view of this context.
799
+ pub(crate) fn view(&self) -> WorkflowContextView {
800
+ self.base.view()
431
801
  }
802
+ }
432
803
 
433
- /// Buffer a command to be sent in the activation reply
434
- pub(crate) fn send(&self, c: RustWfCmd) {
435
- self.chan.send(c).expect("command channel intact");
804
+ impl<W> WorkflowContext<W> {
805
+ /// Create a new wf context from a base context and workflow state.
806
+ pub(crate) fn from_base(base: BaseWorkflowContext, workflow_state: Rc<RefCell<W>>) -> Self {
807
+ Self {
808
+ sync: SyncWorkflowContext {
809
+ base,
810
+ headers: Rc::new(HashMap::new()),
811
+ _phantom: PhantomData,
812
+ },
813
+ workflow_state,
814
+ condition_wakers: Rc::new(RefCell::new(Vec::new())),
815
+ }
436
816
  }
437
817
 
438
- fn send_signal_wf(
818
+ /// Returns a new context with the specified headers set.
819
+ pub(crate) fn with_headers(&self, headers: HashMap<String, Payload>) -> Self {
820
+ Self {
821
+ sync: SyncWorkflowContext {
822
+ base: self.sync.base.clone(),
823
+ headers: Rc::new(headers),
824
+ _phantom: PhantomData,
825
+ },
826
+ workflow_state: self.workflow_state.clone(),
827
+ condition_wakers: self.condition_wakers.clone(),
828
+ }
829
+ }
830
+
831
+ /// Returns a [`SyncWorkflowContext`] extracted from this context.
832
+ pub(crate) fn sync_context(&self) -> SyncWorkflowContext<W> {
833
+ self.sync.clone()
834
+ }
835
+
836
+ // --- Delegated methods from SyncWorkflowContext ---
837
+
838
+ /// Return the workflow's unique identifier
839
+ pub fn workflow_id(&self) -> &str {
840
+ self.sync.workflow_id()
841
+ }
842
+
843
+ /// Return the run id of this workflow execution
844
+ pub fn run_id(&self) -> &str {
845
+ self.sync.run_id()
846
+ }
847
+
848
+ /// Return the namespace the workflow is executing in
849
+ pub fn namespace(&self) -> &str {
850
+ self.sync.namespace()
851
+ }
852
+
853
+ /// Return the task queue the workflow is executing in
854
+ pub fn task_queue(&self) -> &str {
855
+ self.sync.task_queue()
856
+ }
857
+
858
+ /// Return the current time according to the workflow (which is not wall-clock time).
859
+ pub fn workflow_time(&self) -> Option<SystemTime> {
860
+ self.sync.workflow_time()
861
+ }
862
+
863
+ /// Return the length of history so far at this point in the workflow
864
+ pub fn history_length(&self) -> u32 {
865
+ self.sync.history_length()
866
+ }
867
+
868
+ /// Return the deployment version, if any, as it was when this point in the workflow was first
869
+ /// reached. If this code is being executed for the first time, return this Worker's deployment
870
+ /// version if it has one.
871
+ pub fn current_deployment_version(&self) -> Option<WorkerDeploymentVersion> {
872
+ self.sync.current_deployment_version()
873
+ }
874
+
875
+ /// Return current values for workflow search attributes
876
+ pub fn search_attributes(&self) -> impl Deref<Target = SearchAttributes> + '_ {
877
+ self.sync.search_attributes()
878
+ }
879
+
880
+ /// Return the workflow's randomness seed
881
+ pub fn random_seed(&self) -> u64 {
882
+ self.sync.random_seed()
883
+ }
884
+
885
+ /// Returns true if the current workflow task is happening under replay
886
+ pub fn is_replaying(&self) -> bool {
887
+ self.sync.is_replaying()
888
+ }
889
+
890
+ /// Returns true if the server suggests this workflow should continue-as-new
891
+ pub fn continue_as_new_suggested(&self) -> bool {
892
+ self.sync.continue_as_new_suggested()
893
+ }
894
+
895
+ /// Returns the headers for the current handler invocation (signal, update, query, etc.).
896
+ pub fn headers(&self) -> &HashMap<String, Payload> {
897
+ self.sync.headers()
898
+ }
899
+
900
+ /// Returns the [PayloadConverter] currently used by the worker running this workflow.
901
+ pub fn payload_converter(&self) -> &PayloadConverter {
902
+ self.sync.payload_converter()
903
+ }
904
+
905
+ /// Return various information that the workflow was initialized with.
906
+ pub fn workflow_initial_info(&self) -> &InitializeWorkflow {
907
+ self.sync.workflow_initial_info()
908
+ }
909
+
910
+ /// A future that resolves if/when the workflow is cancelled, with the user provided cause
911
+ pub fn cancelled(&self) -> impl FusedFuture<Output = String> + '_ {
912
+ self.sync.cancelled()
913
+ }
914
+
915
+ /// Request to create a timer
916
+ pub fn timer<T: Into<TimerOptions>>(&self, opts: T) -> impl CancellableFuture<TimerResult> {
917
+ self.sync.timer(opts)
918
+ }
919
+
920
+ /// Request to run an activity
921
+ pub fn start_activity<AD: ActivityDefinition>(
439
922
  &self,
440
- target: sig_we::Target,
441
- signal: Signal,
923
+ activity: AD,
924
+ input: impl Into<AD::Input>,
925
+ opts: ActivityOptions,
926
+ ) -> impl CancellableFuture<Result<AD::Output, ActivityExecutionError>>
927
+ where
928
+ AD::Output: TemporalDeserializable,
929
+ {
930
+ self.sync.start_activity(activity, input, opts)
931
+ }
932
+
933
+ /// Request to run a local activity
934
+ pub fn start_local_activity<AD: ActivityDefinition>(
935
+ &self,
936
+ activity: AD,
937
+ input: impl Into<AD::Input>,
938
+ opts: LocalActivityOptions,
939
+ ) -> impl CancellableFuture<Result<AD::Output, ActivityExecutionError>>
940
+ where
941
+ AD::Output: TemporalDeserializable,
942
+ {
943
+ self.sync.start_local_activity(activity, input, opts)
944
+ }
945
+
946
+ /// Creates a child workflow stub with the provided options
947
+ pub fn child_workflow(&self, opts: ChildWorkflowOptions) -> ChildWorkflow {
948
+ self.sync.child_workflow(opts)
949
+ }
950
+
951
+ /// Check (or record) that this workflow history was created with the provided patch
952
+ pub fn patched(&self, patch_id: &str) -> bool {
953
+ self.sync.patched(patch_id)
954
+ }
955
+
956
+ /// Record that this workflow history was created with the provided patch, and it is being
957
+ /// phased out.
958
+ pub fn deprecate_patch(&self, patch_id: &str) -> bool {
959
+ self.sync.deprecate_patch(patch_id)
960
+ }
961
+
962
+ /// Send a signal to an external workflow.
963
+ pub fn signal_workflow(
964
+ &self,
965
+ opts: impl Into<SignalWorkflowOptions>,
442
966
  ) -> impl CancellableFuture<SignalExternalWfResult> {
443
- let seq = self.seq_nums.write().next_signal_external_wf_seq();
444
- let (cmd, unblocker) =
445
- CancellableWFCommandFut::new(CancellableID::SignalExternalWorkflow(seq));
446
- self.send(
447
- CommandCreateRequest {
448
- cmd: WorkflowCommand {
449
- variant: Some(
450
- SignalExternalWorkflowExecution {
451
- seq,
452
- signal_name: signal.signal_name,
453
- args: signal.data.input,
454
- target: Some(target),
455
- headers: signal.data.headers,
456
- }
457
- .into(),
458
- ),
459
- user_metadata: None,
460
- },
461
- unblocker,
462
- }
463
- .into(),
464
- );
465
- cmd
967
+ self.sync.signal_workflow(opts)
466
968
  }
467
969
 
468
- /// Cancel any cancellable operation by ID
469
- fn cancel(&self, cancellable_id: CancellableID) {
470
- self.send(RustWfCmd::Cancel(cancellable_id));
970
+ /// Add or create a set of search attributes
971
+ pub fn upsert_search_attributes(&self, attr_iter: impl IntoIterator<Item = (String, Payload)>) {
972
+ self.sync.upsert_search_attributes(attr_iter)
973
+ }
974
+
975
+ /// Add or create a set of memo fields
976
+ pub fn upsert_memo(&self, attr_iter: impl IntoIterator<Item = (String, Payload)>) {
977
+ self.sync.upsert_memo(attr_iter)
978
+ }
979
+
980
+ /// Force a workflow task failure (EX: in order to retry on non-sticky queue)
981
+ pub fn force_task_fail(&self, with: anyhow::Error) {
982
+ self.sync.force_task_fail(with)
983
+ }
984
+
985
+ /// Request the cancellation of an external workflow.
986
+ pub fn cancel_external(
987
+ &self,
988
+ target: NamespacedWorkflowExecution,
989
+ reason: String,
990
+ ) -> impl FusedFuture<Output = CancelExternalWfResult> {
991
+ self.sync.cancel_external(target, reason)
992
+ }
993
+
994
+ /// Start a nexus operation
995
+ pub fn start_nexus_operation(
996
+ &self,
997
+ opts: NexusOperationOptions,
998
+ ) -> impl CancellableFuture<NexusStartResult> {
999
+ self.sync.start_nexus_operation(opts)
1000
+ }
1001
+
1002
+ /// Create a read-only view of this context.
1003
+ pub(crate) fn view(&self) -> WorkflowContextView {
1004
+ self.sync.view()
1005
+ }
1006
+
1007
+ /// Access workflow state immutably via closure.
1008
+ ///
1009
+ /// The borrow is scoped to the closure and cannot escape, preventing
1010
+ /// borrows from being held across await points.
1011
+ pub fn state<R>(&self, f: impl FnOnce(&W) -> R) -> R {
1012
+ f(&*self.workflow_state.borrow())
1013
+ }
1014
+
1015
+ /// Access workflow state mutably via closure.
1016
+ ///
1017
+ /// The borrow is scoped to the closure and cannot escape, preventing
1018
+ /// borrows from being held across await points.
1019
+ ///
1020
+ /// After the mutation, all wakers registered by pending `wait_condition`
1021
+ /// futures are woken so that waker-based combinators (e.g.
1022
+ /// `FuturesOrdered`) re-poll them on the next pass.
1023
+ pub fn state_mut<R>(&self, f: impl FnOnce(&mut W) -> R) -> R {
1024
+ let result = f(&mut *self.workflow_state.borrow_mut());
1025
+ for waker in self.condition_wakers.borrow_mut().drain(..) {
1026
+ waker.wake();
1027
+ }
1028
+ result
1029
+ }
1030
+
1031
+ /// Wait for some condition on workflow state to become true, yielding the workflow if not.
1032
+ ///
1033
+ /// The condition closure receives an immutable reference to the workflow state,
1034
+ /// which is borrowed only for the duration of each poll (not across await points).
1035
+ pub fn wait_condition<'a>(
1036
+ &'a self,
1037
+ mut condition: impl FnMut(&W) -> bool + 'a,
1038
+ ) -> impl Future<Output = ()> + 'a {
1039
+ future::poll_fn(move |cx: &mut Context<'_>| {
1040
+ if condition(&*self.workflow_state.borrow()) {
1041
+ Poll::Ready(())
1042
+ } else {
1043
+ self.condition_wakers.borrow_mut().push(cx.waker().clone());
1044
+ Poll::Pending
1045
+ }
1046
+ })
471
1047
  }
472
1048
  }
473
1049
 
@@ -514,59 +1090,29 @@ impl WfCtxProtectedDat {
514
1090
  }
515
1091
 
516
1092
  #[derive(Clone, Debug, Default)]
517
- pub(crate) struct WfContextSharedData {
1093
+ pub(crate) struct WorkflowContextSharedData {
518
1094
  /// Maps change ids -> resolved status
519
1095
  pub(crate) changes: HashMap<String, bool>,
520
1096
  pub(crate) is_replaying: bool,
521
1097
  pub(crate) wf_time: Option<SystemTime>,
522
1098
  pub(crate) history_length: u32,
1099
+ pub(crate) continue_as_new_suggested: bool,
523
1100
  pub(crate) current_deployment_version: Option<WorkerDeploymentVersion>,
524
1101
  pub(crate) search_attributes: SearchAttributes,
525
1102
  pub(crate) random_seed: u64,
526
1103
  }
527
1104
 
528
- /// Helper Wrapper that can drain the channel into a Vec<SignalData> in a blocking way. Useful
529
- /// for making sure channels are empty before ContinueAsNew-ing a workflow
530
- pub struct DrainableSignalStream(UnboundedReceiverStream<SignalData>);
531
-
532
- impl DrainableSignalStream {
533
- pub fn drain_all(self) -> Vec<SignalData> {
534
- let mut receiver = self.0.into_inner();
535
- let mut signals = vec![];
536
- while let Ok(s) = receiver.try_recv() {
537
- signals.push(s);
538
- }
539
- signals
540
- }
541
-
542
- pub fn drain_ready(&mut self) -> Vec<SignalData> {
543
- let mut signals = vec![];
544
- while let Some(s) = self.0.next().now_or_never().flatten() {
545
- signals.push(s);
546
- }
547
- signals
548
- }
549
- }
550
-
551
- impl Stream for DrainableSignalStream {
552
- type Item = SignalData;
553
-
554
- fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
555
- Pin::new(&mut self.0).poll_next(cx)
556
- }
557
- }
558
-
559
1105
  /// A Future that can be cancelled.
560
1106
  /// Used in the prototype SDK for cancelling operations like timers and activities.
561
- pub trait CancellableFuture<T>: Future<Output = T> {
1107
+ pub trait CancellableFuture<T>: Future<Output = T> + FusedFuture {
562
1108
  /// Cancel this Future
563
- fn cancel(&self, cx: &WfContext);
1109
+ fn cancel(&self);
564
1110
  }
565
1111
 
566
1112
  /// A Future that can be cancelled with a reason
567
1113
  pub trait CancellableFutureWithReason<T>: CancellableFuture<T> {
568
1114
  /// Cancel this Future with a reason
569
- fn cancel_with_reason(&self, cx: &WfContext, reason: String);
1115
+ fn cancel_with_reason(&self, reason: String);
570
1116
  }
571
1117
 
572
1118
  struct WFCommandFut<T, D> {
@@ -603,8 +1149,6 @@ where
603
1149
 
604
1150
  fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
605
1151
  self.result_rx.poll_unpin(cx).map(|x| {
606
- // SAFETY: Because we can only enter this section once the future has resolved, we
607
- // know it will never be polled again, therefore consuming the option is OK.
608
1152
  let od = self
609
1153
  .other_dat
610
1154
  .take()
@@ -613,23 +1157,40 @@ where
613
1157
  })
614
1158
  }
615
1159
  }
1160
+ impl<T, D> FusedFuture for WFCommandFut<T, D>
1161
+ where
1162
+ T: Unblockable<OtherDat = D>,
1163
+ {
1164
+ fn is_terminated(&self) -> bool {
1165
+ self.other_dat.is_none()
1166
+ }
1167
+ }
616
1168
 
617
1169
  struct CancellableWFCommandFut<T, D, ID = CancellableID> {
618
1170
  cmd_fut: WFCommandFut<T, D>,
619
1171
  cancellable_id: ID,
1172
+ base_ctx: BaseWorkflowContext,
620
1173
  }
621
1174
  impl<T, ID> CancellableWFCommandFut<T, (), ID> {
622
- fn new(cancellable_id: ID) -> (Self, oneshot::Sender<UnblockEvent>) {
623
- Self::new_with_dat(cancellable_id, ())
1175
+ fn new(
1176
+ cancellable_id: ID,
1177
+ base_ctx: BaseWorkflowContext,
1178
+ ) -> (Self, oneshot::Sender<UnblockEvent>) {
1179
+ Self::new_with_dat(cancellable_id, (), base_ctx)
624
1180
  }
625
1181
  }
626
1182
  impl<T, D, ID> CancellableWFCommandFut<T, D, ID> {
627
- fn new_with_dat(cancellable_id: ID, other_dat: D) -> (Self, oneshot::Sender<UnblockEvent>) {
1183
+ fn new_with_dat(
1184
+ cancellable_id: ID,
1185
+ other_dat: D,
1186
+ base_ctx: BaseWorkflowContext,
1187
+ ) -> (Self, oneshot::Sender<UnblockEvent>) {
628
1188
  let (cmd_fut, sender) = WFCommandFut::new_with_dat(other_dat);
629
1189
  (
630
1190
  Self {
631
1191
  cmd_fut,
632
1192
  cancellable_id,
1193
+ base_ctx,
633
1194
  },
634
1195
  sender,
635
1196
  )
@@ -646,50 +1207,74 @@ where
646
1207
  self.cmd_fut.poll_unpin(cx)
647
1208
  }
648
1209
  }
1210
+ impl<T, D, ID> FusedFuture for CancellableWFCommandFut<T, D, ID>
1211
+ where
1212
+ T: Unblockable<OtherDat = D>,
1213
+ {
1214
+ fn is_terminated(&self) -> bool {
1215
+ self.cmd_fut.is_terminated()
1216
+ }
1217
+ }
649
1218
 
650
1219
  impl<T, D, ID> CancellableFuture<T> for CancellableWFCommandFut<T, D, ID>
651
1220
  where
652
1221
  T: Unblockable<OtherDat = D>,
653
1222
  ID: Clone + Into<CancellableID>,
654
1223
  {
655
- fn cancel(&self, cx: &WfContext) {
656
- cx.cancel(self.cancellable_id.clone().into());
1224
+ fn cancel(&self) {
1225
+ self.base_ctx.cancel(self.cancellable_id.clone().into());
657
1226
  }
658
1227
  }
659
1228
  impl<T, D> CancellableFutureWithReason<T> for CancellableWFCommandFut<T, D, CancellableIDWithReason>
660
1229
  where
661
1230
  T: Unblockable<OtherDat = D>,
662
1231
  {
663
- fn cancel_with_reason(&self, cx: &WfContext, reason: String) {
1232
+ fn cancel_with_reason(&self, reason: String) {
664
1233
  let new_id = self.cancellable_id.clone().with_reason(reason);
665
- cx.cancel(new_id);
1234
+ self.base_ctx.cancel(new_id);
666
1235
  }
667
1236
  }
668
1237
 
669
- struct LATimerBackoffFut<'a> {
1238
+ struct LATimerBackoffFut {
670
1239
  la_opts: LocalActivityOptions,
671
- current_fut: Pin<Box<dyn CancellableFuture<ActivityResolution> + Send + Unpin + 'a>>,
672
- timer_fut: Option<Pin<Box<dyn CancellableFuture<TimerResult> + Send + Unpin + 'a>>>,
673
- ctx: &'a WfContext,
1240
+ activity_type: String,
1241
+ arguments: Vec<Payload>,
1242
+ current_fut: Pin<Box<dyn CancellableFuture<ActivityResolution> + Unpin>>,
1243
+ timer_fut: Option<Pin<Box<dyn CancellableFuture<TimerResult> + Unpin>>>,
1244
+ base_ctx: BaseWorkflowContext,
674
1245
  next_attempt: u32,
675
1246
  next_sched_time: Option<prost_types::Timestamp>,
676
1247
  did_cancel: AtomicBool,
1248
+ terminated: bool,
677
1249
  }
678
- impl<'a> LATimerBackoffFut<'a> {
679
- pub(crate) fn new(opts: LocalActivityOptions, ctx: &'a WfContext) -> Self {
1250
+ impl LATimerBackoffFut {
1251
+ pub(crate) fn new(
1252
+ activity_type: String,
1253
+ arguments: Vec<Payload>,
1254
+ opts: LocalActivityOptions,
1255
+ base_ctx: BaseWorkflowContext,
1256
+ ) -> Self {
1257
+ let current_fut = Box::pin(base_ctx.clone().local_activity_no_timer_retry(
1258
+ activity_type.clone(),
1259
+ arguments.clone(),
1260
+ opts.clone(),
1261
+ ));
680
1262
  Self {
681
- la_opts: opts.clone(),
682
- current_fut: Box::pin(ctx.local_activity_no_timer_retry(opts)),
1263
+ la_opts: opts,
1264
+ activity_type,
1265
+ arguments,
1266
+ current_fut,
683
1267
  timer_fut: None,
684
- ctx,
1268
+ base_ctx,
685
1269
  next_attempt: 1,
686
1270
  next_sched_time: None,
687
1271
  did_cancel: AtomicBool::new(false),
1272
+ terminated: false,
688
1273
  }
689
1274
  }
690
1275
  }
691
- impl Unpin for LATimerBackoffFut<'_> {}
692
- impl Future for LATimerBackoffFut<'_> {
1276
+ impl Unpin for LATimerBackoffFut {}
1277
+ impl Future for LATimerBackoffFut {
693
1278
  type Output = ActivityResolution;
694
1279
 
695
1280
  fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
@@ -704,9 +1289,15 @@ impl Future for LATimerBackoffFut<'_> {
704
1289
  opts.attempt = Some(self.next_attempt);
705
1290
  opts.original_schedule_time
706
1291
  .clone_from(&self.next_sched_time);
707
- self.current_fut = Box::pin(self.ctx.local_activity_no_timer_retry(opts));
1292
+ self.current_fut =
1293
+ Box::pin(self.base_ctx.clone().local_activity_no_timer_retry(
1294
+ self.activity_type.clone(),
1295
+ self.arguments.clone(),
1296
+ opts,
1297
+ ));
708
1298
  Poll::Pending
709
1299
  } else {
1300
+ self.terminated = true;
710
1301
  Poll::Ready(ActivityResolution {
711
1302
  status: Some(
712
1303
  activity_resolution::Status::Cancelled(Default::default()),
@@ -725,12 +1316,13 @@ impl Future for LATimerBackoffFut<'_> {
725
1316
  // return cancel status. This can happen if cancel comes after the LA says it wants
726
1317
  // to back off but before we have scheduled the timer.
727
1318
  if self.did_cancel.load(Ordering::Acquire) {
1319
+ self.terminated = true;
728
1320
  return Poll::Ready(ActivityResolution {
729
1321
  status: Some(activity_resolution::Status::Cancelled(Default::default())),
730
1322
  });
731
1323
  }
732
1324
 
733
- let timer_f = self.ctx.timer::<Duration>(
1325
+ let timer_f = self.base_ctx.timer::<Duration>(
734
1326
  b.backoff_duration
735
1327
  .expect("Duration is set")
736
1328
  .try_into()
@@ -741,34 +1333,165 @@ impl Future for LATimerBackoffFut<'_> {
741
1333
  self.next_sched_time.clone_from(&b.original_schedule_time);
742
1334
  return Poll::Pending;
743
1335
  }
1336
+ if poll_res.is_ready() {
1337
+ self.terminated = true;
1338
+ }
744
1339
  poll_res
745
1340
  }
746
1341
  }
747
- impl CancellableFuture<ActivityResolution> for LATimerBackoffFut<'_> {
748
- fn cancel(&self, ctx: &WfContext) {
1342
+ impl FusedFuture for LATimerBackoffFut {
1343
+ fn is_terminated(&self) -> bool {
1344
+ self.terminated
1345
+ }
1346
+ }
1347
+ impl CancellableFuture<ActivityResolution> for LATimerBackoffFut {
1348
+ fn cancel(&self) {
749
1349
  self.did_cancel.store(true, Ordering::Release);
750
1350
  if let Some(tf) = self.timer_fut.as_ref() {
751
- tf.cancel(ctx);
1351
+ tf.cancel();
1352
+ }
1353
+ self.current_fut.cancel();
1354
+ }
1355
+ }
1356
+
1357
+ /// Future for activity results. Either an immediate error or a running activity.
1358
+ enum ActivityFut<F, Output> {
1359
+ /// Immediate error (e.g., input serialization failure). Resolves on first poll.
1360
+ Errored {
1361
+ error: Option<ActivityExecutionError>,
1362
+ _phantom: PhantomData<Output>,
1363
+ },
1364
+ /// Running activity that will deserialize output on completion.
1365
+ Running {
1366
+ inner: F,
1367
+ payload_converter: PayloadConverter,
1368
+ _phantom: PhantomData<Output>,
1369
+ },
1370
+ Terminated,
1371
+ }
1372
+
1373
+ impl<F, Output> ActivityFut<F, Output> {
1374
+ fn eager(err: ActivityExecutionError) -> Self {
1375
+ Self::Errored {
1376
+ error: Some(err),
1377
+ _phantom: PhantomData,
1378
+ }
1379
+ }
1380
+
1381
+ fn running(inner: F, payload_converter: PayloadConverter) -> Self {
1382
+ Self::Running {
1383
+ inner,
1384
+ payload_converter,
1385
+ _phantom: PhantomData,
1386
+ }
1387
+ }
1388
+ }
1389
+
1390
+ impl<F, Output> Unpin for ActivityFut<F, Output> where F: Unpin {}
1391
+
1392
+ impl<F, Output> Future for ActivityFut<F, Output>
1393
+ where
1394
+ F: Future<Output = ActivityResolution> + Unpin,
1395
+ Output: TemporalDeserializable + 'static,
1396
+ {
1397
+ type Output = Result<Output, ActivityExecutionError>;
1398
+
1399
+ fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1400
+ let this = self.get_mut();
1401
+ let poll = match this {
1402
+ ActivityFut::Errored { error, .. } => {
1403
+ Poll::Ready(Err(error.take().expect("polled after completion")))
1404
+ }
1405
+ ActivityFut::Running {
1406
+ inner,
1407
+ payload_converter,
1408
+ ..
1409
+ } => match Pin::new(inner).poll(cx) {
1410
+ Poll::Pending => Poll::Pending,
1411
+ Poll::Ready(resolution) => Poll::Ready({
1412
+ let status = resolution.status.ok_or_else(|| {
1413
+ ActivityExecutionError::Failed(Box::new(Failure {
1414
+ message: "Activity completed without a status".to_string(),
1415
+ ..Default::default()
1416
+ }))
1417
+ })?;
1418
+
1419
+ match status {
1420
+ activity_resolution::Status::Completed(success) => {
1421
+ let payload = success.result.unwrap_or_default();
1422
+ let ctx = SerializationContext {
1423
+ data: &SerializationContextData::Workflow,
1424
+ converter: payload_converter,
1425
+ };
1426
+ payload_converter
1427
+ .from_payload::<Output>(&ctx, payload)
1428
+ .map_err(ActivityExecutionError::Serialization)
1429
+ }
1430
+ activity_resolution::Status::Failed(f) => Err(
1431
+ ActivityExecutionError::Failed(Box::new(f.failure.unwrap_or_default())),
1432
+ ),
1433
+ activity_resolution::Status::Cancelled(c) => {
1434
+ Err(ActivityExecutionError::Cancelled(Box::new(
1435
+ c.failure.unwrap_or_default(),
1436
+ )))
1437
+ }
1438
+ activity_resolution::Status::Backoff(_) => {
1439
+ panic!("DoBackoff should be handled by LATimerBackoffFut")
1440
+ }
1441
+ }
1442
+ }),
1443
+ },
1444
+ ActivityFut::Terminated => panic!("polled after termination"),
1445
+ };
1446
+ if poll.is_ready() {
1447
+ *this = ActivityFut::Terminated;
1448
+ }
1449
+ poll
1450
+ }
1451
+ }
1452
+
1453
+ impl<F, Output> FusedFuture for ActivityFut<F, Output>
1454
+ where
1455
+ F: Future<Output = ActivityResolution> + Unpin,
1456
+ Output: TemporalDeserializable + 'static,
1457
+ {
1458
+ fn is_terminated(&self) -> bool {
1459
+ matches!(self, ActivityFut::Terminated)
1460
+ }
1461
+ }
1462
+
1463
+ impl<F, Output> CancellableFuture<Result<Output, ActivityExecutionError>> for ActivityFut<F, Output>
1464
+ where
1465
+ F: CancellableFuture<ActivityResolution> + Unpin,
1466
+ Output: TemporalDeserializable + 'static,
1467
+ {
1468
+ fn cancel(&self) {
1469
+ if let ActivityFut::Running { inner, .. } = self {
1470
+ inner.cancel()
752
1471
  }
753
- self.current_fut.cancel(ctx);
754
1472
  }
755
1473
  }
756
1474
 
757
1475
  /// A stub representing an unstarted child workflow.
758
- #[derive(Default, Debug, Clone)]
1476
+ #[derive(Clone, derive_more::Debug)]
759
1477
  pub struct ChildWorkflow {
760
1478
  opts: ChildWorkflowOptions,
1479
+ #[debug(skip)]
1480
+ base_ctx: BaseWorkflowContext,
761
1481
  }
762
1482
 
763
1483
  pub(crate) struct ChildWfCommon {
764
1484
  workflow_id: String,
765
1485
  result_future: CancellableWFCommandFut<ChildWorkflowResult, (), CancellableIDWithReason>,
1486
+ base_ctx: BaseWorkflowContext,
766
1487
  }
767
1488
 
768
1489
  /// Child workflow in pending state
1490
+ #[derive(derive_more::Debug)]
769
1491
  pub struct PendingChildWorkflow {
770
1492
  /// The status of the child workflow start
771
1493
  pub status: ChildWorkflowStartStatus,
1494
+ #[debug(skip)]
772
1495
  pub(crate) common: ChildWfCommon,
773
1496
  }
774
1497
 
@@ -787,29 +1510,43 @@ impl PendingChildWorkflow {
787
1510
  }
788
1511
 
789
1512
  /// Child workflow in started state
1513
+ #[derive(derive_more::Debug)]
790
1514
  pub struct StartedChildWorkflow {
791
1515
  /// Run ID of the child workflow
792
1516
  pub run_id: String,
1517
+ #[debug(skip)]
793
1518
  common: ChildWfCommon,
794
1519
  }
795
1520
 
796
1521
  impl ChildWorkflow {
797
1522
  /// Start the child workflow, the returned Future is cancellable.
798
- pub fn start(self, cx: &WfContext) -> impl CancellableFutureWithReason<PendingChildWorkflow> {
799
- let child_seq = cx.seq_nums.write().next_child_workflow_seq();
1523
+ pub fn start(self) -> impl CancellableFutureWithReason<PendingChildWorkflow> {
1524
+ let child_seq = self
1525
+ .base_ctx
1526
+ .inner
1527
+ .seq_nums
1528
+ .borrow_mut()
1529
+ .next_child_workflow_seq();
800
1530
  // Immediately create the command/future for the result, otherwise if the user does
801
1531
  // not await the result until *after* we receive an activation for it, there will be nothing
802
1532
  // to match when unblocking.
803
- let cancel_seq = cx.seq_nums.write().next_cancel_external_wf_seq();
804
- let (result_cmd, unblocker) =
805
- CancellableWFCommandFut::new(CancellableIDWithReason::ExternalWorkflow {
1533
+ let cancel_seq = self
1534
+ .base_ctx
1535
+ .inner
1536
+ .seq_nums
1537
+ .borrow_mut()
1538
+ .next_cancel_external_wf_seq();
1539
+ let (result_cmd, unblocker) = CancellableWFCommandFut::new(
1540
+ CancellableIDWithReason::ExternalWorkflow {
806
1541
  seqnum: cancel_seq,
807
1542
  execution: NamespacedWorkflowExecution {
808
1543
  workflow_id: self.opts.workflow_id.clone(),
809
1544
  ..Default::default()
810
1545
  },
811
- });
812
- cx.send(
1546
+ },
1547
+ self.base_ctx.clone(),
1548
+ );
1549
+ self.base_ctx.send(
813
1550
  CommandSubscribeChildWorkflowCompletion {
814
1551
  seq: child_seq,
815
1552
  unblocker,
@@ -820,13 +1557,15 @@ impl ChildWorkflow {
820
1557
  let common = ChildWfCommon {
821
1558
  workflow_id: self.opts.workflow_id.clone(),
822
1559
  result_future: result_cmd,
1560
+ base_ctx: self.base_ctx.clone(),
823
1561
  };
824
1562
 
825
1563
  let (cmd, unblocker) = CancellableWFCommandFut::new_with_dat(
826
1564
  CancellableIDWithReason::ChildWorkflow { seqnum: child_seq },
827
1565
  common,
1566
+ self.base_ctx.clone(),
828
1567
  );
829
- cx.send(
1568
+ self.base_ctx.send(
830
1569
  CommandCreateRequest {
831
1570
  cmd: self.opts.into_command(child_seq),
832
1571
  unblocker,
@@ -846,8 +1585,8 @@ impl StartedChildWorkflow {
846
1585
  }
847
1586
 
848
1587
  /// Cancel the child workflow
849
- pub fn cancel(&self, cx: &WfContext, reason: String) {
850
- cx.send(RustWfCmd::NewNonblockingCmd(
1588
+ pub fn cancel(&self, reason: String) {
1589
+ self.common.base_ctx.send(RustWfCmd::NewNonblockingCmd(
851
1590
  CancelChildWorkflowExecution {
852
1591
  child_workflow_seq: self.common.result_future.cancellable_id.seq_num(),
853
1592
  reason,
@@ -857,13 +1596,15 @@ impl StartedChildWorkflow {
857
1596
  }
858
1597
 
859
1598
  /// Signal the child workflow
860
- pub fn signal<'a, S: Into<Signal>>(
1599
+ pub fn signal<S: Into<Signal>>(
861
1600
  &self,
862
- cx: &'a WfContext,
863
1601
  data: S,
864
- ) -> impl CancellableFuture<SignalExternalWfResult> + use<'a, S> {
1602
+ ) -> impl CancellableFuture<SignalExternalWfResult> + 'static {
865
1603
  let target = sig_we::Target::ChildWorkflowId(self.common.workflow_id.clone());
866
- cx.send_signal_wf(target, data.into())
1604
+ self.common
1605
+ .base_ctx
1606
+ .clone()
1607
+ .send_signal_wf(target, data.into())
867
1608
  }
868
1609
  }
869
1610
 
@@ -878,6 +1619,7 @@ pub struct StartedNexusOperation {
878
1619
  pub(crate) struct NexusUnblockData {
879
1620
  result_future: Shared<WFCommandFut<NexusOperationResult, ()>>,
880
1621
  schedule_seq: u32,
1622
+ base_ctx: BaseWorkflowContext,
881
1623
  }
882
1624
 
883
1625
  impl StartedNexusOperation {
@@ -885,7 +1627,9 @@ impl StartedNexusOperation {
885
1627
  self.unblock_dat.result_future.clone().await
886
1628
  }
887
1629
 
888
- pub fn cancel(&self, cx: &WfContext) {
889
- cx.cancel(CancellableID::NexusOp(self.unblock_dat.schedule_seq));
1630
+ pub fn cancel(&self) {
1631
+ self.unblock_dat
1632
+ .base_ctx
1633
+ .cancel(CancellableID::NexusOp(self.unblock_dat.schedule_seq));
890
1634
  }
891
1635
  }