@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.
Files changed (194) hide show
  1. package/Cargo.lock +304 -112
  2. package/lib/index.d.ts +8 -6
  3. package/lib/index.js.map +1 -1
  4. package/package.json +9 -4
  5. package/releases/aarch64-apple-darwin/index.node +0 -0
  6. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  7. package/releases/x86_64-apple-darwin/index.node +0 -0
  8. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  9. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  10. package/sdk-core/.buildkite/docker/Dockerfile +2 -2
  11. package/sdk-core/.buildkite/docker/docker-compose.yaml +1 -1
  12. package/sdk-core/.buildkite/pipeline.yml +2 -4
  13. package/sdk-core/.cargo/config.toml +5 -2
  14. package/sdk-core/.github/workflows/heavy.yml +29 -0
  15. package/sdk-core/Cargo.toml +1 -1
  16. package/sdk-core/README.md +20 -10
  17. package/sdk-core/client/src/lib.rs +215 -39
  18. package/sdk-core/client/src/metrics.rs +17 -8
  19. package/sdk-core/client/src/raw.rs +4 -4
  20. package/sdk-core/client/src/retry.rs +32 -20
  21. package/sdk-core/core/Cargo.toml +25 -12
  22. package/sdk-core/core/src/abstractions/take_cell.rs +28 -0
  23. package/sdk-core/core/src/abstractions.rs +204 -14
  24. package/sdk-core/core/src/core_tests/activity_tasks.rs +143 -50
  25. package/sdk-core/core/src/core_tests/child_workflows.rs +6 -5
  26. package/sdk-core/core/src/core_tests/determinism.rs +165 -2
  27. package/sdk-core/core/src/core_tests/local_activities.rs +431 -43
  28. package/sdk-core/core/src/core_tests/queries.rs +34 -16
  29. package/sdk-core/core/src/core_tests/workers.rs +8 -5
  30. package/sdk-core/core/src/core_tests/workflow_tasks.rs +588 -55
  31. package/sdk-core/core/src/ephemeral_server/mod.rs +113 -12
  32. package/sdk-core/core/src/internal_flags.rs +155 -0
  33. package/sdk-core/core/src/lib.rs +16 -9
  34. package/sdk-core/core/src/protosext/mod.rs +1 -1
  35. package/sdk-core/core/src/replay/mod.rs +16 -27
  36. package/sdk-core/core/src/telemetry/log_export.rs +1 -1
  37. package/sdk-core/core/src/telemetry/metrics.rs +69 -35
  38. package/sdk-core/core/src/telemetry/mod.rs +60 -21
  39. package/sdk-core/core/src/telemetry/prometheus_server.rs +19 -13
  40. package/sdk-core/core/src/test_help/mod.rs +73 -14
  41. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +119 -160
  42. package/sdk-core/core/src/worker/activities/activity_task_poller_stream.rs +89 -0
  43. package/sdk-core/core/src/worker/activities/local_activities.rs +379 -129
  44. package/sdk-core/core/src/worker/activities.rs +350 -175
  45. package/sdk-core/core/src/worker/client/mocks.rs +22 -2
  46. package/sdk-core/core/src/worker/client.rs +18 -2
  47. package/sdk-core/core/src/worker/mod.rs +183 -64
  48. package/sdk-core/core/src/worker/workflow/bridge.rs +1 -3
  49. package/sdk-core/core/src/worker/workflow/driven_workflow.rs +3 -5
  50. package/sdk-core/core/src/worker/workflow/history_update.rs +916 -277
  51. package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +216 -183
  52. package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +9 -12
  53. package/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +7 -9
  54. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +160 -87
  55. package/sdk-core/core/src/worker/workflow/machines/complete_workflow_state_machine.rs +13 -14
  56. package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +7 -9
  57. package/sdk-core/core/src/worker/workflow/machines/fail_workflow_state_machine.rs +14 -17
  58. package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +242 -110
  59. package/sdk-core/core/src/worker/workflow/machines/mod.rs +27 -19
  60. package/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +9 -11
  61. package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +321 -206
  62. package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +13 -18
  63. package/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +20 -29
  64. package/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +2 -2
  65. package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +257 -51
  66. package/sdk-core/core/src/worker/workflow/machines/workflow_machines/local_acts.rs +6 -17
  67. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +310 -150
  68. package/sdk-core/core/src/worker/workflow/machines/workflow_task_state_machine.rs +17 -20
  69. package/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +31 -15
  70. package/sdk-core/core/src/worker/workflow/managed_run.rs +1052 -380
  71. package/sdk-core/core/src/worker/workflow/mod.rs +598 -390
  72. package/sdk-core/core/src/worker/workflow/run_cache.rs +40 -57
  73. package/sdk-core/core/src/worker/workflow/wft_extraction.rs +137 -0
  74. package/sdk-core/core/src/worker/workflow/wft_poller.rs +1 -4
  75. package/sdk-core/core/src/worker/workflow/workflow_stream/saved_wf_inputs.rs +117 -0
  76. package/sdk-core/core/src/worker/workflow/workflow_stream/tonic_status_serde.rs +24 -0
  77. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +469 -718
  78. package/sdk-core/core-api/Cargo.toml +2 -1
  79. package/sdk-core/core-api/src/errors.rs +1 -34
  80. package/sdk-core/core-api/src/lib.rs +19 -9
  81. package/sdk-core/core-api/src/telemetry.rs +4 -6
  82. package/sdk-core/core-api/src/worker.rs +19 -1
  83. package/sdk-core/etc/deps.svg +115 -140
  84. package/sdk-core/etc/regen-depgraph.sh +5 -0
  85. package/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +86 -61
  86. package/sdk-core/fsm/rustfsm_trait/src/lib.rs +29 -71
  87. package/sdk-core/histories/ends_empty_wft_complete.bin +0 -0
  88. package/sdk-core/histories/evict_while_la_running_no_interference-16_history.bin +0 -0
  89. package/sdk-core/histories/old_change_marker_format.bin +0 -0
  90. package/sdk-core/protos/api_upstream/.github/CODEOWNERS +2 -1
  91. package/sdk-core/protos/api_upstream/Makefile +6 -6
  92. package/sdk-core/protos/api_upstream/build/go.mod +7 -0
  93. package/sdk-core/protos/api_upstream/build/go.sum +5 -0
  94. package/sdk-core/protos/api_upstream/build/tools.go +29 -0
  95. package/sdk-core/protos/api_upstream/go.mod +6 -0
  96. package/sdk-core/protos/api_upstream/temporal/api/batch/v1/message.proto +9 -2
  97. package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +7 -26
  98. package/sdk-core/protos/api_upstream/temporal/api/common/v1/message.proto +13 -2
  99. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +3 -2
  100. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/command_type.proto +3 -7
  101. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/common.proto +3 -2
  102. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/event_type.proto +8 -8
  103. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +25 -2
  104. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/namespace.proto +2 -2
  105. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/query.proto +2 -2
  106. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/reset.proto +2 -2
  107. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/schedule.proto +2 -2
  108. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +2 -2
  109. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/update.proto +24 -19
  110. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/workflow.proto +2 -2
  111. package/sdk-core/protos/api_upstream/temporal/api/errordetails/v1/message.proto +2 -2
  112. package/sdk-core/protos/api_upstream/temporal/api/failure/v1/message.proto +2 -2
  113. package/sdk-core/protos/api_upstream/temporal/api/filter/v1/message.proto +2 -2
  114. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +49 -26
  115. package/sdk-core/protos/api_upstream/temporal/api/namespace/v1/message.proto +4 -2
  116. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +5 -2
  117. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +2 -2
  118. package/sdk-core/protos/api_upstream/temporal/api/protocol/v1/message.proto +57 -0
  119. package/sdk-core/protos/api_upstream/temporal/api/query/v1/message.proto +2 -2
  120. package/sdk-core/protos/api_upstream/temporal/api/replication/v1/message.proto +2 -2
  121. package/sdk-core/protos/api_upstream/temporal/api/schedule/v1/message.proto +2 -2
  122. package/sdk-core/protos/api_upstream/temporal/api/sdk/v1/task_complete_metadata.proto +63 -0
  123. package/sdk-core/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +2 -2
  124. package/sdk-core/protos/api_upstream/temporal/api/update/v1/message.proto +71 -6
  125. package/sdk-core/protos/api_upstream/temporal/api/version/v1/message.proto +2 -2
  126. package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +2 -2
  127. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +64 -28
  128. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +4 -4
  129. package/sdk-core/protos/local/temporal/sdk/core/activity_result/activity_result.proto +7 -8
  130. package/sdk-core/protos/local/temporal/sdk/core/activity_task/activity_task.proto +10 -7
  131. package/sdk-core/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +19 -30
  132. package/sdk-core/protos/local/temporal/sdk/core/common/common.proto +1 -0
  133. package/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +1 -0
  134. package/sdk-core/protos/local/temporal/sdk/core/external_data/external_data.proto +8 -0
  135. package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +67 -60
  136. package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +85 -84
  137. package/sdk-core/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +9 -3
  138. package/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/request_response.proto +2 -2
  139. package/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/service.proto +2 -2
  140. package/sdk-core/sdk/Cargo.toml +5 -4
  141. package/sdk-core/sdk/src/lib.rs +108 -26
  142. package/sdk-core/sdk/src/workflow_context/options.rs +7 -1
  143. package/sdk-core/sdk/src/workflow_context.rs +24 -17
  144. package/sdk-core/sdk/src/workflow_future.rs +16 -15
  145. package/sdk-core/sdk-core-protos/Cargo.toml +5 -2
  146. package/sdk-core/sdk-core-protos/build.rs +36 -2
  147. package/sdk-core/sdk-core-protos/src/history_builder.rs +138 -106
  148. package/sdk-core/sdk-core-protos/src/history_info.rs +10 -1
  149. package/sdk-core/sdk-core-protos/src/lib.rs +272 -87
  150. package/sdk-core/sdk-core-protos/src/task_token.rs +12 -2
  151. package/sdk-core/test-utils/Cargo.toml +3 -1
  152. package/sdk-core/test-utils/src/canned_histories.rs +106 -296
  153. package/sdk-core/test-utils/src/histfetch.rs +1 -1
  154. package/sdk-core/test-utils/src/lib.rs +82 -23
  155. package/sdk-core/test-utils/src/wf_input_saver.rs +50 -0
  156. package/sdk-core/test-utils/src/workflows.rs +29 -0
  157. package/sdk-core/tests/fuzzy_workflow.rs +130 -0
  158. package/sdk-core/tests/{load_tests.rs → heavy_tests.rs} +125 -51
  159. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +25 -3
  160. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +10 -5
  161. package/sdk-core/tests/integ_tests/metrics_tests.rs +218 -16
  162. package/sdk-core/tests/integ_tests/polling_tests.rs +4 -47
  163. package/sdk-core/tests/integ_tests/queries_tests.rs +5 -128
  164. package/sdk-core/tests/integ_tests/visibility_tests.rs +83 -25
  165. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +161 -72
  166. package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +1 -0
  167. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +6 -13
  168. package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +80 -3
  169. package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +6 -2
  170. package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +3 -10
  171. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +94 -200
  172. package/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +2 -4
  173. package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +34 -28
  174. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +76 -7
  175. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -0
  176. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +18 -14
  177. package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +6 -20
  178. package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +10 -21
  179. package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +7 -8
  180. package/sdk-core/tests/integ_tests/workflow_tests.rs +13 -14
  181. package/sdk-core/tests/main.rs +3 -13
  182. package/sdk-core/tests/runner.rs +75 -36
  183. package/sdk-core/tests/wf_input_replay.rs +32 -0
  184. package/src/conversions.rs +14 -8
  185. package/src/runtime.rs +9 -8
  186. package/ts/index.ts +8 -6
  187. package/sdk-core/bridge-ffi/Cargo.toml +0 -24
  188. package/sdk-core/bridge-ffi/LICENSE.txt +0 -23
  189. package/sdk-core/bridge-ffi/build.rs +0 -25
  190. package/sdk-core/bridge-ffi/include/sdk-core-bridge.h +0 -224
  191. package/sdk-core/bridge-ffi/src/lib.rs +0 -746
  192. package/sdk-core/bridge-ffi/src/wrappers.rs +0 -221
  193. package/sdk-core/protos/local/temporal/sdk/core/bridge/bridge.proto +0 -210
  194. package/sdk-core/sdk/src/conversions.rs +0 -8
