@temporalio/core-bridge 1.11.2 → 1.11.4

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 (119) hide show
  1. package/Cargo.lock +396 -489
  2. package/Cargo.toml +3 -2
  3. package/lib/errors.d.ts +2 -0
  4. package/lib/errors.js +7 -3
  5. package/lib/errors.js.map +1 -1
  6. package/lib/index.d.ts +8 -2
  7. package/lib/index.js.map +1 -1
  8. package/lib/worker-tuner.d.ts +111 -1
  9. package/package.json +3 -3
  10. package/releases/aarch64-apple-darwin/index.node +0 -0
  11. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  12. package/releases/x86_64-apple-darwin/index.node +0 -0
  13. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  14. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  15. package/sdk-core/.github/workflows/per-pr.yml +3 -3
  16. package/sdk-core/Cargo.toml +0 -1
  17. package/sdk-core/client/Cargo.toml +1 -2
  18. package/sdk-core/client/src/lib.rs +21 -13
  19. package/sdk-core/client/src/metrics.rs +1 -1
  20. package/sdk-core/client/src/raw.rs +46 -1
  21. package/sdk-core/core/Cargo.toml +7 -7
  22. package/sdk-core/core/benches/workflow_replay.rs +1 -1
  23. package/sdk-core/core/src/abstractions/take_cell.rs +1 -1
  24. package/sdk-core/core/src/abstractions.rs +98 -10
  25. package/sdk-core/core/src/core_tests/activity_tasks.rs +8 -2
  26. package/sdk-core/core/src/core_tests/local_activities.rs +1 -1
  27. package/sdk-core/core/src/core_tests/mod.rs +3 -3
  28. package/sdk-core/core/src/core_tests/updates.rs +104 -9
  29. package/sdk-core/core/src/core_tests/workers.rs +72 -3
  30. package/sdk-core/core/src/core_tests/workflow_tasks.rs +6 -7
  31. package/sdk-core/core/src/debug_client.rs +78 -0
  32. package/sdk-core/core/src/ephemeral_server/mod.rs +15 -5
  33. package/sdk-core/core/src/lib.rs +30 -4
  34. package/sdk-core/core/src/pollers/mod.rs +1 -1
  35. package/sdk-core/core/src/pollers/poll_buffer.rs +7 -7
  36. package/sdk-core/core/src/replay/mod.rs +4 -4
  37. package/sdk-core/core/src/telemetry/log_export.rs +2 -2
  38. package/sdk-core/core/src/telemetry/metrics.rs +69 -1
  39. package/sdk-core/core/src/telemetry/otel.rs +2 -2
  40. package/sdk-core/core/src/test_help/mod.rs +3 -3
  41. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +3 -3
  42. package/sdk-core/core/src/worker/activities/activity_task_poller_stream.rs +1 -1
  43. package/sdk-core/core/src/worker/activities/local_activities.rs +68 -24
  44. package/sdk-core/core/src/worker/activities.rs +26 -15
  45. package/sdk-core/core/src/worker/client/mocks.rs +10 -4
  46. package/sdk-core/core/src/worker/client.rs +17 -0
  47. package/sdk-core/core/src/worker/mod.rs +71 -13
  48. package/sdk-core/core/src/worker/slot_provider.rs +5 -7
  49. package/sdk-core/core/src/worker/tuner/fixed_size.rs +4 -3
  50. package/sdk-core/core/src/worker/tuner/resource_based.rs +171 -32
  51. package/sdk-core/core/src/worker/tuner.rs +18 -6
  52. package/sdk-core/core/src/worker/workflow/history_update.rs +43 -13
  53. package/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +6 -6
  54. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +6 -5
  55. package/sdk-core/core/src/worker/workflow/managed_run.rs +3 -3
  56. package/sdk-core/core/src/worker/workflow/mod.rs +13 -7
  57. package/sdk-core/core/src/worker/workflow/wft_extraction.rs +7 -7
  58. package/sdk-core/core/src/worker/workflow/wft_poller.rs +2 -2
  59. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +11 -11
  60. package/sdk-core/core-api/Cargo.toml +1 -0
  61. package/sdk-core/core-api/src/worker.rs +84 -30
  62. package/sdk-core/sdk/Cargo.toml +1 -2
  63. package/sdk-core/sdk/src/lib.rs +1 -1
  64. package/sdk-core/sdk/src/workflow_context.rs +9 -8
  65. package/sdk-core/sdk/src/workflow_future.rs +19 -14
  66. package/sdk-core/sdk-core-protos/Cargo.toml +2 -0
  67. package/sdk-core/sdk-core-protos/build.rs +6 -1
  68. package/sdk-core/sdk-core-protos/protos/api_upstream/buf.yaml +1 -0
  69. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +3207 -158
  70. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +2934 -118
  71. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/activity/v1/message.proto +67 -0
  72. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +47 -1
  73. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/event_type.proto +6 -7
  74. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +2 -0
  75. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/reset.proto +5 -3
  76. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +3 -3
  77. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/update.proto +14 -13
  78. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/failure/v1/message.proto +1 -3
  79. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/history/v1/message.proto +22 -0
  80. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/nexus/v1/message.proto +13 -2
  81. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +26 -6
  82. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/schedule/v1/message.proto +5 -0
  83. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/workflow_metadata.proto +6 -0
  84. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +46 -12
  85. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/update/v1/message.proto +18 -19
  86. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflow/v1/message.proto +27 -0
  87. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +192 -19
  88. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +279 -12
  89. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/activity_result/activity_result.proto +1 -1
  90. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/activity_task/activity_task.proto +1 -1
  91. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +1 -1
  92. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/common/common.proto +1 -1
  93. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/core_interface.proto +17 -1
  94. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/external_data/external_data.proto +1 -1
  95. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +1 -1
  96. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +1 -1
  97. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +1 -1
  98. package/sdk-core/sdk-core-protos/src/lib.rs +30 -6
  99. package/sdk-core/test-utils/Cargo.toml +1 -2
  100. package/sdk-core/test-utils/src/lib.rs +2 -2
  101. package/sdk-core/tests/heavy_tests.rs +1 -1
  102. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +2 -2
  103. package/sdk-core/tests/integ_tests/metrics_tests.rs +144 -7
  104. package/sdk-core/tests/integ_tests/queries_tests.rs +1 -1
  105. package/sdk-core/tests/integ_tests/update_tests.rs +109 -5
  106. package/sdk-core/tests/integ_tests/worker_tests.rs +44 -8
  107. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +1 -1
  108. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -1
  109. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +1 -1
  110. package/sdk-core/tests/integ_tests/workflow_tests.rs +3 -2
  111. package/src/conversions/slot_supplier_bridge.rs +287 -0
  112. package/src/conversions.rs +23 -15
  113. package/src/helpers.rs +35 -1
  114. package/src/runtime.rs +7 -3
  115. package/src/worker.rs +1 -1
  116. package/ts/errors.ts +9 -2
  117. package/ts/index.ts +19 -4
  118. package/ts/worker-tuner.ts +123 -1
  119. package/sdk-core/sdk-core-protos/protos/api_upstream/.gitmodules +0 -3
