@temporalio/core-bridge 1.11.3 → 1.11.5

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 (117) hide show
  1. package/Cargo.lock +396 -489
  2. package/Cargo.toml +3 -2
  3. package/lib/errors.js +2 -2
  4. package/lib/errors.js.map +1 -1
  5. package/lib/index.d.ts +8 -2
  6. package/lib/index.js.map +1 -1
  7. package/lib/worker-tuner.d.ts +111 -1
  8. package/package.json +3 -3
  9. package/releases/aarch64-apple-darwin/index.node +0 -0
  10. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  11. package/releases/x86_64-apple-darwin/index.node +0 -0
  12. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  13. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  14. package/sdk-core/.github/workflows/per-pr.yml +3 -3
  15. package/sdk-core/Cargo.toml +0 -1
  16. package/sdk-core/client/Cargo.toml +1 -2
  17. package/sdk-core/client/src/lib.rs +21 -13
  18. package/sdk-core/client/src/metrics.rs +1 -1
  19. package/sdk-core/client/src/raw.rs +46 -1
  20. package/sdk-core/core/Cargo.toml +7 -7
  21. package/sdk-core/core/benches/workflow_replay.rs +1 -1
  22. package/sdk-core/core/src/abstractions/take_cell.rs +1 -1
  23. package/sdk-core/core/src/abstractions.rs +98 -10
  24. package/sdk-core/core/src/core_tests/activity_tasks.rs +8 -2
  25. package/sdk-core/core/src/core_tests/local_activities.rs +1 -1
  26. package/sdk-core/core/src/core_tests/mod.rs +3 -3
  27. package/sdk-core/core/src/core_tests/updates.rs +104 -9
  28. package/sdk-core/core/src/core_tests/workers.rs +72 -3
  29. package/sdk-core/core/src/core_tests/workflow_tasks.rs +6 -7
  30. package/sdk-core/core/src/debug_client.rs +78 -0
  31. package/sdk-core/core/src/ephemeral_server/mod.rs +15 -5
  32. package/sdk-core/core/src/lib.rs +30 -4
  33. package/sdk-core/core/src/pollers/mod.rs +1 -1
  34. package/sdk-core/core/src/pollers/poll_buffer.rs +7 -7
  35. package/sdk-core/core/src/replay/mod.rs +4 -4
  36. package/sdk-core/core/src/telemetry/log_export.rs +2 -2
  37. package/sdk-core/core/src/telemetry/metrics.rs +69 -1
  38. package/sdk-core/core/src/telemetry/otel.rs +2 -2
  39. package/sdk-core/core/src/test_help/mod.rs +3 -3
  40. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +3 -3
  41. package/sdk-core/core/src/worker/activities/activity_task_poller_stream.rs +1 -1
  42. package/sdk-core/core/src/worker/activities/local_activities.rs +68 -24
  43. package/sdk-core/core/src/worker/activities.rs +26 -15
  44. package/sdk-core/core/src/worker/client/mocks.rs +10 -4
  45. package/sdk-core/core/src/worker/client.rs +17 -0
  46. package/sdk-core/core/src/worker/mod.rs +71 -13
  47. package/sdk-core/core/src/worker/slot_provider.rs +5 -7
  48. package/sdk-core/core/src/worker/tuner/fixed_size.rs +4 -3
  49. package/sdk-core/core/src/worker/tuner/resource_based.rs +171 -32
  50. package/sdk-core/core/src/worker/tuner.rs +18 -6
  51. package/sdk-core/core/src/worker/workflow/history_update.rs +43 -13
  52. package/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +6 -6
  53. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +6 -5
  54. package/sdk-core/core/src/worker/workflow/managed_run.rs +3 -3
  55. package/sdk-core/core/src/worker/workflow/mod.rs +13 -7
  56. package/sdk-core/core/src/worker/workflow/wft_extraction.rs +7 -7
  57. package/sdk-core/core/src/worker/workflow/wft_poller.rs +2 -2
  58. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +11 -11
  59. package/sdk-core/core-api/Cargo.toml +1 -0
  60. package/sdk-core/core-api/src/worker.rs +84 -30
  61. package/sdk-core/sdk/Cargo.toml +1 -2
  62. package/sdk-core/sdk/src/lib.rs +1 -1
  63. package/sdk-core/sdk/src/workflow_context.rs +9 -8
  64. package/sdk-core/sdk/src/workflow_future.rs +19 -14
  65. package/sdk-core/sdk-core-protos/Cargo.toml +2 -0
  66. package/sdk-core/sdk-core-protos/build.rs +6 -1
  67. package/sdk-core/sdk-core-protos/protos/api_upstream/buf.yaml +1 -0
  68. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +3207 -158
  69. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +2934 -118
  70. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/activity/v1/message.proto +67 -0
  71. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +47 -1
  72. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/event_type.proto +6 -7
  73. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +2 -0
  74. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/reset.proto +5 -3
  75. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +3 -3
  76. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/update.proto +14 -13
  77. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/failure/v1/message.proto +1 -3
  78. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/history/v1/message.proto +22 -0
  79. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/nexus/v1/message.proto +13 -2
  80. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +26 -6
  81. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/schedule/v1/message.proto +5 -0
  82. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/workflow_metadata.proto +6 -0
  83. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +46 -12
  84. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/update/v1/message.proto +18 -19
  85. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflow/v1/message.proto +27 -0
  86. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +192 -19
  87. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +279 -12
  88. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/activity_result/activity_result.proto +1 -1
  89. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/activity_task/activity_task.proto +1 -1
  90. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +1 -1
  91. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/common/common.proto +1 -1
  92. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/core_interface.proto +17 -1
  93. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/external_data/external_data.proto +1 -1
  94. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +1 -1
  95. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +1 -1
  96. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +1 -1
  97. package/sdk-core/sdk-core-protos/src/lib.rs +30 -6
  98. package/sdk-core/test-utils/Cargo.toml +1 -2
  99. package/sdk-core/test-utils/src/lib.rs +2 -2
  100. package/sdk-core/tests/heavy_tests.rs +1 -1
  101. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +2 -2
  102. package/sdk-core/tests/integ_tests/metrics_tests.rs +144 -7
  103. package/sdk-core/tests/integ_tests/queries_tests.rs +1 -1
  104. package/sdk-core/tests/integ_tests/update_tests.rs +109 -5
  105. package/sdk-core/tests/integ_tests/worker_tests.rs +44 -8
  106. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +1 -1
  107. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -1
  108. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +1 -1
  109. package/sdk-core/tests/integ_tests/workflow_tests.rs +3 -2
  110. package/src/conversions/slot_supplier_bridge.rs +287 -0
  111. package/src/conversions.rs +23 -15
  112. package/src/helpers.rs +35 -1
  113. package/src/runtime.rs +7 -3
  114. package/src/worker.rs +1 -1
  115. package/ts/index.ts +19 -4
  116. package/ts/worker-tuner.ts +123 -1
  117. package/sdk-core/sdk-core-protos/protos/api_upstream/.gitmodules +0 -3
