@temporalio/core-bridge 0.23.0 → 1.0.0
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 +118 -15
- package/Cargo.toml +2 -1
- package/LICENSE.md +1 -1
- package/README.md +1 -1
- package/index.d.ts +47 -18
- package/package.json +7 -7
- 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/.buildkite/docker/docker-compose.yaml +4 -2
- package/sdk-core/ARCHITECTURE.md +9 -7
- package/sdk-core/README.md +5 -1
- package/sdk-core/arch_docs/diagrams/workflow_internals.svg +1 -0
- package/sdk-core/bridge-ffi/src/wrappers.rs +0 -3
- package/sdk-core/client/src/lib.rs +26 -8
- package/sdk-core/client/src/raw.rs +166 -54
- package/sdk-core/client/src/retry.rs +9 -4
- package/sdk-core/client/src/workflow_handle/mod.rs +4 -2
- package/sdk-core/core/Cargo.toml +2 -0
- package/sdk-core/core/src/abstractions.rs +137 -16
- package/sdk-core/core/src/core_tests/activity_tasks.rs +258 -63
- package/sdk-core/core/src/core_tests/child_workflows.rs +1 -2
- package/sdk-core/core/src/core_tests/determinism.rs +2 -2
- package/sdk-core/core/src/core_tests/local_activities.rs +8 -7
- package/sdk-core/core/src/core_tests/queries.rs +146 -60
- package/sdk-core/core/src/core_tests/replay_flag.rs +1 -1
- package/sdk-core/core/src/core_tests/workers.rs +39 -23
- package/sdk-core/core/src/core_tests/workflow_cancels.rs +1 -1
- package/sdk-core/core/src/core_tests/workflow_tasks.rs +387 -280
- package/sdk-core/core/src/lib.rs +6 -4
- package/sdk-core/core/src/pollers/poll_buffer.rs +16 -10
- package/sdk-core/core/src/protosext/mod.rs +6 -6
- package/sdk-core/core/src/retry_logic.rs +1 -1
- package/sdk-core/core/src/telemetry/metrics.rs +21 -7
- package/sdk-core/core/src/telemetry/mod.rs +18 -4
- package/sdk-core/core/src/test_help/mod.rs +341 -109
- package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +18 -9
- package/sdk-core/core/src/worker/activities/local_activities.rs +19 -16
- package/sdk-core/core/src/worker/activities.rs +156 -29
- package/sdk-core/core/src/worker/client.rs +1 -0
- package/sdk-core/core/src/worker/mod.rs +132 -659
- package/sdk-core/core/src/{workflow → worker/workflow}/bridge.rs +1 -1
- package/sdk-core/core/src/{workflow → worker/workflow}/driven_workflow.rs +1 -1
- package/sdk-core/core/src/{workflow → worker/workflow}/history_update.rs +16 -2
- package/sdk-core/core/src/{workflow → worker/workflow}/machines/activity_state_machine.rs +39 -4
- package/sdk-core/core/src/{workflow → worker/workflow}/machines/cancel_external_state_machine.rs +5 -2
- package/sdk-core/core/src/{workflow → worker/workflow}/machines/cancel_workflow_state_machine.rs +1 -1
- package/sdk-core/core/src/{workflow → worker/workflow}/machines/child_workflow_state_machine.rs +2 -4
- package/sdk-core/core/src/{workflow → worker/workflow}/machines/complete_workflow_state_machine.rs +0 -0
- package/sdk-core/core/src/{workflow → worker/workflow}/machines/continue_as_new_workflow_state_machine.rs +1 -1
- package/sdk-core/core/src/{workflow → worker/workflow}/machines/fail_workflow_state_machine.rs +0 -0
- package/sdk-core/core/src/{workflow → worker/workflow}/machines/local_activity_state_machine.rs +2 -5
- package/sdk-core/core/src/{workflow → worker/workflow}/machines/mod.rs +1 -1
- package/sdk-core/core/src/{workflow → worker/workflow}/machines/mutable_side_effect_state_machine.rs +0 -0
- package/sdk-core/core/src/{workflow → worker/workflow}/machines/patch_state_machine.rs +1 -1
- package/sdk-core/core/src/{workflow → worker/workflow}/machines/side_effect_state_machine.rs +0 -0
- package/sdk-core/core/src/{workflow → worker/workflow}/machines/signal_external_state_machine.rs +4 -2
- package/sdk-core/core/src/{workflow → worker/workflow}/machines/timer_state_machine.rs +1 -2
- package/sdk-core/core/src/{workflow → worker/workflow}/machines/transition_coverage.rs +1 -1
- package/sdk-core/core/src/{workflow → worker/workflow}/machines/upsert_search_attributes_state_machine.rs +5 -7
- package/sdk-core/core/src/{workflow → worker/workflow}/machines/workflow_machines/local_acts.rs +2 -2
- package/sdk-core/core/src/{workflow → worker/workflow}/machines/workflow_machines.rs +40 -16
- package/sdk-core/core/src/{workflow → worker/workflow}/machines/workflow_task_state_machine.rs +0 -0
- package/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +198 -0
- package/sdk-core/core/src/worker/workflow/managed_run.rs +627 -0
- package/sdk-core/core/src/worker/workflow/mod.rs +1115 -0
- package/sdk-core/core/src/worker/workflow/run_cache.rs +143 -0
- package/sdk-core/core/src/worker/workflow/wft_poller.rs +88 -0
- package/sdk-core/core/src/worker/workflow/workflow_stream.rs +936 -0
- package/sdk-core/core-api/src/errors.rs +3 -10
- package/sdk-core/core-api/src/lib.rs +2 -1
- package/sdk-core/core-api/src/worker.rs +26 -2
- package/sdk-core/etc/dynamic-config.yaml +2 -0
- package/sdk-core/integ-with-otel.sh +1 -1
- package/sdk-core/protos/api_upstream/Makefile +4 -4
- package/sdk-core/protos/api_upstream/api-linter.yaml +2 -0
- package/sdk-core/protos/api_upstream/buf.yaml +8 -9
- package/sdk-core/protos/api_upstream/temporal/api/cluster/v1/message.proto +83 -0
- package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +7 -1
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/cluster.proto +40 -0
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +3 -0
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/reset.proto +3 -1
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/schedule.proto +60 -0
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/workflow.proto +3 -0
- package/sdk-core/protos/api_upstream/temporal/api/errordetails/v1/message.proto +32 -4
- package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +69 -19
- package/sdk-core/protos/api_upstream/temporal/api/namespace/v1/message.proto +13 -0
- package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +163 -0
- package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +97 -0
- package/sdk-core/protos/api_upstream/temporal/api/schedule/v1/message.proto +300 -0
- package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +25 -0
- package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +180 -3
- package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +53 -3
- package/sdk-core/protos/local/temporal/sdk/core/activity_result/activity_result.proto +2 -2
- package/sdk-core/protos/local/temporal/sdk/core/activity_task/activity_task.proto +6 -5
- package/sdk-core/protos/local/temporal/sdk/core/bridge/bridge.proto +0 -1
- package/sdk-core/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +2 -1
- package/sdk-core/protos/local/temporal/sdk/core/common/common.proto +0 -64
- package/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +2 -1
- package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +11 -8
- package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +30 -25
- package/sdk-core/sdk/src/activity_context.rs +12 -5
- package/sdk-core/sdk/src/app_data.rs +37 -0
- package/sdk-core/sdk/src/lib.rs +76 -43
- package/sdk-core/sdk/src/workflow_context/options.rs +8 -6
- package/sdk-core/sdk/src/workflow_context.rs +14 -19
- package/sdk-core/sdk/src/workflow_future.rs +11 -6
- package/sdk-core/sdk-core-protos/src/history_builder.rs +19 -5
- package/sdk-core/sdk-core-protos/src/history_info.rs +11 -6
- package/sdk-core/sdk-core-protos/src/lib.rs +74 -176
- package/sdk-core/test-utils/src/lib.rs +85 -72
- package/sdk-core/tests/integ_tests/heartbeat_tests.rs +11 -9
- package/sdk-core/tests/integ_tests/polling_tests.rs +12 -0
- package/sdk-core/tests/integ_tests/queries_tests.rs +39 -22
- package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +49 -4
- package/sdk-core/tests/integ_tests/workflow_tests/appdata_propagation.rs +61 -0
- package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +1 -1
- package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +74 -13
- package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +19 -0
- package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -1
- package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +6 -3
- package/sdk-core/tests/integ_tests/workflow_tests.rs +10 -23
- package/sdk-core/tests/load_tests.rs +8 -3
- package/sdk-core/tests/main.rs +2 -1
- package/src/conversions.rs +47 -39
- package/src/errors.rs +10 -21
- package/src/lib.rs +342 -325
- package/sdk-core/core/src/pending_activations.rs +0 -173
- package/sdk-core/core/src/worker/wft_delivery.rs +0 -81
- package/sdk-core/core/src/workflow/mod.rs +0 -478
- package/sdk-core/core/src/workflow/workflow_tasks/cache_manager.rs +0 -194
- package/sdk-core/core/src/workflow/workflow_tasks/concurrency_manager.rs +0 -418
- package/sdk-core/core/src/workflow/workflow_tasks/mod.rs +0 -989
|
@@ -1,478 +0,0 @@
|
|
|
1
|
-
pub(crate) mod workflow_tasks;
|
|
2
|
-
|
|
3
|
-
mod bridge;
|
|
4
|
-
mod driven_workflow;
|
|
5
|
-
mod history_update;
|
|
6
|
-
mod machines;
|
|
7
|
-
|
|
8
|
-
pub(crate) use bridge::WorkflowBridge;
|
|
9
|
-
pub(crate) use driven_workflow::{DrivenWorkflow, WorkflowFetcher};
|
|
10
|
-
pub(crate) use history_update::{HistoryPaginator, HistoryUpdate};
|
|
11
|
-
pub(crate) use machines::WFMachinesError;
|
|
12
|
-
|
|
13
|
-
use crate::{
|
|
14
|
-
telemetry::metrics::MetricsContext,
|
|
15
|
-
worker::{LocalActRequest, LocalActivityResolution},
|
|
16
|
-
};
|
|
17
|
-
use machines::WorkflowMachines;
|
|
18
|
-
use std::{result, sync::mpsc::Sender, time::Duration};
|
|
19
|
-
use temporal_sdk_core_protos::{
|
|
20
|
-
coresdk::{workflow_activation::WorkflowActivation, workflow_commands::*},
|
|
21
|
-
temporal::api::command::v1::Command as ProtoCommand,
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
pub(crate) const LEGACY_QUERY_ID: &str = "legacy_query";
|
|
25
|
-
|
|
26
|
-
type Result<T, E = WFMachinesError> = std::result::Result<T, E>;
|
|
27
|
-
|
|
28
|
-
/// Manages an instance of a [WorkflowMachines], which is not thread-safe, as well as other data
|
|
29
|
-
/// associated with that specific workflow run.
|
|
30
|
-
pub(crate) struct WorkflowManager {
|
|
31
|
-
machines: WorkflowMachines,
|
|
32
|
-
/// Is always `Some` in normal operation. Optional to allow for unit testing with the test
|
|
33
|
-
/// workflow driver, which does not need to complete activations the normal way.
|
|
34
|
-
command_sink: Option<Sender<Vec<WFCommand>>>,
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
impl WorkflowManager {
|
|
38
|
-
/// Create a new workflow manager given workflow history and execution info as would be found
|
|
39
|
-
/// in [PollWorkflowTaskQueueResponse]
|
|
40
|
-
pub fn new(
|
|
41
|
-
history: HistoryUpdate,
|
|
42
|
-
namespace: String,
|
|
43
|
-
workflow_id: String,
|
|
44
|
-
workflow_type: String,
|
|
45
|
-
run_id: String,
|
|
46
|
-
metrics: MetricsContext,
|
|
47
|
-
) -> Self {
|
|
48
|
-
let (wfb, cmd_sink) = WorkflowBridge::new();
|
|
49
|
-
let state_machines = WorkflowMachines::new(
|
|
50
|
-
namespace,
|
|
51
|
-
workflow_id,
|
|
52
|
-
workflow_type,
|
|
53
|
-
run_id,
|
|
54
|
-
history,
|
|
55
|
-
Box::new(wfb).into(),
|
|
56
|
-
metrics,
|
|
57
|
-
);
|
|
58
|
-
Self {
|
|
59
|
-
machines: state_machines,
|
|
60
|
-
command_sink: Some(cmd_sink),
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
#[cfg(test)]
|
|
65
|
-
pub const fn new_from_machines(workflow_machines: WorkflowMachines) -> Self {
|
|
66
|
-
Self {
|
|
67
|
-
machines: workflow_machines,
|
|
68
|
-
command_sink: None,
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
#[derive(Debug)]
|
|
74
|
-
pub struct OutgoingServerCommands {
|
|
75
|
-
pub commands: Vec<ProtoCommand>,
|
|
76
|
-
pub replaying: bool,
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
#[derive(Debug)]
|
|
80
|
-
pub(crate) enum LocalResolution {
|
|
81
|
-
LocalActivity(LocalActivityResolution),
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
impl WorkflowManager {
|
|
85
|
-
/// Given history that was just obtained from the server, pipe it into this workflow's machines.
|
|
86
|
-
///
|
|
87
|
-
/// Should only be called when a workflow has caught up on replay (or is just beginning). It
|
|
88
|
-
/// will return a workflow activation if one is needed.
|
|
89
|
-
pub async fn feed_history_from_server(
|
|
90
|
-
&mut self,
|
|
91
|
-
update: HistoryUpdate,
|
|
92
|
-
) -> Result<WorkflowActivation> {
|
|
93
|
-
self.machines.new_history_from_server(update).await?;
|
|
94
|
-
self.get_next_activation().await
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/// Let this workflow know that something we've been waiting locally on has resolved, like a
|
|
98
|
-
/// local activity or side effect
|
|
99
|
-
///
|
|
100
|
-
/// Returns true if the resolution did anything. EX: If the activity is already canceled and
|
|
101
|
-
/// used the TryCancel or Abandon modes, the resolution is uninteresting.
|
|
102
|
-
pub fn notify_of_local_result(&mut self, resolved: LocalResolution) -> Result<bool> {
|
|
103
|
-
self.machines.local_resolution(resolved)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/// Fetch the next workflow activation for this workflow if one is required. Doing so will apply
|
|
107
|
-
/// the next unapplied workflow task if such a sequence exists in history we already know about.
|
|
108
|
-
///
|
|
109
|
-
/// Callers may also need to call [get_server_commands] after this to issue any pending commands
|
|
110
|
-
/// to the server.
|
|
111
|
-
pub async fn get_next_activation(&mut self) -> Result<WorkflowActivation> {
|
|
112
|
-
// First check if there are already some pending jobs, which can be a result of replay.
|
|
113
|
-
let activation = self.machines.get_wf_activation();
|
|
114
|
-
if !activation.jobs.is_empty() {
|
|
115
|
-
return Ok(activation);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
self.machines.apply_next_wft_from_history().await?;
|
|
119
|
-
Ok(self.machines.get_wf_activation())
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/// If there are no pending jobs for the workflow, apply the next workflow task and check
|
|
123
|
-
/// again if there are any jobs. Importantly, does not *drain* jobs.
|
|
124
|
-
///
|
|
125
|
-
/// Returns true if there are jobs (before or after applying the next WFT).
|
|
126
|
-
pub async fn apply_next_task_if_ready(&mut self) -> Result<bool> {
|
|
127
|
-
if self.machines.has_pending_jobs() {
|
|
128
|
-
return Ok(true);
|
|
129
|
-
}
|
|
130
|
-
loop {
|
|
131
|
-
let consumed_events = self.machines.apply_next_wft_from_history().await?;
|
|
132
|
-
|
|
133
|
-
if consumed_events == 0 || !self.machines.replaying || self.machines.has_pending_jobs()
|
|
134
|
-
{
|
|
135
|
-
// Keep applying tasks while there are events, we are still replaying, and there are
|
|
136
|
-
// no jobs
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
Ok(self.machines.has_pending_jobs())
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/// Typically called after [get_next_activation], use this to retrieve commands to be sent to
|
|
144
|
-
/// the server which have been generated by the machines. Does *not* drain those commands.
|
|
145
|
-
/// See [WorkflowMachines::get_commands].
|
|
146
|
-
pub fn get_server_commands(&mut self) -> OutgoingServerCommands {
|
|
147
|
-
OutgoingServerCommands {
|
|
148
|
-
commands: self.machines.get_commands(),
|
|
149
|
-
replaying: self.machines.replaying,
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/// Remove and return all queued local activities. Once this is called, they need to be
|
|
154
|
-
/// dispatched for execution.
|
|
155
|
-
pub fn drain_queued_local_activities(&mut self) -> Vec<LocalActRequest> {
|
|
156
|
-
self.machines.drain_queued_local_activities()
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/// Feed the workflow machines new commands issued by the executing workflow code, and iterate
|
|
160
|
-
/// the machines.
|
|
161
|
-
pub async fn push_commands(&mut self, cmds: Vec<WFCommand>) -> Result<()> {
|
|
162
|
-
if let Some(cs) = self.command_sink.as_mut() {
|
|
163
|
-
cs.send(cmds).map_err(|_| {
|
|
164
|
-
WFMachinesError::Fatal("Internal error buffering workflow commands".to_string())
|
|
165
|
-
})?;
|
|
166
|
-
}
|
|
167
|
-
self.machines.iterate_machines().await?;
|
|
168
|
-
Ok(())
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/// Determines when workflows are kept in the cache or evicted
|
|
173
|
-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
174
|
-
pub(crate) enum WorkflowCachingPolicy {
|
|
175
|
-
/// Workflows are cached until evicted explicitly or the cache size limit is reached, in which
|
|
176
|
-
/// case they are evicted by least-recently-used ordering.
|
|
177
|
-
Sticky {
|
|
178
|
-
/// The maximum number of workflows that will be kept in the cache
|
|
179
|
-
max_cached_workflows: usize,
|
|
180
|
-
},
|
|
181
|
-
/// Workflows are evicted after each workflow task completion. Note that this is *not* after
|
|
182
|
-
/// each workflow activation - there are often multiple activations per workflow task.
|
|
183
|
-
NonSticky,
|
|
184
|
-
|
|
185
|
-
/// Not a real mode, but good for imitating crashes. Evict workflows after *every* reply,
|
|
186
|
-
/// even if there are pending activations
|
|
187
|
-
#[cfg(test)]
|
|
188
|
-
AfterEveryReply,
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
#[derive(thiserror::Error, Debug, derive_more::From)]
|
|
192
|
-
#[error("Lang provided workflow command with empty variant")]
|
|
193
|
-
pub struct EmptyWorkflowCommandErr;
|
|
194
|
-
|
|
195
|
-
/// [DrivenWorkflow]s respond with these when called, to indicate what they want to do next.
|
|
196
|
-
/// EX: Create a new timer, complete the workflow, etc.
|
|
197
|
-
#[derive(Debug, derive_more::From, derive_more::Display)]
|
|
198
|
-
#[allow(clippy::large_enum_variant)]
|
|
199
|
-
pub enum WFCommand {
|
|
200
|
-
/// Returned when we need to wait for the lang sdk to send us something
|
|
201
|
-
NoCommandsFromLang,
|
|
202
|
-
AddActivity(ScheduleActivity),
|
|
203
|
-
AddLocalActivity(ScheduleLocalActivity),
|
|
204
|
-
RequestCancelActivity(RequestCancelActivity),
|
|
205
|
-
RequestCancelLocalActivity(RequestCancelLocalActivity),
|
|
206
|
-
AddTimer(StartTimer),
|
|
207
|
-
CancelTimer(CancelTimer),
|
|
208
|
-
CompleteWorkflow(CompleteWorkflowExecution),
|
|
209
|
-
FailWorkflow(FailWorkflowExecution),
|
|
210
|
-
QueryResponse(QueryResult),
|
|
211
|
-
ContinueAsNew(ContinueAsNewWorkflowExecution),
|
|
212
|
-
CancelWorkflow(CancelWorkflowExecution),
|
|
213
|
-
SetPatchMarker(SetPatchMarker),
|
|
214
|
-
AddChildWorkflow(StartChildWorkflowExecution),
|
|
215
|
-
CancelUnstartedChild(CancelUnstartedChildWorkflowExecution),
|
|
216
|
-
RequestCancelExternalWorkflow(RequestCancelExternalWorkflowExecution),
|
|
217
|
-
SignalExternalWorkflow(SignalExternalWorkflowExecution),
|
|
218
|
-
CancelSignalWorkflow(CancelSignalWorkflow),
|
|
219
|
-
UpsertSearchAttributes(UpsertWorkflowSearchAttributes),
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
impl TryFrom<WorkflowCommand> for WFCommand {
|
|
223
|
-
type Error = EmptyWorkflowCommandErr;
|
|
224
|
-
|
|
225
|
-
fn try_from(c: WorkflowCommand) -> result::Result<Self, Self::Error> {
|
|
226
|
-
match c.variant.ok_or(EmptyWorkflowCommandErr)? {
|
|
227
|
-
workflow_command::Variant::StartTimer(s) => Ok(Self::AddTimer(s)),
|
|
228
|
-
workflow_command::Variant::CancelTimer(s) => Ok(Self::CancelTimer(s)),
|
|
229
|
-
workflow_command::Variant::ScheduleActivity(s) => Ok(Self::AddActivity(s)),
|
|
230
|
-
workflow_command::Variant::RequestCancelActivity(s) => {
|
|
231
|
-
Ok(Self::RequestCancelActivity(s))
|
|
232
|
-
}
|
|
233
|
-
workflow_command::Variant::CompleteWorkflowExecution(c) => {
|
|
234
|
-
Ok(Self::CompleteWorkflow(c))
|
|
235
|
-
}
|
|
236
|
-
workflow_command::Variant::FailWorkflowExecution(s) => Ok(Self::FailWorkflow(s)),
|
|
237
|
-
workflow_command::Variant::RespondToQuery(s) => Ok(Self::QueryResponse(s)),
|
|
238
|
-
workflow_command::Variant::ContinueAsNewWorkflowExecution(s) => {
|
|
239
|
-
Ok(Self::ContinueAsNew(s))
|
|
240
|
-
}
|
|
241
|
-
workflow_command::Variant::CancelWorkflowExecution(s) => Ok(Self::CancelWorkflow(s)),
|
|
242
|
-
workflow_command::Variant::SetPatchMarker(s) => Ok(Self::SetPatchMarker(s)),
|
|
243
|
-
workflow_command::Variant::StartChildWorkflowExecution(s) => {
|
|
244
|
-
Ok(Self::AddChildWorkflow(s))
|
|
245
|
-
}
|
|
246
|
-
workflow_command::Variant::RequestCancelExternalWorkflowExecution(s) => {
|
|
247
|
-
Ok(Self::RequestCancelExternalWorkflow(s))
|
|
248
|
-
}
|
|
249
|
-
workflow_command::Variant::SignalExternalWorkflowExecution(s) => {
|
|
250
|
-
Ok(Self::SignalExternalWorkflow(s))
|
|
251
|
-
}
|
|
252
|
-
workflow_command::Variant::CancelSignalWorkflow(s) => Ok(Self::CancelSignalWorkflow(s)),
|
|
253
|
-
workflow_command::Variant::CancelUnstartedChildWorkflowExecution(s) => {
|
|
254
|
-
Ok(Self::CancelUnstartedChild(s))
|
|
255
|
-
}
|
|
256
|
-
workflow_command::Variant::ScheduleLocalActivity(s) => Ok(Self::AddLocalActivity(s)),
|
|
257
|
-
workflow_command::Variant::RequestCancelLocalActivity(s) => {
|
|
258
|
-
Ok(Self::RequestCancelLocalActivity(s))
|
|
259
|
-
}
|
|
260
|
-
workflow_command::Variant::UpsertWorkflowSearchAttributesCommandAttributes(s) => {
|
|
261
|
-
Ok(Self::UpsertSearchAttributes(s))
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
|
268
|
-
enum CommandID {
|
|
269
|
-
Timer(u32),
|
|
270
|
-
Activity(u32),
|
|
271
|
-
LocalActivity(u32),
|
|
272
|
-
ChildWorkflowStart(u32),
|
|
273
|
-
SignalExternal(u32),
|
|
274
|
-
CancelExternal(u32),
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/// Details remembered from the workflow execution started event that we may need to recall later.
|
|
278
|
-
/// Is a subset of `WorkflowExecutionStartedEventAttributes`, but avoids holding on to huge fields.
|
|
279
|
-
#[derive(Debug, Clone)]
|
|
280
|
-
pub struct WorkflowStartedInfo {
|
|
281
|
-
workflow_task_timeout: Option<Duration>,
|
|
282
|
-
workflow_execution_timeout: Option<Duration>,
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
#[cfg(test)]
|
|
286
|
-
pub mod managed_wf {
|
|
287
|
-
use super::*;
|
|
288
|
-
use crate::{
|
|
289
|
-
replay::TestHistoryBuilder,
|
|
290
|
-
test_help::TEST_Q,
|
|
291
|
-
workflow::{history_update::TestHBExt, WFCommand, WorkflowFetcher},
|
|
292
|
-
};
|
|
293
|
-
use std::{convert::TryInto, time::Duration};
|
|
294
|
-
use temporal_sdk::{WorkflowFunction, WorkflowResult};
|
|
295
|
-
use temporal_sdk_core_protos::coresdk::{
|
|
296
|
-
activity_result::ActivityExecutionResult,
|
|
297
|
-
common::Payload,
|
|
298
|
-
workflow_activation::{create_evict_activation, remove_from_cache::EvictionReason},
|
|
299
|
-
workflow_completion::{
|
|
300
|
-
workflow_activation_completion::Status, WorkflowActivationCompletion,
|
|
301
|
-
},
|
|
302
|
-
};
|
|
303
|
-
use tokio::{
|
|
304
|
-
sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
|
305
|
-
task::JoinHandle,
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
pub(crate) struct WFFutureDriver {
|
|
309
|
-
completions_rx: UnboundedReceiver<WorkflowActivationCompletion>,
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
#[async_trait::async_trait]
|
|
313
|
-
impl WorkflowFetcher for WFFutureDriver {
|
|
314
|
-
async fn fetch_workflow_iteration_output(&mut self) -> Vec<WFCommand> {
|
|
315
|
-
if let Some(completion) = self.completions_rx.recv().await {
|
|
316
|
-
debug!("Managed wf completion: {}", completion);
|
|
317
|
-
completion
|
|
318
|
-
.status
|
|
319
|
-
.map(|s| match s {
|
|
320
|
-
Status::Successful(s) => s
|
|
321
|
-
.commands
|
|
322
|
-
.into_iter()
|
|
323
|
-
.map(|cmd| cmd.try_into().unwrap())
|
|
324
|
-
.collect(),
|
|
325
|
-
Status::Failed(_) => panic!("Ahh failed"),
|
|
326
|
-
})
|
|
327
|
-
.unwrap_or_default()
|
|
328
|
-
} else {
|
|
329
|
-
// Sender went away so nothing to do here. End of wf/test.
|
|
330
|
-
vec![]
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
#[must_use]
|
|
336
|
-
pub struct ManagedWFFunc {
|
|
337
|
-
mgr: WorkflowManager,
|
|
338
|
-
activation_tx: UnboundedSender<WorkflowActivation>,
|
|
339
|
-
future_handle: Option<JoinHandle<WorkflowResult<()>>>,
|
|
340
|
-
was_shutdown: bool,
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
impl ManagedWFFunc {
|
|
344
|
-
pub fn new(hist: TestHistoryBuilder, func: WorkflowFunction, args: Vec<Payload>) -> Self {
|
|
345
|
-
Self::new_from_update(hist.as_history_update(), func, args)
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
pub fn new_from_update(
|
|
349
|
-
hist: HistoryUpdate,
|
|
350
|
-
func: WorkflowFunction,
|
|
351
|
-
args: Vec<Payload>,
|
|
352
|
-
) -> Self {
|
|
353
|
-
let (completions_tx, completions_rx) = unbounded_channel();
|
|
354
|
-
let (wff, activations) = func.start_workflow(
|
|
355
|
-
"testnamespace".to_string(),
|
|
356
|
-
TEST_Q.to_string(),
|
|
357
|
-
args,
|
|
358
|
-
completions_tx,
|
|
359
|
-
);
|
|
360
|
-
let spawned = tokio::spawn(wff);
|
|
361
|
-
let driver = WFFutureDriver { completions_rx };
|
|
362
|
-
let state_machines = WorkflowMachines::new(
|
|
363
|
-
"test_namespace".to_string(),
|
|
364
|
-
"wfid".to_string(),
|
|
365
|
-
"wftype".to_string(),
|
|
366
|
-
"runid".to_string(),
|
|
367
|
-
hist,
|
|
368
|
-
Box::new(driver).into(),
|
|
369
|
-
Default::default(),
|
|
370
|
-
);
|
|
371
|
-
let mgr = WorkflowManager::new_from_machines(state_machines);
|
|
372
|
-
Self {
|
|
373
|
-
mgr,
|
|
374
|
-
activation_tx: activations,
|
|
375
|
-
future_handle: Some(spawned),
|
|
376
|
-
was_shutdown: false,
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
#[instrument(level = "debug", skip(self))]
|
|
381
|
-
pub(crate) async fn get_next_activation(&mut self) -> Result<WorkflowActivation> {
|
|
382
|
-
let res = self.mgr.get_next_activation().await?;
|
|
383
|
-
debug!("Managed wf next activation: {}", &res);
|
|
384
|
-
self.push_activation_to_wf(&res).await?;
|
|
385
|
-
Ok(res)
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/// Return outgoing server commands as of the last iteration
|
|
389
|
-
pub(crate) fn get_server_commands(&mut self) -> OutgoingServerCommands {
|
|
390
|
-
self.mgr.get_server_commands()
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
pub(crate) fn drain_queued_local_activities(&mut self) -> Vec<LocalActRequest> {
|
|
394
|
-
self.mgr.drain_queued_local_activities()
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/// Feed new history, as if received a new poll result. Returns new activation
|
|
398
|
-
#[instrument(level = "debug", skip(self, update))]
|
|
399
|
-
pub(crate) async fn new_history(
|
|
400
|
-
&mut self,
|
|
401
|
-
update: HistoryUpdate,
|
|
402
|
-
) -> Result<WorkflowActivation> {
|
|
403
|
-
let res = self.mgr.feed_history_from_server(update).await?;
|
|
404
|
-
self.push_activation_to_wf(&res).await?;
|
|
405
|
-
Ok(res)
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/// Say a local activity completed (they always take 1 second in these tests)
|
|
409
|
-
pub(crate) fn complete_local_activity(
|
|
410
|
-
&mut self,
|
|
411
|
-
seq_num: u32,
|
|
412
|
-
result: ActivityExecutionResult,
|
|
413
|
-
) -> Result<bool> {
|
|
414
|
-
self.mgr
|
|
415
|
-
.notify_of_local_result(LocalResolution::LocalActivity(LocalActivityResolution {
|
|
416
|
-
seq: seq_num,
|
|
417
|
-
// We accept normal execution results and do this conversion because there
|
|
418
|
-
// are more helpers for constructing them.
|
|
419
|
-
result: result
|
|
420
|
-
.status
|
|
421
|
-
.expect("LA result must have a status")
|
|
422
|
-
.try_into()
|
|
423
|
-
.expect("LA execution result must be a valid LA result"),
|
|
424
|
-
runtime: Duration::from_secs(1),
|
|
425
|
-
attempt: 1,
|
|
426
|
-
backoff: None,
|
|
427
|
-
// Tests at this level don't use the LA dispatcher, so this is irrelevant
|
|
428
|
-
original_schedule_time: None,
|
|
429
|
-
}))
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/// During testing it can be useful to run through all activations to simulate replay
|
|
433
|
-
/// easily. Returns the last produced activation with jobs in it, or an activation with no
|
|
434
|
-
/// jobs if the first call had no jobs.
|
|
435
|
-
pub(crate) async fn process_all_activations(&mut self) -> Result<WorkflowActivation> {
|
|
436
|
-
let mut last_act = self.get_next_activation().await?;
|
|
437
|
-
let mut next_act = self.get_next_activation().await?;
|
|
438
|
-
while !next_act.jobs.is_empty() {
|
|
439
|
-
last_act = next_act;
|
|
440
|
-
next_act = self.get_next_activation().await?;
|
|
441
|
-
}
|
|
442
|
-
Ok(last_act)
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
pub async fn shutdown(&mut self) -> WorkflowResult<()> {
|
|
446
|
-
self.was_shutdown = true;
|
|
447
|
-
// Send an eviction to ensure wf exits if it has not finished (ex: feeding partial hist)
|
|
448
|
-
let _ = self.activation_tx.send(create_evict_activation(
|
|
449
|
-
"not actually important".to_string(),
|
|
450
|
-
"force shutdown".to_string(),
|
|
451
|
-
EvictionReason::Unspecified,
|
|
452
|
-
));
|
|
453
|
-
self.future_handle.take().unwrap().await.unwrap()
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
#[instrument(level = "debug", skip(self, res))]
|
|
457
|
-
async fn push_activation_to_wf(&mut self, res: &WorkflowActivation) -> Result<()> {
|
|
458
|
-
if res.jobs.is_empty() {
|
|
459
|
-
// Nothing to do here
|
|
460
|
-
return Ok(());
|
|
461
|
-
}
|
|
462
|
-
self.activation_tx
|
|
463
|
-
.send(res.clone())
|
|
464
|
-
.expect("Workflow should not be dropped if we are still sending activations");
|
|
465
|
-
self.mgr.machines.iterate_machines().await?;
|
|
466
|
-
Ok(())
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
impl Drop for ManagedWFFunc {
|
|
471
|
-
fn drop(&mut self) {
|
|
472
|
-
// Double panics cause a SIGILL
|
|
473
|
-
if !self.was_shutdown && !std::thread::panicking() {
|
|
474
|
-
panic!("You must call `shutdown` to properly use ManagedWFFunc in tests")
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
use crate::{telemetry::metrics::MetricsContext, workflow::WorkflowCachingPolicy};
|
|
2
|
-
use lru::LruCache;
|
|
3
|
-
use std::{
|
|
4
|
-
future::Future,
|
|
5
|
-
sync::{
|
|
6
|
-
atomic::{AtomicUsize, Ordering},
|
|
7
|
-
Arc,
|
|
8
|
-
},
|
|
9
|
-
};
|
|
10
|
-
use tokio::sync::Notify;
|
|
11
|
-
|
|
12
|
-
/// Helps to maintain an LRU ordering in which workflow runs have been accessed so that old runs may
|
|
13
|
-
/// be evicted once we reach the cap.
|
|
14
|
-
#[derive(Debug)]
|
|
15
|
-
pub(crate) struct WorkflowCacheManager {
|
|
16
|
-
cache: LruCache<String, ()>,
|
|
17
|
-
metrics: MetricsContext,
|
|
18
|
-
cap_notify: Arc<Notify>,
|
|
19
|
-
cache_size: Arc<AtomicUsize>,
|
|
20
|
-
cap_mutex: Arc<tokio::sync::Mutex<()>>,
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
impl WorkflowCacheManager {
|
|
24
|
-
pub fn new(policy: WorkflowCachingPolicy, metrics: MetricsContext) -> Self {
|
|
25
|
-
let cap = match policy {
|
|
26
|
-
WorkflowCachingPolicy::Sticky {
|
|
27
|
-
max_cached_workflows,
|
|
28
|
-
} => max_cached_workflows,
|
|
29
|
-
_ => 0,
|
|
30
|
-
};
|
|
31
|
-
Self {
|
|
32
|
-
cache: LruCache::new(cap),
|
|
33
|
-
metrics,
|
|
34
|
-
cap_notify: Arc::new(Notify::new()),
|
|
35
|
-
cache_size: Arc::new(AtomicUsize::new(0)),
|
|
36
|
-
cap_mutex: Arc::new(tokio::sync::Mutex::new(())),
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
#[cfg(test)]
|
|
41
|
-
fn new_test(policy: WorkflowCachingPolicy) -> Self {
|
|
42
|
-
Self::new(policy, Default::default())
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/// Resolves once there is an open slot in the cache. The passed in closure can be used to
|
|
46
|
-
/// exit the wait loop if it returns true even if the cache is not below the limit. It will be
|
|
47
|
-
/// re-evaluated every time there is an insert or remove to the cache, even if it did not change
|
|
48
|
-
/// the size.
|
|
49
|
-
pub fn wait_for_capacity<Fun>(&self, early_exit: Fun) -> Option<impl Future<Output = ()>>
|
|
50
|
-
where
|
|
51
|
-
Fun: Fn() -> bool,
|
|
52
|
-
{
|
|
53
|
-
if self.cache.cap() == 0 {
|
|
54
|
-
return None;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
let size = self.cache_size.clone();
|
|
58
|
-
let notify = self.cap_notify.clone();
|
|
59
|
-
let cap = self.cache.cap();
|
|
60
|
-
let mx = self.cap_mutex.clone();
|
|
61
|
-
Some(async move {
|
|
62
|
-
let _l = mx.lock().await;
|
|
63
|
-
while !early_exit() && size.load(Ordering::Acquire) >= cap {
|
|
64
|
-
notify.notified().await;
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/// Inserts a record associated with the run id into the lru cache.
|
|
70
|
-
/// Once cache reaches capacity, overflow records will be returned back to the caller.
|
|
71
|
-
pub fn insert(&mut self, run_id: &str) -> Option<String> {
|
|
72
|
-
let res = if self.cache.len() < self.cache.cap() {
|
|
73
|
-
// Blindly add a record into the cache, since it still has capacity.
|
|
74
|
-
self.cache.put(run_id.to_owned(), ());
|
|
75
|
-
None
|
|
76
|
-
} else if self.cache.cap() == 0 {
|
|
77
|
-
// Run id should be evicted right away as cache size is 0.
|
|
78
|
-
Some(run_id.to_owned())
|
|
79
|
-
} else {
|
|
80
|
-
let maybe_got_evicted = self.cache.peek_lru().map(|r| r.0.clone());
|
|
81
|
-
let not_cached = self.cache.put(run_id.to_owned(), ()).is_none();
|
|
82
|
-
not_cached.then(|| maybe_got_evicted).flatten()
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
self.size_changed();
|
|
86
|
-
|
|
87
|
-
res
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/// If run id exists in the cache it will be moved to the top of the LRU cache.
|
|
91
|
-
pub fn touch(&mut self, run_id: &str) {
|
|
92
|
-
self.cache.get(run_id);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
pub fn remove(&mut self, run_id: &str) {
|
|
96
|
-
self.cache.pop(run_id);
|
|
97
|
-
self.size_changed();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
fn size_changed(&self) {
|
|
101
|
-
let size = self.cache.len();
|
|
102
|
-
self.metrics.cache_size(size as u64);
|
|
103
|
-
self.cache_size.store(size, Ordering::Release);
|
|
104
|
-
self.cap_notify.notify_one();
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
#[cfg(test)]
|
|
109
|
-
mod tests {
|
|
110
|
-
use super::*;
|
|
111
|
-
#[test]
|
|
112
|
-
fn insert_with_overflow() {
|
|
113
|
-
let mut wcm = WorkflowCacheManager::new_test(WorkflowCachingPolicy::Sticky {
|
|
114
|
-
max_cached_workflows: 2,
|
|
115
|
-
});
|
|
116
|
-
assert_matches!(wcm.insert("1"), None);
|
|
117
|
-
assert_matches!(wcm.insert("2"), None);
|
|
118
|
-
assert_matches!(wcm.insert("3"), Some(run_id) => {
|
|
119
|
-
assert_eq!(run_id, "1");
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
#[test]
|
|
124
|
-
fn insert_remove_insert() {
|
|
125
|
-
let mut wcm = WorkflowCacheManager::new_test(WorkflowCachingPolicy::Sticky {
|
|
126
|
-
max_cached_workflows: 1,
|
|
127
|
-
});
|
|
128
|
-
assert_matches!(wcm.insert("1"), None);
|
|
129
|
-
wcm.remove("1");
|
|
130
|
-
assert_matches!(wcm.insert("2"), None);
|
|
131
|
-
assert_matches!(wcm.insert("3"), Some(run_id) => {
|
|
132
|
-
assert_eq!(run_id, "2");
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
#[test]
|
|
137
|
-
fn insert_same_id_twice_doesnt_evict_self() {
|
|
138
|
-
let mut wcm = WorkflowCacheManager::new_test(WorkflowCachingPolicy::Sticky {
|
|
139
|
-
max_cached_workflows: 1,
|
|
140
|
-
});
|
|
141
|
-
assert_matches!(wcm.insert("1"), None);
|
|
142
|
-
assert_matches!(wcm.insert("1"), None);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
#[test]
|
|
146
|
-
fn insert_and_touch() {
|
|
147
|
-
let mut wcm = WorkflowCacheManager::new_test(WorkflowCachingPolicy::Sticky {
|
|
148
|
-
max_cached_workflows: 2,
|
|
149
|
-
});
|
|
150
|
-
assert_matches!(wcm.insert("1"), None);
|
|
151
|
-
assert_matches!(wcm.insert("2"), None);
|
|
152
|
-
wcm.touch("1");
|
|
153
|
-
assert_matches!(wcm.insert("3"), Some(run_id) => {
|
|
154
|
-
assert_eq!(run_id, "2");
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
#[test]
|
|
159
|
-
fn touch_early() {
|
|
160
|
-
let mut wcm = WorkflowCacheManager::new_test(WorkflowCachingPolicy::Sticky {
|
|
161
|
-
max_cached_workflows: 2,
|
|
162
|
-
});
|
|
163
|
-
wcm.touch("1");
|
|
164
|
-
assert_matches!(wcm.insert("1"), None);
|
|
165
|
-
assert_matches!(wcm.insert("2"), None);
|
|
166
|
-
assert_matches!(wcm.insert("3"), Some(run_id) => {
|
|
167
|
-
assert_eq!(run_id, "1");
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
#[test]
|
|
172
|
-
fn zero_cache_size() {
|
|
173
|
-
let mut wcm = WorkflowCacheManager::new_test(WorkflowCachingPolicy::Sticky {
|
|
174
|
-
max_cached_workflows: 0,
|
|
175
|
-
});
|
|
176
|
-
assert_matches!(wcm.insert("1"), Some(run_id) => {
|
|
177
|
-
assert_eq!(run_id, "1");
|
|
178
|
-
});
|
|
179
|
-
assert_matches!(wcm.insert("2"), Some(run_id) => {
|
|
180
|
-
assert_eq!(run_id, "2");
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
#[test]
|
|
185
|
-
fn non_sticky_always_pending_eviction() {
|
|
186
|
-
let mut wcm = WorkflowCacheManager::new_test(WorkflowCachingPolicy::NonSticky);
|
|
187
|
-
assert_matches!(wcm.insert("1"), Some(run_id) => {
|
|
188
|
-
assert_eq!(run_id, "1");
|
|
189
|
-
});
|
|
190
|
-
assert_matches!(wcm.insert("2"), Some(run_id) => {
|
|
191
|
-
assert_eq!(run_id, "2");
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
}
|