@@ -11,7 +11,8 @@ use std::{
11
11
  },
12
12
  };
13
13
  use temporal_sdk_core_api::worker::{
14
- SlotKind, SlotReservationContext, SlotSupplier, SlotSupplierPermit,
14
+ SlotKind, SlotMarkUsedContext, SlotReleaseContext, SlotReservationContext, SlotSupplier,
15
+ SlotSupplierPermit, WorkflowSlotKind,
15
16
  };
16
17
  use tokio::sync::watch;
17
18
  use tokio_util::sync::CancellationToken;
@@ -35,6 +36,18 @@ pub(crate) struct MeteredPermitDealer<SK: SlotKind> {
35
36
  /// there will need to be some associated refactoring.
36
37
  max_permits: Option<usize>,
37
38
  metrics_ctx: MetricsContext,
39
+ /// Only applies to permit dealers for workflow tasks. True if this permit dealer is associated
40
+ /// with a sticky queue poller.
41
+ is_sticky_poller: bool,
42
+ context_data: Arc<PermitDealerContextData>,
43
+ }
44
+
45
+ #[derive(Clone, Debug)]
46
+ #[cfg_attr(test, derive(Default))]
47
+ pub(crate) struct PermitDealerContextData {
48
+ pub(crate) task_queue: String,
49
+ pub(crate) worker_identity: String,
50
+ pub(crate) worker_build_id: String,
38
51
  }
39
52
 
40
53
  impl<SK> MeteredPermitDealer<SK>
@@ -45,6 +58,7 @@ where
45
58
  supplier: Arc<dyn SlotSupplier<SlotKind = SK> + Send + Sync>,
46
59
  metrics_ctx: MetricsContext,
47
60
  max_permits: Option<usize>,
61
+ context_data: Arc<PermitDealerContextData>,
48
62
  ) -> Self {
49
63
  Self {
50
64
  supplier,
@@ -52,6 +66,8 @@ where
52
66
  extant_permits: watch::channel(0),
53
67
  metrics_ctx,
54
68
  max_permits,
69
+ is_sticky_poller: false,
70
+ context_data,
55
71
  }
56
72
  }
57
73
 
@@ -91,6 +107,10 @@ where
91
107
  }
92
108
  }
93
109
 
