@temporalio/core-bridge 1.1.0 → 1.4.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 (124) hide show
  1. package/Cargo.lock +765 -128
  2. package/Cargo.toml +2 -2
  3. package/common.js +7 -3
  4. package/index.d.ts +118 -5
  5. package/index.js +2 -6
  6. package/package.json +2 -3
  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/scripts/build.js +4 -3
  13. package/sdk-core/.buildkite/docker/Dockerfile +2 -1
  14. package/sdk-core/.buildkite/pipeline.yml +2 -0
  15. package/sdk-core/.cargo/config.toml +1 -1
  16. package/sdk-core/ARCHITECTURE.md +2 -2
  17. package/sdk-core/README.md +12 -0
  18. package/sdk-core/bridge-ffi/Cargo.toml +2 -2
  19. package/sdk-core/bridge-ffi/src/lib.rs +2 -2
  20. package/sdk-core/client/Cargo.toml +7 -5
  21. package/sdk-core/client/src/lib.rs +354 -226
  22. package/sdk-core/client/src/metrics.rs +13 -11
  23. package/sdk-core/client/src/raw.rs +352 -107
  24. package/sdk-core/client/src/retry.rs +188 -147
  25. package/sdk-core/client/src/workflow_handle/mod.rs +1 -1
  26. package/sdk-core/core/Cargo.toml +28 -15
  27. package/sdk-core/core/src/core_tests/activity_tasks.rs +98 -33
  28. package/sdk-core/core/src/core_tests/child_workflows.rs +125 -3
  29. package/sdk-core/core/src/core_tests/local_activities.rs +6 -6
  30. package/sdk-core/core/src/core_tests/workers.rs +3 -2
  31. package/sdk-core/core/src/core_tests/workflow_tasks.rs +70 -2
  32. package/sdk-core/core/src/ephemeral_server/mod.rs +515 -0
  33. package/sdk-core/core/src/lib.rs +62 -28
  34. package/sdk-core/core/src/pollers/mod.rs +2 -0
  35. package/sdk-core/core/src/pollers/poll_buffer.rs +4 -4
  36. package/sdk-core/core/src/replay/mod.rs +3 -3
  37. package/sdk-core/core/src/retry_logic.rs +10 -9
  38. package/sdk-core/core/src/telemetry/metrics.rs +48 -39
  39. package/sdk-core/core/src/telemetry/mod.rs +46 -12
  40. package/sdk-core/core/src/telemetry/prometheus_server.rs +17 -13
  41. package/sdk-core/core/src/test_help/mod.rs +18 -8
  42. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +10 -10
  43. package/sdk-core/core/src/worker/activities/local_activities.rs +13 -13
  44. package/sdk-core/core/src/worker/activities.rs +6 -12
  45. package/sdk-core/core/src/worker/client/mocks.rs +1 -0
  46. package/sdk-core/core/src/worker/client.rs +193 -64
  47. package/sdk-core/core/src/worker/mod.rs +14 -19
  48. package/sdk-core/core/src/worker/workflow/driven_workflow.rs +3 -0
  49. package/sdk-core/core/src/worker/workflow/history_update.rs +5 -5
  50. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +133 -85
  51. package/sdk-core/core/src/worker/workflow/machines/mod.rs +3 -2
  52. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +160 -105
  53. package/sdk-core/core/src/worker/workflow/managed_run.rs +2 -1
  54. package/sdk-core/core/src/worker/workflow/mod.rs +62 -58
  55. package/sdk-core/core/src/worker/workflow/run_cache.rs +5 -3
  56. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +7 -5
  57. package/sdk-core/core-api/Cargo.toml +3 -3
  58. package/sdk-core/core-api/src/errors.rs +3 -11
  59. package/sdk-core/core-api/src/worker.rs +7 -0
  60. package/sdk-core/protos/api_upstream/.buildkite/Dockerfile +1 -1
  61. package/sdk-core/protos/api_upstream/.github/CODEOWNERS +1 -1
  62. package/sdk-core/protos/api_upstream/.github/PULL_REQUEST_TEMPLATE.md +2 -6
  63. package/sdk-core/protos/api_upstream/.github/workflows/trigger-api-go-update.yml +29 -0
  64. package/sdk-core/protos/api_upstream/Makefile +2 -2
  65. package/sdk-core/protos/api_upstream/buf.yaml +1 -0
  66. package/sdk-core/protos/api_upstream/temporal/api/batch/v1/message.proto +86 -0
  67. package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +26 -0
  68. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +46 -0
  69. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/command_type.proto +7 -0
  70. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/event_type.proto +14 -0
  71. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/update.proto +51 -0
  72. package/sdk-core/protos/api_upstream/temporal/api/failure/v1/message.proto +18 -0
  73. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +57 -1
  74. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +1 -3
  75. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +4 -2
  76. package/sdk-core/protos/api_upstream/temporal/api/replication/v1/message.proto +11 -0
  77. package/sdk-core/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +23 -0
  78. package/sdk-core/protos/api_upstream/temporal/api/update/v1/message.proto +46 -0
  79. package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +1 -0
  80. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +172 -0
  81. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +30 -0
  82. package/sdk-core/protos/grpc/health/v1/health.proto +63 -0
  83. package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +18 -15
  84. package/sdk-core/protos/testsrv_upstream/Makefile +80 -0
  85. package/sdk-core/protos/testsrv_upstream/api-linter.yaml +38 -0
  86. package/sdk-core/protos/testsrv_upstream/buf.yaml +13 -0
  87. package/sdk-core/protos/testsrv_upstream/dependencies/gogoproto/gogo.proto +141 -0
  88. package/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/request_response.proto +63 -0
  89. package/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/service.proto +90 -0
  90. package/sdk-core/sdk/Cargo.toml +2 -2
  91. package/sdk-core/sdk/src/lib.rs +2 -2
  92. package/sdk-core/sdk/src/workflow_context/options.rs +36 -8
  93. package/sdk-core/sdk/src/workflow_context.rs +30 -6
  94. package/sdk-core/sdk/src/workflow_future.rs +4 -4
  95. package/sdk-core/sdk-core-protos/Cargo.toml +5 -5
  96. package/sdk-core/sdk-core-protos/build.rs +9 -1
  97. package/sdk-core/sdk-core-protos/src/history_builder.rs +6 -1
  98. package/sdk-core/sdk-core-protos/src/lib.rs +93 -32
  99. package/sdk-core/test-utils/Cargo.toml +3 -3
  100. package/sdk-core/test-utils/src/canned_histories.rs +58 -0
  101. package/sdk-core/test-utils/src/lib.rs +35 -12
  102. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +128 -0
  103. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +55 -5
  104. package/sdk-core/tests/integ_tests/polling_tests.rs +2 -1
  105. package/sdk-core/tests/integ_tests/queries_tests.rs +5 -5
  106. package/sdk-core/tests/integ_tests/visibility_tests.rs +93 -0
  107. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +93 -10
  108. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +1 -1
  109. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +14 -14
  110. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +2 -6
  111. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +12 -12
  112. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +12 -1
  113. package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +3 -3
  114. package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +8 -2
  115. package/sdk-core/tests/integ_tests/workflow_tests.rs +19 -4
  116. package/sdk-core/tests/load_tests.rs +2 -1
  117. package/sdk-core/tests/main.rs +17 -0
  118. package/sdk-core/tests/runner.rs +93 -0
  119. package/src/conversions.rs +157 -94
  120. package/src/helpers.rs +190 -0
  121. package/src/lib.rs +10 -912
  122. package/src/runtime.rs +436 -0
  123. package/src/testing.rs +67 -0
  124. package/src/worker.rs +465 -0
