@temporalio/core-bridge 0.23.0 → 1.0.0

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 (135) hide show
  1. package/Cargo.lock +118 -15
  2. package/Cargo.toml +2 -1
  3. package/LICENSE.md +1 -1
  4. package/README.md +1 -1
  5. package/index.d.ts +47 -18
  6. package/package.json +7 -7
  7. package/releases/aarch64-apple-darwin/index.node +0 -0
  8. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  9. package/releases/x86_64-apple-darwin/index.node +0 -0
  10. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  11. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  12. package/sdk-core/.buildkite/docker/docker-compose.yaml +4 -2
  13. package/sdk-core/ARCHITECTURE.md +9 -7
  14. package/sdk-core/README.md +5 -1
  15. package/sdk-core/arch_docs/diagrams/workflow_internals.svg +1 -0
  16. package/sdk-core/bridge-ffi/src/wrappers.rs +0 -3
  17. package/sdk-core/client/src/lib.rs +26 -8
  18. package/sdk-core/client/src/raw.rs +166 -54
  19. package/sdk-core/client/src/retry.rs +9 -4
  20. package/sdk-core/client/src/workflow_handle/mod.rs +4 -2
  21. package/sdk-core/core/Cargo.toml +2 -0
  22. package/sdk-core/core/src/abstractions.rs +137 -16
  23. package/sdk-core/core/src/core_tests/activity_tasks.rs +258 -63
  24. package/sdk-core/core/src/core_tests/child_workflows.rs +1 -2
  25. package/sdk-core/core/src/core_tests/determinism.rs +2 -2
  26. package/sdk-core/core/src/core_tests/local_activities.rs +8 -7
  27. package/sdk-core/core/src/core_tests/queries.rs +146 -60
  28. package/sdk-core/core/src/core_tests/replay_flag.rs +1 -1
  29. package/sdk-core/core/src/core_tests/workers.rs +39 -23
  30. package/sdk-core/core/src/core_tests/workflow_cancels.rs +1 -1
  31. package/sdk-core/core/src/core_tests/workflow_tasks.rs +387 -280
  32. package/sdk-core/core/src/lib.rs +6 -4
  33. package/sdk-core/core/src/pollers/poll_buffer.rs +16 -10
  34. package/sdk-core/core/src/protosext/mod.rs +6 -6
  35. package/sdk-core/core/src/retry_logic.rs +1 -1
  36. package/sdk-core/core/src/telemetry/metrics.rs +21 -7
  37. package/sdk-core/core/src/telemetry/mod.rs +18 -4
  38. package/sdk-core/core/src/test_help/mod.rs +341 -109
  39. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +18 -9
  40. package/sdk-core/core/src/worker/activities/local_activities.rs +19 -16
  41. package/sdk-core/core/src/worker/activities.rs +156 -29
  42. package/sdk-core/core/src/worker/client.rs +1 -0
  43. package/sdk-core/core/src/worker/mod.rs +132 -659
  44. package/sdk-core/core/src/{workflow → worker/workflow}/bridge.rs +1 -1
  45. package/sdk-core/core/src/{workflow → worker/workflow}/driven_workflow.rs +1 -1
  46. package/sdk-core/core/src/{workflow → worker/workflow}/history_update.rs +16 -2
  47. package/sdk-core/core/src/{workflow → worker/workflow}/machines/activity_state_machine.rs +39 -4
  48. package/sdk-core/core/src/{workflow → worker/workflow}/machines/cancel_external_state_machine.rs +5 -2
  49. package/sdk-core/core/src/{workflow → worker/workflow}/machines/cancel_workflow_state_machine.rs +1 -1
  50. package/sdk-core/core/src/{workflow → worker/workflow}/machines/child_workflow_state_machine.rs +2 -4
  51. package/sdk-core/core/src/{workflow → worker/workflow}/machines/complete_workflow_state_machine.rs +0 -0
  52. package/sdk-core/core/src/{workflow → worker/workflow}/machines/continue_as_new_workflow_state_machine.rs +1 -1
  53. package/sdk-core/core/src/{workflow → worker/workflow}/machines/fail_workflow_state_machine.rs +0 -0
  54. package/sdk-core/core/src/{workflow → worker/workflow}/machines/local_activity_state_machine.rs +2 -5
  55. package/sdk-core/core/src/{workflow → worker/workflow}/machines/mod.rs +1 -1
  56. package/sdk-core/core/src/{workflow → worker/workflow}/machines/mutable_side_effect_state_machine.rs +0 -0
  57. package/sdk-core/core/src/{workflow → worker/workflow}/machines/patch_state_machine.rs +1 -1
  58. package/sdk-core/core/src/{workflow → worker/workflow}/machines/side_effect_state_machine.rs +0 -0
  59. package/sdk-core/core/src/{workflow → worker/workflow}/machines/signal_external_state_machine.rs +4 -2
  60. package/sdk-core/core/src/{workflow → worker/workflow}/machines/timer_state_machine.rs +1 -2
  61. package/sdk-core/core/src/{workflow → worker/workflow}/machines/transition_coverage.rs +1 -1
  62. package/sdk-core/core/src/{workflow → worker/workflow}/machines/upsert_search_attributes_state_machine.rs +5 -7
  63. package/sdk-core/core/src/{workflow → worker/workflow}/machines/workflow_machines/local_acts.rs +2 -2
  64. package/sdk-core/core/src/{workflow → worker/workflow}/machines/workflow_machines.rs +40 -16
  65. package/sdk-core/core/src/{workflow → worker/workflow}/machines/workflow_task_state_machine.rs +0 -0
  66. package/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +198 -0
  67. package/sdk-core/core/src/worker/workflow/managed_run.rs +627 -0
  68. package/sdk-core/core/src/worker/workflow/mod.rs +1115 -0
  69. package/sdk-core/core/src/worker/workflow/run_cache.rs +143 -0
  70. package/sdk-core/core/src/worker/workflow/wft_poller.rs +88 -0
  71. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +936 -0
  72. package/sdk-core/core-api/src/errors.rs +3 -10
  73. package/sdk-core/core-api/src/lib.rs +2 -1
  74. package/sdk-core/core-api/src/worker.rs +26 -2
  75. package/sdk-core/etc/dynamic-config.yaml +2 -0
  76. package/sdk-core/integ-with-otel.sh +1 -1
  77. package/sdk-core/protos/api_upstream/Makefile +4 -4
  78. package/sdk-core/protos/api_upstream/api-linter.yaml +2 -0
  79. package/sdk-core/protos/api_upstream/buf.yaml +8 -9
  80. package/sdk-core/protos/api_upstream/temporal/api/cluster/v1/message.proto +83 -0
  81. package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +7 -1
  82. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/cluster.proto +40 -0
  83. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +3 -0
  84. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/reset.proto +3 -1
  85. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/schedule.proto +60 -0
  86. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/workflow.proto +3 -0
  87. package/sdk-core/protos/api_upstream/temporal/api/errordetails/v1/message.proto +32 -4
  88. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +69 -19
  89. package/sdk-core/protos/api_upstream/temporal/api/namespace/v1/message.proto +13 -0
  90. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +163 -0
  91. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +97 -0
  92. package/sdk-core/protos/api_upstream/temporal/api/schedule/v1/message.proto +300 -0
  93. package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +25 -0
  94. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +180 -3
  95. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +53 -3
  96. package/sdk-core/protos/local/temporal/sdk/core/activity_result/activity_result.proto +2 -2
  97. package/sdk-core/protos/local/temporal/sdk/core/activity_task/activity_task.proto +6 -5
  98. package/sdk-core/protos/local/temporal/sdk/core/bridge/bridge.proto +0 -1
  99. package/sdk-core/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +2 -1
  100. package/sdk-core/protos/local/temporal/sdk/core/common/common.proto +0 -64
  101. package/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +2 -1
  102. package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +11 -8
  103. package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +30 -25
  104. package/sdk-core/sdk/src/activity_context.rs +12 -5
  105. package/sdk-core/sdk/src/app_data.rs +37 -0
  106. package/sdk-core/sdk/src/lib.rs +76 -43
  107. package/sdk-core/sdk/src/workflow_context/options.rs +8 -6
  108. package/sdk-core/sdk/src/workflow_context.rs +14 -19
  109. package/sdk-core/sdk/src/workflow_future.rs +11 -6
  110. package/sdk-core/sdk-core-protos/src/history_builder.rs +19 -5
  111. package/sdk-core/sdk-core-protos/src/history_info.rs +11 -6
  112. package/sdk-core/sdk-core-protos/src/lib.rs +74 -176
  113. package/sdk-core/test-utils/src/lib.rs +85 -72
  114. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +11 -9
  115. package/sdk-core/tests/integ_tests/polling_tests.rs +12 -0
  116. package/sdk-core/tests/integ_tests/queries_tests.rs +39 -22
  117. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +49 -4
  118. package/sdk-core/tests/integ_tests/workflow_tests/appdata_propagation.rs +61 -0
  119. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +1 -1
  120. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +74 -13
  121. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +19 -0
  122. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -1
  123. package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +6 -3
  124. package/sdk-core/tests/integ_tests/workflow_tests.rs +10 -23
  125. package/sdk-core/tests/load_tests.rs +8 -3
  126. package/sdk-core/tests/main.rs +2 -1
  127. package/src/conversions.rs +47 -39
  128. package/src/errors.rs +10 -21
  129. package/src/lib.rs +342 -325
  130. package/sdk-core/core/src/pending_activations.rs +0 -173
  131. package/sdk-core/core/src/worker/wft_delivery.rs +0 -81
  132. package/sdk-core/core/src/workflow/mod.rs +0 -478
  133. package/sdk-core/core/src/workflow/workflow_tasks/cache_manager.rs +0 -194
  134. package/sdk-core/core/src/workflow/workflow_tasks/concurrency_manager.rs +0 -418
  135. package/sdk-core/core/src/workflow/workflow_tasks/mod.rs +0 -989