@@ -1,57 +1,99 @@
1
1
  use crate::{
2
- replay::{HistoryInfo, TestHistoryBuilder},
3
- worker::client::WorkerClient,
2
+ protosext::ValidPollWFTQResponse,
3
+ worker::{
4
+ client::WorkerClient,
5
+ workflow::{CacheMissFetchReq, PermittedWFT, PreparedWFT},
6
+ },
4
7
  };
5
- use futures::{future::BoxFuture, stream, stream::BoxStream, FutureExt, Stream, StreamExt};
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
- /// A slimmed down version of a poll workflow task response which includes just the info needed
22
- /// by [WorkflowManager]. History events are expected to be consumed from it and applied to the
23
- /// state machines.
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: BoxStream<'static, Result<HistoryEvent, tonic::Status>>,
26
- /// It is useful to be able to look ahead up to one workflow task beyond the currently
27
- /// requested one. The initial (possibly only) motivation for this being to be able to
28
- /// pre-emptively notify lang about patch markers so that calls to `changed` do not need to
29
- /// be async.
30
- buffered: VecDeque<HistoryEvent>,
31
- pub previous_started_event_id: i64,
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
- write!(
36
- f,
37
- "HistoryUpdate(previous_started_event_id: {})",
38
- self.previous_started_event_id
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
- // Potentially this could actually be a ref w/ lifetime here
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
- wf_id: String,
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
- /// We use this to apply any
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
- pub(crate) fn new(
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
- fn extend_queue_with_new_page(&mut self, resp: GetWorkflowExecutionHistoryResponse) {
106
- self.next_page_token = resp.next_page_token.into();
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(resp.history.map(|h| h.events).unwrap_or_default());
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 = std::mem::take(&mut self.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
- impl Stream for HistoryPaginator {
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(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
128
- if let Some(e) = self.event_queue.pop_front() {
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
- let history_req = if let Some(req) = self.open_history_request.as_mut() {
132
- req
133
- } else {
134
- let npt = match std::mem::replace(&mut self.next_page_token, NextPageToken::Done) {
135
- // If there's no open request and the last page token we got was empty, we're done.
136
- NextPageToken::Done => return Poll::Ready(None),
137
- NextPageToken::FetchFromStart => vec![],
138
- NextPageToken::Next(v) => v,
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
- return match Future::poll(history_req.as_mut(), cx) {
372
+ match Future::poll(history_req, cx) {
153
373
  Poll::Ready(resp) => {
154
- self.open_history_request = None;
374
+ this.open_history_request.set(None);
155
375
  match resp {
156
376
  Err(neterr) => Poll::Ready(Some(Err(neterr))),
157
- Ok(resp) => {
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
- pub fn new(history_iterator: HistoryPaginator, previous_wft_started_id: i64) -> Self {
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: history_iterator.fuse().boxed(),
172
- buffered: VecDeque::new(),
173
- previous_started_event_id: previous_wft_started_id,
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 - should only be used for replaying.
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: stream::iter(events.into_iter().map(Ok)).boxed(),
187
- buffered: VecDeque::new(),
188
- previous_started_event_id: previous_wft_started_id,
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 (inclusive) to
193
- /// the next WFT started event (inclusive). If there is no subsequent WFT started event,
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
- /// Always buffers the WFT sequence *after* the returned one as well, if it is available.
493
+ /// Events are *consumed* by this process, to keep things efficient in workflow machines.
201
494
  ///
202
- /// Can return a tonic error in the event that fetching additional history was needed and failed
203
- pub async fn take_next_wft_sequence(
204
- &mut self,
205
- from_wft_started_id: i64,
206
- ) -> Result<Vec<HistoryEvent>, tonic::Status> {
207
- let (next_wft_events, maybe_bonus_events) = self
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
- if let Some(last_event_id) = next_wft_events.last().map(|he| he.event_id) {
215
- // Always attempt to fetch the *next* WFT sequence as well, to buffer it for lookahead
216
- let (buffer_these_events, maybe_bonus_events) =
217
- self.take_next_wft_sequence_impl(last_event_id).await?;
218
- self.buffered.extend(buffer_these_events);
219
- if !maybe_bonus_events.is_empty() {
220
- self.buffered.extend(maybe_bonus_events);
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
- Ok(next_wft_events)
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 an empty iterator if that has not been called
229
- /// first. May also return an empty iterator or incomplete sequence if we are at the end of
230
- /// history.
231
- pub fn peek_next_wft_sequence(&self) -> impl Iterator<Item = &HistoryEvent> {
232
- self.buffered.iter()
233
- }
234
-
235
- /// Retrieve the next WFT sequence, first from buffered events and then from the real stream.
236
- /// Returns (events up to the next logical wft sequence, extra events that were taken but
237
- /// should be re-appended to the end of the buffer).
238
- async fn take_next_wft_sequence_impl(
239
- &mut self,
240
- from_event_id: i64,
241
- ) -> Result<(Vec<HistoryEvent>, Vec<HistoryEvent>), tonic::Status> {
242
- let mut events_to_next_wft_started: Vec<HistoryEvent> = vec![];
243
-
244
- // This flag tracks if, while determining events to be returned, we have seen the next
245
- // logically significant WFT started event which follows the one that was passed in as a
246
- // parameter. If a WFT fails, times out, or is devoid of commands (ie: a heartbeat) it is
247
- // not significant. So we will stop returning events (exclusive) as soon as we see an event
248
- // following a WFT started that is *not* failed, timed out, or completed with a command.
249
- let mut next_wft_state = NextWftState::NotSeen;
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
- match next_wft_state {
259
- NextWftState::Seen => {
260
- // Must ignore failures and timeouts
261
- if e.event_type() == EventType::WorkflowTaskFailed
262
- || e.event_type() == EventType::WorkflowTaskTimedOut
263
- {
264
- next_wft_state = NextWftState::NotSeen;
265
- return true;
266
- } else if e.event_type() == EventType::WorkflowTaskCompleted {
267
- next_wft_state = NextWftState::SeenCompleted;
268
- return true;
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
- NextWftState::SeenCompleted => {
273
- // If we've seen the WFT be completed, and this event is another scheduled, then
274
- // this was an empty heartbeat we should ignore.
275
- if e.event_type() == EventType::WorkflowTaskScheduled {
276
- next_wft_state = NextWftState::NotSeen;
277
- return true;
278
- }
279
- // Otherwise, we're done here
280
- false
281
- }
282
- NextWftState::NotSeen => true,
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
- // Fetch events from the buffer first, then from the network
287
- let mut event_q = stream::iter(self.buffered.drain(..).map(Ok)).chain(&mut self.events);
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
- let mut extra_e = vec![];
290
- let mut last_seen_id = None;
291
- while let Some(e) = event_q.next().await {
292
- let e = e?;
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
- // This little block prevents us from infinitely fetching work from the server in the
295
- // event that, for whatever reason, it keeps returning stuff we've already seen.
296
- if let Some(last_id) = last_seen_id {
297
- if e.event_id <= last_id {
298
- error!("Server returned history event IDs that went backwards!");
299
- break;
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
- last_seen_id = Some(e.event_id);
303
-
304
- // It's possible to have gotten a new history update without eviction (ex: unhandled
305
- // command on completion), where we may need to skip events we already handled.
306
- if e.event_id > from_event_id {
307
- if !should_pop(&e) {
308
- if next_wft_state == NextWftState::SeenCompleted {
309
- // We have seen the wft completed event, but decided to exit. We don't
310
- // want to return that event as part of this sequence, so include it for
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
- events_to_next_wft_started.push(e);
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
- #[derive(Eq, PartialEq, Debug)]
330
- enum NextWftState {
331
- NotSeen,
332
- Seen,
333
- SeenCompleted,
673
+ NextWFTSeqEndIndex::Incomplete(last_index)
334
674
  }
335
675
 
336
- impl From<HistoryInfo> for HistoryUpdate {
337
- fn from(v: HistoryInfo) -> Self {
338
- Self::new_from_events(v.events().to_vec(), v.previous_started_event_id())
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
- fn as_history_update(&self) -> HistoryUpdate;
344
- }
697
+ pub trait TestHBExt {
698
+ fn as_history_update(&self) -> HistoryUpdate;
699
+ }
345
700
 
346
- impl TestHBExt for TestHistoryBuilder {
347
- fn as_history_update(&self) -> HistoryUpdate {
348
- self.get_full_history_info().unwrap().into()
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
- #[cfg(test)]
353
- pub mod tests {
354
- use super::*;
355
- use crate::{test_help::canned_histories, worker::client::mocks::mock_workflow_client};
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
- #[tokio::test]
358
- async fn consumes_standard_wft_sequence() {
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.take_next_wft_sequence(0).await.unwrap();
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 seq_2 = update.take_next_wft_sequence(3).await.unwrap();
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
- #[tokio::test]
370
- async fn skips_wft_failed() {
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.take_next_wft_sequence(0).await.unwrap();
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.take_next_wft_sequence(3).await.unwrap();
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
- #[tokio::test]
382
- async fn skips_wft_timeout() {
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.take_next_wft_sequence(0).await.unwrap();
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.take_next_wft_sequence(3).await.unwrap();
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
- #[tokio::test]
394
- async fn skips_events_before_desired_wft() {
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).await.unwrap();
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
- #[tokio::test]
404
- async fn history_ends_abruptly() {
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).await.unwrap();
409
- assert_eq!(seq_2.len(), 5);
410
- assert_eq!(seq_2.last().unwrap().event_id, 8);
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
- #[tokio::test]
414
- async fn heartbeats_skipped() {
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.add_get_event_id(EventType::TimerStarted, None);
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.add_full_wf_task();
424
- t.add_get_event_id(EventType::TimerStarted, None);
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.take_next_wft_sequence(0).await.unwrap();
799
+ let seq = next_check_peek(&mut update, 0);
432
800
  assert_eq!(seq.len(), 6);
433
- let seq = update.take_next_wft_sequence(6).await.unwrap();
801
+ let seq = next_check_peek(&mut update, 6);
434
802
  assert_eq!(seq.len(), 13);
435
- let seq = update.take_next_wft_sequence(19).await.unwrap();
803
+ let seq = next_check_peek(&mut update, 19);
436
804
  assert_eq!(seq.len(), 4);
437
- let seq = update.take_next_wft_sequence(23).await.unwrap();
805
+ let seq = next_check_peek(&mut update, 23);
438
806
  assert_eq!(seq.len(), 4);
439
- let seq = update.take_next_wft_sequence(27).await.unwrap();
807
+ let seq = next_check_peek(&mut update, 27);
440
808
  assert_eq!(seq.len(), 2);
441
809
  }
442
810
 
443
- #[tokio::test]
444
- async fn paginator_fetches_new_pages() {
445
- // Note that this test triggers the "event ids that went backwards" error, acceptably.
446
- // Can be fixed by having mock not return earlier events.
447
- let wft_count = 500;
448
- let long_hist = canned_histories::long_sequential_timers(wft_count);
449
- let initial_hist = long_hist.get_history_info(10).unwrap();
450
- let prev_started = initial_hist.previous_started_event_id();
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 = 2;
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 history = long_hist.get_history_info(10 * npt as usize).unwrap();
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(history.into()),
849
+ history: Some(History {
850
+ events: next_chunks.into(),
851
+ }),
462
852
  raw_history: vec![],
463
- next_page_token: vec![npt],
853
+ next_page_token,
464
854
  archived: false,
465
855
  })
466
856
  });
467
857
 
468
- let mut update = HistoryUpdate::new(
469
- HistoryPaginator::new(
470
- initial_hist.into(),
471
- "wfid".to_string(),
472
- "runid".to_string(),
473
- vec![2], // Start at page "2"
474
- Arc::new(mock_client),
475
- ),
476
- prev_started,
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).await.unwrap();
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 _ in 1..wft_count {
485
- let seq = update
486
- .take_next_wft_sequence(last_started_id)
487
- .await
488
- .unwrap();
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 update = HistoryUpdate::new(
513
- HistoryPaginator::new(
514
- partial_task.into(),
515
- "wfid".to_string(),
516
- "runid".to_string(),
517
- // A cache miss means we'll try to fetch from start
518
- NextPageToken::FetchFromStart,
519
- Arc::new(mock_client),
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).await.unwrap();
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).await.unwrap();
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
  }