package/src/lib.rs CHANGED
@@ -1,919 +1,14 @@
1
1
  mod conversions;
2
2
  mod errors;
3
+ mod helpers;
4
+ mod runtime;
5
+ mod testing;
6
+ mod worker;
3
7
 
4
- use crate::conversions::{get_optional, ObjectHandleConversionsExt};
5
- use errors::*;
6
- use futures::stream::StreamExt;
8
+ use crate::runtime::*;
9
+ use crate::worker::*;
7
10
  use neon::prelude::*;
8
- use neon::types::buffer::TypedArray;
9
- use opentelemetry::trace::{FutureExt, SpanContext, TraceContextExt};
10
- use parking_lot::RwLock;
11
- use prost::Message;
12
- use std::collections::HashMap;
13
- use std::ops::Deref;
14
- use std::{
15
- cell::RefCell,
16
- fmt::Display,
17
- future::Future,
18
- sync::Arc,
19
- time::{Duration, SystemTime, UNIX_EPOCH},
20
- };
21
- use temporal_client::{
22
- AnyClient, ClientInitError, ConfiguredClient, WorkflowServiceClientWithMetrics,
23
- };
24
- use temporal_sdk_core::{
25
- api::{
26
- errors::{CompleteActivityError, CompleteWfError, PollActivityError, PollWfError},
27
- Worker as CoreWorkerTrait,
28
- },
29
- fetch_global_buffered_logs, init_replay_worker, init_worker,
30
- protos::{
31
- coresdk::{
32
- workflow_completion::WorkflowActivationCompletion, ActivityHeartbeat,
33
- ActivityTaskCompletion,
34
- },
35
- temporal::api::history::v1::History,
36
- },
37
- telemetry_init, ClientOptions, RetryClient, Worker as CoreWorker, WorkerConfig,
38
- };
39
- use tokio::{
40
- runtime::Runtime,
41
- sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
42
- };
43
- use tokio_stream::wrappers::UnboundedReceiverStream;
44
-
45
- type RawClient = RetryClient<ConfiguredClient<WorkflowServiceClientWithMetrics>>;
46
-
47
- /// A request from JS to bridge to core
48
- enum RuntimeRequest {
49
- /// A request to shutdown the runtime, breaks from the thread loop.
50
- Shutdown {
51
- /// Used to send the result back into JS
52
- callback: Root<JsFunction>,
53
- },
54
- /// A request to create a client in a runtime
55
- CreateClient {
56
- runtime: Arc<RuntimeHandle>,
57
- options: ClientOptions,
58
- headers: Option<HashMap<String, String>>,
59
- /// Used to send the result back into JS
60
- callback: Root<JsFunction>,
61
- },
62
- /// A request to update a client's HTTP request headers
63
- UpdateClientHeaders {
64
- client: Arc<RawClient>,
65
- headers: HashMap<String, String>,
66
- /// Used to send the result back into JS
67
- callback: Root<JsFunction>,
68
- },
69
- /// A request to create a new Worker using a connected client
70
- InitWorker {
71
- /// Worker configuration e.g. limits and task queue
72
- config: WorkerConfig,
73
- /// A client created with a [CreateClient] request
74
- client: Arc<RawClient>,
75
- /// Used to send the result back into JS
76
- callback: Root<JsFunction>,
77
- },
78
- /// A request to register a replay worker
79
- InitReplayWorker {
80
- /// Worker configuration. Must have unique task queue name.
81
- config: WorkerConfig,
82
- /// The history this worker should replay
83
- history: History,
84
- /// Used to send the result back into JS
85
- callback: Root<JsFunction>,
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
- },
102
- /// A request to poll for workflow activations
103
- PollWorkflowActivation {
104
- otel_span: SpanContext,
105
- /// Used to send the result back into JS
106
- callback: Root<JsFunction>,
107
- },
108
- /// A request to complete a single workflow activation
109
- CompleteWorkflowActivation {
110
- completion: WorkflowActivationCompletion,
111
- otel_span: SpanContext,
112
- /// Used to send the result back into JS
113
- callback: Root<JsFunction>,
114
- },
115
- /// A request to poll for activity tasks
116
- PollActivityTask {
117
- otel_span: SpanContext,
118
- /// Used to report completion or error back into JS
119
- callback: Root<JsFunction>,
120
- },
121
- /// A request to complete a single activity task
122
- CompleteActivityTask {
123
- completion: ActivityTaskCompletion,
124
- otel_span: SpanContext,
125
- /// Used to send the result back into JS
126
- callback: Root<JsFunction>,
127
- },
128
- /// A request to send a heartbeat from a running activity
129
- RecordActivityHeartbeat { heartbeat: ActivityHeartbeat },
130
- }
131
-
132
- struct RuntimeHandle {
133
- sender: UnboundedSender<RuntimeRequest>,
134
- }
135
-
136
- /// Box it so we can use the runtime from JS
137
- type BoxedRuntime = JsBox<Arc<RuntimeHandle>>;
138
- impl Finalize for RuntimeHandle {}
139
-
140
- #[derive(Clone)]
141
- struct Client {
142
- runtime: Arc<RuntimeHandle>,
143
- core_client: Arc<RawClient>,
144
- }
145
-
146
- type BoxedClient = JsBox<RefCell<Option<Client>>>;
147
- impl Finalize for Client {}
148
-
149
- /// Worker struct, hold a reference for the channel sender responsible for sending requests from
150
- /// JS to a bridge thread which forwards them to core
151
- struct WorkerHandle {
152
- sender: UnboundedSender<WorkerRequest>,
153
- }
154
-
155
- /// Box it so we can use Worker from JS
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")
166
- }
167
-
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)
170
- where
171
- F: for<'a> FnOnce(&mut TaskContext<'a>) -> NeonResult<Handle<'a, T>> + Send + 'static,
172
- T: Value,
173
- {
174
- channel.send(move |mut cx| {
175
- let callback = callback.into_inner(&mut cx);
176
- let this = cx.undefined();
177
- let error = cx.undefined();
178
- let result = res_fn(&mut cx)?;
179
- let args: Vec<Handle<JsValue>> = vec![error.upcast(), result.upcast()];
180
- callback.call(&mut cx, this, args)?;
181
- Ok(())
182
- });
183
- }
184
-
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)
187
- where
188
- E: Object,
189
- F: for<'a> FnOnce(&mut TaskContext<'a>) -> JsResult<'a, E> + Send + 'static,
190
- {
191
- channel.send(move |mut cx| {
192
- let callback = callback.into_inner(&mut cx);
193
- callback_with_error(&mut cx, callback, error_ctor)
194
- });
195
- }
196
-
197
- /// Call `callback` with given error
198
- fn callback_with_error<'a, C, E, F>(
199
- cx: &mut C,
200
- callback: Handle<JsFunction>,
201
- error_ctor: F,
202
- ) -> NeonResult<()>
203
- where
204
- C: Context<'a>,
205
- E: Object,
206
- F: FnOnce(&mut C) -> JsResult<'a, E> + Send + 'static,
207
- {
208
- let this = cx.undefined();
209
- let error = error_ctor(cx)?;
210
- let result = cx.undefined();
211
- let args: Vec<Handle<JsValue>> = vec![error.upcast(), result.upcast()];
212
- callback.call(cx, this, args)?;
213
- Ok(())
214
- }
215
-
216
- /// Call `callback` with an UnexpectedError created from `err`
217
- fn callback_with_unexpected_error<'a, C, E>(
218
- cx: &mut C,
219
- callback: Handle<JsFunction>,
220
- err: E,
221
- ) -> NeonResult<()>
222
- where
223
- C: Context<'a>,
224
- E: std::fmt::Display,
225
- {
226
- let err_str = format!("{}", err);
227
- callback_with_error(cx, callback, move |cx| {
228
- UNEXPECTED_ERROR.from_string(cx, err_str)
229
- })
230
- }
231
-
232
- /// When Future completes, call given JS callback using a neon::Channel with either error or
233
- /// undefined
234
- async fn void_future_to_js<E, F, ER, EF>(
235
- channel: Arc<Channel>,
236
- callback: Root<JsFunction>,
237
- f: F,
238
- error_function: EF,
239
- ) where
240
- E: Display + Send + 'static,
241
- F: Future<Output = Result<(), E>> + Send,
242
- ER: Object,
243
- EF: for<'a> FnOnce(&mut TaskContext<'a>, E) -> JsResult<'a, ER> + Send + 'static,
244
- {
245
- match f.await {
246
- Ok(()) => {
247
- send_result(channel, callback, |cx| Ok(cx.undefined()));
248
- }
249
- Err(err) => {
250
- send_error(channel, callback, |cx| error_function(cx, err));
251
- }
252
- }
253
- }
254
-
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.
257
- /// Blocks current thread until a [Shutdown] request is received in channel.
258
- fn start_bridge_loop(channel: Arc<Channel>, receiver: &mut UnboundedReceiver<RuntimeRequest>) {
259
- tokio_runtime().block_on(async {
260
- loop {
261
- let request_option = receiver.recv().await;
262
- let request = match request_option {
263
- None => break,
264
- Some(request) => request,
265
- };
266
-
267
- let channel = channel.clone();
268
-
269
- match request {
270
- RuntimeRequest::Shutdown { callback } => {
271
- send_result(channel, callback, |cx| Ok(cx.undefined()));
272
- break;
273
- }
274
- RuntimeRequest::CreateClient {
275
- runtime,
276
- options,
277
- headers,
278
- callback,
279
- } => {
280
- // `metrics_meter` (second arg) can be None here since we don't use the
281
- // returned client directly at the moment, when we repurpose the client to be
282
- // used by a Worker, `init_worker` will attach the correct metrics meter for
283
- // us.
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
- }
312
- }
313
- });
314
- }
315
- RuntimeRequest::UpdateClientHeaders {
316
- client,
317
- headers,
318
- callback,
319
- } => {
320
- client.get_client().set_headers(headers);
321
- send_result(channel.clone(), callback, |cx| Ok(cx.undefined()));
322
- }
323
- RuntimeRequest::PollLogs { callback } => {
324
- let logs = fetch_global_buffered_logs();
325
- send_result(channel.clone(), callback, |cx| {
326
- let logarr = cx.empty_array();
327
- for (i, cl) in logs.into_iter().enumerate() {
328
- // Not much to do here except for panic when there's an
329
- // error here.
330
- let logobj = cx.empty_object();
331
- let level = cx.string(cl.level.to_string());
332
- logobj.set(cx, "level", level).unwrap();
333
- let ts = system_time_to_js(cx, cl.timestamp).unwrap();
334
- logobj.set(cx, "timestamp", ts).unwrap();
335
- let msg = cx.string(cl.message);
336
- logobj.set(cx, "message", msg).unwrap();
337
- logarr.set(cx, i as u32, logobj).unwrap();
338
- }
339
- Ok(logarr)
340
- });
341
- }
342
- RuntimeRequest::InitWorker {
343
- config,
344
- client,
345
- callback,
346
- } => {
347
- let client = (*client).clone();
348
- let worker =
349
- init_worker(config, AnyClient::LowLevel(Box::new(client.into_inner())));
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 }))))
354
- });
355
- }
356
- RuntimeRequest::InitReplayWorker {
357
- config,
358
- history,
359
- callback,
360
- } => {
361
- match init_replay_worker(config, &history) {
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())
371
- }),
372
- };
373
- }
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
- } => {
401
- handle_poll_workflow_activation_request(
402
- &worker, otel_span, channel, callback,
403
- )
404
- .await
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,
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
- },
437
- )
438
- .await;
439
- }
440
- WorkerRequest::CompleteActivityTask {
441
- completion,
442
- otel_span,
443
- callback,
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
- }
471
- }
472
- }
473
- })
474
- .await;
475
- worker.finalize_shutdown().await;
476
- }
477
-
478
- /// Called within the poll loop thread, calls core and triggers JS callback with result
479
- async fn handle_poll_workflow_activation_request(
480
- worker: &CoreWorker,
481
- span_context: SpanContext,
482
- channel: Arc<Channel>,
483
- callback: Root<JsFunction>,
484
- ) {
485
- let otel_ctx = opentelemetry::Context::new().with_remote_span_context(span_context);
486
- match worker
487
- .poll_workflow_activation()
488
- .with_context(otel_ctx)
489
- .await
490
- {
491
- Ok(task) => {
492
- send_result(channel, callback, move |cx| {
493
- let len = task.encoded_len();
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
- };
499
- Ok(result)
500
- });
501
- }
502
- Err(err) => {
503
- send_error(channel, callback, move |cx| match err {
504
- PollWfError::ShutDown => SHUTDOWN_ERROR.from_error(cx, err),
505
- PollWfError::TonicError(_)
506
- | PollWfError::AutocompleteError(CompleteWfError::TonicError(_)) => {
507
- TRANSPORT_ERROR.from_error(cx, err)
508
- }
509
- PollWfError::AutocompleteError(CompleteWfError::MalformedWorkflowCompletion {
510
- reason,
511
- ..
512
- }) => Ok(JsError::type_error(cx, reason)?.upcast()),
513
- });
514
- }
515
- }
516
- }
517
-
518
- /// Called within the poll loop thread, calls core and triggers JS callback with result
519
- async fn handle_poll_activity_task_request(
520
- worker: &CoreWorker,
521
- span_context: SpanContext,
522
- channel: Arc<Channel>,
523
- callback: Root<JsFunction>,
524
- ) {
525
- let otel_ctx = opentelemetry::Context::new().with_remote_span_context(span_context);
526
- match worker.poll_activity_task().with_context(otel_ctx).await {
527
- Ok(task) => {
528
- send_result(channel, callback, move |cx| {
529
- let len = task.encoded_len();
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
- };
535
- Ok(result)
536
- });
537
- }
538
- Err(err) => {
539
- send_error(channel, callback, move |cx| match err {
540
- PollActivityError::ShutDown => SHUTDOWN_ERROR.from_error(cx, err),
541
- PollActivityError::TonicError(_) => TRANSPORT_ERROR.from_error(cx, err),
542
- });
543
- }
544
- }
545
- }
546
-
547
- // Below are functions exported to JS
548
-
549
- /// Initialize Core global telemetry.
550
- /// This should typically be called once on process startup.
551
- fn init_telemetry(mut cx: FunctionContext) -> JsResult<JsUndefined> {
552
- let telemetry_options = cx.argument::<JsObject>(0)?.as_telemetry_options(&mut cx)?;
553
- telemetry_init(&telemetry_options).map_err(|err| {
554
- cx.throw_type_error::<String, ()>(format!("{}", err))
555
- .unwrap_err()
556
- })?;
557
- Ok(cx.undefined())
558
- }
559
-
560
- /// Create the tokio runtime required to run Core.
561
- /// Immediately spawns a poller thread that will block on [RuntimeRequest]s
562
- fn runtime_new(mut cx: FunctionContext) -> JsResult<BoxedRuntime> {
563
- let channel = Arc::new(cx.channel());
564
- let (sender, mut receiver) = unbounded_channel::<RuntimeRequest>();
565
-
566
- std::thread::spawn(move || start_bridge_loop(channel, &mut receiver));
567
-
568
- Ok(cx.boxed(Arc::new(RuntimeHandle { sender })))
569
- }
570
-
571
- /// Create a connected gRPC client which can be used to initialize workers.
572
- /// Client will be returned in the supplied `callback`.
573
- fn client_new(mut cx: FunctionContext) -> JsResult<JsUndefined> {
574
- let runtime = cx.argument::<BoxedRuntime>(0)?;
575
- let opts = cx.argument::<JsObject>(1)?;
576
- let callback = cx.argument::<JsFunction>(2)?;
577
-
578
- let client_options = opts.as_client_options(&mut cx)?;
579
- let headers = match js_optional_getter!(&mut cx, &opts, "metadata", JsObject) {
580
- None => None,
581
- Some(h) => Some(
582
- h.as_hash_map_of_string_to_string(&mut cx)
583
- .map_err(|reason| {
584
- cx.throw_type_error::<_, HashMap<String, String>>(format!(
585
- "Invalid metadata: {}",
586
- reason
587
- ))
588
- .unwrap_err()
589
- })?,
590
- ),
591
- };
592
-
593
- let request = RuntimeRequest::CreateClient {
594
- runtime: (**runtime).clone(),
595
- options: client_options,
596
- headers,
597
- callback: callback.root(&mut cx),
598
- };
599
- if let Err(err) = runtime.sender.send(request) {
600
- callback_with_unexpected_error(&mut cx, callback, err)?;
601
- };
602
-
603
- Ok(cx.undefined())
604
- }
605
-
606
- /// Update a Client's HTTP request headers
607
- fn client_update_headers(mut cx: FunctionContext) -> JsResult<JsUndefined> {
608
- let client = cx.argument::<BoxedClient>(0)?;
609
- let headers = cx
610
- .argument::<JsObject>(1)?
611
- .as_hash_map_of_string_to_string(&mut cx)?;
612
- let callback = cx.argument::<JsFunction>(2)?;
613
-
614
- match &*client.borrow() {
615
- None => {
616
- callback_with_unexpected_error(&mut cx, callback, "Tried to use closed Client")?;
617
- }
618
- Some(client) => {
619
- let request = RuntimeRequest::UpdateClientHeaders {
620
- client: client.core_client.clone(),
621
- headers,
622
- callback: callback.root(&mut cx),
623
- };
624
- if let Err(err) = client.runtime.sender.send(request) {
625
- callback_with_unexpected_error(&mut cx, callback, err)?;
626
- };
627
- }
628
- }
629
-
630
- Ok(cx.undefined())
631
- }
632
-
633
- /// Create a new worker asynchronously.
634
- /// Worker uses the provided connection and returned to JS using supplied `callback`.
635
- fn worker_new(mut cx: FunctionContext) -> JsResult<JsUndefined> {
636
- let client = cx.argument::<BoxedClient>(0)?;
637
- let worker_options = cx.argument::<JsObject>(1)?;
638
- let callback = cx.argument::<JsFunction>(2)?;
639
-
640
- let config = worker_options.as_worker_config(&mut cx)?;
641
- match &*client.borrow() {
642
- None => {
643
- callback_with_unexpected_error(&mut cx, callback, "Tried to use closed Client")?;
644
- }
645
- Some(client) => {
646
- let request = RuntimeRequest::InitWorker {
647
- client: client.core_client.clone(),
648
- config,
649
- callback: callback.root(&mut cx),
650
- };
651
- if let Err(err) = client.runtime.sender.send(request) {
652
- callback_with_unexpected_error(&mut cx, callback, err)?;
653
- };
654
- }
655
- };
656
-
657
- Ok(cx.undefined())
658
- }
659
-
660
- /// Create a new replay worker asynchronously.
661
- /// Worker is returned to JS using supplied callback.
662
- fn replay_worker_new(mut cx: FunctionContext) -> JsResult<JsUndefined> {
663
- let runtime = cx.argument::<BoxedRuntime>(0)?;
664
- let worker_options = cx.argument::<JsObject>(1)?;
665
- let history_binary = cx.argument::<JsArrayBuffer>(2)?;
666
- let callback = cx.argument::<JsFunction>(3)?;
667
-
668
- let config = worker_options.as_worker_config(&mut cx)?;
669
- let data = history_binary.as_slice(&mut cx);
670
- match History::decode_length_delimited(data) {
671
- Ok(history) => {
672
- let request = RuntimeRequest::InitReplayWorker {
673
- config,
674
- history,
675
- callback: callback.root(&mut cx),
676
- };
677
- if let Err(err) = runtime.sender.send(request) {
678
- callback_with_unexpected_error(&mut cx, callback, err)?;
679
- };
680
- }
681
- Err(_) => callback_with_error(&mut cx, callback, |cx| {
682
- JsError::type_error(cx, "Cannot decode History from buffer")
683
- })?,
684
- }
685
-
686
- Ok(cx.undefined())
687
- }
688
-
689
- /// Shutdown the Core instance and break out of the thread loop
690
- fn runtime_shutdown(mut cx: FunctionContext) -> JsResult<JsUndefined> {
691
- let runtime = cx.argument::<BoxedRuntime>(0)?;
692
- let callback = cx.argument::<JsFunction>(1)?;
693
- let request = RuntimeRequest::Shutdown {
694
- callback: callback.root(&mut cx),
695
- };
696
- if let Err(err) = runtime.sender.send(request) {
697
- callback_with_unexpected_error(&mut cx, callback, err)?;
698
- };
699
- Ok(cx.undefined())
700
- }
701
-
702
- /// Request to drain forwarded logs from core
703
- fn poll_logs(mut cx: FunctionContext) -> JsResult<JsUndefined> {
704
- let runtime = cx.argument::<BoxedRuntime>(0)?;
705
- let callback = cx.argument::<JsFunction>(1)?;
706
- let request = RuntimeRequest::PollLogs {
707
- callback: callback.root(&mut cx),
708
- };
709
- if let Err(err) = runtime.sender.send(request) {
710
- callback_with_unexpected_error(&mut cx, callback, err)?;
711
- }
712
- Ok(cx.undefined())
713
- }
714
-
715
- /// Initiate a single workflow activation poll request.
716
- /// There should be only one concurrent poll request for this type.
717
- fn worker_poll_workflow_activation(mut cx: FunctionContext) -> JsResult<JsUndefined> {
718
- let worker = cx.argument::<BoxedWorker>(0)?;
719
- let otel_span = cx.argument::<JsObject>(1)?;
720
- let callback = cx.argument::<JsFunction>(2)?;
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
- }
734
- }
735
- Ok(cx.undefined())
736
- }
737
-
738
- /// Initiate a single activity task poll request.
739
- /// There should be only one concurrent poll request for this type.
740
- fn worker_poll_activity_task(mut cx: FunctionContext) -> JsResult<JsUndefined> {
741
- let worker = cx.argument::<BoxedWorker>(0)?;
742
- let otel_span = cx.argument::<JsObject>(1)?;
743
- let callback = cx.argument::<JsFunction>(2)?;
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
- }
757
- }
758
- Ok(cx.undefined())
759
- }
760
-
761
- /// Submit a workflow activation completion to core.
762
- fn worker_complete_workflow_activation(mut cx: FunctionContext) -> JsResult<JsUndefined> {
763
- let worker = cx.argument::<BoxedWorker>(0)?;
764
- let otel_span = cx.argument::<JsObject>(1)?;
765
- let completion = cx.argument::<JsArrayBuffer>(2)?;
766
- let callback = cx.argument::<JsFunction>(3)?;
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
- }
789
- }
790
- };
791
- Ok(cx.undefined())
792
- }
793
-
794
- /// Submit an activity task completion to core.
795
- fn worker_complete_activity_task(mut cx: FunctionContext) -> JsResult<JsUndefined> {
796
- let worker = cx.argument::<BoxedWorker>(0)?;
797
- let otel_span = cx.argument::<JsObject>(1)?;
798
- let result = cx.argument::<JsArrayBuffer>(2)?;
799
- let callback = cx.argument::<JsFunction>(3)?;
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
- }
820
- }
821
- };
822
- Ok(cx.undefined())
823
- }
824
-
825
- /// Submit an activity heartbeat to core.
826
- fn worker_record_activity_heartbeat(mut cx: FunctionContext) -> JsResult<JsUndefined> {
827
- let worker = cx.argument::<BoxedWorker>(0)?;
828
- let heartbeat = cx.argument::<JsArrayBuffer>(1)?;
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")?,
844
- }
845
- }
846
- };
847
- Ok(cx.undefined())
848
- }
849
-
850
- /// Request shutdown of the worker.
851
- /// Once complete Core will stop polling on new tasks and activations on worker's task queue.
852
- /// Caller should drain any pending tasks and activations and call worker_finalize_shutdown before breaking from
853
- /// the loop to ensure graceful shutdown.
854
- fn worker_initiate_shutdown(mut cx: FunctionContext) -> JsResult<JsUndefined> {
855
- let worker = cx.argument::<BoxedWorker>(0)?;
856
- let callback = cx.argument::<JsFunction>(1)?;
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")
879
- .and_then(|err| cx.throw(err))?;
880
- }
881
-
882
- Ok(cx.undefined())
883
- }
884
-
885
- /// Drop a reference to a Client, once all references are dropped, the Client will be closed.
886
- fn client_close(mut cx: FunctionContext) -> JsResult<JsUndefined> {
887
- let client = cx.argument::<BoxedClient>(0)?;
888
- if client.replace(None).is_none() {
889
- ILLEGAL_STATE_ERROR
890
- .from_string(&mut cx, "Client already closed")
891
- .and_then(|err| cx.throw(err))?;
892
- };
893
- Ok(cx.undefined())
894
- }
895
-
896
- /// Convert Rust SystemTime into a JS array with 2 numbers (seconds, nanos)
897
- fn system_time_to_js<'a, C>(cx: &mut C, time: SystemTime) -> NeonResult<Handle<'a, JsArray>>
898
- where
899
- C: Context<'a>,
900
- {
901
- let nanos = time
902
- .duration_since(UNIX_EPOCH)
903
- .unwrap_or(Duration::ZERO)
904
- .as_nanos();
905
- let only_nanos = cx.number((nanos % 1_000_000_000) as f64);
906
- let ts_seconds = cx.number((nanos / 1_000_000_000) as f64);
907
- let ts = cx.empty_array();
908
- ts.set(cx, 0, ts_seconds).unwrap();
909
- ts.set(cx, 1, only_nanos).unwrap();
910
- Ok(ts)
911
- }
912
-
913
- /// Helper to get the current time in nanosecond resolution.
914
- fn get_time_of_day(mut cx: FunctionContext) -> JsResult<JsArray> {
915
- system_time_to_js(&mut cx, SystemTime::now())
916
- }
11
+ use testing::*;
917
12
 
918
13
  #[neon::main]
919
14
  fn main(mut cx: ModuleContext) -> NeonResult<()> {
@@ -944,5 +39,8 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> {
944
39
  "workerRecordActivityHeartbeat",
945
40
  worker_record_activity_heartbeat,
946
41
  )?;
42
+ cx.export_function("startEphemeralServer", start_ephemeral_server)?;
43
+ cx.export_function("shutdownEphemeralServer", shutdown_ephemeral_server)?;
44
+ cx.export_function("getEphemeralServerTarget", get_ephemeral_server_target)?;
947
45
  Ok(())
948
46
  }