@temporalio/core-bridge 1.5.2 → 1.7.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 +304 -112
- package/lib/index.d.ts +8 -6
- package/lib/index.js.map +1 -1
- package/package.json +9 -4
- 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/Dockerfile +2 -2
- package/sdk-core/.buildkite/docker/docker-compose.yaml +1 -1
- package/sdk-core/.buildkite/pipeline.yml +2 -4
- package/sdk-core/.cargo/config.toml +5 -2
- package/sdk-core/.github/workflows/heavy.yml +29 -0
- package/sdk-core/Cargo.toml +1 -1
- package/sdk-core/README.md +20 -10
- package/sdk-core/client/src/lib.rs +215 -39
- package/sdk-core/client/src/metrics.rs +17 -8
- package/sdk-core/client/src/raw.rs +4 -4
- package/sdk-core/client/src/retry.rs +32 -20
- package/sdk-core/core/Cargo.toml +25 -12
- package/sdk-core/core/src/abstractions/take_cell.rs +28 -0
- package/sdk-core/core/src/abstractions.rs +204 -14
- package/sdk-core/core/src/core_tests/activity_tasks.rs +143 -50
- package/sdk-core/core/src/core_tests/child_workflows.rs +6 -5
- package/sdk-core/core/src/core_tests/determinism.rs +165 -2
- package/sdk-core/core/src/core_tests/local_activities.rs +431 -43
- package/sdk-core/core/src/core_tests/queries.rs +34 -16
- package/sdk-core/core/src/core_tests/workers.rs +8 -5
- package/sdk-core/core/src/core_tests/workflow_tasks.rs +588 -55
- package/sdk-core/core/src/ephemeral_server/mod.rs +113 -12
- package/sdk-core/core/src/internal_flags.rs +155 -0
- package/sdk-core/core/src/lib.rs +16 -9
- package/sdk-core/core/src/protosext/mod.rs +1 -1
- package/sdk-core/core/src/replay/mod.rs +16 -27
- package/sdk-core/core/src/telemetry/log_export.rs +1 -1
- package/sdk-core/core/src/telemetry/metrics.rs +69 -35
- package/sdk-core/core/src/telemetry/mod.rs +60 -21
- package/sdk-core/core/src/telemetry/prometheus_server.rs +19 -13
- package/sdk-core/core/src/test_help/mod.rs +73 -14
- package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +119 -160
- package/sdk-core/core/src/worker/activities/activity_task_poller_stream.rs +89 -0
- package/sdk-core/core/src/worker/activities/local_activities.rs +379 -129
- package/sdk-core/core/src/worker/activities.rs +350 -175
- package/sdk-core/core/src/worker/client/mocks.rs +22 -2
- package/sdk-core/core/src/worker/client.rs +18 -2
- package/sdk-core/core/src/worker/mod.rs +183 -64
- package/sdk-core/core/src/worker/workflow/bridge.rs +1 -3
- package/sdk-core/core/src/worker/workflow/driven_workflow.rs +3 -5
- package/sdk-core/core/src/worker/workflow/history_update.rs +916 -277
- package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +216 -183
- package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +9 -12
- package/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +7 -9
- package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +160 -87
- package/sdk-core/core/src/worker/workflow/machines/complete_workflow_state_machine.rs +13 -14
- package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +7 -9
- package/sdk-core/core/src/worker/workflow/machines/fail_workflow_state_machine.rs +14 -17
- package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +242 -110
- package/sdk-core/core/src/worker/workflow/machines/mod.rs +27 -19
- package/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +9 -11
- package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +321 -206
- package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +13 -18
- package/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +20 -29
- package/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +2 -2
- package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +257 -51
- package/sdk-core/core/src/worker/workflow/machines/workflow_machines/local_acts.rs +6 -17
- package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +310 -150
- package/sdk-core/core/src/worker/workflow/machines/workflow_task_state_machine.rs +17 -20
- package/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +31 -15
- package/sdk-core/core/src/worker/workflow/managed_run.rs +1052 -380
- package/sdk-core/core/src/worker/workflow/mod.rs +598 -390
- package/sdk-core/core/src/worker/workflow/run_cache.rs +40 -57
- package/sdk-core/core/src/worker/workflow/wft_extraction.rs +137 -0
- package/sdk-core/core/src/worker/workflow/wft_poller.rs +1 -4
- package/sdk-core/core/src/worker/workflow/workflow_stream/saved_wf_inputs.rs +117 -0
- package/sdk-core/core/src/worker/workflow/workflow_stream/tonic_status_serde.rs +24 -0
- package/sdk-core/core/src/worker/workflow/workflow_stream.rs +469 -718
- package/sdk-core/core-api/Cargo.toml +2 -1
- package/sdk-core/core-api/src/errors.rs +1 -34
- package/sdk-core/core-api/src/lib.rs +19 -9
- package/sdk-core/core-api/src/telemetry.rs +4 -6
- package/sdk-core/core-api/src/worker.rs +19 -1
- package/sdk-core/etc/deps.svg +115 -140
- package/sdk-core/etc/regen-depgraph.sh +5 -0
- package/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +86 -61
- package/sdk-core/fsm/rustfsm_trait/src/lib.rs +29 -71
- package/sdk-core/histories/ends_empty_wft_complete.bin +0 -0
- package/sdk-core/histories/evict_while_la_running_no_interference-16_history.bin +0 -0
- package/sdk-core/histories/old_change_marker_format.bin +0 -0
- package/sdk-core/protos/api_upstream/.github/CODEOWNERS +2 -1
- package/sdk-core/protos/api_upstream/Makefile +6 -6
- package/sdk-core/protos/api_upstream/build/go.mod +7 -0
- package/sdk-core/protos/api_upstream/build/go.sum +5 -0
- package/sdk-core/protos/api_upstream/build/tools.go +29 -0
- package/sdk-core/protos/api_upstream/go.mod +6 -0
- package/sdk-core/protos/api_upstream/temporal/api/batch/v1/message.proto +9 -2
- package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +7 -26
- package/sdk-core/protos/api_upstream/temporal/api/common/v1/message.proto +13 -2
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +3 -2
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/command_type.proto +3 -7
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/common.proto +3 -2
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/event_type.proto +8 -8
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +25 -2
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/namespace.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/query.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/reset.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/schedule.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/update.proto +24 -19
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/workflow.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/errordetails/v1/message.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/failure/v1/message.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/filter/v1/message.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +49 -26
- package/sdk-core/protos/api_upstream/temporal/api/namespace/v1/message.proto +4 -2
- package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +5 -2
- package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/protocol/v1/message.proto +57 -0
- package/sdk-core/protos/api_upstream/temporal/api/query/v1/message.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/replication/v1/message.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/schedule/v1/message.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/sdk/v1/task_complete_metadata.proto +63 -0
- package/sdk-core/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/update/v1/message.proto +71 -6
- package/sdk-core/protos/api_upstream/temporal/api/version/v1/message.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +64 -28
- package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +4 -4
- package/sdk-core/protos/local/temporal/sdk/core/activity_result/activity_result.proto +7 -8
- package/sdk-core/protos/local/temporal/sdk/core/activity_task/activity_task.proto +10 -7
- package/sdk-core/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +19 -30
- package/sdk-core/protos/local/temporal/sdk/core/common/common.proto +1 -0
- package/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +1 -0
- package/sdk-core/protos/local/temporal/sdk/core/external_data/external_data.proto +8 -0
- package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +67 -60
- package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +85 -84
- package/sdk-core/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +9 -3
- package/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/request_response.proto +2 -2
- package/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/service.proto +2 -2
- package/sdk-core/sdk/Cargo.toml +5 -4
- package/sdk-core/sdk/src/lib.rs +108 -26
- package/sdk-core/sdk/src/workflow_context/options.rs +7 -1
- package/sdk-core/sdk/src/workflow_context.rs +24 -17
- package/sdk-core/sdk/src/workflow_future.rs +16 -15
- package/sdk-core/sdk-core-protos/Cargo.toml +5 -2
- package/sdk-core/sdk-core-protos/build.rs +36 -2
- package/sdk-core/sdk-core-protos/src/history_builder.rs +138 -106
- package/sdk-core/sdk-core-protos/src/history_info.rs +10 -1
- package/sdk-core/sdk-core-protos/src/lib.rs +272 -87
- package/sdk-core/sdk-core-protos/src/task_token.rs +12 -2
- package/sdk-core/test-utils/Cargo.toml +3 -1
- package/sdk-core/test-utils/src/canned_histories.rs +106 -296
- package/sdk-core/test-utils/src/histfetch.rs +1 -1
- package/sdk-core/test-utils/src/lib.rs +82 -23
- package/sdk-core/test-utils/src/wf_input_saver.rs +50 -0
- package/sdk-core/test-utils/src/workflows.rs +29 -0
- package/sdk-core/tests/fuzzy_workflow.rs +130 -0
- package/sdk-core/tests/{load_tests.rs → heavy_tests.rs} +125 -51
- package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +25 -3
- package/sdk-core/tests/integ_tests/heartbeat_tests.rs +10 -5
- package/sdk-core/tests/integ_tests/metrics_tests.rs +218 -16
- package/sdk-core/tests/integ_tests/polling_tests.rs +4 -47
- package/sdk-core/tests/integ_tests/queries_tests.rs +5 -128
- package/sdk-core/tests/integ_tests/visibility_tests.rs +83 -25
- package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +161 -72
- package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +1 -0
- package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +6 -13
- package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +80 -3
- package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +6 -2
- package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +3 -10
- package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +94 -200
- package/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +2 -4
- package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +34 -28
- package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +76 -7
- package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -0
- package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +18 -14
- package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +6 -20
- package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +10 -21
- package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +7 -8
- package/sdk-core/tests/integ_tests/workflow_tests.rs +13 -14
- package/sdk-core/tests/main.rs +3 -13
- package/sdk-core/tests/runner.rs +75 -36
- package/sdk-core/tests/wf_input_replay.rs +32 -0
- package/src/conversions.rs +14 -8
- package/src/runtime.rs +9 -8
- package/ts/index.ts +8 -6
- package/sdk-core/bridge-ffi/Cargo.toml +0 -24
- package/sdk-core/bridge-ffi/LICENSE.txt +0 -23
- package/sdk-core/bridge-ffi/build.rs +0 -25
- package/sdk-core/bridge-ffi/include/sdk-core-bridge.h +0 -224
- package/sdk-core/bridge-ffi/src/lib.rs +0 -746
- package/sdk-core/bridge-ffi/src/wrappers.rs +0 -221
- package/sdk-core/protos/local/temporal/sdk/core/bridge/bridge.proto +0 -210
- package/sdk-core/sdk/src/conversions.rs +0 -8
|
@@ -1,57 +1,99 @@
|
|
|
1
1
|
use crate::{
|
|
2
|
-
|
|
3
|
-
worker::
|
|
2
|
+
protosext::ValidPollWFTQResponse,
|
|
3
|
+
worker::{
|
|
4
|
+
client::WorkerClient,
|
|
5
|
+
workflow::{CacheMissFetchReq, PermittedWFT, PreparedWFT},
|
|
6
|
+
},
|
|
4
7
|
};
|
|
5
|
-
use futures::{future::BoxFuture,
|
|
8
|
+
use futures::{future::BoxFuture, FutureExt, Stream};
|
|
9
|
+
use itertools::Itertools;
|
|
6
10
|
use std::{
|
|
7
11
|
collections::VecDeque,
|
|
8
12
|
fmt::Debug,
|
|
9
13
|
future::Future,
|
|
14
|
+
mem,
|
|
15
|
+
mem::transmute,
|
|
10
16
|
pin::Pin,
|
|
11
17
|
sync::Arc,
|
|
12
18
|
task::{Context, Poll},
|
|
13
19
|
};
|
|
14
20
|
use temporal_sdk_core_protos::temporal::api::{
|
|
15
21
|
enums::v1::EventType,
|
|
16
|
-
history::v1::{History, HistoryEvent},
|
|
17
|
-
workflowservice::v1::GetWorkflowExecutionHistoryResponse,
|
|
22
|
+
history::v1::{history_event, History, HistoryEvent, WorkflowTaskCompletedEventAttributes},
|
|
18
23
|
};
|
|
19
24
|
use tracing::Instrument;
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
lazy_static::lazy_static! {
|
|
27
|
+
static ref EMPTY_FETCH_ERR: tonic::Status
|
|
28
|
+
= tonic::Status::unknown("Fetched empty history page");
|
|
29
|
+
static ref EMPTY_TASK_ERR: tonic::Status
|
|
30
|
+
= tonic::Status::unknown("Received an empty workflow task with no queries or history");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Represents one or more complete WFT sequences. History events are expected to be consumed from
|
|
34
|
+
/// it and applied to the state machines via [HistoryUpdate::take_next_wft_sequence]
|
|
35
|
+
#[cfg_attr(
|
|
36
|
+
feature = "save_wf_inputs",
|
|
37
|
+
derive(serde::Serialize, serde::Deserialize)
|
|
38
|
+
)]
|
|
24
39
|
pub struct HistoryUpdate {
|
|
25
|
-
events:
|
|
26
|
-
///
|
|
27
|
-
///
|
|
28
|
-
///
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
40
|
+
events: Vec<HistoryEvent>,
|
|
41
|
+
/// The event ID of the last started WFT, as according to the WFT which this update was
|
|
42
|
+
/// extracted from. Hence, while processing multiple logical WFTs during replay which were part
|
|
43
|
+
/// of one large history fetched from server, multiple updates may have the same value here.
|
|
44
|
+
pub previous_wft_started_id: i64,
|
|
45
|
+
/// The `started_event_id` field from the WFT which this update is tied to. Multiple updates
|
|
46
|
+
/// may have the same value if they're associated with the same WFT.
|
|
47
|
+
pub wft_started_id: i64,
|
|
48
|
+
/// True if this update contains the final WFT in history, and no more attempts to extract
|
|
49
|
+
/// additional updates should be made.
|
|
50
|
+
has_last_wft: bool,
|
|
32
51
|
}
|
|
33
52
|
impl Debug for HistoryUpdate {
|
|
34
53
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
54
|
+
if self.is_real() {
|
|
55
|
+
write!(
|
|
56
|
+
f,
|
|
57
|
+
"HistoryUpdate(previous_started_event_id: {}, length: {}, first_event_id: {:?})",
|
|
58
|
+
self.previous_wft_started_id,
|
|
59
|
+
self.events.len(),
|
|
60
|
+
self.events.first().map(|e| e.event_id)
|
|
61
|
+
)
|
|
62
|
+
} else {
|
|
63
|
+
write!(f, "DummyHistoryUpdate")
|
|
64
|
+
}
|
|
40
65
|
}
|
|
41
66
|
}
|
|
42
67
|
|
|
68
|
+
#[derive(Debug)]
|
|
69
|
+
pub enum NextWFT {
|
|
70
|
+
ReplayOver,
|
|
71
|
+
WFT(Vec<HistoryEvent>, bool),
|
|
72
|
+
NeedFetch,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#[derive(derive_more::DebugCustom)]
|
|
76
|
+
#[debug(fmt = "HistoryPaginator(run_id: {run_id})")]
|
|
77
|
+
#[cfg_attr(
|
|
78
|
+
feature = "save_wf_inputs",
|
|
79
|
+
derive(serde::Serialize, serde::Deserialize),
|
|
80
|
+
serde(default = "HistoryPaginator::fake_deserialized")
|
|
81
|
+
)]
|
|
43
82
|
pub struct HistoryPaginator {
|
|
44
|
-
|
|
83
|
+
pub(crate) wf_id: String,
|
|
84
|
+
pub(crate) run_id: String,
|
|
85
|
+
pub(crate) previous_wft_started_id: i64,
|
|
86
|
+
pub(crate) wft_started_event_id: i64,
|
|
87
|
+
|
|
88
|
+
#[cfg_attr(feature = "save_wf_inputs", serde(skip))]
|
|
45
89
|
client: Arc<dyn WorkerClient>,
|
|
90
|
+
#[cfg_attr(feature = "save_wf_inputs", serde(skip))]
|
|
46
91
|
event_queue: VecDeque<HistoryEvent>,
|
|
47
|
-
|
|
48
|
-
run_id: String,
|
|
92
|
+
#[cfg_attr(feature = "save_wf_inputs", serde(skip))]
|
|
49
93
|
next_page_token: NextPageToken,
|
|
50
|
-
open_history_request:
|
|
51
|
-
Option<BoxFuture<'static, Result<GetWorkflowExecutionHistoryResponse, tonic::Status>>>,
|
|
52
94
|
/// These are events that should be returned once pagination has finished. This only happens
|
|
53
95
|
/// during cache misses, where we got a partial task but need to fetch history from the start.
|
|
54
|
-
|
|
96
|
+
#[cfg_attr(feature = "save_wf_inputs", serde(skip))]
|
|
55
97
|
final_events: Vec<HistoryEvent>,
|
|
56
98
|
}
|
|
57
99
|
|
|
@@ -77,8 +119,77 @@ impl From<Vec<u8>> for NextPageToken {
|
|
|
77
119
|
}
|
|
78
120
|
|
|
79
121
|
impl HistoryPaginator {
|
|
80
|
-
|
|
122
|
+
/// Use a new poll response to create a new [WFTPaginator], returning it and the
|
|
123
|
+
/// [PreparedWFT] extracted from it that can be fed into workflow state.
|
|
124
|
+
pub(super) async fn from_poll(
|
|
125
|
+
wft: ValidPollWFTQResponse,
|
|
126
|
+
client: Arc<dyn WorkerClient>,
|
|
127
|
+
) -> Result<(Self, PreparedWFT), tonic::Status> {
|
|
128
|
+
let empty_hist = wft.history.events.is_empty();
|
|
129
|
+
let npt = if empty_hist {
|
|
130
|
+
NextPageToken::FetchFromStart
|
|
131
|
+
} else {
|
|
132
|
+
wft.next_page_token.into()
|
|
133
|
+
};
|
|
134
|
+
let mut paginator = HistoryPaginator::new(
|
|
135
|
+
wft.history,
|
|
136
|
+
wft.previous_started_event_id,
|
|
137
|
+
wft.started_event_id,
|
|
138
|
+
wft.workflow_execution.workflow_id.clone(),
|
|
139
|
+
wft.workflow_execution.run_id.clone(),
|
|
140
|
+
npt,
|
|
141
|
+
client,
|
|
142
|
+
);
|
|
143
|
+
if empty_hist && wft.legacy_query.is_none() && wft.query_requests.is_empty() {
|
|
144
|
+
return Err(EMPTY_TASK_ERR.clone());
|
|
145
|
+
}
|
|
146
|
+
let update = if empty_hist {
|
|
147
|
+
HistoryUpdate::from_events(
|
|
148
|
+
[],
|
|
149
|
+
wft.previous_started_event_id,
|
|
150
|
+
wft.started_event_id,
|
|
151
|
+
true,
|
|
152
|
+
)
|
|
153
|
+
.0
|
|
154
|
+
} else {
|
|
155
|
+
paginator.extract_next_update().await?
|
|
156
|
+
};
|
|
157
|
+
let prepared = PreparedWFT {
|
|
158
|
+
task_token: wft.task_token,
|
|
159
|
+
attempt: wft.attempt,
|
|
160
|
+
execution: wft.workflow_execution,
|
|
161
|
+
workflow_type: wft.workflow_type,
|
|
162
|
+
legacy_query: wft.legacy_query,
|
|
163
|
+
query_requests: wft.query_requests,
|
|
164
|
+
update,
|
|
165
|
+
};
|
|
166
|
+
Ok((paginator, prepared))
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
pub(super) async fn from_fetchreq(
|
|
170
|
+
mut req: CacheMissFetchReq,
|
|
171
|
+
client: Arc<dyn WorkerClient>,
|
|
172
|
+
) -> Result<PermittedWFT, tonic::Status> {
|
|
173
|
+
let mut paginator = Self {
|
|
174
|
+
wf_id: req.original_wft.work.execution.workflow_id.clone(),
|
|
175
|
+
run_id: req.original_wft.work.execution.run_id.clone(),
|
|
176
|
+
previous_wft_started_id: req.original_wft.work.update.previous_wft_started_id,
|
|
177
|
+
wft_started_event_id: req.original_wft.work.update.wft_started_id,
|
|
178
|
+
client,
|
|
179
|
+
event_queue: Default::default(),
|
|
180
|
+
next_page_token: NextPageToken::FetchFromStart,
|
|
181
|
+
final_events: vec![],
|
|
182
|
+
};
|
|
183
|
+
let first_update = paginator.extract_next_update().await?;
|
|
184
|
+
req.original_wft.work.update = first_update;
|
|
185
|
+
req.original_wft.paginator = paginator;
|
|
186
|
+
Ok(req.original_wft)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
fn new(
|
|
81
190
|
initial_history: History,
|
|
191
|
+
previous_wft_started_id: i64,
|
|
192
|
+
wft_started_event_id: i64,
|
|
82
193
|
wf_id: String,
|
|
83
194
|
run_id: String,
|
|
84
195
|
next_page_token: impl Into<NextPageToken>,
|
|
@@ -97,20 +208,120 @@ impl HistoryPaginator {
|
|
|
97
208
|
wf_id,
|
|
98
209
|
run_id,
|
|
99
210
|
next_page_token,
|
|
100
|
-
open_history_request: None,
|
|
101
211
|
final_events,
|
|
212
|
+
previous_wft_started_id,
|
|
213
|
+
wft_started_event_id,
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
#[cfg(feature = "save_wf_inputs")]
|
|
218
|
+
pub(super) fn fake_deserialized() -> HistoryPaginator {
|
|
219
|
+
use crate::worker::client::mocks::mock_manual_workflow_client;
|
|
220
|
+
HistoryPaginator {
|
|
221
|
+
client: Arc::new(mock_manual_workflow_client()),
|
|
222
|
+
event_queue: Default::default(),
|
|
223
|
+
wf_id: "".to_string(),
|
|
224
|
+
run_id: "".to_string(),
|
|
225
|
+
next_page_token: NextPageToken::FetchFromStart,
|
|
226
|
+
final_events: vec![],
|
|
227
|
+
previous_wft_started_id: -2,
|
|
228
|
+
wft_started_event_id: -2,
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/// Return at least the next two WFT sequences (as determined by the passed-in ID) as a
|
|
233
|
+
/// [HistoryUpdate]. Two sequences supports the required peek-ahead during replay without
|
|
234
|
+
/// unnecessary back-and-forth.
|
|
235
|
+
///
|
|
236
|
+
/// If there are already enough events buffered in memory, they will all be returned. Including
|
|
237
|
+
/// possibly (likely, during replay) more than just the next two WFTs.
|
|
238
|
+
///
|
|
239
|
+
/// If there are insufficient events to constitute two WFTs, then we will fetch pages until
|
|
240
|
+
/// we have two, or until we are at the end of history.
|
|
241
|
+
pub(crate) async fn extract_next_update(&mut self) -> Result<HistoryUpdate, tonic::Status> {
|
|
242
|
+
loop {
|
|
243
|
+
let no_next_page = !self.get_next_page().await?;
|
|
244
|
+
let current_events = mem::take(&mut self.event_queue);
|
|
245
|
+
let seen_enough_events = current_events
|
|
246
|
+
.back()
|
|
247
|
+
.map(|e| e.event_id)
|
|
248
|
+
.unwrap_or_default()
|
|
249
|
+
>= self.wft_started_event_id;
|
|
250
|
+
if current_events.is_empty() || (no_next_page && !seen_enough_events) {
|
|
251
|
+
// If next page fetching happened, and we still ended up with no or insufficient
|
|
252
|
+
// events, something is wrong. We're expecting there to be more events to be able to
|
|
253
|
+
// extract this update, but server isn't giving us any. We have no choice except to
|
|
254
|
+
// give up and evict.
|
|
255
|
+
error!(
|
|
256
|
+
"We expected to be able to fetch more events but server says there are none"
|
|
257
|
+
);
|
|
258
|
+
return Err(EMPTY_FETCH_ERR.clone());
|
|
259
|
+
}
|
|
260
|
+
let first_event_id = current_events.front().unwrap().event_id;
|
|
261
|
+
// We only *really* have the last WFT if the events go all the way up to at least the
|
|
262
|
+
// WFT started event id. Otherwise we somehow still have partial history.
|
|
263
|
+
let no_more = matches!(self.next_page_token, NextPageToken::Done) && seen_enough_events;
|
|
264
|
+
let (update, extra) = HistoryUpdate::from_events(
|
|
265
|
+
current_events,
|
|
266
|
+
self.previous_wft_started_id,
|
|
267
|
+
self.wft_started_event_id,
|
|
268
|
+
no_more,
|
|
269
|
+
);
|
|
270
|
+
let extra_eid_same = extra
|
|
271
|
+
.first()
|
|
272
|
+
.map(|e| e.event_id == first_event_id)
|
|
273
|
+
.unwrap_or_default();
|
|
274
|
+
// If there are some events at the end of the fetched events which represent only a
|
|
275
|
+
// portion of a complete WFT, retain them to be used in the next extraction.
|
|
276
|
+
self.event_queue = extra.into();
|
|
277
|
+
if !no_more && extra_eid_same {
|
|
278
|
+
// There was not a meaningful WFT in the whole page. We must fetch more.
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
return Ok(update);
|
|
102
282
|
}
|
|
103
283
|
}
|
|
104
284
|
|
|
105
|
-
|
|
106
|
-
|
|
285
|
+
/// Fetches the next page and adds it to the internal queue. Returns true if a fetch was
|
|
286
|
+
/// performed, false if there is no next page.
|
|
287
|
+
async fn get_next_page(&mut self) -> Result<bool, tonic::Status> {
|
|
288
|
+
let history = loop {
|
|
289
|
+
let npt = match mem::replace(&mut self.next_page_token, NextPageToken::Done) {
|
|
290
|
+
// If the last page token we got was empty, we're done.
|
|
291
|
+
NextPageToken::Done => return Ok(false),
|
|
292
|
+
NextPageToken::FetchFromStart => vec![],
|
|
293
|
+
NextPageToken::Next(v) => v,
|
|
294
|
+
};
|
|
295
|
+
debug!(run_id=%self.run_id, "Fetching new history page");
|
|
296
|
+
let fetch_res = self
|
|
297
|
+
.client
|
|
298
|
+
.get_workflow_execution_history(self.wf_id.clone(), Some(self.run_id.clone()), npt)
|
|
299
|
+
.instrument(span!(tracing::Level::TRACE, "fetch_history_in_paginator"))
|
|
300
|
+
.await?;
|
|
301
|
+
|
|
302
|
+
self.next_page_token = fetch_res.next_page_token.into();
|
|
303
|
+
|
|
304
|
+
let history_is_empty = fetch_res
|
|
305
|
+
.history
|
|
306
|
+
.as_ref()
|
|
307
|
+
.map(|h| h.events.is_empty())
|
|
308
|
+
.unwrap_or(true);
|
|
309
|
+
if history_is_empty && matches!(&self.next_page_token, NextPageToken::Next(_)) {
|
|
310
|
+
// If the fetch returned an empty history, but there *was* a next page token,
|
|
311
|
+
// immediately try to get that.
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
// Async doesn't love recursion so we do this instead.
|
|
315
|
+
break fetch_res.history;
|
|
316
|
+
};
|
|
317
|
+
|
|
107
318
|
self.event_queue
|
|
108
|
-
.extend(
|
|
319
|
+
.extend(history.map(|h| h.events).unwrap_or_default());
|
|
109
320
|
if matches!(&self.next_page_token, NextPageToken::Done) {
|
|
110
321
|
// If finished, we need to extend the queue with the final events, skipping any
|
|
111
322
|
// which are already present.
|
|
112
323
|
if let Some(last_event_id) = self.event_queue.back().map(|e| e.event_id) {
|
|
113
|
-
let final_events =
|
|
324
|
+
let final_events = mem::take(&mut self.final_events);
|
|
114
325
|
self.event_queue.extend(
|
|
115
326
|
final_events
|
|
116
327
|
.into_iter()
|
|
@@ -118,374 +329,576 @@ impl HistoryPaginator {
|
|
|
118
329
|
);
|
|
119
330
|
}
|
|
120
331
|
};
|
|
332
|
+
Ok(true)
|
|
121
333
|
}
|
|
122
334
|
}
|
|
123
335
|
|
|
124
|
-
|
|
336
|
+
#[pin_project::pin_project]
|
|
337
|
+
struct StreamingHistoryPaginator {
|
|
338
|
+
inner: HistoryPaginator,
|
|
339
|
+
#[pin]
|
|
340
|
+
open_history_request: Option<BoxFuture<'static, Result<(), tonic::Status>>>,
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
impl StreamingHistoryPaginator {
|
|
344
|
+
// Kept since can be used for history downloading
|
|
345
|
+
#[cfg(test)]
|
|
346
|
+
pub fn new(inner: HistoryPaginator) -> Self {
|
|
347
|
+
Self {
|
|
348
|
+
inner,
|
|
349
|
+
open_history_request: None,
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
impl Stream for StreamingHistoryPaginator {
|
|
125
355
|
type Item = Result<HistoryEvent, tonic::Status>;
|
|
126
356
|
|
|
127
|
-
fn poll_next(
|
|
128
|
-
|
|
357
|
+
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
|
358
|
+
let mut this = self.project();
|
|
359
|
+
|
|
360
|
+
if let Some(e) = this.inner.event_queue.pop_front() {
|
|
129
361
|
return Poll::Ready(Some(Ok(e)));
|
|
130
362
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
};
|
|
140
|
-
debug!(run_id=%self.run_id, "Fetching new history page");
|
|
141
|
-
let gw = self.client.clone();
|
|
142
|
-
let wid = self.wf_id.clone();
|
|
143
|
-
let rid = self.run_id.clone();
|
|
144
|
-
let resp_fut = async move {
|
|
145
|
-
gw.get_workflow_execution_history(wid, Some(rid), npt)
|
|
146
|
-
.instrument(span!(tracing::Level::TRACE, "fetch_history_in_paginator"))
|
|
147
|
-
.await
|
|
148
|
-
};
|
|
149
|
-
self.open_history_request.insert(resp_fut.boxed())
|
|
150
|
-
};
|
|
363
|
+
if this.open_history_request.is_none() {
|
|
364
|
+
// SAFETY: This is safe because the inner paginator cannot be dropped before the future,
|
|
365
|
+
// and the future won't be moved from out of this struct.
|
|
366
|
+
this.open_history_request.set(Some(unsafe {
|
|
367
|
+
transmute(HistoryPaginator::get_next_page(this.inner).boxed())
|
|
368
|
+
}));
|
|
369
|
+
}
|
|
370
|
+
let history_req = this.open_history_request.as_mut().as_pin_mut().unwrap();
|
|
151
371
|
|
|
152
|
-
|
|
372
|
+
match Future::poll(history_req, cx) {
|
|
153
373
|
Poll::Ready(resp) => {
|
|
154
|
-
|
|
374
|
+
this.open_history_request.set(None);
|
|
155
375
|
match resp {
|
|
156
376
|
Err(neterr) => Poll::Ready(Some(Err(neterr))),
|
|
157
|
-
Ok(
|
|
158
|
-
self.extend_queue_with_new_page(resp);
|
|
159
|
-
Poll::Ready(self.event_queue.pop_front().map(Ok))
|
|
160
|
-
}
|
|
377
|
+
Ok(_) => Poll::Ready(this.inner.event_queue.pop_front().map(Ok)),
|
|
161
378
|
}
|
|
162
379
|
}
|
|
163
380
|
Poll::Pending => Poll::Pending,
|
|
164
|
-
}
|
|
381
|
+
}
|
|
165
382
|
}
|
|
166
383
|
}
|
|
167
384
|
|
|
168
385
|
impl HistoryUpdate {
|
|
169
|
-
|
|
386
|
+
/// Sometimes it's useful to take an update out of something without needing to use an option
|
|
387
|
+
/// field. Use this to replace the field with an empty update.
|
|
388
|
+
pub fn dummy() -> Self {
|
|
170
389
|
Self {
|
|
171
|
-
events:
|
|
172
|
-
|
|
173
|
-
|
|
390
|
+
events: vec![],
|
|
391
|
+
previous_wft_started_id: -1,
|
|
392
|
+
wft_started_id: -1,
|
|
393
|
+
has_last_wft: false,
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
pub fn is_real(&self) -> bool {
|
|
397
|
+
self.previous_wft_started_id >= 0
|
|
398
|
+
}
|
|
399
|
+
pub fn first_event_id(&self) -> Option<i64> {
|
|
400
|
+
self.events.get(0).map(|e| e.event_id)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/// Create an instance of an update directly from events. If the passed in event iterator has a
|
|
404
|
+
/// partial WFT sequence at the end, all events after the last complete WFT sequence (ending
|
|
405
|
+
/// with WFT started) are returned back to the caller, since the history update only works in
|
|
406
|
+
/// terms of complete WFT sequences.
|
|
407
|
+
pub fn from_events<I: IntoIterator<Item = HistoryEvent>>(
|
|
408
|
+
events: I,
|
|
409
|
+
previous_wft_started_id: i64,
|
|
410
|
+
wft_started_id: i64,
|
|
411
|
+
has_last_wft: bool,
|
|
412
|
+
) -> (Self, Vec<HistoryEvent>)
|
|
413
|
+
where
|
|
414
|
+
<I as IntoIterator>::IntoIter: Send + 'static,
|
|
415
|
+
{
|
|
416
|
+
let mut all_events: Vec<_> = events.into_iter().collect();
|
|
417
|
+
let mut last_end =
|
|
418
|
+
find_end_index_of_next_wft_seq(all_events.as_slice(), previous_wft_started_id);
|
|
419
|
+
if matches!(last_end, NextWFTSeqEndIndex::Incomplete(_)) {
|
|
420
|
+
return if has_last_wft {
|
|
421
|
+
(
|
|
422
|
+
Self {
|
|
423
|
+
events: all_events,
|
|
424
|
+
previous_wft_started_id,
|
|
425
|
+
wft_started_id,
|
|
426
|
+
has_last_wft,
|
|
427
|
+
},
|
|
428
|
+
vec![],
|
|
429
|
+
)
|
|
430
|
+
} else {
|
|
431
|
+
(
|
|
432
|
+
Self {
|
|
433
|
+
events: vec![],
|
|
434
|
+
previous_wft_started_id,
|
|
435
|
+
wft_started_id,
|
|
436
|
+
has_last_wft,
|
|
437
|
+
},
|
|
438
|
+
all_events,
|
|
439
|
+
)
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
while let NextWFTSeqEndIndex::Complete(next_end_ix) = last_end {
|
|
443
|
+
let next_end_eid = all_events[next_end_ix].event_id;
|
|
444
|
+
// To save skipping all events at the front of this slice, only pass the relevant
|
|
445
|
+
// portion, but that means the returned index must be adjusted, hence the addition.
|
|
446
|
+
let next_end = find_end_index_of_next_wft_seq(&all_events[next_end_ix..], next_end_eid)
|
|
447
|
+
.add(next_end_ix);
|
|
448
|
+
if matches!(next_end, NextWFTSeqEndIndex::Incomplete(_)) {
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
last_end = next_end;
|
|
174
452
|
}
|
|
453
|
+
let remaining_events = if all_events.is_empty() {
|
|
454
|
+
vec![]
|
|
455
|
+
} else {
|
|
456
|
+
all_events.split_off(last_end.index() + 1)
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
(
|
|
460
|
+
Self {
|
|
461
|
+
events: all_events,
|
|
462
|
+
previous_wft_started_id,
|
|
463
|
+
wft_started_id,
|
|
464
|
+
has_last_wft,
|
|
465
|
+
},
|
|
466
|
+
remaining_events,
|
|
467
|
+
)
|
|
175
468
|
}
|
|
176
469
|
|
|
177
|
-
/// Create an instance of an update directly from events
|
|
470
|
+
/// Create an instance of an update directly from events. The passed in events *must* consist
|
|
471
|
+
/// of one or more complete WFT sequences. IE: The event iterator must not end in the middle
|
|
472
|
+
/// of a WFT sequence.
|
|
473
|
+
#[cfg(test)]
|
|
178
474
|
pub fn new_from_events<I: IntoIterator<Item = HistoryEvent>>(
|
|
179
475
|
events: I,
|
|
180
476
|
previous_wft_started_id: i64,
|
|
477
|
+
wft_started_id: i64,
|
|
181
478
|
) -> Self
|
|
182
479
|
where
|
|
183
480
|
<I as IntoIterator>::IntoIter: Send + 'static,
|
|
184
481
|
{
|
|
185
482
|
Self {
|
|
186
|
-
events:
|
|
187
|
-
|
|
188
|
-
|
|
483
|
+
events: events.into_iter().collect(),
|
|
484
|
+
previous_wft_started_id,
|
|
485
|
+
wft_started_id,
|
|
486
|
+
has_last_wft: true,
|
|
189
487
|
}
|
|
190
488
|
}
|
|
191
489
|
|
|
192
|
-
/// Given a workflow task started id, return all events starting at that number (
|
|
193
|
-
/// the next WFT started event (inclusive).
|
|
194
|
-
/// remaining history is returned.
|
|
195
|
-
///
|
|
196
|
-
/// Events are *consumed* by this process, to keep things efficient in workflow machines, and
|
|
197
|
-
/// the function may call out to server to fetch more pages if they are known to exist and
|
|
198
|
-
/// needed to complete the WFT sequence.
|
|
490
|
+
/// Given a workflow task started id, return all events starting at that number (exclusive) to
|
|
491
|
+
/// the next WFT started event (inclusive).
|
|
199
492
|
///
|
|
200
|
-
///
|
|
493
|
+
/// Events are *consumed* by this process, to keep things efficient in workflow machines.
|
|
201
494
|
///
|
|
202
|
-
///
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
.take_next_wft_sequence_impl(from_wft_started_id)
|
|
209
|
-
.await?;
|
|
210
|
-
if !maybe_bonus_events.is_empty() {
|
|
211
|
-
self.buffered.extend(maybe_bonus_events);
|
|
495
|
+
/// If we are out of WFT sequences that can be yielded by this update, it will return an empty
|
|
496
|
+
/// vec, indicating more pages will need to be fetched.
|
|
497
|
+
pub fn take_next_wft_sequence(&mut self, from_wft_started_id: i64) -> NextWFT {
|
|
498
|
+
// First, drop any events from the queue which are earlier than the passed-in id.
|
|
499
|
+
if let Some(ix_first_relevant) = self.starting_index_after_skipping(from_wft_started_id) {
|
|
500
|
+
self.events.drain(0..ix_first_relevant);
|
|
212
501
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
502
|
+
let next_wft_ix = find_end_index_of_next_wft_seq(&self.events, from_wft_started_id);
|
|
503
|
+
match next_wft_ix {
|
|
504
|
+
NextWFTSeqEndIndex::Incomplete(siz) => {
|
|
505
|
+
if self.has_last_wft {
|
|
506
|
+
if siz == 0 {
|
|
507
|
+
NextWFT::ReplayOver
|
|
508
|
+
} else {
|
|
509
|
+
self.build_next_wft(siz)
|
|
510
|
+
}
|
|
511
|
+
} else {
|
|
512
|
+
if siz != 0 {
|
|
513
|
+
panic!(
|
|
514
|
+
"HistoryUpdate was created with an incomplete WFT. This is an SDK bug."
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
NextWFT::NeedFetch
|
|
518
|
+
}
|
|
221
519
|
}
|
|
520
|
+
NextWFTSeqEndIndex::Complete(next_wft_ix) => self.build_next_wft(next_wft_ix),
|
|
222
521
|
}
|
|
522
|
+
}
|
|
223
523
|
|
|
224
|
-
|
|
524
|
+
fn build_next_wft(&mut self, drain_this_much: usize) -> NextWFT {
|
|
525
|
+
NextWFT::WFT(
|
|
526
|
+
self.events.drain(0..=drain_this_much).collect(),
|
|
527
|
+
self.events.is_empty() && self.has_last_wft,
|
|
528
|
+
)
|
|
225
529
|
}
|
|
226
530
|
|
|
227
531
|
/// Lets the caller peek ahead at the next WFT sequence that will be returned by
|
|
228
|
-
/// [take_next_wft_sequence]. Will always return
|
|
229
|
-
/// first. May also return an empty iterator or incomplete sequence if we are at
|
|
230
|
-
/// history.
|
|
231
|
-
pub fn peek_next_wft_sequence(&self) ->
|
|
232
|
-
self
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
let mut should_pop = |e: &HistoryEvent| {
|
|
251
|
-
if e.event_id <= from_event_id {
|
|
252
|
-
return true;
|
|
253
|
-
} else if e.event_type() == EventType::WorkflowTaskStarted {
|
|
254
|
-
next_wft_state = NextWftState::Seen;
|
|
255
|
-
return true;
|
|
532
|
+
/// [take_next_wft_sequence]. Will always return the first available WFT sequence if that has
|
|
533
|
+
/// not been called first. May also return an empty iterator or incomplete sequence if we are at
|
|
534
|
+
/// the end of history.
|
|
535
|
+
pub fn peek_next_wft_sequence(&self, from_wft_started_id: i64) -> &[HistoryEvent] {
|
|
536
|
+
let ix_first_relevant = self
|
|
537
|
+
.starting_index_after_skipping(from_wft_started_id)
|
|
538
|
+
.unwrap_or_default();
|
|
539
|
+
let relevant_events = &self.events[ix_first_relevant..];
|
|
540
|
+
if relevant_events.is_empty() {
|
|
541
|
+
return relevant_events;
|
|
542
|
+
}
|
|
543
|
+
let ix_end = find_end_index_of_next_wft_seq(relevant_events, from_wft_started_id).index();
|
|
544
|
+
&relevant_events[0..=ix_end]
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/// Returns true if this update has the next needed WFT sequence, false if events will need to
|
|
548
|
+
/// be fetched in order to create a complete update with the entire next WFT sequence.
|
|
549
|
+
pub fn can_take_next_wft_sequence(&self, from_wft_started_id: i64) -> bool {
|
|
550
|
+
let next_wft_ix = find_end_index_of_next_wft_seq(&self.events, from_wft_started_id);
|
|
551
|
+
if let NextWFTSeqEndIndex::Incomplete(_) = next_wft_ix {
|
|
552
|
+
if !self.has_last_wft {
|
|
553
|
+
return false;
|
|
256
554
|
}
|
|
555
|
+
}
|
|
556
|
+
true
|
|
557
|
+
}
|
|
257
558
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
false
|
|
559
|
+
/// Returns the next WFT completed event attributes, if any, starting at (inclusive) the
|
|
560
|
+
/// `from_id`
|
|
561
|
+
pub fn peek_next_wft_completed(
|
|
562
|
+
&self,
|
|
563
|
+
from_id: i64,
|
|
564
|
+
) -> Option<&WorkflowTaskCompletedEventAttributes> {
|
|
565
|
+
self.events
|
|
566
|
+
.iter()
|
|
567
|
+
.skip_while(|e| e.event_id < from_id)
|
|
568
|
+
.find_map(|e| match &e.attributes {
|
|
569
|
+
Some(history_event::Attributes::WorkflowTaskCompletedEventAttributes(ref a)) => {
|
|
570
|
+
Some(a)
|
|
271
571
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
};
|
|
572
|
+
_ => None,
|
|
573
|
+
})
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
fn starting_index_after_skipping(&self, from_wft_started_id: i64) -> Option<usize> {
|
|
577
|
+
self.events
|
|
578
|
+
.iter()
|
|
579
|
+
.find_position(|e| e.event_id > from_wft_started_id)
|
|
580
|
+
.map(|(ix, _)| ix)
|
|
581
|
+
}
|
|
582
|
+
}
|
|
285
583
|
|
|
286
|
-
|
|
287
|
-
|
|
584
|
+
#[derive(Debug, Copy, Clone)]
|
|
585
|
+
enum NextWFTSeqEndIndex {
|
|
586
|
+
/// The next WFT sequence is completely contained within the passed-in iterator
|
|
587
|
+
Complete(usize),
|
|
588
|
+
/// The next WFT sequence is not found within the passed-in iterator, and the contained
|
|
589
|
+
/// value is the last index of the iterator.
|
|
590
|
+
Incomplete(usize),
|
|
591
|
+
}
|
|
592
|
+
impl NextWFTSeqEndIndex {
|
|
593
|
+
fn index(self) -> usize {
|
|
594
|
+
match self {
|
|
595
|
+
NextWFTSeqEndIndex::Complete(ix) | NextWFTSeqEndIndex::Incomplete(ix) => ix,
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
fn add(self, val: usize) -> Self {
|
|
599
|
+
match self {
|
|
600
|
+
NextWFTSeqEndIndex::Complete(ix) => NextWFTSeqEndIndex::Complete(ix + val),
|
|
601
|
+
NextWFTSeqEndIndex::Incomplete(ix) => NextWFTSeqEndIndex::Incomplete(ix + val),
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
288
605
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
606
|
+
/// Discovers the index of the last event in next WFT sequence within the passed-in slice
|
|
607
|
+
fn find_end_index_of_next_wft_seq(
|
|
608
|
+
events: &[HistoryEvent],
|
|
609
|
+
from_event_id: i64,
|
|
610
|
+
) -> NextWFTSeqEndIndex {
|
|
611
|
+
if events.is_empty() {
|
|
612
|
+
return NextWFTSeqEndIndex::Incomplete(0);
|
|
613
|
+
}
|
|
614
|
+
let mut last_index = 0;
|
|
615
|
+
let mut saw_any_non_wft_event = false;
|
|
616
|
+
for (ix, e) in events.iter().enumerate() {
|
|
617
|
+
last_index = ix;
|
|
293
618
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
619
|
+
// It's possible to have gotten a new history update without eviction (ex: unhandled
|
|
620
|
+
// command on completion), where we may need to skip events we already handled.
|
|
621
|
+
if e.event_id <= from_event_id {
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if !matches!(
|
|
626
|
+
e.event_type(),
|
|
627
|
+
EventType::WorkflowTaskFailed
|
|
628
|
+
| EventType::WorkflowTaskTimedOut
|
|
629
|
+
| EventType::WorkflowTaskScheduled
|
|
630
|
+
| EventType::WorkflowTaskStarted
|
|
631
|
+
| EventType::WorkflowTaskCompleted
|
|
632
|
+
) {
|
|
633
|
+
saw_any_non_wft_event = true;
|
|
634
|
+
}
|
|
635
|
+
if e.is_final_wf_execution_event() {
|
|
636
|
+
return NextWFTSeqEndIndex::Complete(last_index);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if e.event_type() == EventType::WorkflowTaskStarted {
|
|
640
|
+
if let Some(next_event) = events.get(ix + 1) {
|
|
641
|
+
let et = next_event.event_type();
|
|
642
|
+
// If the next event is WFT timeout or fail, or abrupt WF execution end, that
|
|
643
|
+
// doesn't conclude a WFT sequence.
|
|
644
|
+
if matches!(
|
|
645
|
+
et,
|
|
646
|
+
EventType::WorkflowTaskFailed
|
|
647
|
+
| EventType::WorkflowTaskTimedOut
|
|
648
|
+
| EventType::WorkflowExecutionTimedOut
|
|
649
|
+
| EventType::WorkflowExecutionTerminated
|
|
650
|
+
| EventType::WorkflowExecutionCanceled
|
|
651
|
+
) {
|
|
652
|
+
continue;
|
|
300
653
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
// re-buffering along with the event we're currently on.
|
|
312
|
-
extra_e.push(
|
|
313
|
-
events_to_next_wft_started
|
|
314
|
-
.pop()
|
|
315
|
-
.expect("There is an element here by definition"),
|
|
316
|
-
);
|
|
654
|
+
// If we've never seen an interesting event and the next two events are a completion
|
|
655
|
+
// followed immediately again by scheduled, then this is a WFT heartbeat and also
|
|
656
|
+
// doesn't conclude the sequence.
|
|
657
|
+
else if et == EventType::WorkflowTaskCompleted {
|
|
658
|
+
if let Some(next_next_event) = events.get(ix + 2) {
|
|
659
|
+
if next_next_event.event_type() == EventType::WorkflowTaskScheduled {
|
|
660
|
+
continue;
|
|
661
|
+
} else {
|
|
662
|
+
saw_any_non_wft_event = true;
|
|
663
|
+
}
|
|
317
664
|
}
|
|
318
|
-
extra_e.push(e);
|
|
319
|
-
break;
|
|
320
665
|
}
|
|
321
|
-
|
|
666
|
+
}
|
|
667
|
+
if saw_any_non_wft_event {
|
|
668
|
+
return NextWFTSeqEndIndex::Complete(ix);
|
|
322
669
|
}
|
|
323
670
|
}
|
|
324
|
-
|
|
325
|
-
Ok((events_to_next_wft_started, extra_e))
|
|
326
671
|
}
|
|
327
|
-
}
|
|
328
672
|
|
|
329
|
-
|
|
330
|
-
enum NextWftState {
|
|
331
|
-
NotSeen,
|
|
332
|
-
Seen,
|
|
333
|
-
SeenCompleted,
|
|
673
|
+
NextWFTSeqEndIndex::Incomplete(last_index)
|
|
334
674
|
}
|
|
335
675
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
676
|
+
#[cfg(test)]
|
|
677
|
+
pub mod tests {
|
|
678
|
+
use super::*;
|
|
679
|
+
use crate::{
|
|
680
|
+
replay::{HistoryInfo, TestHistoryBuilder},
|
|
681
|
+
test_help::canned_histories,
|
|
682
|
+
worker::client::mocks::mock_workflow_client,
|
|
683
|
+
};
|
|
684
|
+
use futures_util::TryStreamExt;
|
|
685
|
+
use temporal_sdk_core_protos::temporal::api::workflowservice::v1::GetWorkflowExecutionHistoryResponse;
|
|
686
|
+
|
|
687
|
+
impl From<HistoryInfo> for HistoryUpdate {
|
|
688
|
+
fn from(v: HistoryInfo) -> Self {
|
|
689
|
+
Self::new_from_events(
|
|
690
|
+
v.events().to_vec(),
|
|
691
|
+
v.previous_started_event_id(),
|
|
692
|
+
v.workflow_task_started_event_id(),
|
|
693
|
+
)
|
|
694
|
+
}
|
|
339
695
|
}
|
|
340
|
-
}
|
|
341
696
|
|
|
342
|
-
pub trait TestHBExt {
|
|
343
|
-
|
|
344
|
-
}
|
|
697
|
+
pub trait TestHBExt {
|
|
698
|
+
fn as_history_update(&self) -> HistoryUpdate;
|
|
699
|
+
}
|
|
345
700
|
|
|
346
|
-
impl TestHBExt for TestHistoryBuilder {
|
|
347
|
-
|
|
348
|
-
|
|
701
|
+
impl TestHBExt for TestHistoryBuilder {
|
|
702
|
+
fn as_history_update(&self) -> HistoryUpdate {
|
|
703
|
+
self.get_full_history_info().unwrap().into()
|
|
704
|
+
}
|
|
349
705
|
}
|
|
350
|
-
}
|
|
351
706
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
707
|
+
impl NextWFT {
|
|
708
|
+
fn unwrap_events(self) -> Vec<HistoryEvent> {
|
|
709
|
+
match self {
|
|
710
|
+
NextWFT::WFT(e, _) => e,
|
|
711
|
+
o => panic!("Must be complete WFT: {o:?}"),
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
356
715
|
|
|
357
|
-
|
|
358
|
-
|
|
716
|
+
fn next_check_peek(update: &mut HistoryUpdate, from_id: i64) -> Vec<HistoryEvent> {
|
|
717
|
+
let seq_peeked = update.peek_next_wft_sequence(from_id).to_vec();
|
|
718
|
+
let seq = update.take_next_wft_sequence(from_id).unwrap_events();
|
|
719
|
+
assert_eq!(seq, seq_peeked);
|
|
720
|
+
seq
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
#[test]
|
|
724
|
+
fn consumes_standard_wft_sequence() {
|
|
359
725
|
let timer_hist = canned_histories::single_timer("t");
|
|
360
726
|
let mut update = timer_hist.as_history_update();
|
|
361
|
-
let seq_1 = update
|
|
727
|
+
let seq_1 = next_check_peek(&mut update, 0);
|
|
362
728
|
assert_eq!(seq_1.len(), 3);
|
|
363
729
|
assert_eq!(seq_1.last().unwrap().event_id, 3);
|
|
364
|
-
let
|
|
730
|
+
let seq_2_peeked = update.peek_next_wft_sequence(0).to_vec();
|
|
731
|
+
let seq_2 = next_check_peek(&mut update, 3);
|
|
732
|
+
assert_eq!(seq_2, seq_2_peeked);
|
|
365
733
|
assert_eq!(seq_2.len(), 5);
|
|
366
734
|
assert_eq!(seq_2.last().unwrap().event_id, 8);
|
|
367
735
|
}
|
|
368
736
|
|
|
369
|
-
#[
|
|
370
|
-
|
|
737
|
+
#[test]
|
|
738
|
+
fn skips_wft_failed() {
|
|
371
739
|
let failed_hist = canned_histories::workflow_fails_with_reset_after_timer("t", "runid");
|
|
372
740
|
let mut update = failed_hist.as_history_update();
|
|
373
|
-
let seq_1 = update
|
|
741
|
+
let seq_1 = next_check_peek(&mut update, 0);
|
|
374
742
|
assert_eq!(seq_1.len(), 3);
|
|
375
743
|
assert_eq!(seq_1.last().unwrap().event_id, 3);
|
|
376
|
-
let seq_2 = update
|
|
744
|
+
let seq_2 = next_check_peek(&mut update, 3);
|
|
377
745
|
assert_eq!(seq_2.len(), 8);
|
|
378
746
|
assert_eq!(seq_2.last().unwrap().event_id, 11);
|
|
379
747
|
}
|
|
380
748
|
|
|
381
|
-
#[
|
|
382
|
-
|
|
749
|
+
#[test]
|
|
750
|
+
fn skips_wft_timeout() {
|
|
383
751
|
let failed_hist = canned_histories::wft_timeout_repro();
|
|
384
752
|
let mut update = failed_hist.as_history_update();
|
|
385
|
-
let seq_1 = update
|
|
753
|
+
let seq_1 = next_check_peek(&mut update, 0);
|
|
386
754
|
assert_eq!(seq_1.len(), 3);
|
|
387
755
|
assert_eq!(seq_1.last().unwrap().event_id, 3);
|
|
388
|
-
let seq_2 = update
|
|
756
|
+
let seq_2 = next_check_peek(&mut update, 3);
|
|
389
757
|
assert_eq!(seq_2.len(), 11);
|
|
390
758
|
assert_eq!(seq_2.last().unwrap().event_id, 14);
|
|
391
759
|
}
|
|
392
760
|
|
|
393
|
-
#[
|
|
394
|
-
|
|
761
|
+
#[test]
|
|
762
|
+
fn skips_events_before_desired_wft() {
|
|
395
763
|
let timer_hist = canned_histories::single_timer("t");
|
|
396
764
|
let mut update = timer_hist.as_history_update();
|
|
397
765
|
// We haven't processed the first 3 events, but we should still only get the second sequence
|
|
398
|
-
let seq_2 = update.take_next_wft_sequence(3).
|
|
766
|
+
let seq_2 = update.take_next_wft_sequence(3).unwrap_events();
|
|
399
767
|
assert_eq!(seq_2.len(), 5);
|
|
400
768
|
assert_eq!(seq_2.last().unwrap().event_id, 8);
|
|
401
769
|
}
|
|
402
770
|
|
|
403
|
-
#[
|
|
404
|
-
|
|
771
|
+
#[test]
|
|
772
|
+
fn history_ends_abruptly() {
|
|
405
773
|
let mut timer_hist = canned_histories::single_timer("t");
|
|
406
774
|
timer_hist.add_workflow_execution_terminated();
|
|
407
775
|
let mut update = timer_hist.as_history_update();
|
|
408
|
-
let seq_2 = update.take_next_wft_sequence(3).
|
|
409
|
-
assert_eq!(seq_2.len(),
|
|
410
|
-
assert_eq!(seq_2.last().unwrap().event_id,
|
|
776
|
+
let seq_2 = update.take_next_wft_sequence(3).unwrap_events();
|
|
777
|
+
assert_eq!(seq_2.len(), 6);
|
|
778
|
+
assert_eq!(seq_2.last().unwrap().event_id, 9);
|
|
411
779
|
}
|
|
412
780
|
|
|
413
|
-
#[
|
|
414
|
-
|
|
781
|
+
#[test]
|
|
782
|
+
fn heartbeats_skipped() {
|
|
415
783
|
let mut t = TestHistoryBuilder::default();
|
|
416
784
|
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
417
785
|
t.add_full_wf_task();
|
|
418
|
-
t.add_full_wf_task();
|
|
419
|
-
t.
|
|
786
|
+
t.add_full_wf_task(); // wft started 6
|
|
787
|
+
t.add_by_type(EventType::TimerStarted);
|
|
788
|
+
t.add_full_wf_task(); // wft started 10
|
|
420
789
|
t.add_full_wf_task();
|
|
421
790
|
t.add_full_wf_task();
|
|
422
|
-
t.add_full_wf_task();
|
|
423
|
-
t.
|
|
424
|
-
t.
|
|
425
|
-
t.add_full_wf_task();
|
|
791
|
+
t.add_full_wf_task(); // wft started 19
|
|
792
|
+
t.add_by_type(EventType::TimerStarted);
|
|
793
|
+
t.add_full_wf_task(); // wft started 23
|
|
426
794
|
t.add_we_signaled("whee", vec![]);
|
|
427
795
|
t.add_full_wf_task();
|
|
428
796
|
t.add_workflow_execution_completed();
|
|
429
797
|
|
|
430
798
|
let mut update = t.as_history_update();
|
|
431
|
-
let seq = update
|
|
799
|
+
let seq = next_check_peek(&mut update, 0);
|
|
432
800
|
assert_eq!(seq.len(), 6);
|
|
433
|
-
let seq = update
|
|
801
|
+
let seq = next_check_peek(&mut update, 6);
|
|
434
802
|
assert_eq!(seq.len(), 13);
|
|
435
|
-
let seq = update
|
|
803
|
+
let seq = next_check_peek(&mut update, 19);
|
|
436
804
|
assert_eq!(seq.len(), 4);
|
|
437
|
-
let seq = update
|
|
805
|
+
let seq = next_check_peek(&mut update, 23);
|
|
438
806
|
assert_eq!(seq.len(), 4);
|
|
439
|
-
let seq = update
|
|
807
|
+
let seq = next_check_peek(&mut update, 27);
|
|
440
808
|
assert_eq!(seq.len(), 2);
|
|
441
809
|
}
|
|
442
810
|
|
|
443
|
-
#[
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
811
|
+
#[test]
|
|
812
|
+
fn heartbeat_marker_end() {
|
|
813
|
+
let mut t = TestHistoryBuilder::default();
|
|
814
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
815
|
+
t.add_full_wf_task();
|
|
816
|
+
t.add_full_wf_task();
|
|
817
|
+
t.add_local_activity_result_marker(1, "1", "done".into());
|
|
818
|
+
t.add_workflow_execution_completed();
|
|
819
|
+
|
|
820
|
+
let mut update = t.as_history_update();
|
|
821
|
+
let seq = next_check_peek(&mut update, 3);
|
|
822
|
+
// completed, sched, started
|
|
823
|
+
assert_eq!(seq.len(), 3);
|
|
824
|
+
let seq = next_check_peek(&mut update, 6);
|
|
825
|
+
assert_eq!(seq.len(), 3);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
fn paginator_setup(history: TestHistoryBuilder, chunk_size: usize) -> HistoryPaginator {
|
|
829
|
+
let hinfo = history.get_full_history_info().unwrap();
|
|
830
|
+
let wft_started = hinfo.workflow_task_started_event_id();
|
|
831
|
+
let full_hist = hinfo.into_events();
|
|
832
|
+
let initial_hist = full_hist.chunks(chunk_size).next().unwrap().to_vec();
|
|
451
833
|
let mut mock_client = mock_workflow_client();
|
|
452
834
|
|
|
453
|
-
let mut npt =
|
|
835
|
+
let mut npt = 1;
|
|
454
836
|
mock_client
|
|
455
837
|
.expect_get_workflow_execution_history()
|
|
456
838
|
.returning(move |_, _, passed_npt| {
|
|
457
839
|
assert_eq!(passed_npt, vec![npt]);
|
|
458
|
-
let
|
|
840
|
+
let mut hist_chunks = full_hist.chunks(chunk_size).peekable();
|
|
841
|
+
let next_chunks = hist_chunks.nth(npt.into()).unwrap_or_default();
|
|
459
842
|
npt += 1;
|
|
843
|
+
let next_page_token = if hist_chunks.peek().is_none() {
|
|
844
|
+
vec![]
|
|
845
|
+
} else {
|
|
846
|
+
vec![npt]
|
|
847
|
+
};
|
|
460
848
|
Ok(GetWorkflowExecutionHistoryResponse {
|
|
461
|
-
history: Some(
|
|
849
|
+
history: Some(History {
|
|
850
|
+
events: next_chunks.into(),
|
|
851
|
+
}),
|
|
462
852
|
raw_history: vec![],
|
|
463
|
-
next_page_token
|
|
853
|
+
next_page_token,
|
|
464
854
|
archived: false,
|
|
465
855
|
})
|
|
466
856
|
});
|
|
467
857
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
initial_hist
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
),
|
|
476
|
-
|
|
858
|
+
HistoryPaginator::new(
|
|
859
|
+
History {
|
|
860
|
+
events: initial_hist,
|
|
861
|
+
},
|
|
862
|
+
0,
|
|
863
|
+
wft_started,
|
|
864
|
+
"wfid".to_string(),
|
|
865
|
+
"runid".to_string(),
|
|
866
|
+
vec![1],
|
|
867
|
+
Arc::new(mock_client),
|
|
868
|
+
)
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
#[rstest::rstest]
|
|
872
|
+
#[tokio::test]
|
|
873
|
+
async fn paginator_extracts_updates(#[values(10, 11, 12, 13, 14)] chunk_size: usize) {
|
|
874
|
+
let wft_count = 100;
|
|
875
|
+
let mut paginator = paginator_setup(
|
|
876
|
+
canned_histories::long_sequential_timers(wft_count),
|
|
877
|
+
chunk_size,
|
|
477
878
|
);
|
|
879
|
+
let mut update = paginator.extract_next_update().await.unwrap();
|
|
478
880
|
|
|
479
|
-
let seq = update.take_next_wft_sequence(0).
|
|
881
|
+
let seq = update.take_next_wft_sequence(0).unwrap_events();
|
|
480
882
|
assert_eq!(seq.len(), 3);
|
|
481
883
|
|
|
482
884
|
let mut last_event_id = 3;
|
|
483
885
|
let mut last_started_id = 3;
|
|
484
|
-
for
|
|
485
|
-
let seq =
|
|
486
|
-
.take_next_wft_sequence(last_started_id)
|
|
487
|
-
|
|
488
|
-
|
|
886
|
+
for i in 1..wft_count {
|
|
887
|
+
let seq = {
|
|
888
|
+
match update.take_next_wft_sequence(last_started_id) {
|
|
889
|
+
NextWFT::WFT(seq, _) => seq,
|
|
890
|
+
NextWFT::NeedFetch => {
|
|
891
|
+
update = paginator.extract_next_update().await.unwrap();
|
|
892
|
+
update
|
|
893
|
+
.take_next_wft_sequence(last_started_id)
|
|
894
|
+
.unwrap_events()
|
|
895
|
+
}
|
|
896
|
+
NextWFT::ReplayOver => {
|
|
897
|
+
assert_eq!(i, wft_count - 1);
|
|
898
|
+
break;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
};
|
|
489
902
|
for e in &seq {
|
|
490
903
|
last_event_id += 1;
|
|
491
904
|
assert_eq!(e.event_id, last_event_id);
|
|
@@ -495,10 +908,139 @@ pub mod tests {
|
|
|
495
908
|
}
|
|
496
909
|
}
|
|
497
910
|
|
|
911
|
+
#[tokio::test]
|
|
912
|
+
async fn paginator_streams() {
|
|
913
|
+
let wft_count = 10;
|
|
914
|
+
let paginator = StreamingHistoryPaginator::new(paginator_setup(
|
|
915
|
+
canned_histories::long_sequential_timers(wft_count),
|
|
916
|
+
10,
|
|
917
|
+
));
|
|
918
|
+
let everything: Vec<_> = paginator.try_collect().await.unwrap();
|
|
919
|
+
assert_eq!(everything.len(), (wft_count + 1) * 5);
|
|
920
|
+
everything.iter().fold(1, |event_id, e| {
|
|
921
|
+
assert_eq!(event_id, e.event_id);
|
|
922
|
+
e.event_id + 1
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
fn three_wfts_then_heartbeats() -> TestHistoryBuilder {
|
|
927
|
+
let mut t = TestHistoryBuilder::default();
|
|
928
|
+
// Start with two complete normal WFTs
|
|
929
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
930
|
+
t.add_full_wf_task(); // wft start - 3
|
|
931
|
+
t.add_by_type(EventType::TimerStarted);
|
|
932
|
+
t.add_full_wf_task(); // wft start - 7
|
|
933
|
+
t.add_by_type(EventType::TimerStarted);
|
|
934
|
+
t.add_full_wf_task(); // wft start - 11
|
|
935
|
+
for _ in 1..50 {
|
|
936
|
+
// Add a bunch of heartbeats with no commands, which count as one task
|
|
937
|
+
t.add_full_wf_task();
|
|
938
|
+
}
|
|
939
|
+
t.add_workflow_execution_completed();
|
|
940
|
+
t
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
#[tokio::test]
|
|
944
|
+
async fn needs_fetch_if_ending_in_middle_of_wft_seq() {
|
|
945
|
+
let t = three_wfts_then_heartbeats();
|
|
946
|
+
let mut ends_in_middle_of_seq = t.as_history_update().events;
|
|
947
|
+
ends_in_middle_of_seq.truncate(19);
|
|
948
|
+
// The update should contain the first two complete WFTs, ending on the 8th event which
|
|
949
|
+
// is WFT started. The remaining events should be returned. False flags means the creator
|
|
950
|
+
// knows there are more events, so we should return need fetch
|
|
951
|
+
let (mut update, remaining) = HistoryUpdate::from_events(
|
|
952
|
+
ends_in_middle_of_seq,
|
|
953
|
+
0,
|
|
954
|
+
t.get_full_history_info()
|
|
955
|
+
.unwrap()
|
|
956
|
+
.workflow_task_started_event_id(),
|
|
957
|
+
false,
|
|
958
|
+
);
|
|
959
|
+
assert_eq!(remaining[0].event_id, 8);
|
|
960
|
+
assert_eq!(remaining.last().unwrap().event_id, 19);
|
|
961
|
+
let seq = update.take_next_wft_sequence(0).unwrap_events();
|
|
962
|
+
assert_eq!(seq.last().unwrap().event_id, 3);
|
|
963
|
+
let seq = update.take_next_wft_sequence(3).unwrap_events();
|
|
964
|
+
assert_eq!(seq.last().unwrap().event_id, 7);
|
|
965
|
+
let next = update.take_next_wft_sequence(7);
|
|
966
|
+
assert_matches!(next, NextWFT::NeedFetch);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// Like the above, but if the history happens to be cut off at a wft boundary, (even though
|
|
970
|
+
// there may have been many heartbeats after we have no way of knowing about), it's going to
|
|
971
|
+
// count events 7-20 as a WFT since there is started, completed, timer command, ..heartbeats..
|
|
972
|
+
#[tokio::test]
|
|
973
|
+
async fn needs_fetch_after_complete_seq_with_heartbeats() {
|
|
974
|
+
let t = three_wfts_then_heartbeats();
|
|
975
|
+
let mut ends_in_middle_of_seq = t.as_history_update().events;
|
|
976
|
+
ends_in_middle_of_seq.truncate(20);
|
|
977
|
+
let (mut update, remaining) = HistoryUpdate::from_events(
|
|
978
|
+
ends_in_middle_of_seq,
|
|
979
|
+
0,
|
|
980
|
+
t.get_full_history_info()
|
|
981
|
+
.unwrap()
|
|
982
|
+
.workflow_task_started_event_id(),
|
|
983
|
+
false,
|
|
984
|
+
);
|
|
985
|
+
assert!(remaining.is_empty());
|
|
986
|
+
let seq = update.take_next_wft_sequence(0).unwrap_events();
|
|
987
|
+
assert_eq!(seq.last().unwrap().event_id, 3);
|
|
988
|
+
let seq = update.take_next_wft_sequence(3).unwrap_events();
|
|
989
|
+
assert_eq!(seq.last().unwrap().event_id, 7);
|
|
990
|
+
let seq = update.take_next_wft_sequence(7).unwrap_events();
|
|
991
|
+
assert_eq!(seq.last().unwrap().event_id, 20);
|
|
992
|
+
let next = update.take_next_wft_sequence(20);
|
|
993
|
+
assert_matches!(next, NextWFT::NeedFetch);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
#[rstest::rstest]
|
|
997
|
+
#[tokio::test]
|
|
998
|
+
async fn paginator_works_with_wft_over_multiple_pages(
|
|
999
|
+
#[values(10, 11, 12, 13, 14)] chunk_size: usize,
|
|
1000
|
+
) {
|
|
1001
|
+
let t = three_wfts_then_heartbeats();
|
|
1002
|
+
let mut paginator = paginator_setup(t, chunk_size);
|
|
1003
|
+
let mut update = paginator.extract_next_update().await.unwrap();
|
|
1004
|
+
let mut last_id = 0;
|
|
1005
|
+
loop {
|
|
1006
|
+
let seq = update.take_next_wft_sequence(last_id);
|
|
1007
|
+
match seq {
|
|
1008
|
+
NextWFT::WFT(seq, _) => {
|
|
1009
|
+
last_id = seq.last().unwrap().event_id;
|
|
1010
|
+
}
|
|
1011
|
+
NextWFT::NeedFetch => {
|
|
1012
|
+
update = paginator.extract_next_update().await.unwrap();
|
|
1013
|
+
}
|
|
1014
|
+
NextWFT::ReplayOver => break,
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
assert_eq!(last_id, 160);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
#[tokio::test]
|
|
1021
|
+
async fn task_just_before_heartbeat_chain_is_taken() {
|
|
1022
|
+
let t = three_wfts_then_heartbeats();
|
|
1023
|
+
let mut update = t.as_history_update();
|
|
1024
|
+
let seq = update.take_next_wft_sequence(0).unwrap_events();
|
|
1025
|
+
assert_eq!(seq.last().unwrap().event_id, 3);
|
|
1026
|
+
let seq = update.take_next_wft_sequence(3).unwrap_events();
|
|
1027
|
+
assert_eq!(seq.last().unwrap().event_id, 7);
|
|
1028
|
+
let seq = update.take_next_wft_sequence(7).unwrap_events();
|
|
1029
|
+
assert_eq!(seq.last().unwrap().event_id, 158);
|
|
1030
|
+
let seq = update.take_next_wft_sequence(158).unwrap_events();
|
|
1031
|
+
assert_eq!(seq.last().unwrap().event_id, 160);
|
|
1032
|
+
assert_eq!(
|
|
1033
|
+
seq.last().unwrap().event_type(),
|
|
1034
|
+
EventType::WorkflowExecutionCompleted
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
498
1038
|
#[tokio::test]
|
|
499
1039
|
async fn handles_cache_misses() {
|
|
500
1040
|
let timer_hist = canned_histories::single_timer("t");
|
|
501
1041
|
let partial_task = timer_hist.get_one_wft(2).unwrap();
|
|
1042
|
+
let prev_started_wft_id = partial_task.previous_started_event_id();
|
|
1043
|
+
let wft_started_id = partial_task.workflow_task_started_event_id();
|
|
502
1044
|
let mut history_from_get: GetWorkflowExecutionHistoryResponse =
|
|
503
1045
|
timer_hist.get_history_info(2).unwrap().into();
|
|
504
1046
|
// Chop off the last event, which is WFT started, which server doesn't return in get
|
|
@@ -509,24 +1051,121 @@ pub mod tests {
|
|
|
509
1051
|
.expect_get_workflow_execution_history()
|
|
510
1052
|
.returning(move |_, _, _| Ok(history_from_get.clone()));
|
|
511
1053
|
|
|
512
|
-
let mut
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
),
|
|
521
|
-
1,
|
|
1054
|
+
let mut paginator = HistoryPaginator::new(
|
|
1055
|
+
partial_task.into(),
|
|
1056
|
+
prev_started_wft_id,
|
|
1057
|
+
wft_started_id,
|
|
1058
|
+
"wfid".to_string(),
|
|
1059
|
+
"runid".to_string(),
|
|
1060
|
+
// A cache miss means we'll try to fetch from start
|
|
1061
|
+
NextPageToken::FetchFromStart,
|
|
1062
|
+
Arc::new(mock_client),
|
|
522
1063
|
);
|
|
1064
|
+
let mut update = paginator.extract_next_update().await.unwrap();
|
|
523
1065
|
// We expect if we try to take the first task sequence that the first event is the first
|
|
524
1066
|
// event in the sequence.
|
|
525
|
-
let seq = update.take_next_wft_sequence(0).
|
|
1067
|
+
let seq = update.take_next_wft_sequence(0).unwrap_events();
|
|
526
1068
|
assert_eq!(seq[0].event_id, 1);
|
|
527
|
-
let seq = update.take_next_wft_sequence(3).
|
|
1069
|
+
let seq = update.take_next_wft_sequence(3).unwrap_events();
|
|
528
1070
|
// Verify anything extra (which should only ever be WFT started) was re-appended to the
|
|
529
1071
|
// end of the event iteration after fetching the old history.
|
|
530
1072
|
assert_eq!(seq.last().unwrap().event_id, 8);
|
|
531
1073
|
}
|
|
1074
|
+
|
|
1075
|
+
#[test]
|
|
1076
|
+
fn la_marker_chunking() {
|
|
1077
|
+
let mut t = TestHistoryBuilder::default();
|
|
1078
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
1079
|
+
t.add_full_wf_task();
|
|
1080
|
+
t.add_we_signaled("whatever", vec![]);
|
|
1081
|
+
t.add_full_wf_task(); // started - 7
|
|
1082
|
+
t.add_local_activity_result_marker(1, "hi", Default::default());
|
|
1083
|
+
let act_s = t.add_activity_task_scheduled("1");
|
|
1084
|
+
let act_st = t.add_activity_task_started(act_s);
|
|
1085
|
+
t.add_activity_task_completed(act_s, act_st, Default::default());
|
|
1086
|
+
t.add_workflow_task_scheduled_and_started();
|
|
1087
|
+
t.add_workflow_task_timed_out();
|
|
1088
|
+
t.add_workflow_task_scheduled_and_started();
|
|
1089
|
+
t.add_workflow_task_timed_out();
|
|
1090
|
+
t.add_workflow_task_scheduled_and_started();
|
|
1091
|
+
|
|
1092
|
+
let mut update = t.as_history_update();
|
|
1093
|
+
let seq = next_check_peek(&mut update, 0);
|
|
1094
|
+
assert_eq!(seq.len(), 3);
|
|
1095
|
+
let seq = next_check_peek(&mut update, 3);
|
|
1096
|
+
assert_eq!(seq.len(), 4);
|
|
1097
|
+
let seq = next_check_peek(&mut update, 7);
|
|
1098
|
+
assert_eq!(seq.len(), 13);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
#[tokio::test]
|
|
1102
|
+
async fn handles_blank_fetch_response() {
|
|
1103
|
+
let timer_hist = canned_histories::single_timer("t");
|
|
1104
|
+
let partial_task = timer_hist.get_one_wft(2).unwrap();
|
|
1105
|
+
let prev_started_wft_id = partial_task.previous_started_event_id();
|
|
1106
|
+
let wft_started_id = partial_task.workflow_task_started_event_id();
|
|
1107
|
+
let mut mock_client = mock_workflow_client();
|
|
1108
|
+
mock_client
|
|
1109
|
+
.expect_get_workflow_execution_history()
|
|
1110
|
+
.returning(move |_, _, _| Ok(Default::default()));
|
|
1111
|
+
|
|
1112
|
+
let mut paginator = HistoryPaginator::new(
|
|
1113
|
+
partial_task.into(),
|
|
1114
|
+
prev_started_wft_id,
|
|
1115
|
+
wft_started_id,
|
|
1116
|
+
"wfid".to_string(),
|
|
1117
|
+
"runid".to_string(),
|
|
1118
|
+
// A cache miss means we'll try to fetch from start
|
|
1119
|
+
NextPageToken::FetchFromStart,
|
|
1120
|
+
Arc::new(mock_client),
|
|
1121
|
+
);
|
|
1122
|
+
let err = paginator.extract_next_update().await.unwrap_err();
|
|
1123
|
+
assert_matches!(err.code(), tonic::Code::Unknown);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
#[tokio::test]
|
|
1127
|
+
async fn handles_empty_page_with_next_token() {
|
|
1128
|
+
let timer_hist = canned_histories::single_timer("t");
|
|
1129
|
+
let partial_task = timer_hist.get_one_wft(2).unwrap();
|
|
1130
|
+
let prev_started_wft_id = partial_task.previous_started_event_id();
|
|
1131
|
+
let wft_started_id = partial_task.workflow_task_started_event_id();
|
|
1132
|
+
let full_resp: GetWorkflowExecutionHistoryResponse =
|
|
1133
|
+
timer_hist.get_full_history_info().unwrap().into();
|
|
1134
|
+
let mut mock_client = mock_workflow_client();
|
|
1135
|
+
mock_client
|
|
1136
|
+
.expect_get_workflow_execution_history()
|
|
1137
|
+
.returning(move |_, _, _| {
|
|
1138
|
+
Ok(GetWorkflowExecutionHistoryResponse {
|
|
1139
|
+
history: Some(History { events: vec![] }),
|
|
1140
|
+
raw_history: vec![],
|
|
1141
|
+
next_page_token: vec![2],
|
|
1142
|
+
archived: false,
|
|
1143
|
+
})
|
|
1144
|
+
})
|
|
1145
|
+
.times(1);
|
|
1146
|
+
mock_client
|
|
1147
|
+
.expect_get_workflow_execution_history()
|
|
1148
|
+
.returning(move |_, _, _| Ok(full_resp.clone()))
|
|
1149
|
+
.times(1);
|
|
1150
|
+
|
|
1151
|
+
let mut paginator = HistoryPaginator::new(
|
|
1152
|
+
partial_task.into(),
|
|
1153
|
+
prev_started_wft_id,
|
|
1154
|
+
wft_started_id,
|
|
1155
|
+
"wfid".to_string(),
|
|
1156
|
+
"runid".to_string(),
|
|
1157
|
+
// A cache miss means we'll try to fetch from start
|
|
1158
|
+
NextPageToken::FetchFromStart,
|
|
1159
|
+
Arc::new(mock_client),
|
|
1160
|
+
);
|
|
1161
|
+
let mut update = paginator.extract_next_update().await.unwrap();
|
|
1162
|
+
let seq = update.take_next_wft_sequence(0).unwrap_events();
|
|
1163
|
+
assert_eq!(seq.last().unwrap().event_id, 3);
|
|
1164
|
+
let seq = update.take_next_wft_sequence(3).unwrap_events();
|
|
1165
|
+
assert_eq!(seq.last().unwrap().event_id, 8);
|
|
1166
|
+
assert_matches!(update.take_next_wft_sequence(8), NextWFT::ReplayOver);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// TODO: Test we dont re-feed pointless updates if fetching returns <= events we already
|
|
1170
|
+
// processed
|
|
532
1171
|
}
|