110
+ pub(crate) fn get_extant_count_rcv(&self) -> watch::Receiver<usize> {
111
+ self.extant_permits.1.clone()
112
+ }
113
+
94
114
  fn build_owned(&self, res: SlotSupplierPermit) -> OwnedMeteredSemPermit<SK> {
95
115
  self.unused_claimants.fetch_add(1, Ordering::Release);
96
116
  self.extant_permits.0.send_modify(|ep| *ep += 1);
@@ -117,14 +137,17 @@ where
117
137
  mrc(false);
118
138
 
119
139
  OwnedMeteredSemPermit {
120
- _inner: res,
121
140
  unused_claimants: Some(self.unused_claimants.clone()),
141
+ release_ctx: ReleaseCtx {
142
+ permit: res,
143
+ stored_info: None,
144
+ },
122
145
  use_fn: Box::new(move |info| {
123
146
  supp_c.mark_slot_used(info);
124
147
  metric_rec(false)
125
148
  }),
126
- release_fn: Box::new(move || {
127
- supp_c_c.release_slot();
149
+ release_fn: Box::new(move |info| {
150
+ supp_c_c.release_slot(info);
128
151
  ep_tx_c.send_modify(|ep| *ep -= 1);
129
152
  mrc(true)
130
153
  }),
@@ -132,10 +155,67 @@ where
132
155
  }
133
156
  }
134
157
 
158
+ impl MeteredPermitDealer<WorkflowSlotKind> {
159
+ pub(crate) fn into_sticky(mut self) -> Self {
160
+ self.is_sticky_poller = true;
161
+ self
162
+ }
163
+ }
164
+
135
165
  impl<SK: SlotKind> SlotReservationContext for MeteredPermitDealer<SK> {
166
+ fn task_queue(&self) -> &str {
167
+ &self.context_data.task_queue
168
+ }
169
+
170
+ fn worker_identity(&self) -> &str {
171
+ &self.context_data.worker_identity
172
+ }
173
+
174
+ fn worker_build_id(&self) -> &str {
175
+ &self.context_data.worker_build_id
176
+ }
177
+
136
178
  fn num_issued_slots(&self) -> usize {
137
179
  *self.extant_permits.1.borrow()
138
180
  }
181
+
182
+ fn is_sticky(&self) -> bool {
183
+ self.is_sticky_poller
184
+ }
185
+ }
186
+
187
+ struct UseCtx<'a, SK: SlotKind> {
188
+ stored_info: &'a SK::Info,
189
+ permit: &'a SlotSupplierPermit,
190
+ }
191
+
192
+ impl<'a, SK: SlotKind> SlotMarkUsedContext for UseCtx<'a, SK> {
193
+ type SlotKind = SK;
194
+
195
+ fn permit(&self) -> &SlotSupplierPermit {
196
+ self.permit
197
+ }
198
+
199
+ fn info(&self) -> &<Self::SlotKind as SlotKind>::Info {
200
+ self.stored_info
201
+ }
202
+ }
203
+
204
+ struct ReleaseCtx<SK: SlotKind> {
205
+ permit: SlotSupplierPermit,
206
+ stored_info: Option<SK::Info>,
207
+ }
208
+
209
+ impl<SK: SlotKind> SlotReleaseContext for ReleaseCtx<SK> {
210
+ type SlotKind = SK;
211
+
212
+ fn permit(&self) -> &SlotSupplierPermit {
213
+ &self.permit
214
+ }
215
+
216
+ fn info(&self) -> Option<&<Self::SlotKind as SlotKind>::Info> {
217
+ self.stored_info.as_ref()
218
+ }
139
219
  }
140
220
 
141
221
  /// A version of [MeteredPermitDealer] that can be closed and supports waiting for close to complete.
@@ -240,20 +320,22 @@ impl<SK: SlotKind> Drop for TrackedOwnedMeteredSemPermit<SK> {
240
320
  /// Wraps an [SlotSupplierPermit] to update metrics & when it's dropped
241
321
  #[clippy::has_significant_drop]
242
322
  pub(crate) struct OwnedMeteredSemPermit<SK: SlotKind> {
243
- _inner: SlotSupplierPermit,
244
323
  /// See [MeteredPermitDealer::unused_claimants]. If present when dropping, used to decrement the
245
324
  /// count.
246
325
  unused_claimants: Option<Arc<AtomicUsize>>,
326
+ /// The actual [SlotSupplierPermit] is stored in here
327
+ release_ctx: ReleaseCtx<SK>,
328
+ #[allow(clippy::type_complexity)] // not really tho, bud
329
+ use_fn: Box<dyn Fn(&UseCtx<SK>) + Send + Sync>,
247
330
  #[allow(clippy::type_complexity)] // not really tho, bud
248
- use_fn: Box<dyn Fn(SK::Info<'_>) + Send + Sync>,
249
- release_fn: Box<dyn Fn() + Send + Sync>,
331
+ release_fn: Box<dyn Fn(&ReleaseCtx<SK>) + Send + Sync>,
250
332
  }
251
333
  impl<SK: SlotKind> Drop for OwnedMeteredSemPermit<SK> {
252
334
  fn drop(&mut self) {
253
335
  if let Some(uc) = self.unused_claimants.take() {
254
336
  uc.fetch_sub(1, Ordering::Release);
255
337
  }
256
- (self.release_fn)()
338
+ (self.release_fn)(&self.release_ctx);
257
339
  }
258
340
  }
259
341
  impl<SK: SlotKind> Debug for OwnedMeteredSemPermit<SK> {
@@ -264,11 +346,16 @@ impl<SK: SlotKind> Debug for OwnedMeteredSemPermit<SK> {
264
346
  impl<SK: SlotKind> OwnedMeteredSemPermit<SK> {
265
347
  /// Should be called once this permit is actually being "used" for the work it was meant to
266
348
  /// permit.
267
- pub(crate) fn into_used(mut self, info: SK::Info<'_>) -> UsedMeteredSemPermit<SK> {
349
+ pub(crate) fn into_used(mut self, info: SK::Info) -> UsedMeteredSemPermit<SK> {
268
350
  if let Some(uc) = self.unused_claimants.take() {
269
351
  uc.fetch_sub(1, Ordering::Release);
270
352
  }
271
- (self.use_fn)(info);
353
+ let ctx = UseCtx {
354
+ stored_info: &info,
355
+ permit: &self.release_ctx.permit,
356
+ };
357
+ (self.use_fn)(&ctx);
358
+ self.release_ctx.stored_info = Some(info);
272
359
  UsedMeteredSemPermit(self)
273
360
  }
274
361
  }
@@ -298,6 +385,7 @@ pub(crate) mod tests {
298
385
  Arc::new(FixedSizeSlotSupplier::new(size)),
299
386
  MetricsContext::no_op(),
300
387
  None,
388
+ Arc::new(Default::default()),
301
389
  )
302
390
  }
303
391
 
@@ -9,7 +9,7 @@ use crate::{
9
9
  worker::client::mocks::{mock_manual_workflow_client, mock_workflow_client},
10
10
  ActivityHeartbeat, Worker,
11
11
  };
12
- use futures::FutureExt;
12
+ use futures_util::FutureExt;
13
13
  use itertools::Itertools;
14
14
  use std::{
15
15
  cell::RefCell,
@@ -145,6 +145,7 @@ async fn heartbeats_report_cancels_only_once() {
145
145
  .returning(|_, _| {
146
146
  Ok(RecordActivityTaskHeartbeatResponse {
147
147
  cancel_requested: true,
148
+ activity_paused: false,
148
149
  })
149
150
  });
150
151
  mock_client
@@ -270,6 +271,7 @@ async fn activity_cancel_interrupts_poll() {
270
271
  async {
271
272
  Ok(RecordActivityTaskHeartbeatResponse {
272
273
  cancel_requested: true,
274
+ activity_paused: false,
273
275
  })
274
276
  }
275
277
  .boxed()
@@ -390,10 +392,12 @@ async fn many_concurrent_heartbeat_cancels() {
390
392
  if calls < 5 {
391
393
  Ok(RecordActivityTaskHeartbeatResponse {
392
394
  cancel_requested: false,
395
+ activity_paused: false,
393
396
  })
394
397
  } else {
395
398
  Ok(RecordActivityTaskHeartbeatResponse {
396
399
  cancel_requested: true,
400
+ activity_paused: false,
397
401
  })
398
402
  }
399
403
  }
@@ -512,6 +516,7 @@ async fn can_heartbeat_acts_during_shutdown() {
512
516
  .returning(|_, _| {
513
517
  Ok(RecordActivityTaskHeartbeatResponse {
514
518
  cancel_requested: false,
519
+ activity_paused: false,
515
520
  })
516
521
  });
517
522
  mock_client
@@ -565,6 +570,7 @@ async fn complete_act_with_fail_flushes_heartbeat() {
565
570
  *lsp.borrow_mut() = payload;
566
571
  Ok(RecordActivityTaskHeartbeatResponse {
567
572
  cancel_requested: false,
573
+ activity_paused: false,
568
574
  })
569
575
  });
570
576
  mock_client
@@ -674,7 +680,7 @@ async fn no_eager_activities_requested_when_worker_options_disable_remote_activi
674
680
  let mut mock_poller = mock_manual_poller();
675
681
  mock_poller
676
682
  .expect_poll()
677
- .returning(|| futures::future::pending().boxed());
683
+ .returning(|| futures_util::future::pending().boxed());
678
684
  mock.set_act_poller(Box::new(mock_poller));
679
685
  mock.worker_cfg(|wc| {
680
686
  wc.max_cached_workflows = 2;
@@ -9,7 +9,7 @@ use crate::{
9
9
  };
10
10
  use anyhow::anyhow;
11
11
  use crossbeam_queue::SegQueue;
12
- use futures::{future::join_all, FutureExt};
12
+ use futures_util::{future::join_all, FutureExt};
13
13
  use std::{
14
14
  collections::HashMap,
15
15
  ops::Sub,
@@ -15,8 +15,8 @@ use crate::{
15
15
  worker::client::mocks::{mock_manual_workflow_client, mock_workflow_client},
16
16
  Worker,
17
17
  };
18
- use futures::FutureExt;
19
- use once_cell::sync::Lazy;
18
+ use futures_util::FutureExt;
19
+ use std::sync::LazyLock;
20
20
  use std::time::Duration;
21
21
  use temporal_sdk_core_api::Worker as WorkerTrait;
22
22
  use temporal_sdk_core_protos::coresdk::workflow_completion::WorkflowActivationCompletion;
@@ -46,7 +46,7 @@ async fn after_shutdown_server_is_not_polled() {
46
46
  }
47
47
 
48
48
  // Better than cloning a billion arcs...
49
- static BARR: Lazy<Barrier> = Lazy::new(|| Barrier::new(3));
49
+ static BARR: LazyLock<Barrier> = LazyLock::new(|| Barrier::new(3));
50
50
 
51
51
  #[tokio::test]
52
52
  async fn shutdown_interrupts_both_polls() {
@@ -1,4 +1,5 @@
1
1
  use crate::{
2
+ prost_dur,
2
3
  test_help::{
3
4
  build_mock_pollers, hist_to_poll_resp, mock_worker, MockPollCfg, PollWFTRespExt,
4
5
  ResponseType,
@@ -10,7 +11,8 @@ use temporal_sdk_core_protos::{
10
11
  coresdk::{
11
12
  workflow_activation::{workflow_activation_job, WorkflowActivationJob},
12
13
  workflow_commands::{
13
- update_response::Response, CompleteWorkflowExecution, ScheduleActivity, UpdateResponse,
14
+ update_response::Response, CompleteWorkflowExecution, ScheduleActivity, StartTimer,
15
+ UpdateResponse,
14
16
  },
15
17
  workflow_completion::WorkflowActivationCompletion,
16
18
  },
@@ -41,17 +43,25 @@ async fn replay_with_empty_first_task() {
41
43
  mock.worker_cfg(|wc| wc.max_cached_workflows = 1);
42
44
  let core = mock_worker(mock);
43
45
 
46
+ // In this task imagine we are waiting on the first update being sent, hence no commands come
47
+ // out, and on replay the first activation should only be init.
44
48
  let task = core.poll_workflow_activation().await.unwrap();
45
49
  assert_matches!(
46
50
  task.jobs.as_slice(),
47
- [
48
- WorkflowActivationJob {
49
- variant: Some(workflow_activation_job::Variant::InitializeWorkflow(_)),
50
- },
51
- WorkflowActivationJob {
52
- variant: Some(workflow_activation_job::Variant::DoUpdate(_)),
53
- },
54
- ]
51
+ [WorkflowActivationJob {
52
+ variant: Some(workflow_activation_job::Variant::InitializeWorkflow(_)),
53
+ },]
54
+ );
55
+ core.complete_workflow_activation(WorkflowActivationCompletion::empty(task.run_id))
56
+ .await
57
+ .unwrap();
58
+
59
+ let task = core.poll_workflow_activation().await.unwrap();
60
+ assert_matches!(
61
+ task.jobs.as_slice(),
62
+ [WorkflowActivationJob {
63
+ variant: Some(workflow_activation_job::Variant::DoUpdate(_)),
64
+ },]
55
65
  );
56
66
  core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
57
67
  task.run_id,
@@ -229,3 +239,88 @@ async fn speculative_wft_with_command_event() {
229
239
  );
230
240
  core.complete_execution(&task.run_id).await;
231
241
  }
242
+
243
+ #[tokio::test]
244
+ async fn replay_with_signal_and_update_same_task() {
245
+ // Imitating a signal creating a command before update validator runs
246
+ let mut t = TestHistoryBuilder::default();
247
+ t.add_by_type(EventType::WorkflowExecutionStarted);
248
+ t.add_full_wf_task();
249
+ t.add_we_signaled("hi", vec![]);
250
+ t.add_full_wf_task();
251
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
252
+ let accept_id = t.add_update_accepted("upd1", "update");
253
+ t.add_timer_fired(timer_started_event_id, "1".to_string());
254
+ t.add_full_wf_task();
255
+ t.add_update_completed(accept_id);
256
+ t.add_workflow_execution_completed();
257
+
258
+ let mock = MockPollCfg::from_resps(t, [ResponseType::AllHistory]);
259
+ let mut mock = build_mock_pollers(mock);
260
+ mock.worker_cfg(|wc| wc.max_cached_workflows = 1);
261
+ let core = mock_worker(mock);
262
+
263
+ // In this task imagine we are waiting on the first update being sent, hence no commands come
264
+ // out, and on replay the first activation should only be init.
265
+ let task = core.poll_workflow_activation().await.unwrap();
266
+ assert_matches!(
267
+ task.jobs.as_slice(),
268
+ [WorkflowActivationJob {
269
+ variant: Some(workflow_activation_job::Variant::InitializeWorkflow(_)),
270
+ },]
271
+ );
272
+ core.complete_workflow_activation(WorkflowActivationCompletion::empty(task.run_id))
273
+ .await
274
+ .unwrap();
275
+
276
+ let task = core.poll_workflow_activation().await.unwrap();
277
+ assert_matches!(
278
+ task.jobs.as_slice(),
279
+ [
280
+ WorkflowActivationJob {
281
+ variant: Some(workflow_activation_job::Variant::SignalWorkflow(_)),
282
+ },
283
+ WorkflowActivationJob {
284
+ variant: Some(workflow_activation_job::Variant::DoUpdate(_)),
285
+ }
286
+ ]
287
+ );
288
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
289
+ task.run_id,
290
+ vec![
291
+ StartTimer {
292
+ seq: 1,
293
+ start_to_fire_timeout: Some(prost_dur!(from_secs(1))),
294
+ }
295
+ .into(),
296
+ UpdateResponse {
297
+ protocol_instance_id: "upd1".to_string(),
298
+ response: Some(Response::Accepted(())),
299
+ }
300
+ .into(),
301
+ ],
302
+ ))
303
+ .await
304
+ .unwrap();
305
+
306
+ let task = core.poll_workflow_activation().await.unwrap();
307
+ assert_matches!(
308
+ task.jobs.as_slice(),
309
+ [WorkflowActivationJob {
310
+ variant: Some(workflow_activation_job::Variant::FireTimer(_)),
311
+ },]
312
+ );
313
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
314
+ task.run_id,
315
+ vec![
316
+ UpdateResponse {
317
+ protocol_instance_id: "upd1".to_string(),
318
+ response: Some(Response::Completed(Payload::default())),
319
+ }
320
+ .into(),
321
+ CompleteWorkflowExecution { result: None }.into(),
322
+ ],
323
+ ))
324
+ .await
325
+ .unwrap();
326
+ }
@@ -4,8 +4,13 @@ use crate::{
4
4
  build_fake_worker, build_mock_pollers, canned_histories, mock_worker, test_worker_cfg,
5
5
  MockPollCfg, MockWorkerInputs, MocksHolder, ResponseType, WorkerExt,
6
6
  },
7
- worker,
8
- worker::client::mocks::mock_workflow_client,
7
+ worker::{
8
+ self,
9
+ client::{
10
+ mocks::{mock_workflow_client, DEFAULT_TEST_CAPABILITIES, DEFAULT_WORKERS_REGISTRY},
11
+ MockWorkerClient,
12
+ },
13
+ },
9
14
  PollActivityError, PollWfError,
10
15
  };
11
16
  use futures_util::{stream, stream::StreamExt};
@@ -18,7 +23,7 @@ use temporal_sdk_core_protos::{
18
23
  workflow_completion::WorkflowActivationCompletion,
19
24
  },
20
25
  temporal::api::workflowservice::v1::{
21
- PollWorkflowTaskQueueResponse, RespondWorkflowTaskCompletedResponse,
26
+ PollWorkflowTaskQueueResponse, RespondWorkflowTaskCompletedResponse, ShutdownWorkerResponse,
22
27
  },
23
28
  };
24
29
  use temporal_sdk_core_test_utils::start_timer_cmd;
@@ -295,3 +300,67 @@ async fn worker_can_shutdown_after_never_polling_ok(#[values(true, false)] poll_
295
300
  break;
296
301
  }
297
302
  }
303
+
304
+ #[rstest::rstest]
305
+ #[case::ok(true, true)]
306
+ #[case::best_effort(true, false)]
307
+ #[case::not_sticky(false, true)]
308
+ #[tokio::test]
309
+ async fn worker_shutdown_api(#[case] use_cache: bool, #[case] api_success: bool) {
310
+ // Manually need to create MockWorkerClient because we want to specify
311
+ // the expected number of calls for shutdown_worker.
312
+ // This will no longer be needed if
313
+ // https://github.com/asomers/mockall/issues/283 is implemented.
314
+ let mut mock = MockWorkerClient::new();
315
+ mock.expect_capabilities()
316
+ .returning(|| Some(*DEFAULT_TEST_CAPABILITIES));
317
+ mock.expect_workers()
318
+ .returning(|| DEFAULT_WORKERS_REGISTRY.clone());
319
+ mock.expect_is_mock().returning(|| true);
320
+ if use_cache {
321
+ if api_success {
322
+ mock.expect_shutdown_worker()
323
+ .times(1)
324
+ .returning(|_| Ok(ShutdownWorkerResponse {}));
325
+ } else {
326
+ // worker.shutdown() should succeed even if shutdown_worker fails
327
+ mock.expect_shutdown_worker()
328
+ .times(1)
329
+ .returning(|_| Err(tonic::Status::unavailable("fake shutdown error")));
330
+ }
331
+ } else {
332
+ mock.expect_shutdown_worker().times(0);
333
+ }
334
+
335
+ let t = canned_histories::single_timer("1");
336
+ let mut mh =
337
+ MockPollCfg::from_resp_batches("fakeid", t, [1.into(), ResponseType::AllHistory], mock);
338
+ mh.enforce_correct_number_of_polls = false;
339
+ let mut mock = build_mock_pollers(mh);
340
+ mock.worker_cfg(|w| w.max_cached_workflows = if use_cache { 1 } else { 0 });
341
+ let worker = mock_worker(mock);
342
+
343
+ let res = worker.poll_workflow_activation().await.unwrap();
344
+ assert_eq!(res.jobs.len(), 1);
345
+ let run_id = res.run_id;
346
+
347
+ tokio::join!(worker.shutdown(), async {
348
+ // Need to complete task for shutdown to finish
349
+ worker
350
+ .complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
351
+ run_id.clone(),
352
+ workflow_command::Variant::StartTimer(StartTimer {
353
+ seq: 1,
354
+ start_to_fire_timeout: Some(prost_dur!(from_secs(1))),
355
+ }),
356
+ ))
357
+ .await
358
+ .unwrap();
359
+
360
+ // Shutdown proceeds if the only outstanding activations are evictions
361
+ assert_matches!(
362
+ worker.poll_workflow_activation().await.unwrap_err(),
363
+ PollWfError::ShutDown
364
+ );
365
+ });
366
+ }
@@ -16,7 +16,7 @@ use crate::{
16
16
  },
17
17
  Worker,
18
18
  };
19
- use futures::{stream, FutureExt};
19
+ use futures_util::{stream, FutureExt};
20
20
  use mockall::TimesRange;
21
21
  use rstest::{fixture, rstest};
22
22
  use std::{
@@ -33,7 +33,8 @@ use temporal_sdk::{ActivityOptions, CancellableFuture, WfContext};
33
33
  use temporal_sdk_core_api::{
34
34
  errors::PollWfError,
35
35
  worker::{
36
- SlotKind, SlotReservationContext, SlotSupplier, SlotSupplierPermit, WorkflowSlotKind,
36
+ SlotMarkUsedContext, SlotReleaseContext, SlotReservationContext, SlotSupplier,
37
+ SlotSupplierPermit, WorkflowSlotKind,
37
38
  },
38
39
  Worker as WorkerTrait,
39
40
  };
@@ -2421,9 +2422,7 @@ async fn lang_internal_flag_with_update() {
2421
2422
  let mut t = TestHistoryBuilder::default();
2422
2423
  t.add_by_type(EventType::WorkflowExecutionStarted);
2423
2424
  t.add_full_wf_task();
2424
- t.set_flags_first_wft(&[1, 2], &[]);
2425
- t.add_full_wf_task();
2426
- t.set_flags_last_wft(&[], &[1]);
2425
+ t.set_flags_last_wft(&[1, 2], &[1]);
2427
2426
  let updid = t.add_update_accepted("upd1", "upd");
2428
2427
  t.add_update_completed(updid);
2429
2428
  t.add_workflow_execution_completed();
@@ -3028,8 +3027,8 @@ async fn slot_provider_cant_hand_out_more_permits_than_cache_size() {
3028
3027
  fn try_reserve_slot(&self, _: &dyn SlotReservationContext) -> Option<SlotSupplierPermit> {
3029
3028
  Some(SlotSupplierPermit::default())
3030
3029
  }
3031
- fn mark_slot_used(&self, _: <Self::SlotKind as SlotKind>::Info<'_>) {}
3032
- fn release_slot(&self) {}
3030
+ fn mark_slot_used(&self, _ctx: &dyn SlotMarkUsedContext<SlotKind = Self::SlotKind>) {}
3031
+ fn release_slot(&self, _: &dyn SlotReleaseContext<SlotKind = Self::SlotKind>) {}
3033
3032
  fn available_slots(&self) -> Option<usize> {
3034
3033
  None
3035
3034
  }
@@ -0,0 +1,78 @@
1
+ //! Defines an http client that is used for the VSCode debug plugin and any other associated
2
+ //! machinery.
3
+
4
+ use anyhow::Context;
5
+ use hyper::http::HeaderValue;
6
+ use prost::Message;
7
+ use reqwest::{self, header::HeaderMap};
8
+ use std::time::Duration;
9
+ use temporal_sdk_core_protos::temporal::api::history::v1::History;
10
+ use url::Url;
11
+
12
+ /// A client for interacting with the VSCode debug plugin
13
+ #[derive(Clone)]
14
+ pub struct DebugClient {
15
+ /// URL for the local instance of the debugger server
16
+ debugger_url: Url,
17
+ client: reqwest::Client,
18
+ }
19
+
20
+ #[derive(Clone, serde::Serialize)]
21
+ struct WFTStartedMsg {
22
+ event_id: i64,
23
+ }
24
+
25
+ impl DebugClient {
26
+ /// Create a new instance of a DebugClient with the specified url and client name/version
27
+ /// strings.
28
+ pub fn new(
29
+ url: String,
30
+ client_name: &str,
31
+ client_version: &str,
32
+ ) -> Result<DebugClient, anyhow::Error> {
33
+ let mut client = reqwest::ClientBuilder::new();
34
+ client = client.default_headers({
35
+ let mut hm = HeaderMap::new();
36
+ hm.insert("temporal-client-name", HeaderValue::from_str(client_name)?);
37
+ hm.insert(
38
+ "temporal-client-version",
39
+ HeaderValue::from_str(client_version)?,
40
+ );
41
+ hm
42
+ });
43
+ let client = client.build()?;
44
+ Ok(DebugClient {
45
+ debugger_url: Url::parse(&url).context(
46
+ "debugger url malformed, is the TEMPORAL_DEBUGGER_PLUGIN_URL env var correct?",
47
+ )?,
48
+ client,
49
+ })
50
+ }
51
+
52
+ /// Get the history from the instance of the debug plugin server
53
+ pub async fn get_history(&self) -> Result<History, anyhow::Error> {
54
+ let url = self.debugger_url.join("history")?;
55
+ let resp = self.client.get(url).send().await?;
56
+
57
+ let bytes = resp.bytes().await?;
58
+ Ok(History::decode(bytes)?)
59
+ }
60
+
61
+ /// Post to current-wft-started to tell the debug plugin which event we've most recently made it
62
+ /// to
63
+ pub async fn post_wft_started(
64
+ &self,
65
+ event_id: &i64,
66
+ ) -> Result<reqwest::Response, anyhow::Error> {
67
+ let url = self.debugger_url.join("current-wft-started")?;
68
+ Ok(self
69
+ .client
70
+ .get(url)
71
+ .timeout(Duration::from_secs(5))
72
+ .json(&WFTStartedMsg {
73
+ event_id: *event_id,
74
+ })
75
+ .send()
76
+ .await?)
77
+ }
78
+ }
@@ -3,7 +3,7 @@
3
3
 
4
4
  use anyhow::anyhow;
5
5
  use flate2::read::GzDecoder;
6
- use futures::StreamExt;
6
+ use futures_util::StreamExt;
7
7
  use serde::Deserialize;
8
8
  use std::{
9
9
  fs::OpenOptions,
@@ -37,10 +37,13 @@ pub struct TemporalDevServerConfig {
37
37
  /// Port to use or obtains a free one if none given.
38
38
  #[builder(default)]
39
39
  pub port: Option<u16>,
40
+ /// Port to use for the UI server or obtains a free one if none given.
41
+ #[builder(default)]
42
+ pub ui_port: Option<u16>,
40
43
  /// Sqlite DB filename if persisting or non-persistent if none.
41
44
  #[builder(default)]
42
45
  pub db_filename: Option<String>,
43
- /// Whether to enable the UI.
46
+ /// Whether to enable the UI. If ui_port is set, assumes true.
44
47
  #[builder(default)]
45
48
  pub ui: bool,
46
49
  /// Log format and level
@@ -101,7 +104,15 @@ impl TemporalDevServerConfig {
101
104
  args.push("--filename".to_owned());
102
105
  args.push(db_filename.clone());
103
106
  }
104
- if !self.ui {
107
+ if let Some(ui_port) = self.ui_port {
108
+ args.push("--ui-port".to_owned());
109
+ args.push(ui_port.to_string());
110
+ } else if self.ui {
111
+ // Caps at u16 max which is also max port value
112
+ let port = port.saturating_add(1000);
113
+ args.push("--ui-port".to_owned());
114
+ args.push(port.to_string());
115
+ } else {
105
116
  args.push("--headless".to_owned());
106
117
  }
107
118
  args.extend(self.extra_args.clone());
@@ -490,7 +501,7 @@ async fn lazy_download_exe(
490
501
  info!("Downloading {} to {}", uri, dest.display());
491
502
  download_and_extract(client, uri, file_to_extract, &mut temp_file)
492
503
  .await
493
- .map_err(|err| {
504
+ .inspect_err(|_| {
494
505
  // Failed to download, just remove file
495
506
  if let Err(err) = std::fs::remove_file(temp_dest) {
496
507
  warn!(
@@ -499,7 +510,6 @@ async fn lazy_download_exe(
499
510
  err
500
511
  );
501
512
  }
502
- err
503
513
  })
504
514
  }
505
515
  }?;