@@ -3,7 +3,7 @@ use parking_lot::Mutex;
3
3
  use std::{
4
4
  marker::PhantomData,
5
5
  sync::{
6
- atomic::{AtomicU64, Ordering},
6
+ atomic::{AtomicU64, AtomicUsize, Ordering},
7
7
  Arc, OnceLock,
8
8
  },
9
9
  time::{Duration, Instant},
@@ -11,7 +11,8 @@ use std::{
11
11
  use temporal_sdk_core_api::{
12
12
  telemetry::metrics::{CoreMeter, GaugeF64, MetricAttributes, TemporalMeter},
13
13
  worker::{
14
- ActivitySlotKind, LocalActivitySlotKind, SlotKind, SlotReservationContext, SlotSupplier,
14
+ ActivitySlotKind, LocalActivitySlotKind, SlotInfo, SlotInfoTrait, SlotKind, SlotKindType,
15
+ SlotMarkUsedContext, SlotReleaseContext, SlotReservationContext, SlotSupplier,
15
16
  SlotSupplierPermit, WorkerTuner, WorkflowSlotKind,
16
17
  },
17
18
  };
@@ -124,6 +125,12 @@ pub(crate) struct ResourceBasedSlotsForType<MI, SK> {
124
125
 
125
126
  last_slot_issued_tx: watch::Sender<Instant>,
126
127
  last_slot_issued_rx: watch::Receiver<Instant>,
128
+
129
+ // Only used for workflow slots - count of issued non-sticky slots
130
+ issued_nonsticky: AtomicUsize,
131
+ // Only used for workflow slots - count of issued sticky slots
132
+ issued_sticky: AtomicUsize,
133
+
127
134
  _slot_kind: PhantomData<SK>,
128
135
  }
129
136
  /// Allows for the full customization of the PID options for a resource based tuner
@@ -241,42 +248,51 @@ where
241
248
 
242
249
  async fn reserve_slot(&self, ctx: &dyn SlotReservationContext) -> SlotSupplierPermit {
243
250
  loop {
244
- if ctx.num_issued_slots() < self.opts.min_slots {
245
- return self.issue_slot();
251
+ if let Some(value) = self.issue_if_below_minimums(ctx) {
252
+ return value;
253
+ }
254
+ let must_wait_for = self
255
+ .opts
256
+ .ramp_throttle
257
+ .saturating_sub(self.time_since_last_issued());
258
+ if must_wait_for > Duration::from_millis(0) {
259
+ tokio::time::sleep(must_wait_for).await;
260
+ }
261
+ if let Some(p) = self.try_reserve_slot(ctx) {
262
+ return p;
246
263
  } else {
247
- let must_wait_for = self
248
- .opts
249
- .ramp_throttle
250
- .saturating_sub(self.time_since_last_issued());
251
- if must_wait_for > Duration::from_millis(0) {
252
- tokio::time::sleep(must_wait_for).await;
253
- }
254
- if let Some(p) = self.try_reserve_slot(ctx) {
255
- return p;
256
- } else {
257
- tokio::time::sleep(Duration::from_millis(10)).await;
258
- }
264
+ tokio::time::sleep(Duration::from_millis(10)).await;
259
265
  }
260
266
  }
261
267
  }
262
268
 
263
269
  fn try_reserve_slot(&self, ctx: &dyn SlotReservationContext) -> Option<SlotSupplierPermit> {
264
- let num_issued = ctx.num_issued_slots();
265
- if num_issued < self.opts.min_slots
266
- || (self.time_since_last_issued() > self.opts.ramp_throttle
267
- && num_issued < self.opts.max_slots
268
- && self.inner.pid_decision()
269
- && self.inner.can_reserve())
270
+ if let v @ Some(_) = self.issue_if_below_minimums(ctx) {
271
+ return v;
272
+ }
273
+ if self.time_since_last_issued() > self.opts.ramp_throttle
274
+ && ctx.num_issued_slots() < self.opts.max_slots
275
+ && self.inner.pid_decision()
276
+ && self.inner.can_reserve()
270
277
  {
271
- Some(self.issue_slot())
278
+ Some(self.issue_slot(ctx))
272
279
  } else {
273
280
  None
274
281
  }
275
282
  }
276
283
 
277
- fn mark_slot_used(&self, _info: SK::Info<'_>) {}
284
+ fn mark_slot_used(&self, _ctx: &dyn SlotMarkUsedContext<SlotKind = Self::SlotKind>) {}
278
285
 
279
- fn release_slot(&self) {}
286
+ fn release_slot(&self, ctx: &dyn SlotReleaseContext<SlotKind = Self::SlotKind>) {
287
+ // Really could use specialization here
288
+ if let Some(SlotInfo::Workflow(info)) = ctx.info().map(|i| i.downcast()) {
289
+ if info.is_sticky {
290
+ self.issued_sticky.fetch_sub(1, Ordering::Relaxed);
291
+ } else {
292
+ self.issued_nonsticky.fetch_sub(1, Ordering::Relaxed);
293
+ }
294
+ }
295
+ }
280
296
  }
281
297
 
282
298
  impl<MI, SK> ResourceBasedSlotsForType<MI, SK>
@@ -291,11 +307,41 @@ where
291
307
  last_slot_issued_tx: tx,
292
308
  last_slot_issued_rx: rx,
293
309
  inner,
310
+ issued_nonsticky: Default::default(),
311
+ issued_sticky: Default::default(),
294
312
  _slot_kind: PhantomData,
295
313
  }
296
314
  }
297
315
 
298
- fn issue_slot(&self) -> SlotSupplierPermit {
316
+ // Always be willing to hand out at least 1 slot for sticky and 1 for non-sticky to
317
+ // avoid getting stuck.
318
+ fn issue_if_below_minimums(
319
+ &self,
320
+ ctx: &dyn SlotReservationContext,
321
+ ) -> Option<SlotSupplierPermit> {
322
+ if ctx.num_issued_slots() < self.opts.min_slots {
323
+ return Some(self.issue_slot(ctx));
324
+ }
325
+ if SK::kind() == SlotKindType::Workflow
326
+ && (ctx.is_sticky() && self.issued_sticky.load(Ordering::Relaxed) == 0
327
+ || !ctx.is_sticky() && self.issued_nonsticky.load(Ordering::Relaxed) == 0)
328
+ {
329
+ return Some(self.issue_slot(ctx));
330
+ }
331
+
332
+ None
333
+ }
334
+
335
+ fn issue_slot(&self, ctx: &dyn SlotReservationContext) -> SlotSupplierPermit {
336
+ // Always be willing to hand out at least 1 slot for sticky and 1 for non-sticky to avoid
337
+ // getting stuck.
338
+ if SK::kind() == SlotKindType::Workflow {
339
+ if ctx.is_sticky() {
340
+ self.issued_sticky.fetch_add(1, Ordering::Relaxed);
341
+ } else {
342
+ self.issued_nonsticky.fetch_add(1, Ordering::Relaxed);
343
+ }
344
+ }
299
345
  let _ = self.last_slot_issued_tx.send(Instant::now());
300
346
  SlotSupplierPermit::default()
301
347
  }
@@ -406,7 +452,7 @@ impl<MI: SystemResourceInfo + Sync + Send> ResourceController<MI> {
406
452
  #[derive(Debug)]
407
453
  pub struct RealSysInfo {
408
454
  sys: Mutex<sysinfo::System>,
409
- total_mem: u64,
455
+ total_mem: AtomicU64,
410
456
  cur_mem_usage: AtomicU64,
411
457
  cur_cpu_usage: AtomicU64,
412
458
  last_refresh: AtomicCell<Instant>,
@@ -421,7 +467,7 @@ impl RealSysInfo {
421
467
  last_refresh: AtomicCell::new(Instant::now()),
422
468
  cur_mem_usage: AtomicU64::new(0),
423
469
  cur_cpu_usage: AtomicU64::new(0),
424
- total_mem,
470
+ total_mem: AtomicU64::new(total_mem),
425
471
  };
426
472
  s.refresh();
427
473
  s
@@ -441,17 +487,28 @@ impl RealSysInfo {
441
487
  lock.refresh_cpu_usage();
442
488
  let mem = lock.used_memory();
443
489
  let cpu = lock.global_cpu_usage() as f64 / 100.;
490
+ if let Some(cgroup_limits) = lock.cgroup_limits() {
491
+ self.total_mem
492
+ .store(cgroup_limits.total_memory, Ordering::Release);
493
+ self.cur_mem_usage.store(
494
+ cgroup_limits.total_memory - cgroup_limits.free_memory,
495
+ Ordering::Release,
496
+ );
497
+ }
444
498
  self.cur_mem_usage.store(mem, Ordering::Release);
445
499
  self.cur_cpu_usage.store(cpu.to_bits(), Ordering::Release);
446
500
  self.last_refresh.store(Instant::now());
447
501
  }
448
502
  }
503
+
449
504
  impl SystemResourceInfo for RealSysInfo {
450
505
  fn total_mem(&self) -> u64 {
451
- self.total_mem
506
+ self.total_mem.load(Ordering::Acquire)
452
507
  }
453
508
 
454
509
  fn used_mem(&self) -> u64 {
510
+ // TODO: This should really happen on a background thread since it's getting called from
511
+ // the async reserve
455
512
  self.refresh_if_needed();
456
513
  self.cur_mem_usage.load(Ordering::Acquire)
457
514
  }
@@ -512,10 +569,47 @@ mod tests {
512
569
  max_slots: 100,
513
570
  ramp_throttle: Duration::from_millis(0),
514
571
  });
515
- let pd = MeteredPermitDealer::new(rbs.clone(), MetricsContext::no_op(), None);
572
+ let pd = MeteredPermitDealer::new(
573
+ rbs.clone(),
574
+ MetricsContext::no_op(),
575
+ None,
576
+ Arc::new(Default::default()),
577
+ );
578
+ let pd_s = pd.clone().into_sticky();
579
+ // Start with too high usage
580
+ used.store(90_000, Ordering::Release);
581
+ // Show workflow will always allow 1 each of sticky/non-sticky
582
+ assert!(rbs.try_reserve_slot(&pd).is_some());
583
+ assert!(rbs.try_reserve_slot(&pd_s).is_some());
584
+ assert!(rbs.try_reserve_slot(&pd).is_none());
585
+ assert!(rbs.try_reserve_slot(&pd_s).is_none());
586
+ used.store(0, Ordering::Release);
587
+ // Now it's willing to hand out slots again when usage is zero
516
588
  assert!(rbs.try_reserve_slot(&pd).is_some());
589
+ assert!(rbs.try_reserve_slot(&pd_s).is_some());
590
+ }
591
+
592
+ #[test]
593
+ fn mem_activity_sync() {
594
+ let (fmis, used) = FakeMIS::new();
595
+ let rbs = Arc::new(ResourceController::new_with_sysinfo(test_options(), fmis))
596
+ .as_kind::<ActivitySlotKind>(ResourceSlotOptions {
597
+ min_slots: 0,
598
+ max_slots: 100,
599
+ ramp_throttle: Duration::from_millis(0),
600
+ });
601
+ let pd = MeteredPermitDealer::new(
602
+ rbs.clone(),
603
+ MetricsContext::no_op(),
604
+ None,
605
+ Arc::new(Default::default()),
606
+ );
607
+ // Start with too high usage
517
608
  used.store(90_000, Ordering::Release);
518
609
  assert!(rbs.try_reserve_slot(&pd).is_none());
610
+ used.store(0, Ordering::Release);
611
+ // Now it's willing to hand out slots again when usage is zero
612
+ assert!(rbs.try_reserve_slot(&pd).is_some());
519
613
  }
520
614
 
521
615
  #[tokio::test]
@@ -528,7 +622,47 @@ mod tests {
528
622
  max_slots: 100,
529
623
  ramp_throttle: Duration::from_millis(0),
530
624
  });
531
- let pd = MeteredPermitDealer::new(rbs.clone(), MetricsContext::no_op(), None);
625
+ let pd = MeteredPermitDealer::new(
626
+ rbs.clone(),
627
+ MetricsContext::no_op(),
628
+ None,
629
+ Arc::new(Default::default()),
630
+ );
631
+ let pd_s = pd.clone().into_sticky();
632
+ let order = crossbeam_queue::ArrayQueue::new(2);
633
+ // Show workflow will always allow 1 each of sticky/non-sticky
634
+ let _p1 = rbs.reserve_slot(&pd).await;
635
+ let _p2 = rbs.reserve_slot(&pd_s).await;
636
+ // Now we need to have some memory get freed before the next call resolves
637
+ let waits_free = async {
638
+ rbs.reserve_slot(&pd).await;
639
+ order.push(2).unwrap();
640
+ };
641
+ let frees = async {
642
+ used.store(70_000, Ordering::Release);
643
+ order.push(1).unwrap();
644
+ };
645
+ tokio::join!(waits_free, frees);
646
+ assert_eq!(order.pop(), Some(1));
647
+ assert_eq!(order.pop(), Some(2));
648
+ }
649
+
650
+ #[tokio::test]
651
+ async fn mem_activity_async() {
652
+ let (fmis, used) = FakeMIS::new();
653
+ used.store(90_000, Ordering::Release);
654
+ let rbs = Arc::new(ResourceController::new_with_sysinfo(test_options(), fmis))
655
+ .as_kind::<ActivitySlotKind>(ResourceSlotOptions {
656
+ min_slots: 0,
657
+ max_slots: 100,
658
+ ramp_throttle: Duration::from_millis(0),
659
+ });
660
+ let pd = MeteredPermitDealer::new(
661
+ rbs.clone(),
662
+ MetricsContext::no_op(),
663
+ None,
664
+ Arc::new(Default::default()),
665
+ );
532
666
  let order = crossbeam_queue::ArrayQueue::new(2);
533
667
  let waits_free = async {
534
668
  rbs.reserve_slot(&pd).await;
@@ -552,7 +686,12 @@ mod tests {
552
686
  max_slots: 100,
553
687
  ramp_throttle: Duration::from_millis(0),
554
688
  });
555
- let pd = MeteredPermitDealer::new(rbs.clone(), MetricsContext::no_op(), None);
689
+ let pd = MeteredPermitDealer::new(
690
+ rbs.clone(),
691
+ MetricsContext::no_op(),
692
+ None,
693
+ Arc::new(Default::default()),
694
+ );
556
695
  used.store(90_000, Ordering::Release);
557
696
  let _p1 = pd.try_acquire_owned().unwrap();
558
697
  let _p2 = pd.try_acquire_owned().unwrap();
@@ -11,7 +11,7 @@ use std::sync::{Arc, OnceLock};
11
11
  use temporal_sdk_core_api::{
12
12
  telemetry::metrics::TemporalMeter,
13
13
  worker::{
14
- ActivitySlotKind, LocalActivitySlotKind, SlotSupplier, WorkerConfig, WorkerTuner,
14
+ ActivitySlotKind, LocalActivitySlotKind, SlotKind, SlotSupplier, WorkerConfig, WorkerTuner,
15
15
  WorkflowSlotKind,
16
16
  },
17
17
  };
@@ -32,13 +32,13 @@ pub struct TunerHolder {
32
32
  pub struct TunerHolderOptions {
33
33
  /// Options for workflow slots
34
34
  #[builder(default, setter(strip_option))]
35
- pub workflow_slot_options: Option<SlotSupplierOptions>,
35
+ pub workflow_slot_options: Option<SlotSupplierOptions<WorkflowSlotKind>>,
36
36
  /// Options for activity slots
37
37
  #[builder(default, setter(strip_option))]
38
- pub activity_slot_options: Option<SlotSupplierOptions>,
38
+ pub activity_slot_options: Option<SlotSupplierOptions<ActivitySlotKind>>,
39
39
  /// Options for local activity slots
40
40
  #[builder(default, setter(strip_option))]
41
- pub local_activity_slot_options: Option<SlotSupplierOptions>,
41
+ pub local_activity_slot_options: Option<SlotSupplierOptions<LocalActivitySlotKind>>,
42
42
  /// Options that will apply to all resource based slot suppliers. Must be set if any slot
43
43
  /// options are [SlotSupplierOptions::ResourceBased]
44
44
  #[builder(default, setter(strip_option))]
@@ -67,6 +67,9 @@ impl TunerHolderOptions {
67
67
  .workflow_task_slot_supplier(),
68
68
  );
69
69
  }
70
+ Some(SlotSupplierOptions::Custom(ss)) => {
71
+ builder.workflow_slot_supplier(ss);
72
+ }
70
73
  None => {}
71
74
  }
72
75
  match self.activity_slot_options {
@@ -82,6 +85,9 @@ impl TunerHolderOptions {
82
85
  .activity_task_slot_supplier(),
83
86
  );
84
87
  }
88
+ Some(SlotSupplierOptions::Custom(ss)) => {
89
+ builder.activity_slot_supplier(ss);
90
+ }
85
91
  None => {}
86
92
  }
