@temporalio/core-bridge 1.11.8 → 1.12.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.
- package/Cargo.lock +219 -193
- package/Cargo.toml +27 -8
- package/README.md +5 -0
- package/index.js +72 -12
- package/lib/errors.d.ts +25 -0
- package/lib/errors.js +76 -1
- package/lib/errors.js.map +1 -1
- package/lib/index.d.ts +11 -478
- package/lib/index.js +28 -5
- package/lib/index.js.map +1 -1
- package/lib/native.d.ts +330 -0
- package/lib/{worker-tuner.js → native.js} +1 -1
- package/lib/native.js.map +1 -0
- package/package.json +7 -3
- package/releases/aarch64-apple-darwin/index.node +0 -0
- package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
- package/releases/x86_64-apple-darwin/index.node +0 -0
- package/releases/x86_64-pc-windows-msvc/index.node +0 -0
- package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
- package/sdk-core/.cargo/config.toml +8 -2
- package/sdk-core/.cargo/multi-worker-manual-test +15 -0
- package/sdk-core/.github/workflows/per-pr.yml +40 -11
- package/sdk-core/AGENTS.md +73 -0
- package/sdk-core/ARCHITECTURE.md +71 -23
- package/sdk-core/Cargo.toml +1 -1
- package/sdk-core/README.md +4 -4
- package/sdk-core/arch_docs/workflow_task_chunking.md +51 -0
- package/sdk-core/client/Cargo.toml +1 -1
- package/sdk-core/client/src/lib.rs +49 -13
- package/sdk-core/client/src/metrics.rs +15 -16
- package/sdk-core/client/src/proxy.rs +2 -2
- package/sdk-core/client/src/raw.rs +54 -8
- package/sdk-core/client/src/retry.rs +109 -13
- package/sdk-core/client/src/worker_registry/mod.rs +4 -4
- package/sdk-core/client/src/workflow_handle/mod.rs +1 -1
- package/sdk-core/core/Cargo.toml +28 -8
- package/sdk-core/core/src/abstractions.rs +62 -10
- package/sdk-core/core/src/core_tests/activity_tasks.rs +180 -8
- package/sdk-core/core/src/core_tests/mod.rs +4 -4
- package/sdk-core/core/src/core_tests/queries.rs +18 -4
- package/sdk-core/core/src/core_tests/workers.rs +3 -3
- package/sdk-core/core/src/core_tests/workflow_tasks.rs +191 -25
- package/sdk-core/core/src/ephemeral_server/mod.rs +10 -3
- package/sdk-core/core/src/internal_flags.rs +14 -14
- package/sdk-core/core/src/lib.rs +5 -2
- package/sdk-core/core/src/pollers/mod.rs +1 -1
- package/sdk-core/core/src/pollers/poll_buffer.rs +495 -164
- package/sdk-core/core/src/protosext/mod.rs +3 -3
- package/sdk-core/core/src/replay/mod.rs +3 -3
- package/sdk-core/core/src/telemetry/metrics.rs +13 -4
- package/sdk-core/core/src/telemetry/mod.rs +72 -70
- package/sdk-core/core/src/telemetry/otel.rs +51 -54
- package/sdk-core/core/src/test_help/mod.rs +9 -3
- package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +31 -11
- package/sdk-core/core/src/worker/activities/local_activities.rs +35 -28
- package/sdk-core/core/src/worker/activities.rs +58 -30
- package/sdk-core/core/src/worker/client/mocks.rs +3 -3
- package/sdk-core/core/src/worker/client.rs +155 -53
- package/sdk-core/core/src/worker/mod.rs +103 -95
- package/sdk-core/core/src/worker/nexus.rs +100 -73
- package/sdk-core/core/src/worker/tuner/resource_based.rs +14 -6
- package/sdk-core/core/src/worker/tuner.rs +4 -13
- package/sdk-core/core/src/worker/workflow/history_update.rs +47 -51
- package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +1 -4
- package/sdk-core/core/src/worker/workflow/machines/nexus_operation_state_machine.rs +127 -32
- package/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +1 -2
- package/sdk-core/core/src/worker/workflow/machines/update_state_machine.rs +1 -1
- package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +55 -42
- package/sdk-core/core/src/worker/workflow/managed_run.rs +45 -35
- package/sdk-core/core/src/worker/workflow/mod.rs +200 -97
- package/sdk-core/core/src/worker/workflow/wft_poller.rs +175 -4
- package/sdk-core/core/src/worker/workflow/workflow_stream.rs +38 -36
- package/sdk-core/core-api/Cargo.toml +8 -0
- package/sdk-core/core-api/src/envconfig.rs +1544 -0
- package/sdk-core/core-api/src/lib.rs +2 -0
- package/sdk-core/core-api/src/telemetry/metrics.rs +8 -0
- package/sdk-core/core-api/src/telemetry.rs +36 -3
- package/sdk-core/core-api/src/worker.rs +301 -75
- package/sdk-core/docker/docker-compose-telem.yaml +1 -0
- package/sdk-core/etc/prometheus.yaml +6 -2
- package/sdk-core/histories/long_local_activity_with_update-0_history.bin +0 -0
- package/sdk-core/histories/long_local_activity_with_update-1_history.bin +0 -0
- package/sdk-core/histories/long_local_activity_with_update-2_history.bin +0 -0
- package/sdk-core/histories/long_local_activity_with_update-3_history.bin +0 -0
- package/sdk-core/sdk/src/activity_context.rs +5 -0
- package/sdk-core/sdk/src/interceptors.rs +73 -3
- package/sdk-core/sdk/src/lib.rs +15 -16
- package/sdk-core/sdk/src/workflow_context/options.rs +10 -0
- package/sdk-core/sdk/src/workflow_context.rs +48 -29
- package/sdk-core/sdk/src/workflow_future.rs +5 -6
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/.github/workflows/push-to-buf.yml +20 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/CODEOWNERS +6 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/README.md +17 -6
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/VERSION +1 -1
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/buf.lock +7 -2
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/buf.yaml +2 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/cloudservice/v1/request_response.proto +78 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/cloudservice/v1/service.proto +29 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/identity/v1/message.proto +74 -32
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/namespace/v1/message.proto +45 -15
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/nexus/v1/message.proto +7 -1
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/operation/v1/message.proto +3 -3
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/region/v1/message.proto +3 -3
- package/sdk-core/sdk-core-protos/protos/api_upstream/LICENSE +1 -1
- package/sdk-core/sdk-core-protos/protos/api_upstream/buf.yaml +2 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +1103 -88
- package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +1233 -151
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/activity/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/batch/v1/message.proto +19 -24
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/command/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +12 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/deployment/v1/message.proto +45 -45
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/command_type.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/common.proto +15 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/deployment.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/event_type.proto +4 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/namespace.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/nexus.proto +0 -20
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/query.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/reset.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/schedule.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/update.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/workflow.proto +4 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/errordetails/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/export/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/failure/v1/message.proto +2 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/filter/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/history/v1/message.proto +75 -49
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/namespace/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/nexus/v1/message.proto +0 -20
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/protocol/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/query/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/replication/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/rules/v1/message.proto +90 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/schedule/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/enhanced_stack_trace.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/task_complete_metadata.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/user_metadata.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/workflow_metadata.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +17 -38
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/update/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/version/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflow/v1/message.proto +151 -44
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +172 -65
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +69 -28
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/activity_task/activity_task.proto +18 -0
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/common/common.proto +5 -0
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/nexus/nexus.proto +16 -1
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +21 -15
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +3 -0
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +3 -0
- package/sdk-core/sdk-core-protos/src/lib.rs +60 -16
- package/sdk-core/test-utils/src/lib.rs +157 -39
- package/sdk-core/tests/cloud_tests.rs +86 -0
- package/sdk-core/tests/fuzzy_workflow.rs +23 -26
- package/sdk-core/tests/global_metric_tests.rs +116 -0
- package/sdk-core/tests/heavy_tests.rs +127 -7
- package/sdk-core/tests/integ_tests/client_tests.rs +2 -8
- package/sdk-core/tests/integ_tests/metrics_tests.rs +100 -106
- package/sdk-core/tests/integ_tests/polling_tests.rs +94 -8
- package/sdk-core/tests/integ_tests/update_tests.rs +75 -6
- package/sdk-core/tests/integ_tests/worker_tests.rs +54 -5
- package/sdk-core/tests/integ_tests/worker_versioning_tests.rs +240 -0
- package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +41 -3
- package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +168 -8
- package/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +285 -15
- package/sdk-core/tests/integ_tests/workflow_tests/priority.rs +12 -4
- package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +3 -2
- package/sdk-core/tests/integ_tests/workflow_tests.rs +124 -74
- package/sdk-core/tests/main.rs +3 -51
- package/sdk-core/tests/manual_tests.rs +430 -0
- package/sdk-core/tests/runner.rs +28 -2
- package/src/client.rs +565 -0
- package/src/helpers/abort_controller.rs +204 -0
- package/src/helpers/callbacks.rs +299 -0
- package/src/helpers/errors.rs +302 -0
- package/src/helpers/future.rs +44 -0
- package/src/helpers/handles.rs +191 -0
- package/src/helpers/inspect.rs +18 -0
- package/src/helpers/json_string.rs +58 -0
- package/src/helpers/mod.rs +20 -0
- package/src/helpers/properties.rs +71 -0
- package/src/helpers/try_from_js.rs +213 -0
- package/src/helpers/try_into_js.rs +129 -0
- package/src/lib.rs +28 -40
- package/src/logs.rs +111 -0
- package/src/metrics.rs +325 -0
- package/src/runtime.rs +409 -498
- package/src/testing.rs +315 -57
- package/src/worker.rs +907 -378
- package/ts/errors.ts +57 -0
- package/ts/index.ts +10 -596
- package/ts/native.ts +496 -0
- package/lib/worker-tuner.d.ts +0 -167
- package/lib/worker-tuner.js.map +0 -1
- package/src/conversions/slot_supplier_bridge.rs +0 -291
- package/src/conversions.rs +0 -618
- package/src/errors.rs +0 -38
- package/src/helpers.rs +0 -297
- package/ts/worker-tuner.ts +0 -193
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
use std::sync::{
|
|
2
|
+
Arc, OnceLock,
|
|
3
|
+
atomic::{AtomicU8, Ordering, fence},
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
use neon::{
|
|
7
|
+
handle::{Handle, Root},
|
|
8
|
+
object::Object,
|
|
9
|
+
prelude::{Context, JsResult},
|
|
10
|
+
types::{JsFunction, JsObject, JsValue},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
use super::{BridgeResult, JsCallback, TryIntoJs, errors::IntoThrow as _};
|
|
14
|
+
|
|
15
|
+
pub type JsAbortController = JsObject;
|
|
16
|
+
pub type JsAbortSignal = JsValue;
|
|
17
|
+
|
|
18
|
+
/// An object that models a JavaScript `AbortController`, and its corresponding `AbortSignal`,
|
|
19
|
+
/// allowing the Rust side to fire that signal if/when needed, e.g. when dropped from the Rust side.
|
|
20
|
+
///
|
|
21
|
+
/// The JS counterpart object is lazily instantiated when the signal gets converted to JS (through
|
|
22
|
+
/// the `TryIntoJs` trait); this ensures that the Rust side can be created without a JS `Context`.
|
|
23
|
+
pub struct AbortController {
|
|
24
|
+
inner: Arc<AbortControllerInner>,
|
|
25
|
+
drop_abort_reason: String,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// An object that models the signal of a JavaScript `AbortController`.
|
|
29
|
+
pub struct AbortSignal {
|
|
30
|
+
inner: Arc<AbortControllerInner>,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// The inner state of an `AbortController`, shared between the Rust and JS sides.
|
|
34
|
+
struct AbortControllerInner {
|
|
35
|
+
// The fact that we require a `Context` to initialize the JS counterpart means that we are running
|
|
36
|
+
// on the Node's thread, which guarantees that there can't be multiple threads calling into that
|
|
37
|
+
// function concurrently; that should in theory aleviate the need to use a lock on `js_counterpart`.
|
|
38
|
+
//
|
|
39
|
+
// It is however possible for the rust-side controller to get aborted from a non-Node thread
|
|
40
|
+
// while the JS-side controller is being created on the Node thread, in which case we don't
|
|
41
|
+
// want the Rust-side thread to get blocked for the JS-side to complete instantiation.
|
|
42
|
+
//
|
|
43
|
+
// By modelling the "JS initialization" and "is aborted" states as two distinct independant
|
|
44
|
+
// structures, we ensure that we're never blocking execution of either thread. This however
|
|
45
|
+
// means that either step may happen before the other, so we need to be careful not to miss
|
|
46
|
+
// sending the abort signal. The good news is that nothing bad will happen if we call the JS
|
|
47
|
+
// abort callback multiple times.
|
|
48
|
+
js_counterpart: OnceLock<AbortControllerJsCounterpart>,
|
|
49
|
+
aborted: OnceLock<String>,
|
|
50
|
+
|
|
51
|
+
state: AtomicU8,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
struct AbortControllerJsCounterpart {
|
|
55
|
+
controller: Root<JsObject>,
|
|
56
|
+
abort: JsCallback<(String,), ()>,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const STATE_UNINITIALIZED: u8 = 0;
|
|
60
|
+
const STATE_ARMED: u8 = 1 << 0;
|
|
61
|
+
const STATE_ABORTED: u8 = 1 << 1;
|
|
62
|
+
const STATE_DISARMED: u8 = STATE_ARMED | STATE_ABORTED;
|
|
63
|
+
|
|
64
|
+
impl AbortController {
|
|
65
|
+
/// Create a new `AbortController`.
|
|
66
|
+
///
|
|
67
|
+
/// The `drop_abort_reason` string will be used as the reason for the abort
|
|
68
|
+
/// if the controller is dropped from the Rust side.
|
|
69
|
+
#[must_use]
|
|
70
|
+
pub fn new(drop_abort_reason: String) -> Self {
|
|
71
|
+
let inner = AbortControllerInner {
|
|
72
|
+
js_counterpart: OnceLock::new(),
|
|
73
|
+
aborted: OnceLock::new(),
|
|
74
|
+
state: AtomicU8::new(STATE_UNINITIALIZED),
|
|
75
|
+
};
|
|
76
|
+
let inner = Arc::new(inner);
|
|
77
|
+
Self {
|
|
78
|
+
inner,
|
|
79
|
+
drop_abort_reason,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// Get an associated `AbortSignal` object for this controller. This method can
|
|
84
|
+
/// be called at any time (i.e. even after the controller has been cancelled),
|
|
85
|
+
/// any number of times (i.e. to pass a same signal to multiple JS functions),
|
|
86
|
+
/// and from any thread.
|
|
87
|
+
#[must_use]
|
|
88
|
+
pub fn get_signal(&self) -> AbortSignal {
|
|
89
|
+
AbortSignal {
|
|
90
|
+
inner: self.inner.clone(),
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// Abort the controller, causing the JS side `signal` to fire.
|
|
95
|
+
///
|
|
96
|
+
/// This method can be called at any time (i.e. even before the controller has been armed,
|
|
97
|
+
/// or after it has been disarmed), and from any thread.
|
|
98
|
+
pub fn abort(&self, reason: impl Into<String>) {
|
|
99
|
+
self.inner.abort(reason);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// Disarm the controller, so that it can no longer be aborted.
|
|
103
|
+
///
|
|
104
|
+
/// Once a controller has been disarmed, it's abort status will not change anymore. It is
|
|
105
|
+
/// recommended to call this method once it is known that the signal is no longer needed
|
|
106
|
+
/// on the JS side, e.g. when the called JS function has returned, as this will prevent the
|
|
107
|
+
/// overhead of implicit abortion when the controller is dropped.
|
|
108
|
+
pub fn disarm(&self) {
|
|
109
|
+
self.inner.disarm();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
impl Drop for AbortController {
|
|
114
|
+
fn drop(&mut self) {
|
|
115
|
+
self.abort(self.drop_abort_reason.clone());
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
120
|
+
|
|
121
|
+
impl TryIntoJs for AbortSignal {
|
|
122
|
+
type Output = JsAbortSignal;
|
|
123
|
+
fn try_into_js<'cx>(self, cx: &mut impl Context<'cx>) -> JsResult<'cx, JsAbortSignal> {
|
|
124
|
+
let controller = self.inner.get_or_init_controller(cx).into_throw(cx)?;
|
|
125
|
+
controller.get(cx, "signal")
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
impl AbortControllerInner {
|
|
130
|
+
/// Create the JS `AbortController` if it hasn't been created yet.
|
|
131
|
+
/// Returns a reference to the signal object that can be passed to JS.
|
|
132
|
+
fn get_or_init_controller<'cx, C: Context<'cx>>(
|
|
133
|
+
&self,
|
|
134
|
+
cx: &mut C,
|
|
135
|
+
) -> BridgeResult<Handle<'cx, JsAbortController>> {
|
|
136
|
+
if let Some(js_counterpart) = self.js_counterpart.get() {
|
|
137
|
+
// Already initialized, return the controller
|
|
138
|
+
return Ok(js_counterpart.controller.to_inner(cx).upcast());
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Not initialized yet, create the JS AbortController
|
|
142
|
+
let global = cx.global_object();
|
|
143
|
+
let abort_controller_class = global.get::<JsFunction, _, _>(cx, "AbortController")?;
|
|
144
|
+
let js_controller = abort_controller_class.construct(cx, [])?;
|
|
145
|
+
|
|
146
|
+
let abort_fn = js_controller.get::<JsFunction, _, _>(cx, "abort")?;
|
|
147
|
+
let abort_cb = JsCallback::new(cx, abort_fn, Some(js_controller));
|
|
148
|
+
|
|
149
|
+
let js_counterpart = AbortControllerJsCounterpart {
|
|
150
|
+
controller: js_controller.root(cx),
|
|
151
|
+
abort: abort_cb,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
let controller = match self.js_counterpart.set(js_counterpart) {
|
|
155
|
+
Ok(()) => {
|
|
156
|
+
// Ordering: Write to `state` implies previous write to `js_counterpart` is visible to other threads
|
|
157
|
+
if self.state.fetch_or(STATE_ARMED, Ordering::Release) == STATE_ABORTED {
|
|
158
|
+
// Ordering: Previous concurrent write to `aborted` must be visible at this point
|
|
159
|
+
fence(Ordering::Acquire);
|
|
160
|
+
|
|
161
|
+
// The controller was aborted before it was armed; immediately call the abort callback
|
|
162
|
+
|
|
163
|
+
// Fire and forget
|
|
164
|
+
let _ = self
|
|
165
|
+
.js_counterpart
|
|
166
|
+
.get()
|
|
167
|
+
.unwrap()
|
|
168
|
+
.abort
|
|
169
|
+
.call(cx, (self.aborted.get().unwrap().clone(),));
|
|
170
|
+
}
|
|
171
|
+
js_controller
|
|
172
|
+
}
|
|
173
|
+
Err(js_counterpart) => js_counterpart.controller.to_inner(cx).upcast(),
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
Ok(controller)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/// Immediately abort the `AbortController`, causing the JS side `signal` to fire.
|
|
180
|
+
fn abort(&self, reason: impl Into<String>) {
|
|
181
|
+
let reason = reason.into();
|
|
182
|
+
if self.aborted.set(reason.clone()) == Ok(()) {
|
|
183
|
+
// If we haven't created the JS AbortController yet, there's nothing to abort
|
|
184
|
+
// Ordering: Write to `state` implies previous write to `aborted` is visible to other threads
|
|
185
|
+
if self.state.fetch_or(STATE_ABORTED, Ordering::Release) == STATE_ARMED {
|
|
186
|
+
// Ordering: Previous concurrent write to `js_counterpart` must be visible at this point
|
|
187
|
+
fence(Ordering::Acquire);
|
|
188
|
+
|
|
189
|
+
// Fire and forget
|
|
190
|
+
let _ = self
|
|
191
|
+
.js_counterpart
|
|
192
|
+
.get()
|
|
193
|
+
.unwrap()
|
|
194
|
+
.abort
|
|
195
|
+
.call_on_js_thread((reason,));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
fn disarm(&self) {
|
|
201
|
+
// Ordering: this requires no dependency on any other state
|
|
202
|
+
self.state.store(STATE_DISARMED, Ordering::Relaxed);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
use std::{marker::PhantomData, sync::Arc};
|
|
2
|
+
|
|
3
|
+
use neon::{
|
|
4
|
+
event::Channel,
|
|
5
|
+
handle::{Handle, Root},
|
|
6
|
+
object::Object,
|
|
7
|
+
prelude::Context,
|
|
8
|
+
types::{JsFunction, JsFuture, JsObject, JsPromise, JsValue, Value as _},
|
|
9
|
+
};
|
|
10
|
+
use tracing::error;
|
|
11
|
+
|
|
12
|
+
use super::{BridgeError, BridgeResult, TryFromJs, TryIntoJs, errors::IntoThrow as _};
|
|
13
|
+
|
|
14
|
+
/// A callback is a JS function that is meant to be called by the Rust side.
|
|
15
|
+
/// A `JsCallback` is a callback that returns a value synchronously.
|
|
16
|
+
#[derive(Debug)]
|
|
17
|
+
pub struct JsCallback<Args, Ret>
|
|
18
|
+
where
|
|
19
|
+
Args: TryIntoJsArgs + Send + Sync,
|
|
20
|
+
Ret: TryFromJs + Send + Sync,
|
|
21
|
+
{
|
|
22
|
+
inner: Arc<CallbackInner<Args, Ret>>,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
impl<Args, Ret> Clone for JsCallback<Args, Ret>
|
|
26
|
+
where
|
|
27
|
+
Args: TryIntoJsArgs + Send + Sync,
|
|
28
|
+
Ret: TryFromJs + Send + Sync,
|
|
29
|
+
{
|
|
30
|
+
fn clone(&self) -> Self {
|
|
31
|
+
Self {
|
|
32
|
+
inner: self.inner.clone(),
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
impl<Args, Ret> JsCallback<Args, Ret>
|
|
38
|
+
where
|
|
39
|
+
Args: TryIntoJsArgs + Send + Sync + 'static,
|
|
40
|
+
Ret: TryFromJs + Send + Sync + 'static,
|
|
41
|
+
{
|
|
42
|
+
pub fn new<'cx, C: Context<'cx>>(
|
|
43
|
+
cx: &mut C,
|
|
44
|
+
func: Handle<JsFunction>,
|
|
45
|
+
this: Option<Handle<JsObject>>,
|
|
46
|
+
) -> Self {
|
|
47
|
+
Self {
|
|
48
|
+
inner: Arc::new(CallbackInner {
|
|
49
|
+
this: this.map(|t| t.root(cx)),
|
|
50
|
+
func: func.root(cx),
|
|
51
|
+
func_name: func
|
|
52
|
+
.to_string(cx)
|
|
53
|
+
.map_or_else(|_| "anonymous func".to_owned(), |s| s.value(cx)),
|
|
54
|
+
chan: cx.channel(),
|
|
55
|
+
_marker: PhantomData,
|
|
56
|
+
}),
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Synchronously call the callback from the current JS thread.
|
|
61
|
+
pub fn call<'cx, C: Context<'cx>>(&self, cx: &mut C, args: Args) -> BridgeResult<Ret> {
|
|
62
|
+
let inner = self.inner.clone();
|
|
63
|
+
inner.call(cx, args)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Call the callback on the JS thread and return a handle to the result.
|
|
67
|
+
pub fn call_on_js_thread(&self, args: Args) -> BridgeResult<neon::event::JoinHandle<Ret>> {
|
|
68
|
+
let inner = self.inner.clone();
|
|
69
|
+
|
|
70
|
+
let ret = inner
|
|
71
|
+
.chan
|
|
72
|
+
.clone()
|
|
73
|
+
.try_send(move |mut cx| inner.call(&mut cx, args).into_throw(&mut cx))
|
|
74
|
+
.map_err(|e| BridgeError::Other(e.into()))?;
|
|
75
|
+
|
|
76
|
+
Ok(ret)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
pub fn call_and_block(&self, args: Args) -> BridgeResult<Ret> {
|
|
80
|
+
let join_handle = self.call_on_js_thread(args)?;
|
|
81
|
+
|
|
82
|
+
// This is... unfortunate but since this method is called from an async context way up
|
|
83
|
+
// the stack, but is not async itself AND we need some way to get the result from the JS
|
|
84
|
+
// callback, we must use this roundabout way of blocking. Simply calling `join` on the
|
|
85
|
+
// channel send won't work - it'll panic because it calls block_on internally.
|
|
86
|
+
let callback_res = futures::executor::block_on(join_handle);
|
|
87
|
+
|
|
88
|
+
match callback_res {
|
|
89
|
+
Ok(x) => Ok(x),
|
|
90
|
+
Err(e) => Err(BridgeError::Other(e.into())),
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// Unfortunately, the `TryFromJS` trait doesn't have access to the containing object, and therefore
|
|
96
|
+
/// can't preserve the `this` context. If the JS side API presents the callback as a method on some
|
|
97
|
+
/// object, then the function should be `bind(this)`'d on the JS side before passing it to Rust.
|
|
98
|
+
impl<Args, Ret> TryFromJs for JsCallback<Args, Ret>
|
|
99
|
+
where
|
|
100
|
+
Args: TryIntoJsArgs + Send + Sync + 'static,
|
|
101
|
+
Ret: TryFromJs + Send + Sync + 'static,
|
|
102
|
+
{
|
|
103
|
+
fn try_from_js<'cx, 'b>(
|
|
104
|
+
cx: &mut impl Context<'cx>,
|
|
105
|
+
js_value: Handle<'b, JsValue>,
|
|
106
|
+
) -> BridgeResult<Self> {
|
|
107
|
+
let func = js_value.downcast::<JsFunction, _>(cx)?;
|
|
108
|
+
Ok(Self::new(cx, func, None))
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
113
|
+
|
|
114
|
+
pub struct JsAsyncCallback<Args, Ret>
|
|
115
|
+
where
|
|
116
|
+
Args: TryIntoJsArgs + Send + Sync,
|
|
117
|
+
Ret: TryFromJs + Send + Sync + 'static,
|
|
118
|
+
{
|
|
119
|
+
inner: Arc<CallbackInner<Args, JsFuture<BridgeResult<Ret>>>>,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
impl<Args, Ret> JsAsyncCallback<Args, Ret>
|
|
123
|
+
where
|
|
124
|
+
Args: TryIntoJsArgs + Send + Sync + 'static,
|
|
125
|
+
Ret: TryFromJs + Send + Sync + 'static,
|
|
126
|
+
{
|
|
127
|
+
pub fn new<'cx, C: Context<'cx>>(
|
|
128
|
+
cx: &mut C,
|
|
129
|
+
func: Handle<JsFunction>,
|
|
130
|
+
this: Option<Handle<JsObject>>,
|
|
131
|
+
) -> Self {
|
|
132
|
+
Self {
|
|
133
|
+
inner: Arc::new(CallbackInner {
|
|
134
|
+
this: this.map(|t| t.root(cx)),
|
|
135
|
+
func: func.root(cx),
|
|
136
|
+
func_name: func
|
|
137
|
+
.to_string(cx)
|
|
138
|
+
.map_or_else(|_| "anonymous func".to_owned(), |s| s.value(cx)),
|
|
139
|
+
chan: cx.channel(),
|
|
140
|
+
_marker: PhantomData,
|
|
141
|
+
}),
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
pub async fn call(&self, args: Args) -> BridgeResult<Ret> {
|
|
146
|
+
let inner = self.inner.clone();
|
|
147
|
+
|
|
148
|
+
let join_handle = inner
|
|
149
|
+
.chan
|
|
150
|
+
.clone()
|
|
151
|
+
.try_send(move |mut cx| inner.call(&mut cx, args).into_throw(&mut cx))
|
|
152
|
+
.map_err(|e| BridgeError::Other(e.into()))?;
|
|
153
|
+
|
|
154
|
+
// Wait for the JS function to return a Promise...
|
|
155
|
+
let res = join_handle.await;
|
|
156
|
+
let future = res.map_err(|e| BridgeError::Other(e.into()))?;
|
|
157
|
+
|
|
158
|
+
// ... and then wait for the Promise to resolve
|
|
159
|
+
let res = future.await;
|
|
160
|
+
let res = res.map_err(|e| BridgeError::Other(e.into()))??;
|
|
161
|
+
Ok(res)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// Unfortunately, the `TryFromJS` trait doesn't have access to the containing object, and therefore
|
|
166
|
+
/// can't preserve the `this` context. If the JS side API presents the callback as a method on some
|
|
167
|
+
/// object, then the function should be `bind(this)`'d on the JS side before passing it to Rust.
|
|
168
|
+
impl<Args, Ret> TryFromJs for JsAsyncCallback<Args, Ret>
|
|
169
|
+
where
|
|
170
|
+
Args: TryIntoJsArgs + Send + Sync + 'static,
|
|
171
|
+
Ret: TryFromJs + Send + Sync + 'static,
|
|
172
|
+
{
|
|
173
|
+
fn try_from_js<'cx, 'b>(
|
|
174
|
+
cx: &mut impl Context<'cx>,
|
|
175
|
+
js_value: Handle<'b, JsValue>,
|
|
176
|
+
) -> BridgeResult<Self> {
|
|
177
|
+
let func = js_value.downcast::<JsFunction, _>(cx)?;
|
|
178
|
+
Ok(Self::new(cx, func, None))
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
impl<R: TryFromJs + Send + Sync + 'static> TryFromJs for JsFuture<BridgeResult<R>> {
|
|
183
|
+
fn try_from_js<'cx, 'b>(
|
|
184
|
+
cx: &mut impl Context<'cx>,
|
|
185
|
+
js_value: Handle<'b, JsValue>,
|
|
186
|
+
) -> BridgeResult<Self> {
|
|
187
|
+
let promise = js_value.downcast::<JsPromise, _>(cx)?;
|
|
188
|
+
let future = promise.to_future(cx, |mut cx, result| match result {
|
|
189
|
+
Ok(value) => {
|
|
190
|
+
// Promise resolved, but there might still be an error trying to
|
|
191
|
+
// convert the promise's returned value to the desired type.
|
|
192
|
+
let val = R::try_from_js(&mut cx, value);
|
|
193
|
+
match val {
|
|
194
|
+
Ok(val) => Ok(Ok(val)),
|
|
195
|
+
Err(e) => Ok(Err(e)),
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
Err(e) => {
|
|
199
|
+
// Promise failed to resolve
|
|
200
|
+
let err_str = e.to_string(&mut cx)?.value(&mut cx);
|
|
201
|
+
Ok(Err(BridgeError::UnexpectedError(err_str)))
|
|
202
|
+
}
|
|
203
|
+
})?;
|
|
204
|
+
|
|
205
|
+
Ok(future)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
210
|
+
|
|
211
|
+
#[derive(Debug)]
|
|
212
|
+
struct CallbackInner<Args, Ret>
|
|
213
|
+
where
|
|
214
|
+
Args: TryIntoJsArgs + Send + Sync,
|
|
215
|
+
Ret: TryFromJs + Send + Sync,
|
|
216
|
+
{
|
|
217
|
+
this: Option<Root<JsObject>>,
|
|
218
|
+
func: Root<JsFunction>,
|
|
219
|
+
func_name: String,
|
|
220
|
+
chan: Channel,
|
|
221
|
+
_marker: PhantomData<(Args, Ret)>,
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
impl<Args: TryIntoJsArgs + Send + Sync, Ret: TryFromJs + Send + Sync> CallbackInner<Args, Ret> {
|
|
225
|
+
fn call<'a, C: Context<'a>>(&self, cx: &mut C, args: Args) -> BridgeResult<Ret> {
|
|
226
|
+
let this: Handle<'a, JsValue> = self
|
|
227
|
+
.this
|
|
228
|
+
.as_ref()
|
|
229
|
+
.map_or(cx.undefined().upcast(), |t| t.to_inner(cx).upcast());
|
|
230
|
+
|
|
231
|
+
// Convert the arguments to a JS array using the new trait
|
|
232
|
+
let js_args = args.try_into_js_args(cx)?;
|
|
233
|
+
|
|
234
|
+
// Call the function with the JS arguments directly
|
|
235
|
+
let ret = cx
|
|
236
|
+
.try_catch(|cx| self.func.to_inner(cx).call(cx, this, js_args))
|
|
237
|
+
.map_err(|e| {
|
|
238
|
+
let err_str = format!(
|
|
239
|
+
"Error calling JS callback '{}' on JS thread, {:?}",
|
|
240
|
+
self.func_name, e
|
|
241
|
+
);
|
|
242
|
+
error!(err_str);
|
|
243
|
+
BridgeError::UnexpectedError(err_str)
|
|
244
|
+
})?;
|
|
245
|
+
|
|
246
|
+
<Ret>::try_from_js(cx, ret)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
251
|
+
|
|
252
|
+
/// A trait to build an arguments array for a JS function call from a tuple.
|
|
253
|
+
pub trait TryIntoJsArgs {
|
|
254
|
+
fn try_into_js_args<'cx, 'a>(
|
|
255
|
+
self,
|
|
256
|
+
cx: &mut impl Context<'cx>,
|
|
257
|
+
) -> BridgeResult<Vec<Handle<'a, JsValue>>>
|
|
258
|
+
where
|
|
259
|
+
'cx: 'a;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
impl TryIntoJsArgs for () {
|
|
263
|
+
fn try_into_js_args<'cx, 'a>(
|
|
264
|
+
self,
|
|
265
|
+
_cx: &mut impl Context<'cx>,
|
|
266
|
+
) -> BridgeResult<Vec<Handle<'a, JsValue>>>
|
|
267
|
+
where
|
|
268
|
+
'cx: 'a,
|
|
269
|
+
{
|
|
270
|
+
Ok(Vec::new())
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
impl<T0: TryIntoJs> TryIntoJsArgs for (T0,) {
|
|
275
|
+
fn try_into_js_args<'cx, 'a>(
|
|
276
|
+
self,
|
|
277
|
+
cx: &mut impl Context<'cx>,
|
|
278
|
+
) -> BridgeResult<Vec<Handle<'a, JsValue>>>
|
|
279
|
+
where
|
|
280
|
+
'cx: 'a,
|
|
281
|
+
{
|
|
282
|
+
let js_value = self.0.try_into_js(cx)?;
|
|
283
|
+
Ok(vec![js_value.upcast()])
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
impl<T0: TryIntoJs, T1: TryIntoJs> TryIntoJsArgs for (T0, T1) {
|
|
288
|
+
fn try_into_js_args<'cx, 'a>(
|
|
289
|
+
self,
|
|
290
|
+
cx: &mut impl Context<'cx>,
|
|
291
|
+
) -> BridgeResult<Vec<Handle<'a, JsValue>>>
|
|
292
|
+
where
|
|
293
|
+
'cx: 'a,
|
|
294
|
+
{
|
|
295
|
+
let v0 = self.0.try_into_js(cx)?;
|
|
296
|
+
let v1 = self.1.try_into_js(cx)?;
|
|
297
|
+
Ok(vec![v0.upcast(), v1.upcast()])
|
|
298
|
+
}
|
|
299
|
+
}
|