package/src/lib.rs CHANGED
@@ -3,12 +3,14 @@ mod errors;
3
3
 
4
4
  use crate::conversions::{get_optional, ObjectHandleConversionsExt};
5
5
  use errors::*;
6
+ use futures::stream::StreamExt;
6
7
  use neon::prelude::*;
7
- use once_cell::sync::OnceCell;
8
+ use neon::types::buffer::TypedArray;
8
9
  use opentelemetry::trace::{FutureExt, SpanContext, TraceContextExt};
9
10
  use parking_lot::RwLock;
10
11
  use prost::Message;
11
12
  use std::collections::HashMap;
13
+ use std::ops::Deref;
12
14
  use std::{
13
15
  cell::RefCell,
14
16
  fmt::Display,
@@ -22,7 +24,7 @@ use temporal_client::{
22
24
  use temporal_sdk_core::{
23
25
  api::{
24
26
  errors::{CompleteActivityError, CompleteWfError, PollActivityError, PollWfError},
25
- Worker as CoreWorker,
27
+ Worker as CoreWorkerTrait,
26
28
  },
27
29
  fetch_global_buffered_logs, init_replay_worker, init_worker,
28
30
  protos::{
@@ -32,17 +34,18 @@ use temporal_sdk_core::{
32
34
  },
33
35
  temporal::api::history::v1::History,
34
36
  },
35
- telemetry_init, ClientOptions, RetryClient, WorkerConfig,
37
+ telemetry_init, ClientOptions, RetryClient, Worker as CoreWorker, WorkerConfig,
36
38
  };
37
39
  use tokio::{
38
40
  runtime::Runtime,
39
41
  sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
40
42
  };
43
+ use tokio_stream::wrappers::UnboundedReceiverStream;
41
44
 
42
45
  type RawClient = RetryClient<ConfiguredClient<WorkflowServiceClientWithMetrics>>;
43
46
 
44
47
  /// A request from JS to bridge to core
45
- enum Request {
48
+ enum RuntimeRequest {
46
49
  /// A request to shutdown the runtime, breaks from the thread loop.
47
50
  Shutdown {
48
51
  /// Used to send the result back into JS
@@ -63,16 +66,8 @@ enum Request {
63
66
  /// Used to send the result back into JS
64
67
  callback: Root<JsFunction>,
65
68
  },
66
- /// A request to shutdown a worker, the worker instance will remain active to
67
- /// allow draining of pending tasks
68
- ShutdownWorker {
69
- worker: Arc<dyn CoreWorker>,
70
- /// Used to send the result back into JS
71
- callback: Root<JsFunction>,
72
- },
73
69
  /// A request to create a new Worker using a connected client
74
70
  InitWorker {
75
- runtime: Arc<RuntimeHandle>,
76
71
  /// Worker configuration e.g. limits and task queue
77
72
  config: WorkerConfig,
78
73
  /// A client created with a [CreateClient] request
@@ -82,7 +77,6 @@ enum Request {
82
77
  },
83
78
  /// A request to register a replay worker
84
79
  InitReplayWorker {
85
- runtime: Arc<RuntimeHandle>,
86
80
  /// Worker configuration. Must have unique task queue name.
87
81
  config: WorkerConfig,
88
82
  /// The history this worker should replay
@@ -90,16 +84,29 @@ enum Request {
90
84
  /// Used to send the result back into JS
91
85
  callback: Root<JsFunction>,
92
86
  },
87
+ /// A request to drain logs from core so they can be emitted in node
88
+ PollLogs {
89
+ /// Logs are sent to this function
90
+ callback: Root<JsFunction>,
91
+ },
92
+ }
93
+
94
+ #[derive(Debug)]
95
+ enum WorkerRequest {
96
+ /// A request to shutdown a worker, the worker instance will remain active to
97
+ /// allow draining of pending tasks
98
+ InitiateShutdown {
99
+ /// Used to send the result back into JS
100
+ callback: Root<JsFunction>,
101
+ },
93
102
  /// A request to poll for workflow activations
94
103
  PollWorkflowActivation {
95
- worker: Arc<dyn CoreWorker>,
96
104
  otel_span: SpanContext,
97
105
  /// Used to send the result back into JS
98
106
  callback: Root<JsFunction>,
99
107
  },
100
108
  /// A request to complete a single workflow activation
101
109
  CompleteWorkflowActivation {
102
- worker: Arc<dyn CoreWorker>,
103
110
  completion: WorkflowActivationCompletion,
104
111
  otel_span: SpanContext,
105
112
  /// Used to send the result back into JS
@@ -107,33 +114,23 @@ enum Request {
107
114
  },
108
115
  /// A request to poll for activity tasks
109
116
  PollActivityTask {
110
- worker: Arc<dyn CoreWorker>,
111
117
  otel_span: SpanContext,
112
118
  /// Used to report completion or error back into JS
113
119
  callback: Root<JsFunction>,
114
120
  },
115
121
  /// A request to complete a single activity task
116
122
  CompleteActivityTask {
117
- worker: Arc<dyn CoreWorker>,
118
123
  completion: ActivityTaskCompletion,
119
124
  otel_span: SpanContext,
120
125
  /// Used to send the result back into JS
121
126
  callback: Root<JsFunction>,
122
127
  },
123
128
  /// A request to send a heartbeat from a running activity
124
- RecordActivityHeartbeat {
125
- worker: Arc<dyn CoreWorker>,
126
- heartbeat: ActivityHeartbeat,
127
- },
128
- /// A request to drain logs from core so they can be emitted in node
129
- PollLogs {
130
- /// Logs are sent to this function
131
- callback: Root<JsFunction>,
132
- },
129
+ RecordActivityHeartbeat { heartbeat: ActivityHeartbeat },
133
130
  }
134
131
 
135
132
  struct RuntimeHandle {
136
- sender: UnboundedSender<Request>,
133
+ sender: UnboundedSender<RuntimeRequest>,
137
134
  }
138
135
 
139
136
  /// Box it so we can use the runtime from JS
@@ -151,34 +148,30 @@ impl Finalize for Client {}
151
148
 
152
149
  /// Worker struct, hold a reference for the channel sender responsible for sending requests from
153
150
  /// JS to a bridge thread which forwards them to core
154
- struct Worker {
155
- runtime: Arc<RuntimeHandle>,
156
- core_worker: Arc<dyn CoreWorker>,
151
+ struct WorkerHandle {
152
+ sender: UnboundedSender<WorkerRequest>,
157
153
  }
158
154
 
159
155
  /// Box it so we can use Worker from JS
160
- type BoxedWorker = JsBox<Worker>;
161
- impl Finalize for Worker {}
162
-
163
- /// Lazy-inits or returns a global tokio runtime that we use for interactions with Core(s)
164
- fn tokio_runtime() -> &'static Runtime {
165
- static INSTANCE: OnceCell<Runtime> = OnceCell::new();
166
- INSTANCE.get_or_init(|| {
167
- tokio::runtime::Builder::new_multi_thread()
168
- .enable_all()
169
- .thread_name("core")
170
- .build()
171
- .expect("Tokio runtime must construct properly")
172
- })
156
+ type BoxedWorker = JsBox<RefCell<Option<WorkerHandle>>>;
157
+ impl Finalize for WorkerHandle {}
158
+
159
+ /// Inits a multi-threaded tokio runtime used to interact with sdk-core APIs
160
+ fn tokio_runtime() -> Runtime {
161
+ tokio::runtime::Builder::new_multi_thread()
162
+ .enable_all()
163
+ .thread_name("core")
164
+ .build()
165
+ .expect("Tokio runtime must construct properly")
173
166
  }
174
167
 
175
- /// Send a result to JS via callback using an [EventQueue]
176
- fn send_result<F, T>(queue: Arc<EventQueue>, callback: Root<JsFunction>, res_fn: F)
168
+ /// Send a result to JS via callback using a [Channel]
169
+ fn send_result<F, T>(channel: Arc<Channel>, callback: Root<JsFunction>, res_fn: F)
177
170
  where
178
171
  F: for<'a> FnOnce(&mut TaskContext<'a>) -> NeonResult<Handle<'a, T>> + Send + 'static,
179
172
  T: Value,
180
173
  {
181
- queue.send(move |mut cx| {
174
+ channel.send(move |mut cx| {
182
175
  let callback = callback.into_inner(&mut cx);
183
176
  let this = cx.undefined();
184
177
  let error = cx.undefined();
@@ -189,13 +182,13 @@ where
189
182
  });
190
183
  }
191
184
 
192
- /// Send an error to JS via callback using an [EventQueue]
193
- fn send_error<E, F>(queue: Arc<EventQueue>, callback: Root<JsFunction>, error_ctor: F)
185
+ /// Send an error to JS via callback using a [Channel]
186
+ fn send_error<E, F>(channel: Arc<Channel>, callback: Root<JsFunction>, error_ctor: F)
194
187
  where
195
188
  E: Object,
196
189
  F: for<'a> FnOnce(&mut TaskContext<'a>) -> JsResult<'a, E> + Send + 'static,
197
190
  {
198
- queue.send(move |mut cx| {
191
+ channel.send(move |mut cx| {
199
192
  let callback = callback.into_inner(&mut cx);
200
193
  callback_with_error(&mut cx, callback, error_ctor)
201
194
  });
@@ -236,33 +229,33 @@ where
236
229
  })
237
230
  }
238
231
 
239
- /// When Future completes, call given JS callback using a neon::EventQueue with either error or
232
+ /// When Future completes, call given JS callback using a neon::Channel with either error or
240
233
  /// undefined
241
234
  async fn void_future_to_js<E, F, ER, EF>(
242
- queue: Arc<EventQueue>,
235
+ channel: Arc<Channel>,
243
236
  callback: Root<JsFunction>,
244
237
  f: F,
245
238
  error_function: EF,
246
239
  ) where
247
240
  E: Display + Send + 'static,
248
- F: Future<Output = Result<(), E>> + Send + 'static,
241
+ F: Future<Output = Result<(), E>> + Send,
249
242
  ER: Object,
250
243
  EF: for<'a> FnOnce(&mut TaskContext<'a>, E) -> JsResult<'a, ER> + Send + 'static,
251
244
  {
252
245
  match f.await {
253
246
  Ok(()) => {
254
- send_result(queue, callback, |cx| Ok(cx.undefined()));
247
+ send_result(channel, callback, |cx| Ok(cx.undefined()));
255
248
  }
256
249
  Err(err) => {
257
- send_error(queue, callback, |cx| error_function(cx, err));
250
+ send_error(channel, callback, |cx| error_function(cx, err));
258
251
  }
259
252
  }
260
253
  }
261
254
 
262
- /// Builds a tokio runtime and starts polling on [Request]s via an internal channel.
263
- /// Bridges requests from JS to core and sends responses back to JS using a neon::EventQueue.
255
+ /// Builds a tokio runtime and starts polling on [RuntimeRequest]s via an internal channel.
256
+ /// Bridges requests from JS to core and sends responses back to JS using a neon::Channel.
264
257
  /// Blocks current thread until a [Shutdown] request is received in channel.
265
- fn start_bridge_loop(event_queue: Arc<EventQueue>, receiver: &mut UnboundedReceiver<Request>) {
258
+ fn start_bridge_loop(channel: Arc<Channel>, receiver: &mut UnboundedReceiver<RuntimeRequest>) {
266
259
  tokio_runtime().block_on(async {
267
260
  loop {
268
261
  let request_option = receiver.recv().await;
@@ -271,14 +264,14 @@ fn start_bridge_loop(event_queue: Arc<EventQueue>, receiver: &mut UnboundedRecei
271
264
  Some(request) => request,
272
265
  };
273
266
 
274
- let event_queue = event_queue.clone();
267
+ let channel = channel.clone();
275
268
 
276
269
  match request {
277
- Request::Shutdown { callback } => {
278
- send_result(event_queue, callback, |cx| Ok(cx.undefined()));
270
+ RuntimeRequest::Shutdown { callback } => {
271
+ send_result(channel, callback, |cx| Ok(cx.undefined()));
279
272
  break;
280
273
  }
281
- Request::CreateClient {
274
+ RuntimeRequest::CreateClient {
282
275
  runtime,
283
276
  options,
284
277
  headers,
@@ -288,43 +281,48 @@ fn start_bridge_loop(event_queue: Arc<EventQueue>, receiver: &mut UnboundedRecei
288
281
  // returned client directly at the moment, when we repurpose the client to be
289
282
  // used by a Worker, `init_worker` will attach the correct metrics meter for
290
283
  // us.
291
- match options
292
- .connect_no_namespace(None, headers.map(|h| Arc::new(RwLock::new(h))))
293
- .await
294
- {
295
- Err(err) => {
296
- send_error(event_queue.clone(), callback, |cx| match err {
297
- ClientInitError::SystemInfoCallError(e) => TRANSPORT_ERROR
298
- .from_error(cx, format!("Failed to call GetSystemInfo: {}", e)),
299
- ClientInitError::TonicTransportError(e) => {
300
- TRANSPORT_ERROR.from_error(cx, e)
301
- }
302
- ClientInitError::InvalidUri(e) => {
303
- Ok(JsError::type_error(cx, format!("{}", e))?.upcast())
304
- }
305
- });
306
- }
307
- Ok(client) => {
308
- send_result(event_queue.clone(), callback, |cx| {
309
- Ok(cx.boxed(RefCell::new(Some(Client {
310
- runtime,
311
- core_client: Arc::new(client),
312
- }))))
313
- });
284
+ tokio::spawn(async move {
285
+ match options
286
+ .connect_no_namespace(None, headers.map(|h| Arc::new(RwLock::new(h))))
287
+ .await
288
+ {
289
+ Err(err) => {
290
+ send_error(channel.clone(), callback, |cx| match err {
291
+ ClientInitError::SystemInfoCallError(e) => TRANSPORT_ERROR
292
+ .from_string(
293
+ cx,
294
+ format!("Failed to call GetSystemInfo: {}", e),
295
+ ),
296
+ ClientInitError::TonicTransportError(e) => {
297
+ TRANSPORT_ERROR.from_error(cx, e)
298
+ }
299
+ ClientInitError::InvalidUri(e) => {
300
+ Ok(JsError::type_error(cx, format!("{}", e))?.upcast())
301
+ }
302
+ });
303
+ }
304
+ Ok(client) => {
305
+ send_result(channel.clone(), callback, |cx| {
306
+ Ok(cx.boxed(RefCell::new(Some(Client {
307
+ runtime,
308
+ core_client: Arc::new(client),
309
+ }))))
310
+ });
311
+ }
314
312
  }
315
- }
313
+ });
316
314
  }
317
- Request::UpdateClientHeaders {
315
+ RuntimeRequest::UpdateClientHeaders {
318
316
  client,
319
317
  headers,
320
318
  callback,
321
319
  } => {
322
320
  client.get_client().set_headers(headers);
323
- send_result(event_queue.clone(), callback, |cx| Ok(cx.undefined()));
321
+ send_result(channel.clone(), callback, |cx| Ok(cx.undefined()));
324
322
  }
325
- Request::PollLogs { callback } => {
323
+ RuntimeRequest::PollLogs { callback } => {
326
324
  let logs = fetch_global_buffered_logs();
327
- send_result(event_queue.clone(), callback, |cx| {
325
+ send_result(channel.clone(), callback, |cx| {
328
326
  let logarr = cx.empty_array();
329
327
  for (i, cl) in logs.into_iter().enumerate() {
330
328
  // Not much to do here except for panic when there's an
@@ -341,21 +339,7 @@ fn start_bridge_loop(event_queue: Arc<EventQueue>, receiver: &mut UnboundedRecei
341
339
  Ok(logarr)
342
340
  });
343
341
  }
344
- Request::ShutdownWorker { worker, callback } => {
345
- tokio::spawn(void_future_to_js(
346
- event_queue,
347
- callback,
348
- async move {
349
- worker.shutdown().await;
350
- // Wrap the empty result in a valid Result object
351
- let result: Result<(), String> = Ok(());
352
- result
353
- },
354
- |cx, err| UNEXPECTED_ERROR.from_error(cx, err),
355
- ));
356
- }
357
- Request::InitWorker {
358
- runtime,
342
+ RuntimeRequest::InitWorker {
359
343
  config,
360
344
  client,
361
345
  callback,
@@ -363,134 +347,139 @@ fn start_bridge_loop(event_queue: Arc<EventQueue>, receiver: &mut UnboundedRecei
363
347
  let client = (*client).clone();
364
348
  let worker =
365
349
  init_worker(config, AnyClient::LowLevel(Box::new(client.into_inner())));
366
- send_result(event_queue.clone(), callback, |cx| {
367
- Ok(cx.boxed(Worker {
368
- core_worker: Arc::new(worker),
369
- runtime,
370
- }))
350
+ let (tx, rx) = unbounded_channel();
351
+ tokio::spawn(start_worker_loop(worker, rx, channel.clone()));
352
+ send_result(channel.clone(), callback, |cx| {
353
+ Ok(cx.boxed(RefCell::new(Some(WorkerHandle { sender: tx }))))
371
354
  });
372
355
  }
373
- Request::InitReplayWorker {
374
- runtime,
356
+ RuntimeRequest::InitReplayWorker {
375
357
  config,
376
358
  history,
377
359
  callback,
378
360
  } => {
379
361
  match init_replay_worker(config, &history) {
380
- Ok(worker) => send_result(event_queue.clone(), callback, |cx| {
381
- Ok(cx.boxed(Worker {
382
- core_worker: Arc::new(worker),
383
- runtime,
384
- }))
385
- }),
386
- Err(err) => send_error(event_queue.clone(), callback, |cx| {
387
- UNEXPECTED_ERROR.from_error(cx, err)
362
+ Ok(worker) => {
363
+ let (tx, rx) = unbounded_channel();
364
+ tokio::spawn(start_worker_loop(worker, rx, channel.clone()));
365
+ send_result(channel.clone(), callback, |cx| {
366
+ Ok(cx.boxed(RefCell::new(Some(WorkerHandle { sender: tx }))))
367
+ })
368
+ }
369
+ Err(err) => send_error(channel.clone(), callback, move |cx| {
370
+ UNEXPECTED_ERROR.from_error(cx, err.deref())
388
371
  }),
389
372
  };
390
373
  }
391
- Request::PollWorkflowActivation {
392
- worker,
393
- otel_span,
394
- callback,
395
- } => {
396
- tokio::spawn(async move {
374
+ }
375
+ }
376
+ })
377
+ }
378
+
379
+ /// Polls on [WorkerRequest]s via given channel.
380
+ /// Bridges requests from JS to core and sends responses back to JS using a neon::Channel.
381
+ /// Returns when the given channel is dropped.
382
+ async fn start_worker_loop(
383
+ worker: CoreWorker,
384
+ rx: UnboundedReceiver<WorkerRequest>,
385
+ channel: Arc<Channel>,
386
+ ) {
387
+ UnboundedReceiverStream::new(rx)
388
+ .for_each_concurrent(None, |request| {
389
+ let worker = &worker;
390
+ let channel = channel.clone();
391
+ async move {
392
+ match request {
393
+ WorkerRequest::InitiateShutdown { callback } => {
394
+ worker.initiate_shutdown();
395
+ send_result(channel, callback, |cx| Ok(cx.undefined()));
396
+ }
397
+ WorkerRequest::PollWorkflowActivation {
398
+ otel_span,
399
+ callback,
400
+ } => {
397
401
  handle_poll_workflow_activation_request(
398
- &*worker,
399
- otel_span,
400
- event_queue,
401
- callback,
402
+ &worker, otel_span, channel, callback,
402
403
  )
403
404
  .await
404
- });
405
- }
406
- Request::PollActivityTask {
407
- worker,
408
- otel_span,
409
- callback,
410
- } => {
411
- tokio::spawn(async move {
412
- handle_poll_activity_task_request(
413
- &*worker,
414
- otel_span,
415
- event_queue,
405
+ }
406
+ WorkerRequest::PollActivityTask {
407
+ otel_span,
408
+ callback,
409
+ } => {
410
+ handle_poll_activity_task_request(&worker, otel_span, channel, callback)
411
+ .await
412
+ }
413
+ WorkerRequest::CompleteWorkflowActivation {
414
+ completion,
415
+ otel_span,
416
+ callback,
417
+ } => {
418
+ let otel_ctx =
419
+ opentelemetry::Context::new().with_remote_span_context(otel_span);
420
+ void_future_to_js(
421
+ channel,
416
422
  callback,
423
+ async move {
424
+ worker
425
+ .complete_workflow_activation(completion)
426
+ .with_context(otel_ctx)
427
+ .await
428
+ },
429
+ |cx, err| match err {
430
+ CompleteWfError::TonicError(_) => {
431
+ TRANSPORT_ERROR.from_error(cx, err)
432
+ }
433
+ CompleteWfError::MalformedWorkflowCompletion { reason, .. } => {
434
+ Ok(JsError::type_error(cx, reason)?.upcast())
435
+ }
436
+ },
417
437
  )
418
- .await
419
- });
420
- }
421
- Request::CompleteWorkflowActivation {
422
- worker,
423
- completion,
424
- otel_span,
425
- callback,
426
- } => {
427
- let otel_ctx =
428
- opentelemetry::Context::new().with_remote_span_context(otel_span);
429
- tokio::spawn(void_future_to_js(
430
- event_queue,
431
- callback,
432
- async move {
433
- worker
434
- .complete_workflow_activation(completion)
435
- .with_context(otel_ctx)
436
- .await
437
- },
438
- |cx, err| match err {
439
- CompleteWfError::NoWorkerForQueue(queue_name) => {
440
- let args = vec![cx.string(queue_name).upcast()];
441
- NO_WORKER_ERROR.construct(cx, args)
442
- }
443
- CompleteWfError::TonicError(_) => TRANSPORT_ERROR.from_error(cx, err),
444
- CompleteWfError::MalformedWorkflowCompletion { reason, .. } => {
445
- Ok(JsError::type_error(cx, reason)?.upcast())
446
- }
447
- },
448
- ));
449
- }
450
- Request::CompleteActivityTask {
451
- worker,
452
- completion,
453
- otel_span,
454
- callback,
455
- } => {
456
- let otel_ctx =
457
- opentelemetry::Context::new().with_remote_span_context(otel_span);
458
- tokio::spawn(void_future_to_js(
459
- event_queue,
438
+ .await;
439
+ }
440
+ WorkerRequest::CompleteActivityTask {
441
+ completion,
442
+ otel_span,
460
443
  callback,
461
- async move {
462
- worker
463
- .complete_activity_task(completion)
464
- .with_context(otel_ctx)
465
- .await
466
- },
467
- |cx, err| match err {
468
- CompleteActivityError::MalformedActivityCompletion {
469
- reason, ..
470
- } => Ok(JsError::type_error(cx, reason)?.upcast()),
471
- CompleteActivityError::TonicError(_) => {
472
- TRANSPORT_ERROR.from_error(cx, err)
473
- }
474
- CompleteActivityError::NoWorkerForQueue(queue_name) => {
475
- let args = vec![cx.string(queue_name).upcast()];
476
- NO_WORKER_ERROR.construct(cx, args)
477
- }
478
- },
479
- ));
480
- }
481
- Request::RecordActivityHeartbeat { worker, heartbeat } => {
482
- worker.record_activity_heartbeat(heartbeat)
444
+ } => {
445
+ let otel_ctx =
446
+ opentelemetry::Context::new().with_remote_span_context(otel_span);
447
+ void_future_to_js(
448
+ channel,
449
+ callback,
450
+ async move {
451
+ worker
452
+ .complete_activity_task(completion)
453
+ .with_context(otel_ctx)
454
+ .await
455
+ },
456
+ |cx, err| match err {
457
+ CompleteActivityError::MalformedActivityCompletion {
458
+ reason,
459
+ ..
460
+ } => Ok(JsError::type_error(cx, reason)?.upcast()),
461
+ CompleteActivityError::TonicError(_) => {
462
+ TRANSPORT_ERROR.from_error(cx, err)
463
+ }
464
+ },
465
+ )
466
+ .await;
467
+ }
468
+ WorkerRequest::RecordActivityHeartbeat { heartbeat } => {
469
+ worker.record_activity_heartbeat(heartbeat)
470
+ }
483
471
  }
484
472
  }
485
- }
486
- })
473
+ })
474
+ .await;
475
+ worker.finalize_shutdown().await;
487
476
  }
488
477
 
489
478
  /// Called within the poll loop thread, calls core and triggers JS callback with result
490
479
  async fn handle_poll_workflow_activation_request(
491
- worker: &dyn CoreWorker,
480
+ worker: &CoreWorker,
492
481
  span_context: SpanContext,
493
- event_queue: Arc<EventQueue>,
482
+ channel: Arc<Channel>,
494
483
  callback: Root<JsFunction>,
495
484
  ) {
496
485
  let otel_ctx = opentelemetry::Context::new().with_remote_span_context(span_context);
@@ -500,24 +489,19 @@ async fn handle_poll_workflow_activation_request(
500
489
  .await
501
490
  {
502
491
  Ok(task) => {
503
- send_result(event_queue, callback, move |cx| {
492
+ send_result(channel, callback, move |cx| {
504
493
  let len = task.encoded_len();
505
- let mut result = JsArrayBuffer::new(cx, len as u32)?;
506
- cx.borrow_mut(&mut result, |data| {
507
- let mut slice = data.as_mut_slice::<u8>();
508
- if task.encode(&mut slice).is_err() {
509
- panic!("Failed to encode task")
510
- };
511
- });
494
+ let mut result = JsArrayBuffer::new(cx, len)?;
495
+ let mut slice = result.as_mut_slice(cx);
496
+ if task.encode(&mut slice).is_err() {
497
+ panic!("Failed to encode task")
498
+ };
512
499
  Ok(result)
513
500
  });
514
501
  }
515
502
  Err(err) => {
516
- send_error(event_queue, callback, move |cx| match err {
503
+ send_error(channel, callback, move |cx| match err {
517
504
  PollWfError::ShutDown => SHUTDOWN_ERROR.from_error(cx, err),
518
- PollWfError::AutocompleteError(CompleteWfError::NoWorkerForQueue(_)) => {
519
- UNEXPECTED_ERROR.from_error(cx, "TODO this error shouldn't exist")
520
- }
521
505
  PollWfError::TonicError(_)
522
506
  | PollWfError::AutocompleteError(CompleteWfError::TonicError(_)) => {
523
507
  TRANSPORT_ERROR.from_error(cx, err)
@@ -533,28 +517,26 @@ async fn handle_poll_workflow_activation_request(
533
517
 
534
518
  /// Called within the poll loop thread, calls core and triggers JS callback with result
535
519
  async fn handle_poll_activity_task_request(
536
- worker: &dyn CoreWorker,
520
+ worker: &CoreWorker,
537
521
  span_context: SpanContext,
538
- event_queue: Arc<EventQueue>,
522
+ channel: Arc<Channel>,
539
523
  callback: Root<JsFunction>,
540
524
  ) {
541
525
  let otel_ctx = opentelemetry::Context::new().with_remote_span_context(span_context);
542
526
  match worker.poll_activity_task().with_context(otel_ctx).await {
543
527
  Ok(task) => {
544
- send_result(event_queue, callback, move |cx| {
528
+ send_result(channel, callback, move |cx| {
545
529
  let len = task.encoded_len();
546
- let mut result = JsArrayBuffer::new(cx, len as u32)?;
547
- cx.borrow_mut(&mut result, |data| {
548
- let mut slice = data.as_mut_slice::<u8>();
549
- if task.encode(&mut slice).is_err() {
550
- panic!("Failed to encode task")
551
- };
552
- });
530
+ let mut result = JsArrayBuffer::new(cx, len)?;
531
+ let mut slice = result.as_mut_slice(cx);
532
+ if task.encode(&mut slice).is_err() {
533
+ panic!("Failed to encode task")
534
+ };
553
535
  Ok(result)
554
536
  });
555
537
  }
556
538
  Err(err) => {
557
- send_error(event_queue, callback, move |cx| match err {
539
+ send_error(channel, callback, move |cx| match err {
558
540
  PollActivityError::ShutDown => SHUTDOWN_ERROR.from_error(cx, err),
559
541
  PollActivityError::TonicError(_) => TRANSPORT_ERROR.from_error(cx, err),
560
542
  });
@@ -576,12 +558,12 @@ fn init_telemetry(mut cx: FunctionContext) -> JsResult<JsUndefined> {
576
558
  }
577
559
 
578
560
  /// Create the tokio runtime required to run Core.
579
- /// Immediately spawns a poller thread that will block on [Request]s
561
+ /// Immediately spawns a poller thread that will block on [RuntimeRequest]s
580
562
  fn runtime_new(mut cx: FunctionContext) -> JsResult<BoxedRuntime> {
581
- let queue = Arc::new(cx.queue());
582
- let (sender, mut receiver) = unbounded_channel::<Request>();
563
+ let channel = Arc::new(cx.channel());
564
+ let (sender, mut receiver) = unbounded_channel::<RuntimeRequest>();
583
565
 
584
- std::thread::spawn(move || start_bridge_loop(queue, &mut receiver));
566
+ std::thread::spawn(move || start_bridge_loop(channel, &mut receiver));
585
567
 
586
568
  Ok(cx.boxed(Arc::new(RuntimeHandle { sender })))
587
569
  }
@@ -594,13 +576,13 @@ fn client_new(mut cx: FunctionContext) -> JsResult<JsUndefined> {
594
576
  let callback = cx.argument::<JsFunction>(2)?;
595
577
 
596
578
  let client_options = opts.as_client_options(&mut cx)?;
597
- let headers = match js_optional_getter!(&mut cx, &opts, "headers", JsObject) {
579
+ let headers = match js_optional_getter!(&mut cx, &opts, "metadata", JsObject) {
598
580
  None => None,
599
581
  Some(h) => Some(
600
582
  h.as_hash_map_of_string_to_string(&mut cx)
601
583
  .map_err(|reason| {
602
584
  cx.throw_type_error::<_, HashMap<String, String>>(format!(
603
- "Invalid headers: {}",
585
+ "Invalid metadata: {}",
604
586
  reason
605
587
  ))
606
588
  .unwrap_err()
@@ -608,7 +590,7 @@ fn client_new(mut cx: FunctionContext) -> JsResult<JsUndefined> {
608
590
  ),
609
591
  };
610
592
 
611
- let request = Request::CreateClient {
593
+ let request = RuntimeRequest::CreateClient {
612
594
  runtime: (**runtime).clone(),
613
595
  options: client_options,
614
596
  headers,
@@ -634,7 +616,7 @@ fn client_update_headers(mut cx: FunctionContext) -> JsResult<JsUndefined> {
634
616
  callback_with_unexpected_error(&mut cx, callback, "Tried to use closed Client")?;
635
617
  }
636
618
  Some(client) => {
637
- let request = Request::UpdateClientHeaders {
619
+ let request = RuntimeRequest::UpdateClientHeaders {
638
620
  client: client.core_client.clone(),
639
621
  headers,
640
622
  callback: callback.root(&mut cx),
@@ -661,9 +643,8 @@ fn worker_new(mut cx: FunctionContext) -> JsResult<JsUndefined> {
661
643
  callback_with_unexpected_error(&mut cx, callback, "Tried to use closed Client")?;
662
644
  }
663
645
  Some(client) => {
664
- let request = Request::InitWorker {
646
+ let request = RuntimeRequest::InitWorker {
665
647
  client: client.core_client.clone(),
666
- runtime: client.runtime.clone(),
667
648
  config,
668
649
  callback: callback.root(&mut cx),
669
650
  };
@@ -685,13 +666,10 @@ fn replay_worker_new(mut cx: FunctionContext) -> JsResult<JsUndefined> {
685
666
  let callback = cx.argument::<JsFunction>(3)?;
686
667
 
687
668
  let config = worker_options.as_worker_config(&mut cx)?;
688
-
689
- match cx.borrow(&history_binary, |data| {
690
- History::decode_length_delimited(data.as_slice::<u8>())
691
- }) {
669
+ let data = history_binary.as_slice(&mut cx);
670
+ match History::decode_length_delimited(data) {
692
671
  Ok(history) => {
693
- let request = Request::InitReplayWorker {
694
- runtime: (**runtime).clone(),
672
+ let request = RuntimeRequest::InitReplayWorker {
695
673
  config,
696
674
  history,
697
675
  callback: callback.root(&mut cx),
@@ -712,7 +690,7 @@ fn replay_worker_new(mut cx: FunctionContext) -> JsResult<JsUndefined> {
712
690
  fn runtime_shutdown(mut cx: FunctionContext) -> JsResult<JsUndefined> {
713
691
  let runtime = cx.argument::<BoxedRuntime>(0)?;
714
692
  let callback = cx.argument::<JsFunction>(1)?;
715
- let request = Request::Shutdown {
693
+ let request = RuntimeRequest::Shutdown {
716
694
  callback: callback.root(&mut cx),
717
695
  };
718
696
  if let Err(err) = runtime.sender.send(request) {
@@ -725,7 +703,7 @@ fn runtime_shutdown(mut cx: FunctionContext) -> JsResult<JsUndefined> {
725
703
  fn poll_logs(mut cx: FunctionContext) -> JsResult<JsUndefined> {
726
704
  let runtime = cx.argument::<BoxedRuntime>(0)?;
727
705
  let callback = cx.argument::<JsFunction>(1)?;
728
- let request = Request::PollLogs {
706
+ let request = RuntimeRequest::PollLogs {
729
707
  callback: callback.root(&mut cx),
730
708
  };
731
709
  if let Err(err) = runtime.sender.send(request) {
@@ -740,13 +718,19 @@ fn worker_poll_workflow_activation(mut cx: FunctionContext) -> JsResult<JsUndefi
740
718
  let worker = cx.argument::<BoxedWorker>(0)?;
741
719
  let otel_span = cx.argument::<JsObject>(1)?;
742
720
  let callback = cx.argument::<JsFunction>(2)?;
743
- let request = Request::PollWorkflowActivation {
744
- worker: worker.core_worker.clone(),
745
- otel_span: otel_span.as_otel_span_context(&mut cx)?,
746
- callback: callback.root(&mut cx),
747
- };
748
- if let Err(err) = worker.runtime.sender.send(request) {
749
- callback_with_unexpected_error(&mut cx, callback, err)?;
721
+ match &*worker.borrow() {
722
+ None => {
723
+ callback_with_unexpected_error(&mut cx, callback, "Tried to use closed Worker")?;
724
+ }
725
+ Some(worker) => {
726
+ let request = WorkerRequest::PollWorkflowActivation {
727
+ otel_span: otel_span.as_otel_span_context(&mut cx)?,
728
+ callback: callback.root(&mut cx),
729
+ };
730
+ if let Err(err) = worker.sender.send(request) {
731
+ callback_with_unexpected_error(&mut cx, callback, err)?;
732
+ }
733
+ }
750
734
  }
751
735
  Ok(cx.undefined())
752
736
  }
@@ -757,13 +741,19 @@ fn worker_poll_activity_task(mut cx: FunctionContext) -> JsResult<JsUndefined> {
757
741
  let worker = cx.argument::<BoxedWorker>(0)?;
758
742
  let otel_span = cx.argument::<JsObject>(1)?;
759
743
  let callback = cx.argument::<JsFunction>(2)?;
760
- let request = Request::PollActivityTask {
761
- worker: worker.core_worker.clone(),
762
- otel_span: otel_span.as_otel_span_context(&mut cx)?,
763
- callback: callback.root(&mut cx),
764
- };
765
- if let Err(err) = worker.runtime.sender.send(request) {
766
- callback_with_unexpected_error(&mut cx, callback, err)?;
744
+ match &*worker.borrow() {
745
+ None => {
746
+ callback_with_unexpected_error(&mut cx, callback, "Tried to use closed Worker")?;
747
+ }
748
+ Some(worker) => {
749
+ let request = WorkerRequest::PollActivityTask {
750
+ otel_span: otel_span.as_otel_span_context(&mut cx)?,
751
+ callback: callback.root(&mut cx),
752
+ };
753
+ if let Err(err) = worker.sender.send(request) {
754
+ callback_with_unexpected_error(&mut cx, callback, err)?;
755
+ }
756
+ }
767
757
  }
768
758
  Ok(cx.undefined())
769
759
  }
@@ -774,24 +764,29 @@ fn worker_complete_workflow_activation(mut cx: FunctionContext) -> JsResult<JsUn
774
764
  let otel_span = cx.argument::<JsObject>(1)?;
775
765
  let completion = cx.argument::<JsArrayBuffer>(2)?;
776
766
  let callback = cx.argument::<JsFunction>(3)?;
777
- let result = cx.borrow(&completion, |data| {
778
- WorkflowActivationCompletion::decode_length_delimited(data.as_slice::<u8>())
779
- });
780
- match result {
781
- Ok(completion) => {
782
- let request = Request::CompleteWorkflowActivation {
783
- worker: worker.core_worker.clone(),
784
- completion,
785
- otel_span: otel_span.as_otel_span_context(&mut cx)?,
786
- callback: callback.root(&mut cx),
787
- };
788
- if let Err(err) = worker.runtime.sender.send(request) {
789
- callback_with_unexpected_error(&mut cx, callback, err)?;
790
- };
767
+ match &*worker.borrow() {
768
+ None => {
769
+ callback_with_unexpected_error(&mut cx, callback, "Tried to use closed Worker")?;
770
+ }
771
+ Some(worker) => {
772
+ match WorkflowActivationCompletion::decode_length_delimited(
773
+ completion.as_slice(&mut cx),
774
+ ) {
775
+ Ok(completion) => {
776
+ let request = WorkerRequest::CompleteWorkflowActivation {
777
+ completion,
778
+ otel_span: otel_span.as_otel_span_context(&mut cx)?,
779
+ callback: callback.root(&mut cx),
780
+ };
781
+ if let Err(err) = worker.sender.send(request) {
782
+ callback_with_unexpected_error(&mut cx, callback, err)?;
783
+ };
784
+ }
785
+ Err(_) => callback_with_error(&mut cx, callback, |cx| {
786
+ JsError::type_error(cx, "Cannot decode Completion from buffer")
787
+ })?,
788
+ }
791
789
  }
792
- Err(_) => callback_with_error(&mut cx, callback, |cx| {
793
- JsError::type_error(cx, "Cannot decode Completion from buffer")
794
- })?,
795
790
  };
796
791
  Ok(cx.undefined())
797
792
  }
@@ -802,24 +797,27 @@ fn worker_complete_activity_task(mut cx: FunctionContext) -> JsResult<JsUndefine
802
797
  let otel_span = cx.argument::<JsObject>(1)?;
803
798
  let result = cx.argument::<JsArrayBuffer>(2)?;
804
799
  let callback = cx.argument::<JsFunction>(3)?;
805
- let result = cx.borrow(&result, |data| {
806
- ActivityTaskCompletion::decode_length_delimited(data.as_slice::<u8>())
807
- });
808
- match result {
809
- Ok(completion) => {
810
- let request = Request::CompleteActivityTask {
811
- worker: worker.core_worker.clone(),
812
- completion,
813
- otel_span: otel_span.as_otel_span_context(&mut cx)?,
814
- callback: callback.root(&mut cx),
815
- };
816
- if let Err(err) = worker.runtime.sender.send(request) {
817
- callback_with_unexpected_error(&mut cx, callback, err)?;
818
- };
800
+ match &*worker.borrow() {
801
+ None => {
802
+ callback_with_unexpected_error(&mut cx, callback, "Tried to use closed Worker")?;
803
+ }
804
+ Some(worker) => {
805
+ match ActivityTaskCompletion::decode_length_delimited(result.as_slice(&mut cx)) {
806
+ Ok(completion) => {
807
+ let request = WorkerRequest::CompleteActivityTask {
808
+ completion,
809
+ otel_span: otel_span.as_otel_span_context(&mut cx)?,
810
+ callback: callback.root(&mut cx),
811
+ };
812
+ if let Err(err) = worker.sender.send(request) {
813
+ callback_with_unexpected_error(&mut cx, callback, err)?;
814
+ };
815
+ }
816
+ Err(_) => callback_with_error(&mut cx, callback, |cx| {
817
+ JsError::type_error(cx, "Cannot decode Completion from buffer")
818
+ })?,
819
+ }
819
820
  }
820
- Err(_) => callback_with_error(&mut cx, callback, |cx| {
821
- JsError::type_error(cx, "Cannot decode Completion from buffer")
822
- })?,
823
821
  };
824
822
  Ok(cx.undefined())
825
823
  }
@@ -828,41 +826,59 @@ fn worker_complete_activity_task(mut cx: FunctionContext) -> JsResult<JsUndefine
828
826
  fn worker_record_activity_heartbeat(mut cx: FunctionContext) -> JsResult<JsUndefined> {
829
827
  let worker = cx.argument::<BoxedWorker>(0)?;
830
828
  let heartbeat = cx.argument::<JsArrayBuffer>(1)?;
831
- let heartbeat = cx.borrow(&heartbeat, |data| {
832
- ActivityHeartbeat::decode_length_delimited(data.as_slice::<u8>())
833
- });
834
- match heartbeat {
835
- Ok(heartbeat) => {
836
- let request = Request::RecordActivityHeartbeat {
837
- worker: worker.core_worker.clone(),
838
- heartbeat,
839
- };
840
- match worker.runtime.sender.send(request) {
841
- Err(err) => UNEXPECTED_ERROR
842
- .from_error(&mut cx, err)
843
- .and_then(|err| cx.throw(err)),
844
- _ => Ok(cx.undefined()),
829
+ match &*worker.borrow() {
830
+ None => UNEXPECTED_ERROR
831
+ .from_string(&mut cx, "Tried to use closed Worker")
832
+ .and_then(|err| cx.throw(err))?,
833
+ Some(worker) => {
834
+ match ActivityHeartbeat::decode_length_delimited(heartbeat.as_slice(&mut cx)) {
835
+ Ok(heartbeat) => {
836
+ let request = WorkerRequest::RecordActivityHeartbeat { heartbeat };
837
+ if let Err(err) = worker.sender.send(request) {
838
+ UNEXPECTED_ERROR
839
+ .from_error(&mut cx, err)
840
+ .and_then(|err| cx.throw(err))?;
841
+ }
842
+ }
843
+ Err(_) => cx.throw_type_error("Cannot decode ActivityHeartbeat from buffer")?,
845
844
  }
846
845
  }
847
- Err(_) => cx.throw_type_error("Cannot decode ActivityHeartbeat from buffer"),
848
- }
846
+ };
847
+ Ok(cx.undefined())
849
848
  }
850
849
 
851
850
  /// Request shutdown of the worker.
852
851
  /// Once complete Core will stop polling on new tasks and activations on worker's task queue.
853
- /// Caller should drain any pending tasks and activations before breaking from
852
+ /// Caller should drain any pending tasks and activations and call worker_finalize_shutdown before breaking from
854
853
  /// the loop to ensure graceful shutdown.
855
- fn worker_shutdown(mut cx: FunctionContext) -> JsResult<JsUndefined> {
854
+ fn worker_initiate_shutdown(mut cx: FunctionContext) -> JsResult<JsUndefined> {
856
855
  let worker = cx.argument::<BoxedWorker>(0)?;
857
856
  let callback = cx.argument::<JsFunction>(1)?;
858
- if let Err(err) = worker.runtime.sender.send(Request::ShutdownWorker {
859
- worker: worker.core_worker.clone(),
860
- callback: callback.root(&mut cx),
861
- }) {
862
- UNEXPECTED_ERROR
863
- .from_error(&mut cx, err)
857
+ match &*worker.borrow() {
858
+ None => {
859
+ callback_with_unexpected_error(&mut cx, callback, "Tried to use closed Worker")?;
860
+ }
861
+ Some(worker) => {
862
+ if let Err(err) = worker.sender.send(WorkerRequest::InitiateShutdown {
863
+ callback: callback.root(&mut cx),
864
+ }) {
865
+ UNEXPECTED_ERROR
866
+ .from_error(&mut cx, err)
867
+ .and_then(|err| cx.throw(err))?;
868
+ };
869
+ }
870
+ }
871
+ Ok(cx.undefined())
872
+ }
873
+
874
+ fn worker_finalize_shutdown(mut cx: FunctionContext) -> JsResult<JsUndefined> {
875
+ let worker = cx.argument::<BoxedWorker>(0)?;
876
+ if worker.replace(None).is_none() {
877
+ ILLEGAL_STATE_ERROR
878
+ .from_string(&mut cx, "Worker already closed")
864
879
  .and_then(|err| cx.throw(err))?;
865
- };
880
+ }
881
+
866
882
  Ok(cx.undefined())
867
883
  }
868
884
 
@@ -871,7 +887,7 @@ fn client_close(mut cx: FunctionContext) -> JsResult<JsUndefined> {
871
887
  let client = cx.argument::<BoxedClient>(0)?;
872
888
  if client.replace(None).is_none() {
873
889
  ILLEGAL_STATE_ERROR
874
- .from_error(&mut cx, "Client already closed")
890
+ .from_string(&mut cx, "Client already closed")
875
891
  .and_then(|err| cx.throw(err))?;
876
892
  };
877
893
  Ok(cx.undefined())
@@ -909,7 +925,8 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> {
909
925
  cx.export_function("clientUpdateHeaders", client_update_headers)?;
910
926
  cx.export_function("newWorker", worker_new)?;
911
927
  cx.export_function("newReplayWorker", replay_worker_new)?;
912
- cx.export_function("workerShutdown", worker_shutdown)?;
928
+ cx.export_function("workerInitiateShutdown", worker_initiate_shutdown)?;
929
+ cx.export_function("workerFinalizeShutdown", worker_finalize_shutdown)?;
913
930
  cx.export_function("clientClose", client_close)?;
914
931
  cx.export_function("runtimeShutdown", runtime_shutdown)?;
915
932
  cx.export_function("pollLogs", poll_logs)?;