87
93
  match self.local_activity_slot_options {
@@ -97,6 +103,9 @@ impl TunerHolderOptions {
97
103
  .local_activity_slot_supplier(),
98
104
  );
99
105
  }
106
+ Some(SlotSupplierOptions::Custom(ss)) => {
107
+ builder.local_activity_slot_supplier(ss);
108
+ }
100
109
  None => {}
101
110
  }
102
111
  Ok(builder.build())
@@ -104,8 +113,8 @@ impl TunerHolderOptions {
104
113
  }
105
114
 
106
115
  /// Options for known kinds of slot suppliers
107
- #[derive(Clone, Debug)]
108
- pub enum SlotSupplierOptions {
116
+ #[derive(Clone, derive_more::Debug)]
117
+ pub enum SlotSupplierOptions<SK: SlotKind> {
109
118
  /// Options for a [FixedSizeSlotSupplier]
110
119
  FixedSize {
111
120
  /// The number of slots the fixed supplier will have
@@ -113,6 +122,9 @@ pub enum SlotSupplierOptions {
113
122
  },
114
123
  /// Options for a [ResourceBasedSlots]
115
124
  ResourceBased(ResourceSlotOptions),
125
+ /// A user-implemented slot supplier
126
+ #[debug("Custom")]
127
+ Custom(Arc<dyn SlotSupplier<SlotKind = SK> + Send + Sync>),
116
128
  }
117
129
 
118
130
  impl TunerHolderOptionsBuilder {
@@ -5,9 +5,8 @@ use crate::{
5
5
  workflow::{CacheMissFetchReq, PermittedWFT, PreparedWFT},
6
6
  },
7
7
  };
8
- use futures::{future::BoxFuture, FutureExt, Stream, TryFutureExt};
8
+ use futures_util::{future::BoxFuture, FutureExt, Stream, TryFutureExt};
9
9
  use itertools::Itertools;
10
- use once_cell::sync::Lazy;
11
10
  use std::{
12
11
  collections::VecDeque,
13
12
  fmt::Debug,
@@ -15,18 +14,21 @@ use std::{
15
14
  mem,
16
15
  mem::transmute,
17
16
  pin::Pin,
18
- sync::Arc,
17
+ sync::{Arc, LazyLock},
19
18
  task::{Context, Poll},
20
19
  };
21
20
  use temporal_sdk_core_protos::temporal::api::{
22
21
  enums::v1::EventType,
23
- history::v1::{history_event, History, HistoryEvent, WorkflowTaskCompletedEventAttributes},
22
+ history::v1::{
23
+ history_event, history_event::Attributes, History, HistoryEvent,
24
+ WorkflowTaskCompletedEventAttributes,
25
+ },
24
26
  };
25
27
  use tracing::Instrument;
26
28
 
27
- static EMPTY_FETCH_ERR: Lazy<tonic::Status> =
28
- Lazy::new(|| tonic::Status::unknown("Fetched empty history page"));
29
- static EMPTY_TASK_ERR: Lazy<tonic::Status> = Lazy::new(|| {
29
+ static EMPTY_FETCH_ERR: LazyLock<tonic::Status> =
30
+ LazyLock::new(|| tonic::Status::unknown("Fetched empty history page"));
31
+ static EMPTY_TASK_ERR: LazyLock<tonic::Status> = LazyLock::new(|| {
30
32
  tonic::Status::unknown("Received an empty workflow task with no queries or history")
31
33
  });
32
34
 
@@ -166,7 +168,7 @@ impl HistoryPaginator {
166
168
  }
167
169
 
168
170
  pub(super) async fn from_fetchreq(
169
- mut req: CacheMissFetchReq,
171
+ mut req: Box<CacheMissFetchReq>,
170
172
  client: Arc<dyn WorkerClient>,
171
173
  ) -> Result<PermittedWFT, tonic::Status> {
172
174
  let mut paginator = Self {
@@ -697,6 +699,7 @@ fn find_end_index_of_next_wft_seq(
697
699
  }
698
700
  let mut last_index = 0;
699
701
  let mut saw_any_command_event = false;
702
+ let mut wft_started_event_id_to_index = vec![];
700
703
  for (ix, e) in events.iter().enumerate() {
701
704
  last_index = ix;
702
705
 
@@ -714,12 +717,13 @@ fn find_end_index_of_next_wft_seq(
714
717
  }
715
718
 
716
719
  if e.event_type() == EventType::WorkflowTaskStarted {
720
+ wft_started_event_id_to_index.push((e.event_id, ix));
717
721
  if let Some(next_event) = events.get(ix + 1) {
718
- let et = next_event.event_type();
722
+ let next_event_type = next_event.event_type();
719
723
  // If the next event is WFT timeout or fail, or abrupt WF execution end, that
720
724
  // doesn't conclude a WFT sequence.
721
725
  if matches!(
722
- et,
726
+ next_event_type,
723
727
  EventType::WorkflowTaskFailed
724
728
  | EventType::WorkflowTaskTimedOut
725
729
  | EventType::WorkflowExecutionTimedOut
@@ -731,11 +735,38 @@ fn find_end_index_of_next_wft_seq(
731
735
  // If we've never seen an interesting event and the next two events are a completion
732
736
  // followed immediately again by scheduled, then this is a WFT heartbeat and also
733
737
  // doesn't conclude the sequence.
734
- else if et == EventType::WorkflowTaskCompleted {
738
+ else if next_event_type == EventType::WorkflowTaskCompleted {
735
739
  if let Some(next_next_event) = events.get(ix + 2) {
736
740
  if next_next_event.event_type() == EventType::WorkflowTaskScheduled {
737
741
  continue;
738
742
  } else {
743
+ // If we see an update accepted command after WFT completed, we want to
744
+ // conclude the WFT sequence where that update should have been
745
+ // processed. We don't need to check for any other command types,
746
+ // because the only thing that can run before an update validator is a
747
+ // signal handler - but if a signal handler ran then there would have
748
+ // been a previous signal event, and we would've already concluded the
749
+ // previous WFT sequence.
750
+ if let Some(
751
+ Attributes::WorkflowExecutionUpdateAcceptedEventAttributes(
752
+ ref attr,
753
+ ),
754
+ ) = next_next_event.attributes
755
+ {
756
+ // Find index of closest WFT started before sequencing id
757
+ if let Some(ret_ix) = wft_started_event_id_to_index
758
+ .iter()
759
+ .rev()
760
+ .find_map(|(eid, ix)| {
761
+ if *eid < attr.accepted_request_sequencing_event_id {
762
+ return Some(*ix);
763
+ }
764
+ None
765
+ })
766
+ {
767
+ return NextWFTSeqEndIndex::Complete(ret_ix);
768
+ }
769
+ }
739
770
  return NextWFTSeqEndIndex::Complete(ix);
740
771
  }
741
772
  } else if !has_last_wft && !saw_any_command_event {
@@ -766,8 +797,7 @@ mod tests {
766
797
  test_help::{canned_histories, hist_to_poll_resp, mock_sdk_cfg, MockPollCfg, ResponseType},
767
798
  worker::client::mocks::mock_workflow_client,
768
799
  };
769
- use futures::StreamExt;
770
- use futures_util::TryStreamExt;
800
+ use futures_util::{StreamExt, TryStreamExt};
771
801
  use std::sync::atomic::{AtomicUsize, Ordering};
772
802
  use temporal_client::WorkflowOptions;
773
803
  use temporal_sdk::WfContext;
@@ -4,7 +4,7 @@
4
4
  //! never ever be removed from behind `#[cfg(test)]` compilation.
5
5
 
6
6
  use dashmap::{mapref::entry::Entry, DashMap, DashSet};
7
- use once_cell::sync::Lazy;
7
+ use std::sync::LazyLock;
8
8
  use std::{
9
9
  path::PathBuf,
10
10
  sync::{
@@ -16,11 +16,11 @@ use std::{
16
16
  };
17
17
 
18
18
  // During test we want to know about which transitions we've covered in state machines
19
- static COVERED_TRANSITIONS: Lazy<DashMap<String, DashSet<CoveredTransition>>> =
20
- Lazy::new(DashMap::new);
21
- static COVERAGE_SENDER: Lazy<SyncSender<(String, CoveredTransition)>> =
22
- Lazy::new(spawn_save_coverage_at_end);
23
- static THREAD_HANDLE: Lazy<Mutex<Option<JoinHandle<()>>>> = Lazy::new(|| Mutex::new(None));
19
+ static COVERED_TRANSITIONS: LazyLock<DashMap<String, DashSet<CoveredTransition>>> =
20
+ LazyLock::new(DashMap::new);
21
+ static COVERAGE_SENDER: LazyLock<SyncSender<(String, CoveredTransition)>> =
22
+ LazyLock::new(spawn_save_coverage_at_end);
23
+ static THREAD_HANDLE: LazyLock<Mutex<Option<JoinHandle<()>>>> = LazyLock::new(|| Mutex::new(None));
24
24
 
25
25
  #[derive(Eq, PartialEq, Hash, Debug)]
26
26
  struct CoveredTransition {
@@ -635,8 +635,8 @@ impl WorkflowMachines {
635
635
  event.attributes,
636
636
  Some(history_event::Attributes::WorkflowExecutionUpdateAdmittedEventAttributes(_)),
637
637
  ) {
638
- // The server has sent a durable update admitted event: create the message that would have been sent
639
- // for a non-durable update request message.
638
+ // The server has sent a durable update admitted event: create the message that
639
+ // would have been sent for a non-durable update request message.
640
640
  let msg = IncomingProtocolMessage::try_from(&event).context(
641
641
  "Failed to create protocol message from WorkflowExecutionUpdateAdmittedEvent",
642
642
  )?;
@@ -745,9 +745,10 @@ impl WorkflowMachines {
745
745
  history_event::Attributes::WorkflowExecutionUpdateAcceptedEventAttributes(ref atts),
746
746
  ) = e.attributes
747
747
  {
748
- // We've encountered an UpdateAccepted event during replay: pretend that we received the message we
749
- // would have when receiving an update request under not-replay. If this event was preceded by an
750
- // UpdateAdmitted event, then use the message that we created when we encountered that.
748
+ // We've encountered an UpdateAccepted event during replay: pretend that we received
749
+ // the message we would have when receiving an update request under not-replay. If
750
+ // this event was preceded by an UpdateAdmitted event, then use the message that we
751
+ // created when we encountered that.
751
752
  delayed_actions.push(DelayedAction::ProtocolMessage(
752
753
  update_admitted_event_messages
753
754
  .remove(&atts.protocol_instance_id)
@@ -369,7 +369,7 @@ impl ManagedRun {
369
369
  mut commands: Vec<WFCommand>,
370
370
  used_flags: Vec<u32>,
371
371
  resp_chan: Option<oneshot::Sender<ActivationCompleteResult>>,
372
- ) -> Result<RunUpdateAct, NextPageReq> {
372
+ ) -> Result<RunUpdateAct, Box<NextPageReq>> {
373
373
  let activation_was_only_eviction = self.activation_is_eviction();
374
374
  let (task_token, has_pending_query, start_time) = if let Some(entry) = self.wft.as_ref() {
375
375
  (
@@ -447,10 +447,10 @@ impl ManagedRun {
447
447
  return if let Some(paginator) = self.paginator.take() {
448
448
  debug!("Need to fetch a history page before next WFT can be applied");
449
449
  self.completion_waiting_on_page_fetch = Some(rac);
450
- Err(NextPageReq {
450
+ Err(Box::new(NextPageReq {
451
451
  paginator,
452
452
  span: Span::current(),
453
- })
453
+ }))
454
454
  } else {
455
455
  Ok(self.update_to_acts(Err(RunUpdateErr {
456
456
  source: WFMachinesError::Fatal(
@@ -38,8 +38,7 @@ use crate::{
38
38
  MetricsContext,
39
39
  };
40
40
  use anyhow::anyhow;
41
- use futures::{stream::BoxStream, Stream, StreamExt};
42
- use futures_util::{future::abortable, stream};
41
+ use futures_util::{future::abortable, stream, stream::BoxStream, Stream, StreamExt};
43
42
  use itertools::Itertools;
44
43
  use prost_types::TimestampError;
45
44
  use std::{
@@ -121,7 +120,7 @@ pub(crate) struct Workflows {
121
120
  /// If set, can be used to reserve activity task slots for eager-return of new activity tasks.
122
121
  activity_tasks_handle: Option<ActivitiesFromWFTsHandle>,
123
122
  /// Ensures we stay at or below this worker's maximum concurrent workflow task limit
124
- wft_semaphore: Arc<MeteredPermitDealer<WorkflowSlotKind>>,
123
+ wft_semaphore: MeteredPermitDealer<WorkflowSlotKind>,
125
124
  local_act_mgr: Arc<LocalActivityManager>,
126
125
  ever_polled: AtomicBool,
127
126
  }
@@ -149,7 +148,7 @@ impl Workflows {
149
148
  basics: WorkflowBasics,
150
149
  sticky_attrs: Option<StickyExecutionAttributes>,
151
150
  client: Arc<dyn WorkerClient>,
152
- wft_semaphore: Arc<MeteredPermitDealer<WorkflowSlotKind>>,
151
+ wft_semaphore: MeteredPermitDealer<WorkflowSlotKind>,
153
152
  wft_stream: impl Stream<Item = WFTStreamIn> + Send + 'static,
154
153
  local_activity_request_sink: impl LocalActivityRequestSink,
155
154
  local_act_mgr: Arc<LocalActivityManager>,
@@ -195,8 +194,8 @@ impl Workflows {
195
194
  local_activity_request_sink,
196
195
  );
197
196
 
198
- // However, we want to avoid plowing ahead until we've been asked to poll at least
199
- // once. This supports activity-only workers.
197
+ // However, we want to avoid plowing ahead until we've been asked to poll at
198
+ // least once. This supports activity-only workers.
200
199
  let do_poll = tokio::select! {
201
200
  sp = start_polling_rx => {
202
201
  sp.is_ok()
@@ -560,7 +559,7 @@ impl Workflows {
560
559
 
561
560
  /// Must be called after every activation completion has finished
562
561
  fn post_activation(&self, msg: PostActivationMsg) {
563
- self.send_local(msg);
562
+ self.send_local(Box::new(msg));
564
563
  }
565
564
 
566
565
  /// Handle server errors from either completing or failing a workflow task. Un-handleable errors
@@ -715,6 +714,13 @@ impl Workflows {
715
714
  }
716
715
  }
717
716
  }
717
+
718
+ pub(super) fn get_sticky_queue_name(&self) -> Option<String> {
719
+ self.sticky_attrs
720
+ .as_ref()
721
+ .and_then(|sa| sa.worker_task_queue.as_ref())
722
+ .map(|tq| tq.name.clone())
723
+ }
718
724
  }
719
725
 
720
726
  /// Returned when a cache miss happens and we need to fetch history from the beginning to
@@ -9,11 +9,10 @@ use crate::{
9
9
  },
10
10
  },
11
11
  };
12
- use futures::Stream;
13
- use futures_util::{stream, stream::PollNext, FutureExt, StreamExt};
12
+ use futures_util::{stream, stream::PollNext, FutureExt, Stream, StreamExt};
14
13
  use std::{future, sync::Arc};
15
- use temporal_sdk_core_api::worker::{WorkflowSlotInfo, WorkflowSlotKind};
16
- use temporal_sdk_core_protos::TaskToken;
14
+ use temporal_sdk_core_api::worker::WorkflowSlotKind;
15
+ use temporal_sdk_core_protos::{coresdk::WorkflowSlotInfo, TaskToken};
17
16
  use tracing::Span;
18
17
 
19
18
  /// Transforms incoming validated WFTs and history fetching requests into [PermittedWFT]s ready
@@ -50,8 +49,8 @@ pub(crate) type WFTStreamIn = Result<
50
49
  >;
51
50
  #[derive(derive_more::From, Debug)]
52
51
  pub(super) enum HistoryFetchReq {
53
- Full(CacheMissFetchReq, Arc<HistfetchRC>),
54
- NextPage(NextPageReq, Arc<HistfetchRC>),
52
+ Full(Box<CacheMissFetchReq>, Arc<HistfetchRC>),
53
+ NextPage(Box<NextPageReq>, Arc<HistfetchRC>),
55
54
  }
56
55
  /// Used inside of `Arc`s to ensure we don't shutdown while there are outstanding fetches.
57
56
  #[derive(Debug)]
@@ -76,7 +75,8 @@ impl WFTExtractor {
76
75
  Ok(match HistoryPaginator::from_poll(wft, client).await {
77
76
  Ok((pag, prep)) => WFTExtractorOutput::NewWFT(PermittedWFT {
78
77
  permit: permit.into_used(WorkflowSlotInfo {
79
- workflow_type: prep.workflow_type.as_str(),
78
+ workflow_type: prep.workflow_type.clone(),
79
+ is_sticky: prep.is_incremental(),
80
80
  }),
81
81
  work: prep,
82
82
  paginator: pag,