@temporalio/core-bridge 1.8.2 → 1.8.3
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/package.json +3 -3
- package/releases/aarch64-apple-darwin/index.node +0 -0
- package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
- package/releases/x86_64-apple-darwin/index.node +0 -0
- package/releases/x86_64-pc-windows-msvc/index.node +0 -0
- package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
- package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +2 -2
- package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +7 -0
- package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +54 -37
- package/sdk-core/sdk/src/workflow_context.rs +2 -2
- package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +35 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@temporalio/core-bridge",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.3",
|
|
4
4
|
"description": "Temporal.io SDK Core<>Node bridge",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@opentelemetry/api": "^1.4.1",
|
|
26
|
-
"@temporalio/common": "1.8.
|
|
26
|
+
"@temporalio/common": "1.8.3",
|
|
27
27
|
"arg": "^5.0.2",
|
|
28
28
|
"cargo-cp-artifact": "^0.1.6",
|
|
29
29
|
"which": "^2.0.2"
|
|
@@ -53,5 +53,5 @@
|
|
|
53
53
|
"publishConfig": {
|
|
54
54
|
"access": "public"
|
|
55
55
|
},
|
|
56
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "7d1c0ec969b897b4f32bd5a8eda9819a03bd2f83"
|
|
57
57
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -235,14 +235,14 @@ impl StartCommandCreated {
|
|
|
235
235
|
if event_dat.wf_id != state.workflow_id {
|
|
236
236
|
return TransitionResult::Err(WFMachinesError::Nondeterminism(format!(
|
|
237
237
|
"Child workflow id of scheduled event '{}' does not \
|
|
238
|
-
match child workflow id of
|
|
238
|
+
match child workflow id of command '{}'",
|
|
239
239
|
event_dat.wf_id, state.workflow_id
|
|
240
240
|
)));
|
|
241
241
|
}
|
|
242
242
|
if event_dat.wf_type != state.workflow_type {
|
|
243
243
|
return TransitionResult::Err(WFMachinesError::Nondeterminism(format!(
|
|
244
244
|
"Child workflow type of scheduled event '{}' does not \
|
|
245
|
-
match child workflow type of
|
|
245
|
+
match child workflow type of command '{}'",
|
|
246
246
|
event_dat.wf_type, state.workflow_type
|
|
247
247
|
)));
|
|
248
248
|
}
|
|
@@ -270,6 +270,13 @@ impl TryFrom<HistEventData> for PatchMachineEvents {
|
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
+
impl PatchMachine {
|
|
274
|
+
/// Returns true if this patch machine has the same id as the one provided
|
|
275
|
+
pub(crate) fn matches_patch(&self, id: &str) -> bool {
|
|
276
|
+
self.shared_state.patch_id == id
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
273
280
|
#[cfg(test)]
|
|
274
281
|
mod tests {
|
|
275
282
|
use crate::{
|
|
@@ -138,7 +138,7 @@ pub(crate) struct WorkflowMachines {
|
|
|
138
138
|
current_wf_task_commands: VecDeque<CommandAndMachine>,
|
|
139
139
|
|
|
140
140
|
/// Information about patch markers we have already seen while replaying history
|
|
141
|
-
|
|
141
|
+
encountered_patch_markers: HashMap<String, ChangeInfo>,
|
|
142
142
|
|
|
143
143
|
/// Contains extra local-activity related data
|
|
144
144
|
local_activity_data: LocalActivityData,
|
|
@@ -255,7 +255,7 @@ impl WorkflowMachines {
|
|
|
255
255
|
id_to_machine: Default::default(),
|
|
256
256
|
commands: Default::default(),
|
|
257
257
|
current_wf_task_commands: Default::default(),
|
|
258
|
-
|
|
258
|
+
encountered_patch_markers: Default::default(),
|
|
259
259
|
local_activity_data: LocalActivityData::default(),
|
|
260
260
|
have_seen_terminal_event: false,
|
|
261
261
|
}
|
|
@@ -547,7 +547,7 @@ impl WorkflowMachines {
|
|
|
547
547
|
.peek_next_wft_sequence(last_handled_wft_started_id)
|
|
548
548
|
{
|
|
549
549
|
if let Some((patch_id, _)) = e.get_patch_marker_details() {
|
|
550
|
-
self.
|
|
550
|
+
self.encountered_patch_markers.insert(
|
|
551
551
|
patch_id.clone(),
|
|
552
552
|
ChangeInfo {
|
|
553
553
|
created_command: false,
|
|
@@ -718,7 +718,7 @@ impl WorkflowMachines {
|
|
|
718
718
|
let consumed_cmd = loop {
|
|
719
719
|
if let Some(peek_machine) = self.commands.front() {
|
|
720
720
|
let mach = self.machine(peek_machine.machine);
|
|
721
|
-
match
|
|
721
|
+
match patch_marker_handling(event, mach, next_event)? {
|
|
722
722
|
EventHandlingOutcome::SkipCommand => {
|
|
723
723
|
self.commands.pop_front();
|
|
724
724
|
continue;
|
|
@@ -1138,7 +1138,7 @@ impl WorkflowMachines {
|
|
|
1138
1138
|
WFCommand::SetPatchMarker(attrs) => {
|
|
1139
1139
|
// Do not create commands for change IDs that we have already created commands
|
|
1140
1140
|
// for.
|
|
1141
|
-
let encountered_entry = self.
|
|
1141
|
+
let encountered_entry = self.encountered_patch_markers.get(&attrs.patch_id);
|
|
1142
1142
|
if !matches!(encountered_entry,
|
|
1143
1143
|
Some(ChangeInfo {created_command}) if *created_command)
|
|
1144
1144
|
{
|
|
@@ -1147,17 +1147,17 @@ impl WorkflowMachines {
|
|
|
1147
1147
|
self.replaying,
|
|
1148
1148
|
attrs.deprecated,
|
|
1149
1149
|
encountered_entry.is_some(),
|
|
1150
|
-
self.
|
|
1150
|
+
self.encountered_patch_markers.keys().map(|s| s.as_str()),
|
|
1151
1151
|
self.observed_internal_flags.clone(),
|
|
1152
1152
|
)?;
|
|
1153
1153
|
let mkey =
|
|
1154
1154
|
self.add_cmd_to_wf_task(patch_machine, CommandIdKind::NeverResolves);
|
|
1155
1155
|
self.process_machine_responses(mkey, other_cmds)?;
|
|
1156
1156
|
|
|
1157
|
-
if let Some(ci) = self.
|
|
1157
|
+
if let Some(ci) = self.encountered_patch_markers.get_mut(&attrs.patch_id) {
|
|
1158
1158
|
ci.created_command = true;
|
|
1159
1159
|
} else {
|
|
1160
|
-
self.
|
|
1160
|
+
self.encountered_patch_markers.insert(
|
|
1161
1161
|
attrs.patch_id,
|
|
1162
1162
|
ChangeInfo {
|
|
1163
1163
|
created_command: true,
|
|
@@ -1360,45 +1360,62 @@ enum EventHandlingOutcome {
|
|
|
1360
1360
|
|
|
1361
1361
|
/// Special handling for patch markers, when handling command events as in
|
|
1362
1362
|
/// [WorkflowMachines::handle_command_event]
|
|
1363
|
-
fn
|
|
1363
|
+
fn patch_marker_handling(
|
|
1364
1364
|
event: &HistoryEvent,
|
|
1365
1365
|
mach: &Machines,
|
|
1366
1366
|
next_event: Option<&HistoryEvent>,
|
|
1367
1367
|
) -> Result<EventHandlingOutcome> {
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1368
|
+
let patch_machine = match mach {
|
|
1369
|
+
Machines::PatchMachine(pm) => Some(pm),
|
|
1370
|
+
_ => None,
|
|
1371
|
+
};
|
|
1372
|
+
let patch_details = event.get_patch_marker_details();
|
|
1373
|
+
fn skip_one_or_two_events(next_event: Option<&HistoryEvent>) -> Result<EventHandlingOutcome> {
|
|
1374
|
+
// Also ignore the subsequent upsert event if present
|
|
1375
|
+
let mut skip_next_event = false;
|
|
1376
|
+
if let Some(Attributes::UpsertWorkflowSearchAttributesEventAttributes(atts)) =
|
|
1377
|
+
next_event.and_then(|ne| ne.attributes.as_ref())
|
|
1378
|
+
{
|
|
1379
|
+
if let Some(ref sa) = atts.search_attributes {
|
|
1380
|
+
skip_next_event = sa.indexed_fields.contains_key(VERSION_SEARCH_ATTR_KEY);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
Ok(EventHandlingOutcome::SkipEvent { skip_next_event })
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
if let Some((patch_name, deprecated)) = patch_details {
|
|
1388
|
+
if let Some(pm) = patch_machine {
|
|
1389
|
+
// If the next machine *is* a patch machine, but this marker is deprecated, it may
|
|
1390
|
+
// either apply to this machine (the `deprecate_patch` call is still in workflow code) -
|
|
1391
|
+
// or it could be another `patched` or `deprecate_patch` call for a *different* patch,
|
|
1392
|
+
// which we should also permit. In the latter case, we should skip this event.
|
|
1393
|
+
if !pm.matches_patch(&patch_name) && deprecated {
|
|
1394
|
+
skip_one_or_two_events(next_event)
|
|
1395
|
+
} else {
|
|
1396
|
+
Ok(EventHandlingOutcome::Normal)
|
|
1397
|
+
}
|
|
1398
|
+
} else {
|
|
1399
|
+
// Version markers can be skipped in the event they are deprecated
|
|
1371
1400
|
// Is deprecated. We can simply ignore this event, as deprecated change
|
|
1372
1401
|
// markers are allowed without matching changed calls.
|
|
1373
1402
|
if deprecated {
|
|
1374
|
-
debug!("Deprecated patch marker tried against
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
if let Some(ref sa) = atts.search_attributes {
|
|
1382
|
-
skip_next_event = sa.indexed_fields.contains_key(VERSION_SEARCH_ATTR_KEY);
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
return Ok(EventHandlingOutcome::SkipEvent { skip_next_event });
|
|
1403
|
+
debug!("Deprecated patch marker tried against non-patch machine, skipping.");
|
|
1404
|
+
skip_one_or_two_events(next_event)
|
|
1405
|
+
} else {
|
|
1406
|
+
Err(WFMachinesError::Nondeterminism(format!(
|
|
1407
|
+
"Non-deprecated patch marker encountered for change {patch_name}, but there is \
|
|
1408
|
+
no corresponding change command!"
|
|
1409
|
+
)))
|
|
1387
1410
|
}
|
|
1388
|
-
return Err(WFMachinesError::Nondeterminism(format!(
|
|
1389
|
-
"Non-deprecated patch marker encountered for change {patch_name}, \
|
|
1390
|
-
but there is no corresponding change command!"
|
|
1391
|
-
)));
|
|
1392
|
-
}
|
|
1393
|
-
// Patch machines themselves may also not *have* matching markers, where non-deprecated
|
|
1394
|
-
// calls take the old path, and deprecated calls assume history is produced by a new-code
|
|
1395
|
-
// worker.
|
|
1396
|
-
if matches!(mach, Machines::PatchMachine(_)) {
|
|
1397
|
-
debug!("Skipping non-matching event against patch machine");
|
|
1398
|
-
return Ok(EventHandlingOutcome::SkipCommand);
|
|
1399
1411
|
}
|
|
1412
|
+
} else if patch_machine.is_some() {
|
|
1413
|
+
debug!("Skipping non-matching event against patch machine");
|
|
1414
|
+
Ok(EventHandlingOutcome::SkipCommand)
|
|
1415
|
+
} else {
|
|
1416
|
+
// Not a patch machine or a patch event
|
|
1417
|
+
Ok(EventHandlingOutcome::Normal)
|
|
1400
1418
|
}
|
|
1401
|
-
Ok(EventHandlingOutcome::Normal)
|
|
1402
1419
|
}
|
|
1403
1420
|
|
|
1404
1421
|
#[derive(derive_more::From)]
|
|
@@ -249,8 +249,8 @@ impl WfContext {
|
|
|
249
249
|
|
|
250
250
|
/// Record that this workflow history was created with the provided patch, and it is being
|
|
251
251
|
/// phased out.
|
|
252
|
-
pub fn deprecate_patch(&self, patch_id: &str) {
|
|
253
|
-
self.patch_impl(patch_id, true)
|
|
252
|
+
pub fn deprecate_patch(&self, patch_id: &str) -> bool {
|
|
253
|
+
self.patch_impl(patch_id, true)
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
fn patch_impl(&self, patch_id: &str, deprecated: bool) -> bool {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
use std::{
|
|
2
|
-
sync::
|
|
2
|
+
sync::{
|
|
3
|
+
atomic::{AtomicBool, Ordering},
|
|
4
|
+
Arc,
|
|
5
|
+
},
|
|
3
6
|
time::Duration,
|
|
4
7
|
};
|
|
5
8
|
|
|
@@ -117,3 +120,34 @@ async fn patched_on_second_workflow_task_is_deterministic() {
|
|
|
117
120
|
starter.start_with_worker(wf_name, &mut worker).await;
|
|
118
121
|
worker.run_until_done().await.unwrap();
|
|
119
122
|
}
|
|
123
|
+
|
|
124
|
+
#[tokio::test]
|
|
125
|
+
async fn can_remove_deprecated_patch_near_other_patch() {
|
|
126
|
+
let wf_name = "can_add_change_markers";
|
|
127
|
+
let mut starter = CoreWfStarter::new(wf_name);
|
|
128
|
+
starter.no_remote_activities();
|
|
129
|
+
let mut worker = starter.worker().await;
|
|
130
|
+
let did_die = Arc::new(AtomicBool::new(false));
|
|
131
|
+
worker.register_wf(wf_name.to_owned(), move |ctx: WfContext| {
|
|
132
|
+
let did_die = did_die.clone();
|
|
133
|
+
async move {
|
|
134
|
+
ctx.timer(Duration::from_millis(200)).await;
|
|
135
|
+
if !did_die.load(Ordering::Acquire) {
|
|
136
|
+
assert!(ctx.deprecate_patch("getting-deprecated"));
|
|
137
|
+
assert!(ctx.patched("staying"));
|
|
138
|
+
} else {
|
|
139
|
+
assert!(ctx.patched("staying"));
|
|
140
|
+
}
|
|
141
|
+
ctx.timer(Duration::from_millis(200)).await;
|
|
142
|
+
|
|
143
|
+
if !did_die.load(Ordering::Acquire) {
|
|
144
|
+
did_die.store(true, Ordering::Release);
|
|
145
|
+
ctx.force_task_fail(anyhow::anyhow!("i'm ded"));
|
|
146
|
+
}
|
|
147
|
+
Ok(().into())
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
starter.start_with_worker(wf_name, &mut worker).await;
|
|
152
|
+
worker.run_until_done().await.unwrap();
|
|
153
|
+
}
|