@temporalio/core-bridge 0.20.1 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +136 -127
- package/index.d.ts +1 -0
- package/package.json +3 -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/core/src/abstractions.rs +10 -3
- package/sdk-core/core/src/core_tests/queries.rs +145 -2
- package/sdk-core/core/src/core_tests/workflow_tasks.rs +44 -33
- package/sdk-core/core/src/worker/activities/local_activities.rs +58 -6
- package/sdk-core/core/src/worker/mod.rs +18 -6
- package/sdk-core/core/src/workflow/machines/workflow_machines.rs +9 -2
- package/sdk-core/core/src/workflow/mod.rs +5 -2
- package/sdk-core/core/src/workflow/workflow_tasks/concurrency_manager.rs +1 -1
- package/sdk-core/core/src/workflow/workflow_tasks/mod.rs +98 -46
- package/sdk-core/sdk/src/lib.rs +1 -1
- package/sdk-core/sdk-core-protos/src/history_info.rs +3 -7
- package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +26 -8
- package/src/errors.rs +9 -2
- package/src/lib.rs +39 -16
|
@@ -57,7 +57,7 @@ pub struct WorkflowTaskManager {
|
|
|
57
57
|
pending_activations: PendingActivations,
|
|
58
58
|
/// Holds activations which are purely query activations needed to respond to legacy queries.
|
|
59
59
|
/// Activations may only be added here for runs which do not have other pending activations.
|
|
60
|
-
|
|
60
|
+
pending_queries: SegQueue<WorkflowActivation>,
|
|
61
61
|
/// Holds poll wft responses from the server that need to be applied
|
|
62
62
|
ready_buffered_wft: SegQueue<ValidPollWFTQResponse>,
|
|
63
63
|
/// Used to wake blocked workflow task polling
|
|
@@ -74,9 +74,8 @@ pub struct WorkflowTaskManager {
|
|
|
74
74
|
#[derive(Clone, Debug)]
|
|
75
75
|
pub(crate) struct OutstandingTask {
|
|
76
76
|
pub info: WorkflowTaskInfo,
|
|
77
|
-
///
|
|
78
|
-
|
|
79
|
-
pub legacy_query: Option<QueryWorkflow>,
|
|
77
|
+
/// Set if the outstanding task has quer(ies) which must be fulfilled upon finishing replay
|
|
78
|
+
pub pending_queries: Vec<QueryWorkflow>,
|
|
80
79
|
start_time: Instant,
|
|
81
80
|
}
|
|
82
81
|
|
|
@@ -150,6 +149,13 @@ pub(crate) enum ActivationAction {
|
|
|
150
149
|
RespondLegacyQuery { result: QueryResult },
|
|
151
150
|
}
|
|
152
151
|
|
|
152
|
+
#[derive(Debug, Eq, PartialEq, Hash)]
|
|
153
|
+
pub(crate) enum EvictionRequestResult {
|
|
154
|
+
EvictionIssued(Option<u32>),
|
|
155
|
+
NotFound,
|
|
156
|
+
EvictionAlreadyOutstanding,
|
|
157
|
+
}
|
|
158
|
+
|
|
153
159
|
macro_rules! machine_mut {
|
|
154
160
|
($myself:ident, $run_id:ident, $clos:expr) => {{
|
|
155
161
|
$myself
|
|
@@ -172,7 +178,7 @@ impl WorkflowTaskManager {
|
|
|
172
178
|
Self {
|
|
173
179
|
workflow_machines: WorkflowConcurrencyManager::new(),
|
|
174
180
|
pending_activations: Default::default(),
|
|
175
|
-
|
|
181
|
+
pending_queries: Default::default(),
|
|
176
182
|
ready_buffered_wft: Default::default(),
|
|
177
183
|
pending_activations_notifier,
|
|
178
184
|
cache_manager: Mutex::new(WorkflowCacheManager::new(eviction_policy, metrics.clone())),
|
|
@@ -181,8 +187,8 @@ impl WorkflowTaskManager {
|
|
|
181
187
|
}
|
|
182
188
|
|
|
183
189
|
pub(crate) fn next_pending_activation(&self) -> Option<WorkflowActivation> {
|
|
184
|
-
// Dispatch pending
|
|
185
|
-
if let leg_q @ Some(_) = self.
|
|
190
|
+
// Dispatch pending queries first
|
|
191
|
+
if let leg_q @ Some(_) = self.pending_queries.pop() {
|
|
186
192
|
return leg_q;
|
|
187
193
|
}
|
|
188
194
|
// It is important that we do not issue pending activations for any workflows which already
|
|
@@ -201,12 +207,18 @@ impl WorkflowTaskManager {
|
|
|
201
207
|
if let Some(reason) = pending_info.needs_eviction {
|
|
202
208
|
act.append_evict_job(reason);
|
|
203
209
|
}
|
|
204
|
-
|
|
205
|
-
|
|
210
|
+
// If for whatever reason we triggered a pending activation but there wasn't
|
|
211
|
+
// actually any work to be done, just ignore that.
|
|
212
|
+
if !act.jobs.is_empty() {
|
|
213
|
+
self.insert_outstanding_activation(&act)?;
|
|
214
|
+
self.cache_manager.lock().touch(&act.run_id);
|
|
215
|
+
Ok(Some(act))
|
|
216
|
+
} else {
|
|
217
|
+
Ok(None)
|
|
218
|
+
}
|
|
206
219
|
})
|
|
207
220
|
{
|
|
208
|
-
|
|
209
|
-
Some(act)
|
|
221
|
+
act
|
|
210
222
|
} else {
|
|
211
223
|
self.request_eviction(
|
|
212
224
|
&pending_info.run_id,
|
|
@@ -247,7 +259,7 @@ impl WorkflowTaskManager {
|
|
|
247
259
|
run_id: &str,
|
|
248
260
|
message: impl Into<String>,
|
|
249
261
|
reason: EvictionReason,
|
|
250
|
-
) ->
|
|
262
|
+
) -> EvictionRequestResult {
|
|
251
263
|
if self.workflow_machines.exists(run_id) {
|
|
252
264
|
if !self.activation_has_eviction(run_id) {
|
|
253
265
|
let message = message.into();
|
|
@@ -256,13 +268,17 @@ impl WorkflowTaskManager {
|
|
|
256
268
|
self.pending_activations
|
|
257
269
|
.notify_needs_eviction(run_id, message, reason);
|
|
258
270
|
self.pending_activations_notifier.notify_waiters();
|
|
271
|
+
EvictionRequestResult::EvictionIssued(
|
|
272
|
+
self.workflow_machines
|
|
273
|
+
.get_task(run_id)
|
|
274
|
+
.map(|wt| wt.info.attempt),
|
|
275
|
+
)
|
|
276
|
+
} else {
|
|
277
|
+
EvictionRequestResult::EvictionAlreadyOutstanding
|
|
259
278
|
}
|
|
260
|
-
self.workflow_machines
|
|
261
|
-
.get_task(run_id)
|
|
262
|
-
.map(|wt| wt.info.attempt)
|
|
263
279
|
} else {
|
|
264
280
|
warn!(%run_id, "Eviction requested for unknown run");
|
|
265
|
-
|
|
281
|
+
EvictionRequestResult::NotFound
|
|
266
282
|
}
|
|
267
283
|
}
|
|
268
284
|
|
|
@@ -304,9 +320,11 @@ impl WorkflowTaskManager {
|
|
|
304
320
|
return NewWfTaskOutcome::TaskBuffered;
|
|
305
321
|
};
|
|
306
322
|
|
|
323
|
+
let start_event_id = work.history.events.first().map(|e| e.event_id);
|
|
307
324
|
debug!(
|
|
308
325
|
task_token = %&work.task_token,
|
|
309
326
|
history_length = %work.history.events.len(),
|
|
327
|
+
start_event_id = ?start_event_id,
|
|
310
328
|
attempt = %work.attempt,
|
|
311
329
|
run_id = %work.workflow_execution.run_id,
|
|
312
330
|
"Applying new workflow task from server"
|
|
@@ -320,33 +338,45 @@ impl WorkflowTaskManager {
|
|
|
320
338
|
.take()
|
|
321
339
|
.map(|q| query_to_job(LEGACY_QUERY_ID.to_string(), q));
|
|
322
340
|
|
|
323
|
-
let (info, mut next_activation) =
|
|
341
|
+
let (info, mut next_activation, mut pending_queries) =
|
|
324
342
|
match self.instantiate_or_update_workflow(work, client).await {
|
|
325
|
-
Ok(
|
|
343
|
+
Ok(res) => res,
|
|
326
344
|
Err(e) => {
|
|
327
345
|
return NewWfTaskOutcome::Evict(e);
|
|
328
346
|
}
|
|
329
347
|
};
|
|
330
348
|
|
|
349
|
+
if !pending_queries.is_empty() && legacy_query.is_some() {
|
|
350
|
+
error!(
|
|
351
|
+
"Server issued both normal and legacy queries. This should not happen. Please \
|
|
352
|
+
file a bug report."
|
|
353
|
+
);
|
|
354
|
+
return NewWfTaskOutcome::Evict(WorkflowUpdateError {
|
|
355
|
+
source: WFMachinesError::Fatal(
|
|
356
|
+
"Server issued both normal and legacy query".to_string(),
|
|
357
|
+
),
|
|
358
|
+
run_id: next_activation.run_id,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
331
362
|
// Immediately dispatch query activation if no other jobs
|
|
332
|
-
let
|
|
333
|
-
if
|
|
363
|
+
if let Some(lq) = legacy_query {
|
|
364
|
+
if next_activation.jobs.is_empty() {
|
|
334
365
|
debug!("Dispatching legacy query {}", &lq);
|
|
335
366
|
next_activation
|
|
336
367
|
.jobs
|
|
337
368
|
.push(workflow_activation_job::Variant::QueryWorkflow(lq).into());
|
|
369
|
+
} else {
|
|
370
|
+
pending_queries.push(lq);
|
|
338
371
|
}
|
|
339
|
-
|
|
340
|
-
} else {
|
|
341
|
-
legacy_query
|
|
342
|
-
};
|
|
372
|
+
}
|
|
343
373
|
|
|
344
374
|
self.workflow_machines
|
|
345
375
|
.insert_wft(
|
|
346
376
|
&next_activation.run_id,
|
|
347
377
|
OutstandingTask {
|
|
348
378
|
info,
|
|
349
|
-
|
|
379
|
+
pending_queries,
|
|
350
380
|
start_time: task_start_time,
|
|
351
381
|
},
|
|
352
382
|
)
|
|
@@ -388,11 +418,11 @@ impl WorkflowTaskManager {
|
|
|
388
418
|
return Ok(None);
|
|
389
419
|
}
|
|
390
420
|
|
|
391
|
-
let (task_token,
|
|
421
|
+
let (task_token, has_pending_query, start_time) =
|
|
392
422
|
if let Some(entry) = self.workflow_machines.get_task(run_id) {
|
|
393
423
|
(
|
|
394
424
|
entry.info.task_token.clone(),
|
|
395
|
-
entry.
|
|
425
|
+
!entry.pending_queries.is_empty(),
|
|
396
426
|
entry.start_time,
|
|
397
427
|
)
|
|
398
428
|
} else {
|
|
@@ -493,7 +523,7 @@ impl WorkflowTaskManager {
|
|
|
493
523
|
let must_heartbeat = self
|
|
494
524
|
.wait_for_local_acts_or_heartbeat(run_id, wft_heartbeat_deadline)
|
|
495
525
|
.await;
|
|
496
|
-
let is_query_playback =
|
|
526
|
+
let is_query_playback = has_pending_query && query_responses.is_empty();
|
|
497
527
|
|
|
498
528
|
// We only actually want to send commands back to the server if there are no more
|
|
499
529
|
// pending activations and we are caught up on replay. We don't want to complete a wft
|
|
@@ -559,9 +589,10 @@ impl WorkflowTaskManager {
|
|
|
559
589
|
FailedActivationOutcome::ReportLegacyQueryFailure(tt)
|
|
560
590
|
} else {
|
|
561
591
|
// Blow up any cached data associated with the workflow
|
|
562
|
-
let should_report = self
|
|
563
|
-
|
|
564
|
-
|
|
592
|
+
let should_report = match self.request_eviction(run_id, failstr, reason) {
|
|
593
|
+
EvictionRequestResult::EvictionIssued(Some(attempt)) => attempt <= 1,
|
|
594
|
+
_ => false,
|
|
595
|
+
};
|
|
565
596
|
if should_report {
|
|
566
597
|
FailedActivationOutcome::Report(tt)
|
|
567
598
|
} else {
|
|
@@ -578,7 +609,8 @@ impl WorkflowTaskManager {
|
|
|
578
609
|
&self,
|
|
579
610
|
poll_wf_resp: ValidPollWFTQResponse,
|
|
580
611
|
client: Arc<WorkerClientBag>,
|
|
581
|
-
) -> Result<(WorkflowTaskInfo, WorkflowActivation), WorkflowUpdateError>
|
|
612
|
+
) -> Result<(WorkflowTaskInfo, WorkflowActivation, Vec<QueryWorkflow>), WorkflowUpdateError>
|
|
613
|
+
{
|
|
582
614
|
let run_id = poll_wf_resp.workflow_execution.run_id.clone();
|
|
583
615
|
|
|
584
616
|
let wft_info = WorkflowTaskInfo {
|
|
@@ -592,11 +624,16 @@ impl WorkflowTaskManager {
|
|
|
592
624
|
.get(0)
|
|
593
625
|
.map(|ev| ev.event_id > 1)
|
|
594
626
|
.unwrap_or_default();
|
|
627
|
+
let poll_resp_is_incremental =
|
|
628
|
+
poll_resp_is_incremental || poll_wf_resp.history.events.is_empty();
|
|
629
|
+
|
|
630
|
+
let mut did_miss_cache = !poll_resp_is_incremental;
|
|
595
631
|
|
|
596
632
|
let page_token = if !self.workflow_machines.exists(&run_id) && poll_resp_is_incremental {
|
|
597
633
|
debug!(run_id=?run_id, "Workflow task has partial history, but workflow is not in \
|
|
598
634
|
cache. Will fetch history");
|
|
599
635
|
self.metrics.sticky_cache_miss();
|
|
636
|
+
did_miss_cache = true;
|
|
600
637
|
NextPageToken::FetchFromStart
|
|
601
638
|
} else {
|
|
602
639
|
poll_wf_resp.next_page_token.into()
|
|
@@ -625,16 +662,26 @@ impl WorkflowTaskManager {
|
|
|
625
662
|
.await
|
|
626
663
|
{
|
|
627
664
|
Ok(mut activation) => {
|
|
628
|
-
// If there are in-poll queries, insert jobs for those queries into the activation
|
|
665
|
+
// If there are in-poll queries, insert jobs for those queries into the activation,
|
|
666
|
+
// but only if we hit the cache. If we didn't, those queries will need to be dealt
|
|
667
|
+
// with once replay is over
|
|
668
|
+
let mut pending_queries = vec![];
|
|
629
669
|
if !poll_wf_resp.query_requests.is_empty() {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
670
|
+
if !did_miss_cache {
|
|
671
|
+
let query_jobs = poll_wf_resp
|
|
672
|
+
.query_requests
|
|
673
|
+
.into_iter()
|
|
674
|
+
.map(|q| workflow_activation_job::Variant::QueryWorkflow(q).into());
|
|
675
|
+
activation.jobs.extend(query_jobs);
|
|
676
|
+
} else {
|
|
677
|
+
poll_wf_resp
|
|
678
|
+
.query_requests
|
|
679
|
+
.into_iter()
|
|
680
|
+
.for_each(|q| pending_queries.push(q));
|
|
681
|
+
}
|
|
635
682
|
}
|
|
636
683
|
|
|
637
|
-
Ok((wft_info, activation))
|
|
684
|
+
Ok((wft_info, activation, pending_queries))
|
|
638
685
|
}
|
|
639
686
|
Err(source) => Err(WorkflowUpdateError { source, run_id }),
|
|
640
687
|
}
|
|
@@ -661,16 +708,18 @@ impl WorkflowTaskManager {
|
|
|
661
708
|
// removed from the outstanding tasks map
|
|
662
709
|
let retme = if !self.pending_activations.has_pending(run_id) {
|
|
663
710
|
if !just_evicted {
|
|
664
|
-
// Check if there was a
|
|
665
|
-
// a new pending activation for it.
|
|
711
|
+
// Check if there was a pending query which must be fulfilled, and if there is
|
|
712
|
+
// create a new pending activation for it.
|
|
666
713
|
if let Some(ref mut ot) = &mut *self
|
|
667
714
|
.workflow_machines
|
|
668
715
|
.get_task_mut(run_id)
|
|
669
716
|
.expect("Machine must exist")
|
|
670
717
|
{
|
|
671
|
-
if
|
|
672
|
-
|
|
673
|
-
|
|
718
|
+
if !ot.pending_queries.is_empty() {
|
|
719
|
+
for query in ot.pending_queries.drain(..) {
|
|
720
|
+
let na = create_query_activation(run_id.to_string(), [query]);
|
|
721
|
+
self.pending_queries.push(na);
|
|
722
|
+
}
|
|
674
723
|
self.pending_activations_notifier.notify_waiters();
|
|
675
724
|
return false;
|
|
676
725
|
}
|
|
@@ -725,7 +774,8 @@ impl WorkflowTaskManager {
|
|
|
725
774
|
run_id: &str,
|
|
726
775
|
resolved: LocalResolution,
|
|
727
776
|
) -> Result<(), WorkflowUpdateError> {
|
|
728
|
-
self
|
|
777
|
+
let result_was_important = self
|
|
778
|
+
.workflow_machines
|
|
729
779
|
.access_sync(run_id, |wfm: &mut WorkflowManager| {
|
|
730
780
|
wfm.notify_of_local_result(resolved)
|
|
731
781
|
})?
|
|
@@ -734,7 +784,9 @@ impl WorkflowTaskManager {
|
|
|
734
784
|
run_id: run_id.to_string(),
|
|
735
785
|
})?;
|
|
736
786
|
|
|
737
|
-
|
|
787
|
+
if result_was_important {
|
|
788
|
+
self.needs_activation(run_id);
|
|
789
|
+
}
|
|
738
790
|
Ok(())
|
|
739
791
|
}
|
|
740
792
|
|
package/sdk-core/sdk/src/lib.rs
CHANGED
|
@@ -389,7 +389,7 @@ impl ActivityHalf {
|
|
|
389
389
|
tokio::spawn(ACT_CANCEL_TOK.scope(ct, async move {
|
|
390
390
|
let mut inputs = start.input;
|
|
391
391
|
let arg = inputs.pop().unwrap_or_default();
|
|
392
|
-
let output = (
|
|
392
|
+
let output = (act_fn.act_func)(arg).await;
|
|
393
393
|
let result = match output {
|
|
394
394
|
Ok(res) => ActivityExecutionResult::ok(res),
|
|
395
395
|
Err(err) => match err.downcast::<ActivityCancelledError>() {
|
|
@@ -117,17 +117,13 @@ impl HistoryInfo {
|
|
|
117
117
|
/// Remove events from the beginning of this history such that it looks like what would've been
|
|
118
118
|
/// delivered on a sticky queue where the previously started task was the one before the last
|
|
119
119
|
/// task in this history.
|
|
120
|
-
///
|
|
121
|
-
/// This is not *fully* accurate in that it will include commands that were part of the last
|
|
122
|
-
/// WFT completion, which the server would typically not include, but it's good enough for
|
|
123
|
-
/// testing.
|
|
124
120
|
pub fn make_incremental(&mut self) {
|
|
125
121
|
let last_complete_ix = self
|
|
126
122
|
.events
|
|
127
123
|
.iter()
|
|
128
124
|
.rposition(|he| he.event_type() == EventType::WorkflowTaskCompleted)
|
|
129
125
|
.expect("Must be a WFT completed event in history");
|
|
130
|
-
self.events.drain(0
|
|
126
|
+
self.events.drain(0..last_complete_ix);
|
|
131
127
|
}
|
|
132
128
|
|
|
133
129
|
pub fn events(&self) -> &[HistoryEvent] {
|
|
@@ -223,7 +219,7 @@ mod tests {
|
|
|
223
219
|
fn incremental_works() {
|
|
224
220
|
let t = single_timer("timer1");
|
|
225
221
|
let hi = t.get_one_wft(2).unwrap();
|
|
226
|
-
assert_eq!(hi.events().len(),
|
|
227
|
-
assert_eq!(hi.events()[0].event_id,
|
|
222
|
+
assert_eq!(hi.events().len(), 5);
|
|
223
|
+
assert_eq!(hi.events()[0].event_id, 4);
|
|
228
224
|
}
|
|
229
225
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
use std::time::Duration;
|
|
2
|
-
use temporal_client::
|
|
2
|
+
use temporal_client::WorkflowOptions;
|
|
3
3
|
use temporal_sdk::{WfContext, WfExitValue, WorkflowResult};
|
|
4
4
|
use temporal_sdk_core_protos::coresdk::workflow_commands::ContinueAsNewWorkflowExecution;
|
|
5
5
|
use temporal_sdk_core_test_utils::CoreWfStarter;
|
|
@@ -33,13 +33,31 @@ async fn continue_as_new_happy_path() {
|
|
|
33
33
|
)
|
|
34
34
|
.await
|
|
35
35
|
.unwrap();
|
|
36
|
+
// The four additional runs
|
|
37
|
+
worker.incr_expected_run_count(4);
|
|
36
38
|
worker.run_until_done().await.unwrap();
|
|
39
|
+
}
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
#[tokio::test]
|
|
42
|
+
async fn continue_as_new_multiple_concurrent() {
|
|
43
|
+
let wf_name = "continue_as_new_multiple_concurrent";
|
|
44
|
+
let mut starter = CoreWfStarter::new(wf_name);
|
|
45
|
+
starter.max_cached_workflows(3).max_wft(3);
|
|
46
|
+
let mut worker = starter.worker().await;
|
|
47
|
+
worker.register_wf(wf_name.to_string(), continue_as_new_wf);
|
|
48
|
+
|
|
49
|
+
let wf_names = (1..=20).map(|i| format!("{}-{}", wf_name, i));
|
|
50
|
+
for name in wf_names.clone() {
|
|
51
|
+
worker
|
|
52
|
+
.submit_wf(
|
|
53
|
+
name.to_string(),
|
|
54
|
+
wf_name.to_string(),
|
|
55
|
+
vec![[1].into()],
|
|
56
|
+
WorkflowOptions::default(),
|
|
57
|
+
)
|
|
58
|
+
.await
|
|
59
|
+
.unwrap();
|
|
60
|
+
}
|
|
61
|
+
worker.incr_expected_run_count(20 * 4);
|
|
62
|
+
worker.run_until_done().await.unwrap();
|
|
45
63
|
}
|
package/src/errors.rs
CHANGED
|
@@ -10,6 +10,8 @@ pub static SHUTDOWN_ERROR: OnceCell<Root<JsFunction>> = OnceCell::new();
|
|
|
10
10
|
pub static NO_WORKER_ERROR: OnceCell<Root<JsFunction>> = OnceCell::new();
|
|
11
11
|
/// Something unexpected happened, considered fatal
|
|
12
12
|
pub static UNEXPECTED_ERROR: OnceCell<Root<JsFunction>> = OnceCell::new();
|
|
13
|
+
/// Used in different parts of the project to signal that something unexpected has happened
|
|
14
|
+
pub static ILLEGAL_STATE_ERROR: OnceCell<Root<JsFunction>> = OnceCell::new();
|
|
13
15
|
|
|
14
16
|
static ALREADY_REGISTERED_ERRORS: OnceCell<bool> = OnceCell::new();
|
|
15
17
|
|
|
@@ -70,9 +72,9 @@ pub fn register_errors(mut cx: FunctionContext) -> JsResult<JsUndefined> {
|
|
|
70
72
|
let res = ALREADY_REGISTERED_ERRORS.set(true);
|
|
71
73
|
if res.is_err() {
|
|
72
74
|
// Don't do anything if errors are already registered
|
|
73
|
-
return Ok(cx.undefined())
|
|
75
|
+
return Ok(cx.undefined());
|
|
74
76
|
}
|
|
75
|
-
|
|
77
|
+
|
|
76
78
|
let mapping = cx.argument::<JsObject>(0)?;
|
|
77
79
|
let shutdown_error = mapping
|
|
78
80
|
.get(&mut cx, "ShutdownError")?
|
|
@@ -90,11 +92,16 @@ pub fn register_errors(mut cx: FunctionContext) -> JsResult<JsUndefined> {
|
|
|
90
92
|
.get(&mut cx, "UnexpectedError")?
|
|
91
93
|
.downcast_or_throw::<JsFunction, FunctionContext>(&mut cx)?
|
|
92
94
|
.root(&mut cx);
|
|
95
|
+
let illegal_state_error = mapping
|
|
96
|
+
.get(&mut cx, "IllegalStateError")?
|
|
97
|
+
.downcast_or_throw::<JsFunction, FunctionContext>(&mut cx)?
|
|
98
|
+
.root(&mut cx);
|
|
93
99
|
|
|
94
100
|
TRANSPORT_ERROR.get_or_try_init(|| Ok(transport_error))?;
|
|
95
101
|
SHUTDOWN_ERROR.get_or_try_init(|| Ok(shutdown_error))?;
|
|
96
102
|
NO_WORKER_ERROR.get_or_try_init(|| Ok(no_worker_error))?;
|
|
97
103
|
UNEXPECTED_ERROR.get_or_try_init(|| Ok(unexpected_error))?;
|
|
104
|
+
ILLEGAL_STATE_ERROR.get_or_try_init(|| Ok(illegal_state_error))?;
|
|
98
105
|
|
|
99
106
|
Ok(cx.undefined())
|
|
100
107
|
}
|
package/src/lib.rs
CHANGED
|
@@ -8,6 +8,7 @@ use once_cell::sync::OnceCell;
|
|
|
8
8
|
use opentelemetry::trace::{FutureExt, SpanContext, TraceContextExt};
|
|
9
9
|
use prost::Message;
|
|
10
10
|
use std::{
|
|
11
|
+
cell::RefCell,
|
|
11
12
|
fmt::Display,
|
|
12
13
|
future::Future,
|
|
13
14
|
sync::Arc,
|
|
@@ -135,7 +136,7 @@ struct Client {
|
|
|
135
136
|
core_client: Arc<RawClient>,
|
|
136
137
|
}
|
|
137
138
|
|
|
138
|
-
type BoxedClient = JsBox<Client
|
|
139
|
+
type BoxedClient = JsBox<RefCell<Option<Client>>>;
|
|
139
140
|
impl Finalize for Client {}
|
|
140
141
|
|
|
141
142
|
/// Worker struct, hold a reference for the channel sender responsible for sending requests from
|
|
@@ -291,10 +292,10 @@ fn start_bridge_loop(event_queue: Arc<EventQueue>, receiver: &mut UnboundedRecei
|
|
|
291
292
|
}
|
|
292
293
|
Ok(client) => {
|
|
293
294
|
send_result(event_queue.clone(), callback, |cx| {
|
|
294
|
-
Ok(cx.boxed(Client {
|
|
295
|
+
Ok(cx.boxed(RefCell::new(Some(Client {
|
|
295
296
|
runtime,
|
|
296
297
|
core_client: Arc::new(client),
|
|
297
|
-
}))
|
|
298
|
+
}))))
|
|
298
299
|
});
|
|
299
300
|
}
|
|
300
301
|
}
|
|
@@ -590,15 +591,23 @@ fn worker_new(mut cx: FunctionContext) -> JsResult<JsUndefined> {
|
|
|
590
591
|
let callback = cx.argument::<JsFunction>(2)?;
|
|
591
592
|
|
|
592
593
|
let config = worker_options.as_worker_config(&mut cx)?;
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
594
|
+
match &*client.borrow() {
|
|
595
|
+
None => {
|
|
596
|
+
callback_with_error(&mut cx, callback, move |cx| {
|
|
597
|
+
UNEXPECTED_ERROR.from_string(cx, "Tried to use closed Client".to_string())
|
|
598
|
+
})?;
|
|
599
|
+
}
|
|
600
|
+
Some(client) => {
|
|
601
|
+
let request = Request::InitWorker {
|
|
602
|
+
client: client.core_client.clone(),
|
|
603
|
+
runtime: client.runtime.clone(),
|
|
604
|
+
config,
|
|
605
|
+
callback: callback.root(&mut cx),
|
|
606
|
+
};
|
|
607
|
+
if let Err(err) = client.runtime.sender.send(request) {
|
|
608
|
+
callback_with_unexpected_error(&mut cx, callback, err)?;
|
|
609
|
+
};
|
|
610
|
+
}
|
|
602
611
|
};
|
|
603
612
|
|
|
604
613
|
Ok(cx.undefined())
|
|
@@ -783,13 +792,26 @@ fn worker_record_activity_heartbeat(mut cx: FunctionContext) -> JsResult<JsUndef
|
|
|
783
792
|
fn worker_shutdown(mut cx: FunctionContext) -> JsResult<JsUndefined> {
|
|
784
793
|
let worker = cx.argument::<BoxedWorker>(0)?;
|
|
785
794
|
let callback = cx.argument::<JsFunction>(1)?;
|
|
786
|
-
|
|
795
|
+
if let Err(err) = worker.runtime.sender.send(Request::ShutdownWorker {
|
|
787
796
|
worker: worker.core_worker.clone(),
|
|
788
797
|
callback: callback.root(&mut cx),
|
|
789
798
|
}) {
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
799
|
+
UNEXPECTED_ERROR
|
|
800
|
+
.from_error(&mut cx, err)
|
|
801
|
+
.and_then(|err| cx.throw(err))?;
|
|
802
|
+
};
|
|
803
|
+
Ok(cx.undefined())
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/// Drop a reference to a Client, once all references are dropped, the Client will be closed.
|
|
807
|
+
fn client_close(mut cx: FunctionContext) -> JsResult<JsUndefined> {
|
|
808
|
+
let client = cx.argument::<BoxedClient>(0)?;
|
|
809
|
+
if client.replace(None).is_none() {
|
|
810
|
+
ILLEGAL_STATE_ERROR
|
|
811
|
+
.from_error(&mut cx, "Client already closed")
|
|
812
|
+
.and_then(|err| cx.throw(err))?;
|
|
813
|
+
};
|
|
814
|
+
Ok(cx.undefined())
|
|
793
815
|
}
|
|
794
816
|
|
|
795
817
|
/// Convert Rust SystemTime into a JS array with 2 numbers (seconds, nanos)
|
|
@@ -824,6 +846,7 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> {
|
|
|
824
846
|
cx.export_function("newWorker", worker_new)?;
|
|
825
847
|
cx.export_function("newReplayWorker", replay_worker_new)?;
|
|
826
848
|
cx.export_function("workerShutdown", worker_shutdown)?;
|
|
849
|
+
cx.export_function("clientClose", client_close)?;
|
|
827
850
|
cx.export_function("runtimeShutdown", runtime_shutdown)?;
|
|
828
851
|
cx.export_function("pollLogs", poll_logs)?;
|
|
829
852
|
cx.export_function(
|