@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.
- package/Cargo.lock +396 -489
- package/Cargo.toml +3 -2
- package/lib/errors.d.ts +2 -0
- package/lib/errors.js +7 -3
- package/lib/errors.js.map +1 -1
- package/lib/index.d.ts +8 -2
- package/lib/index.js.map +1 -1
- package/lib/worker-tuner.d.ts +111 -1
- 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/.github/workflows/per-pr.yml +3 -3
- package/sdk-core/Cargo.toml +0 -1
- package/sdk-core/client/Cargo.toml +1 -2
- package/sdk-core/client/src/lib.rs +21 -13
- package/sdk-core/client/src/metrics.rs +1 -1
- package/sdk-core/client/src/raw.rs +46 -1
- package/sdk-core/core/Cargo.toml +7 -7
- package/sdk-core/core/benches/workflow_replay.rs +1 -1
- package/sdk-core/core/src/abstractions/take_cell.rs +1 -1
- package/sdk-core/core/src/abstractions.rs +98 -10
- package/sdk-core/core/src/core_tests/activity_tasks.rs +8 -2
- package/sdk-core/core/src/core_tests/local_activities.rs +1 -1
- package/sdk-core/core/src/core_tests/mod.rs +3 -3
- package/sdk-core/core/src/core_tests/updates.rs +104 -9
- package/sdk-core/core/src/core_tests/workers.rs +72 -3
- package/sdk-core/core/src/core_tests/workflow_tasks.rs +6 -7
- package/sdk-core/core/src/debug_client.rs +78 -0
- package/sdk-core/core/src/ephemeral_server/mod.rs +15 -5
- package/sdk-core/core/src/lib.rs +30 -4
- package/sdk-core/core/src/pollers/mod.rs +1 -1
- package/sdk-core/core/src/pollers/poll_buffer.rs +7 -7
- package/sdk-core/core/src/replay/mod.rs +4 -4
- package/sdk-core/core/src/telemetry/log_export.rs +2 -2
- package/sdk-core/core/src/telemetry/metrics.rs +69 -1
- package/sdk-core/core/src/telemetry/otel.rs +2 -2
- package/sdk-core/core/src/test_help/mod.rs +3 -3
- package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +3 -3
- package/sdk-core/core/src/worker/activities/activity_task_poller_stream.rs +1 -1
- package/sdk-core/core/src/worker/activities/local_activities.rs +68 -24
- package/sdk-core/core/src/worker/activities.rs +26 -15
- package/sdk-core/core/src/worker/client/mocks.rs +10 -4
- package/sdk-core/core/src/worker/client.rs +17 -0
- package/sdk-core/core/src/worker/mod.rs +71 -13
- package/sdk-core/core/src/worker/slot_provider.rs +5 -7
- package/sdk-core/core/src/worker/tuner/fixed_size.rs +4 -3
- package/sdk-core/core/src/worker/tuner/resource_based.rs +171 -32
- package/sdk-core/core/src/worker/tuner.rs +18 -6
- package/sdk-core/core/src/worker/workflow/history_update.rs +43 -13
- package/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +6 -6
- package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +6 -5
- package/sdk-core/core/src/worker/workflow/managed_run.rs +3 -3
- package/sdk-core/core/src/worker/workflow/mod.rs +13 -7
- package/sdk-core/core/src/worker/workflow/wft_extraction.rs +7 -7
- package/sdk-core/core/src/worker/workflow/wft_poller.rs +2 -2
- package/sdk-core/core/src/worker/workflow/workflow_stream.rs +11 -11
- package/sdk-core/core-api/Cargo.toml +1 -0
- package/sdk-core/core-api/src/worker.rs +84 -30
- package/sdk-core/sdk/Cargo.toml +1 -2
- package/sdk-core/sdk/src/lib.rs +1 -1
- package/sdk-core/sdk/src/workflow_context.rs +9 -8
- package/sdk-core/sdk/src/workflow_future.rs +19 -14
- package/sdk-core/sdk-core-protos/Cargo.toml +2 -0
- package/sdk-core/sdk-core-protos/build.rs +6 -1
- package/sdk-core/sdk-core-protos/protos/api_upstream/buf.yaml +1 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +3207 -158
- package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +2934 -118
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/activity/v1/message.proto +67 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +47 -1
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/event_type.proto +6 -7
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +2 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/reset.proto +5 -3
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +3 -3
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/update.proto +14 -13
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/failure/v1/message.proto +1 -3
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/history/v1/message.proto +22 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/nexus/v1/message.proto +13 -2
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +26 -6
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/schedule/v1/message.proto +5 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/workflow_metadata.proto +6 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +46 -12
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/update/v1/message.proto +18 -19
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflow/v1/message.proto +27 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +192 -19
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +279 -12
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/activity_result/activity_result.proto +1 -1
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/activity_task/activity_task.proto +1 -1
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +1 -1
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/common/common.proto +1 -1
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/core_interface.proto +17 -1
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/external_data/external_data.proto +1 -1
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +1 -1
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +1 -1
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +1 -1
- package/sdk-core/sdk-core-protos/src/lib.rs +30 -6
- package/sdk-core/test-utils/Cargo.toml +1 -2
- package/sdk-core/test-utils/src/lib.rs +2 -2
- package/sdk-core/tests/heavy_tests.rs +1 -1
- package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +2 -2
- package/sdk-core/tests/integ_tests/metrics_tests.rs +144 -7
- package/sdk-core/tests/integ_tests/queries_tests.rs +1 -1
- package/sdk-core/tests/integ_tests/update_tests.rs +109 -5
- package/sdk-core/tests/integ_tests/worker_tests.rs +44 -8
- package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +1 -1
- package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -1
- package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +1 -1
- package/sdk-core/tests/integ_tests/workflow_tests.rs +3 -2
- package/src/conversions/slot_supplier_bridge.rs +287 -0
- package/src/conversions.rs +23 -15
- package/src/helpers.rs +35 -1
- package/src/runtime.rs +7 -3
- package/src/worker.rs +1 -1
- package/ts/errors.ts +9 -2
- package/ts/index.ts +19 -4
- package/ts/worker-tuner.ts +123 -1
- 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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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(||
|
|
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;
|
|
@@ -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
|
|
19
|
-
use
|
|
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:
|
|
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,
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
.
|
|
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
|
}?;
|