@temporalio/core-bridge 0.19.2 → 0.20.2

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 (125) hide show
  1. package/Cargo.lock +90 -157
  2. package/Cargo.toml +1 -0
  3. package/index.d.ts +11 -27
  4. package/package.json +3 -3
  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 +1 -1
  11. package/sdk-core/.buildkite/docker/docker-compose.yaml +1 -1
  12. package/sdk-core/.cargo/config.toml +1 -0
  13. package/sdk-core/CODEOWNERS +1 -1
  14. package/sdk-core/bridge-ffi/include/sdk-core-bridge.h +119 -86
  15. package/sdk-core/bridge-ffi/src/lib.rs +311 -315
  16. package/sdk-core/bridge-ffi/src/wrappers.rs +108 -113
  17. package/sdk-core/client/Cargo.toml +13 -9
  18. package/sdk-core/client/LICENSE.txt +23 -0
  19. package/sdk-core/client/src/lib.rs +286 -174
  20. package/sdk-core/client/src/metrics.rs +86 -12
  21. package/sdk-core/client/src/raw.rs +566 -0
  22. package/sdk-core/client/src/retry.rs +137 -99
  23. package/sdk-core/core/Cargo.toml +15 -10
  24. package/sdk-core/core/LICENSE.txt +23 -0
  25. package/sdk-core/core/benches/workflow_replay.rs +79 -0
  26. package/sdk-core/core/src/abstractions.rs +38 -0
  27. package/sdk-core/core/src/core_tests/activity_tasks.rs +108 -182
  28. package/sdk-core/core/src/core_tests/child_workflows.rs +16 -11
  29. package/sdk-core/core/src/core_tests/determinism.rs +24 -12
  30. package/sdk-core/core/src/core_tests/local_activities.rs +53 -27
  31. package/sdk-core/core/src/core_tests/mod.rs +30 -43
  32. package/sdk-core/core/src/core_tests/queries.rs +82 -81
  33. package/sdk-core/core/src/core_tests/workers.rs +111 -296
  34. package/sdk-core/core/src/core_tests/workflow_cancels.rs +4 -4
  35. package/sdk-core/core/src/core_tests/workflow_tasks.rs +257 -242
  36. package/sdk-core/core/src/lib.rs +73 -318
  37. package/sdk-core/core/src/pollers/mod.rs +4 -6
  38. package/sdk-core/core/src/pollers/poll_buffer.rs +20 -14
  39. package/sdk-core/core/src/protosext/mod.rs +7 -10
  40. package/sdk-core/core/src/replay/mod.rs +11 -150
  41. package/sdk-core/core/src/telemetry/metrics.rs +35 -2
  42. package/sdk-core/core/src/telemetry/mod.rs +49 -16
  43. package/sdk-core/core/src/telemetry/prometheus_server.rs +14 -35
  44. package/sdk-core/core/src/test_help/mod.rs +104 -170
  45. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +57 -34
  46. package/sdk-core/core/src/worker/activities/local_activities.rs +95 -23
  47. package/sdk-core/core/src/worker/activities.rs +23 -16
  48. package/sdk-core/core/src/worker/client/mocks.rs +86 -0
  49. package/sdk-core/core/src/worker/client.rs +209 -0
  50. package/sdk-core/core/src/worker/mod.rs +207 -108
  51. package/sdk-core/core/src/workflow/driven_workflow.rs +21 -6
  52. package/sdk-core/core/src/workflow/history_update.rs +107 -24
  53. package/sdk-core/core/src/workflow/machines/activity_state_machine.rs +2 -3
  54. package/sdk-core/core/src/workflow/machines/child_workflow_state_machine.rs +2 -3
  55. package/sdk-core/core/src/workflow/machines/mod.rs +20 -17
  56. package/sdk-core/core/src/workflow/machines/signal_external_state_machine.rs +56 -19
  57. package/sdk-core/core/src/workflow/machines/transition_coverage.rs +5 -0
  58. package/sdk-core/core/src/workflow/machines/upsert_search_attributes_state_machine.rs +230 -22
  59. package/sdk-core/core/src/workflow/machines/workflow_machines.rs +81 -115
  60. package/sdk-core/core/src/workflow/machines/workflow_task_state_machine.rs +4 -4
  61. package/sdk-core/core/src/workflow/mod.rs +13 -1
  62. package/sdk-core/core/src/workflow/workflow_tasks/concurrency_manager.rs +70 -11
  63. package/sdk-core/core/src/workflow/workflow_tasks/mod.rs +65 -41
  64. package/sdk-core/core-api/Cargo.toml +9 -1
  65. package/sdk-core/core-api/LICENSE.txt +23 -0
  66. package/sdk-core/core-api/src/errors.rs +7 -38
  67. package/sdk-core/core-api/src/lib.rs +44 -52
  68. package/sdk-core/core-api/src/worker.rs +10 -2
  69. package/sdk-core/etc/deps.svg +127 -96
  70. package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +11 -7
  71. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +10 -0
  72. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/namespace.proto +6 -1
  73. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/workflow.proto +6 -0
  74. package/sdk-core/protos/api_upstream/temporal/api/errordetails/v1/message.proto +6 -0
  75. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +2 -1
  76. package/sdk-core/protos/api_upstream/temporal/api/replication/v1/message.proto +3 -0
  77. package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +12 -0
  78. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +25 -0
  79. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +4 -0
  80. package/sdk-core/protos/local/temporal/sdk/core/bridge/bridge.proto +19 -35
  81. package/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +2 -6
  82. package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +53 -11
  83. package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +14 -7
  84. package/sdk-core/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +3 -5
  85. package/sdk-core/sdk/Cargo.toml +16 -2
  86. package/sdk-core/sdk/LICENSE.txt +23 -0
  87. package/sdk-core/sdk/src/interceptors.rs +11 -0
  88. package/sdk-core/sdk/src/lib.rs +139 -151
  89. package/sdk-core/sdk/src/workflow_context/options.rs +86 -1
  90. package/sdk-core/sdk/src/workflow_context.rs +36 -17
  91. package/sdk-core/sdk/src/workflow_future.rs +19 -25
  92. package/sdk-core/sdk-core-protos/Cargo.toml +1 -1
  93. package/sdk-core/sdk-core-protos/build.rs +1 -0
  94. package/sdk-core/sdk-core-protos/src/history_info.rs +17 -4
  95. package/sdk-core/sdk-core-protos/src/lib.rs +251 -47
  96. package/sdk-core/test-utils/Cargo.toml +3 -1
  97. package/sdk-core/test-utils/src/canned_histories.rs +27 -0
  98. package/sdk-core/test-utils/src/histfetch.rs +3 -3
  99. package/sdk-core/test-utils/src/lib.rs +223 -68
  100. package/sdk-core/tests/integ_tests/client_tests.rs +27 -4
  101. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +93 -14
  102. package/sdk-core/tests/integ_tests/polling_tests.rs +18 -12
  103. package/sdk-core/tests/integ_tests/queries_tests.rs +50 -53
  104. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +117 -103
  105. package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +8 -1
  106. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +10 -5
  107. package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +7 -1
  108. package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +32 -9
  109. package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +7 -1
  110. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +76 -15
  111. package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +19 -3
  112. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +39 -42
  113. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +84 -0
  114. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +30 -8
  115. package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +21 -6
  116. package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +26 -16
  117. package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +66 -0
  118. package/sdk-core/tests/integ_tests/workflow_tests.rs +78 -74
  119. package/sdk-core/tests/load_tests.rs +9 -6
  120. package/sdk-core/tests/main.rs +43 -10
  121. package/src/conversions.rs +7 -12
  122. package/src/lib.rs +322 -357
  123. package/sdk-core/client/src/mocks.rs +0 -167
  124. package/sdk-core/core/src/worker/dispatcher.rs +0 -171
  125. package/sdk-core/protos/local/temporal/sdk/core/bridge/service.proto +0 -61
@@ -1,16 +1,18 @@
1
- use crate::workflow::WFCommand;
1
+ use crate::workflow::{WFCommand, WorkflowStartedInfo};
2
2
  use std::collections::VecDeque;
3
3
  use temporal_sdk_core_protos::{
4
4
  coresdk::workflow_activation::{
5
- workflow_activation_job, CancelWorkflow, SignalWorkflow, WorkflowActivationJob,
5
+ start_workflow_from_attribs, workflow_activation_job, CancelWorkflow, SignalWorkflow,
6
+ WorkflowActivationJob,
6
7
  },
7
8
  temporal::api::history::v1::WorkflowExecutionStartedEventAttributes,
9
+ utilities::TryIntoOrNone,
8
10
  };
9
11
 
10
12
  /// Abstracts away the concept of an actual workflow implementation, handling sending it new
11
13
  /// jobs and fetching output from it.
12
14
  pub struct DrivenWorkflow {
13
- started_attrs: Option<WorkflowExecutionStartedEventAttributes>,
15
+ started_attrs: Option<WorkflowStartedInfo>,
14
16
  fetcher: Box<dyn WorkflowFetcher>,
15
17
  /// Outgoing activation jobs that need to be sent to the lang sdk
16
18
  outgoing_wf_activation_jobs: VecDeque<workflow_activation_job::Variant>,
@@ -31,13 +33,26 @@ where
31
33
 
32
34
  impl DrivenWorkflow {
33
35
  /// Start the workflow
34
- pub fn start(&mut self, attribs: WorkflowExecutionStartedEventAttributes) {
36
+ pub fn start(
37
+ &mut self,
38
+ workflow_id: String,
39
+ randomness_seed: u64,
40
+ attribs: WorkflowExecutionStartedEventAttributes,
41
+ ) {
35
42
  debug!(run_id = %attribs.original_execution_run_id, "Driven WF start");
36
- self.started_attrs = Some(attribs);
43
+ let started_info = WorkflowStartedInfo {
44
+ workflow_task_timeout: attribs.workflow_task_timeout.clone().try_into_or_none(),
45
+ workflow_execution_timeout: attribs
46
+ .workflow_execution_timeout
47
+ .clone()
48
+ .try_into_or_none(),
49
+ };
50
+ self.send_job(start_workflow_from_attribs(attribs, workflow_id, randomness_seed).into());
51
+ self.started_attrs = Some(started_info);
37
52
  }
38
53
 
39
54
  /// Return the attributes from the workflow execution started event if this workflow has started
40
- pub fn get_started_attrs(&self) -> Option<&WorkflowExecutionStartedEventAttributes> {
55
+ pub fn get_started_info(&self) -> Option<&WorkflowStartedInfo> {
41
56
  self.started_attrs.as_ref()
42
57
  }
43
58
 
@@ -1,6 +1,6 @@
1
1
  use crate::{
2
2
  replay::{HistoryInfo, TestHistoryBuilder},
3
- ServerGatewayApis,
3
+ worker::client::WorkerClientBag,
4
4
  };
5
5
  use futures::{future::BoxFuture, stream, stream::BoxStream, FutureExt, Stream, StreamExt};
6
6
  use std::{
@@ -30,32 +30,84 @@ pub struct HistoryUpdate {
30
30
  }
31
31
 
32
32
  pub struct HistoryPaginator {
33
- gateway: Arc<dyn ServerGatewayApis + Send + Sync>,
33
+ // Potentially this could actually be a ref w/ lifetime here
34
+ client: Arc<WorkerClientBag>,
34
35
  event_queue: VecDeque<HistoryEvent>,
35
36
  wf_id: String,
36
37
  run_id: String,
37
- next_page_token: Vec<u8>,
38
+ next_page_token: NextPageToken,
38
39
  open_history_request:
39
40
  Option<BoxFuture<'static, Result<GetWorkflowExecutionHistoryResponse, tonic::Status>>>,
41
+ /// These are events that should be returned once pagination has finished. This only happens
42
+ /// during cache misses, where we got a partial task but need to fetch history from the start.
43
+ /// We use this to apply any
44
+ final_events: Vec<HistoryEvent>,
45
+ }
46
+
47
+ #[derive(Clone, Debug)]
48
+ pub enum NextPageToken {
49
+ /// There is no page token, we need to fetch history from the beginning
50
+ FetchFromStart,
51
+ /// There is a page token
52
+ Next(Vec<u8>),
53
+ /// There is no page token, we are done fetching history
54
+ Done,
55
+ }
56
+
57
+ // If we're converting from a page token from the server, if it's empty, then we're done.
58
+ impl From<Vec<u8>> for NextPageToken {
59
+ fn from(page_token: Vec<u8>) -> Self {
60
+ if page_token.is_empty() {
61
+ NextPageToken::Done
62
+ } else {
63
+ NextPageToken::Next(page_token)
64
+ }
65
+ }
40
66
  }
41
67
 
42
68
  impl HistoryPaginator {
43
- pub fn new(
69
+ pub(crate) fn new(
44
70
  initial_history: History,
45
71
  wf_id: String,
46
72
  run_id: String,
47
- next_page_token: Vec<u8>,
48
- gateway: Arc<dyn ServerGatewayApis + Send + Sync>,
73
+ next_page_token: impl Into<NextPageToken>,
74
+ client: Arc<WorkerClientBag>,
49
75
  ) -> Self {
76
+ let next_page_token = next_page_token.into();
77
+ let (event_queue, final_events) =
78
+ if matches!(next_page_token, NextPageToken::FetchFromStart) {
79
+ (VecDeque::new(), initial_history.events)
80
+ } else {
81
+ (initial_history.events.into(), vec![])
82
+ };
50
83
  Self {
51
- gateway,
52
- event_queue: initial_history.events.into(),
84
+ client,
85
+ event_queue,
53
86
  wf_id,
54
87
  run_id,
55
88
  next_page_token,
56
89
  open_history_request: None,
90
+ final_events,
57
91
  }
58
92
  }
93
+
94
+ fn extend_queue_with_new_page(&mut self, resp: GetWorkflowExecutionHistoryResponse) {
95
+ self.next_page_token = resp.next_page_token.into();
96
+ self.event_queue
97
+ .extend(resp.history.map(|h| h.events).unwrap_or_default());
98
+ if matches!(&self.next_page_token, NextPageToken::Done) {
99
+ // If finished, we need to extend the queue with the final events, skipping any
100
+ // which are already present.
101
+ if let Some(last_event_id) = self.event_queue.back().map(|e| e.event_id) {
102
+ let final_events = std::mem::take(&mut self.final_events);
103
+ self.event_queue.extend(
104
+ final_events
105
+ .into_iter()
106
+ .skip_while(|e2| e2.event_id <= last_event_id),
107
+ );
108
+ }
109
+ };
110
+ }
59
111
  }
60
112
 
61
113
  impl Stream for HistoryPaginator {
@@ -65,20 +117,19 @@ impl Stream for HistoryPaginator {
65
117
  if let Some(e) = self.event_queue.pop_front() {
66
118
  return Poll::Ready(Some(Ok(e)));
67
119
  }
68
- if self.next_page_token.is_empty() {
69
- return Poll::Ready(None);
70
- }
71
-
72
120
  let history_req = if let Some(req) = self.open_history_request.as_mut() {
73
121
  req
74
122
  } else {
123
+ let npt = match std::mem::replace(&mut self.next_page_token, NextPageToken::Done) {
124
+ // If there's no open request and the last page token we got was empty, we're done.
125
+ NextPageToken::Done => return Poll::Ready(None),
126
+ NextPageToken::FetchFromStart => vec![],
127
+ NextPageToken::Next(v) => v,
128
+ };
75
129
  debug!(run_id=%self.run_id, "Fetching new history page");
76
- // We're out of stored events and we have a page token - fetch additional history from
77
- // the server. Note that server can return page tokens that point to an empty page.
78
- let gw = self.gateway.clone();
130
+ let gw = self.client.clone();
79
131
  let wid = self.wf_id.clone();
80
132
  let rid = self.run_id.clone();
81
- let npt = self.next_page_token.clone();
82
133
  let resp_fut =
83
134
  async move { gw.get_workflow_execution_history(wid, Some(rid), npt).await };
84
135
  self.open_history_request.insert(resp_fut.boxed())
@@ -90,9 +141,7 @@ impl Stream for HistoryPaginator {
90
141
  match resp {
91
142
  Err(neterr) => Poll::Ready(Some(Err(neterr))),
92
143
  Ok(resp) => {
93
- self.next_page_token = resp.next_page_token;
94
- self.event_queue
95
- .extend(resp.history.map(|h| h.events).unwrap_or_default());
144
+ self.extend_queue_with_new_page(resp);
96
145
  Poll::Ready(self.event_queue.pop_front().map(Ok))
97
146
  }
98
147
  }
@@ -255,8 +304,7 @@ impl TestHBExt for TestHistoryBuilder {
255
304
  #[cfg(test)]
256
305
  pub mod tests {
257
306
  use super::*;
258
- use crate::test_help::canned_histories;
259
- use temporal_client::mocks::mock_gateway;
307
+ use crate::{test_help::canned_histories, worker::client::mocks::mock_workflow_client};
260
308
 
261
309
  #[tokio::test]
262
310
  async fn consumes_standard_wft_sequence() {
@@ -312,10 +360,10 @@ pub mod tests {
312
360
  let long_hist = canned_histories::long_sequential_timers(wft_count);
313
361
  let initial_hist = long_hist.get_history_info(10).unwrap();
314
362
  let prev_started = initial_hist.previous_started_event_id();
315
- let mut mock_gateway = mock_gateway();
363
+ let mut mock_client = mock_workflow_client();
316
364
 
317
365
  let mut npt = 2;
318
- mock_gateway
366
+ mock_client
319
367
  .expect_get_workflow_execution_history()
320
368
  .returning(move |_, _, passed_npt| {
321
369
  assert_eq!(passed_npt, vec![npt]);
@@ -335,7 +383,7 @@ pub mod tests {
335
383
  "wfid".to_string(),
336
384
  "runid".to_string(),
337
385
  vec![2], // Start at page "2"
338
- Arc::new(mock_gateway),
386
+ Arc::new(mock_client.into()),
339
387
  ),
340
388
  prev_started,
341
389
  );
@@ -358,4 +406,39 @@ pub mod tests {
358
406
  last_started_id += 5;
359
407
  }
360
408
  }
409
+
410
+ #[tokio::test]
411
+ async fn handles_cache_misses() {
412
+ let timer_hist = canned_histories::single_timer("t");
413
+ let partial_task = timer_hist.get_one_wft(2).unwrap();
414
+ let mut history_from_get: GetWorkflowExecutionHistoryResponse =
415
+ timer_hist.get_history_info(2).unwrap().into();
416
+ // Chop off the last event, which is WFT started, which server doesn't return in get
417
+ // history
418
+ history_from_get.history.as_mut().map(|h| h.events.pop());
419
+ let mut mock_client = mock_workflow_client();
420
+ mock_client
421
+ .expect_get_workflow_execution_history()
422
+ .returning(move |_, _, _| Ok(history_from_get.clone()));
423
+
424
+ let mut update = HistoryUpdate::new(
425
+ HistoryPaginator::new(
426
+ partial_task.into(),
427
+ "wfid".to_string(),
428
+ "runid".to_string(),
429
+ // A cache miss means we'll try to fetch from start
430
+ NextPageToken::FetchFromStart,
431
+ Arc::new(mock_client.into()),
432
+ ),
433
+ 1,
434
+ );
435
+ // We expect if we try to take the first task sequence that the first event is the first
436
+ // event in the sequence.
437
+ let seq = update.take_next_wft_sequence(0).await.unwrap();
438
+ assert_eq!(seq[0].event_id, 1);
439
+ let seq = update.take_next_wft_sequence(3).await.unwrap();
440
+ // Verify anything extra (which should only ever be WFT started) was re-appended to the
441
+ // end of the event iteration after fetching the old history.
442
+ assert_eq!(seq.last().unwrap().event_id, 8);
443
+ }
361
444
  }
@@ -737,9 +737,8 @@ fn convert_payloads(
737
737
  ) -> Result<Option<Payload>, WFMachinesError> {
738
738
  result.map(TryInto::try_into).transpose().map_err(|pe| {
739
739
  WFMachinesError::Fatal(format!(
740
- "Not exactly one payload in activity result ({}) for event: {}",
741
- pe,
742
- event_info.map(|e| e.event.clone()).unwrap_or_default()
740
+ "Not exactly one payload in activity result ({}) for event: {:?}",
741
+ pe, event_info
743
742
  ))
744
743
  })
745
744
  }
@@ -606,9 +606,8 @@ fn convert_payloads(
606
606
  ) -> Result<Option<Payload>, WFMachinesError> {
607
607
  result.map(TryInto::try_into).transpose().map_err(|pe| {
608
608
  WFMachinesError::Fatal(format!(
609
- "Not exactly one payload in child workflow result ({}) for event: {}",
610
- pe,
611
- event_info.map(|e| e.event.clone()).unwrap_or_default()
609
+ "Not exactly one payload in child workflow result ({}) for event: {:?}",
610
+ pe, event_info
612
611
  ))
613
612
  })
614
613
  }
@@ -15,7 +15,6 @@ mod patch_state_machine;
15
15
  mod side_effect_state_machine;
16
16
  mod signal_external_state_machine;
17
17
  mod timer_state_machine;
18
- #[allow(unused)]
19
18
  mod upsert_search_attributes_state_machine;
20
19
  mod workflow_task_state_machine;
21
20
 
@@ -42,9 +41,12 @@ use std::{
42
41
  fmt::{Debug, Display},
43
42
  };
44
43
  use temporal_sdk_core_protos::temporal::api::{
45
- command::v1::Command as ProtoCommand, enums::v1::CommandType, history::v1::HistoryEvent,
44
+ command::v1::Command as ProtoCommand,
45
+ enums::v1::{CommandType, EventType},
46
+ history::v1::HistoryEvent,
46
47
  };
47
48
  use timer_state_machine::TimerMachine;
49
+ use upsert_search_attributes_state_machine::UpsertSearchAttributesMachine;
48
50
  use workflow_machines::MachineResponse;
49
51
  use workflow_task_state_machine::WorkflowTaskMachine;
50
52
 
@@ -65,6 +67,7 @@ enum MachineKind {
65
67
  SignalExternalWorkflow,
66
68
  CancelExternalWorkflow,
67
69
  LocalActivity,
70
+ UpsertSearchAttributes,
68
71
  }
69
72
 
70
73
  #[enum_dispatch::enum_dispatch]
@@ -82,6 +85,7 @@ enum Machines {
82
85
  SignalExternalMachine,
83
86
  TimerMachine,
84
87
  WorkflowTaskMachine,
88
+ UpsertSearchAttributesMachine,
85
89
  }
86
90
 
87
91
  /// Extends [rustfsm::StateMachine] with some functionality specific to the temporal SDK.
@@ -104,7 +108,7 @@ trait TemporalStateMachine: Send {
104
108
  /// to update the overall state of the workflow. EX: To issue outgoing WF activations.
105
109
  fn handle_event(
106
110
  &mut self,
107
- event: &HistoryEvent,
111
+ event: HistoryEvent,
108
112
  has_next_event: bool,
109
113
  ) -> Result<Vec<MachineResponse>, WFMachinesError>;
110
114
 
@@ -168,7 +172,7 @@ where
168
172
 
169
173
  fn handle_event(
170
174
  &mut self,
171
- event: &HistoryEvent,
175
+ event: HistoryEvent,
172
176
  has_next_event: bool,
173
177
  ) -> Result<Vec<MachineResponse>, WFMachinesError> {
174
178
  debug!(
@@ -177,22 +181,20 @@ where
177
181
  state = %self.state(),
178
182
  "handling event"
179
183
  );
180
- let converted_event: <Self as StateMachine>::Event = event.clone().try_into()?;
184
+ let event_info = EventInfo {
185
+ event_id: event.event_id,
186
+ event_type: event.event_type(),
187
+ has_next_event,
188
+ };
189
+ let converted_event: <Self as StateMachine>::Event = event.try_into()?;
181
190
 
182
191
  match OnEventWrapper::on_event_mut(self, converted_event) {
183
- Ok(c) => process_machine_commands(
184
- self,
185
- c,
186
- Some(EventInfo {
187
- event,
188
- has_next_event,
189
- }),
190
- ),
192
+ Ok(c) => process_machine_commands(self, c, Some(event_info)),
191
193
  Err(MachineError::InvalidTransition) => Err(WFMachinesError::Fatal(format!(
192
- "{} in state {} says the transition is invalid during event {}",
194
+ "{} in state {} says the transition is invalid during event {:?}",
193
195
  self.name(),
194
196
  self.state(),
195
- event
197
+ event_info
196
198
  ))),
197
199
  Err(MachineError::Underlying(e)) => Err(e.into()),
198
200
  }
@@ -263,8 +265,9 @@ trait WFMachinesAdapter: StateMachine {
263
265
  }
264
266
 
265
267
  #[derive(Debug, Copy, Clone)]
266
- struct EventInfo<'a> {
267
- event: &'a HistoryEvent,
268
+ struct EventInfo {
269
+ event_id: i64,
270
+ event_type: EventType,
268
271
  has_next_event: bool,
269
272
  }
270
273
 
@@ -6,8 +6,11 @@ use rustfsm::{fsm, MachineError, TransitionResult};
6
6
  use std::convert::TryFrom;
7
7
  use temporal_sdk_core_protos::{
8
8
  coresdk::{
9
- common::{NamespacedWorkflowExecution, Payload},
9
+ common::NamespacedWorkflowExecution,
10
10
  workflow_activation::ResolveSignalExternalWorkflow,
11
+ workflow_commands::{
12
+ signal_external_workflow_execution as sig_we, SignalExternalWorkflowExecution,
13
+ },
11
14
  IntoPayloadsExt,
12
15
  },
13
16
  temporal::api::{
@@ -62,15 +65,29 @@ pub(super) enum SignalExternalCommand {
62
65
  }
63
66
 
64
67
  pub(super) fn new_external_signal(
65
- seq: u32,
66
- workflow_execution: NamespacedWorkflowExecution,
67
- signal_name: String,
68
- payloads: impl IntoIterator<Item = Payload>,
69
- only_child: bool,
70
- ) -> NewMachineWithCommand {
68
+ attrs: SignalExternalWorkflowExecution,
69
+ this_namespace: &str,
70
+ ) -> Result<NewMachineWithCommand, WFMachinesError> {
71
+ let (workflow_execution, only_child) = match attrs.target {
72
+ None => {
73
+ return Err(WFMachinesError::Fatal(
74
+ "Signal external workflow command had empty target field".to_string(),
75
+ ))
76
+ }
77
+ Some(sig_we::Target::ChildWorkflowId(wfid)) => (
78
+ NamespacedWorkflowExecution {
79
+ namespace: this_namespace.to_string(),
80
+ workflow_id: wfid,
81
+ run_id: "".to_string(),
82
+ },
83
+ true,
84
+ ),
85
+ Some(sig_we::Target::WorkflowExecution(we)) => (we, false),
86
+ };
87
+
71
88
  let mut s = SignalExternalMachine {
72
89
  state: Created {}.into(),
73
- shared_state: SharedState { seq },
90
+ shared_state: SharedState { seq: attrs.seq },
74
91
  };
75
92
  OnEventWrapper::on_event_mut(&mut s, SignalExternalMachineEvents::Schedule)
76
93
  .expect("Scheduling signal external wf command doesn't fail");
@@ -81,9 +98,13 @@ pub(super) fn new_external_signal(
81
98
  workflow_id: workflow_execution.workflow_id,
82
99
  run_id: workflow_execution.run_id,
83
100
  }),
84
- header: None,
85
- signal_name,
86
- input: payloads.into_payloads(),
101
+ header: if attrs.headers.is_empty() {
102
+ None
103
+ } else {
104
+ Some(attrs.headers.into())
105
+ },
106
+ signal_name: attrs.signal_name,
107
+ input: attrs.args.into_payloads(),
87
108
  // Is deprecated
88
109
  control: "".to_string(),
89
110
  child_workflow_only: only_child,
@@ -93,10 +114,10 @@ pub(super) fn new_external_signal(
93
114
  command_type: CommandType::SignalExternalWorkflowExecution as i32,
94
115
  attributes: Some(cmd_attrs),
95
116
  };
96
- NewMachineWithCommand {
117
+ Ok(NewMachineWithCommand {
97
118
  command: cmd,
98
119
  machine: s.into(),
99
- }
120
+ })
100
121
  }
101
122
 
102
123
  #[derive(Default, Clone)]
@@ -287,7 +308,9 @@ mod tests {
287
308
  use super::*;
288
309
  use crate::{replay::TestHistoryBuilder, workflow::managed_wf::ManagedWFFunc};
289
310
  use std::mem::discriminant;
290
- use temporal_sdk::{CancellableFuture, WfContext, WorkflowFunction, WorkflowResult};
311
+ use temporal_sdk::{
312
+ CancellableFuture, SignalWorkflowOptions, WfContext, WorkflowFunction, WorkflowResult,
313
+ };
291
314
  use temporal_sdk_core_protos::coresdk::workflow_activation::{
292
315
  workflow_activation_job, WorkflowActivationJob,
293
316
  };
@@ -295,9 +318,9 @@ mod tests {
295
318
  const SIGNAME: &str = "signame";
296
319
 
297
320
  async fn signal_sender(ctx: WfContext) -> WorkflowResult<()> {
298
- let res = ctx
299
- .signal_workflow("fake_wid", "fake_rid", SIGNAME, b"hi!")
300
- .await;
321
+ let mut dat = SignalWorkflowOptions::new("fake_wid", "fake_rid", SIGNAME, [b"hi!"]);
322
+ dat.with_header("tupac", b"shakur");
323
+ let res = ctx.signal_workflow(dat).await;
301
324
  if res.is_err() {
302
325
  Err(anyhow::anyhow!("Signal fail!"))
303
326
  } else {
@@ -325,12 +348,21 @@ mod tests {
325
348
  let wff = WorkflowFunction::new(signal_sender);
326
349
  let mut wfm = ManagedWFFunc::new(t, wff, vec![]);
327
350
  wfm.get_next_activation().await.unwrap();
328
- let cmds = wfm.get_server_commands().commands;
351
+ let mut cmds = wfm.get_server_commands().commands;
329
352
  assert_eq!(cmds.len(), 1);
330
353
  assert_eq!(
331
354
  cmds[0].command_type(),
332
355
  CommandType::SignalExternalWorkflowExecution
333
356
  );
357
+ assert_matches!(
358
+ cmds.remove(0).attributes.unwrap(),
359
+ command::Attributes::SignalExternalWorkflowExecutionCommandAttributes(attrs) => {
360
+ assert_eq!(attrs.signal_name, SIGNAME);
361
+ assert_eq!(attrs.input.unwrap().payloads[0],
362
+ b"hi!".into());
363
+ assert_eq!(*attrs.header.unwrap().fields.get("tupac").unwrap(), b"shakur".into());
364
+ }
365
+ );
334
366
  wfm.get_next_activation().await.unwrap();
335
367
  let cmds = wfm.get_server_commands().commands;
336
368
  assert_eq!(cmds.len(), 1);
@@ -354,7 +386,12 @@ mod tests {
354
386
  t.add_workflow_execution_completed();
355
387
 
356
388
  let wff = WorkflowFunction::new(|ctx: WfContext| async move {
357
- let sig = ctx.signal_workflow("fake_wid", "fake_rid", SIGNAME, b"hi!");
389
+ let sig = ctx.signal_workflow(SignalWorkflowOptions::new(
390
+ "fake_wid",
391
+ "fake_rid",
392
+ SIGNAME,
393
+ [b"hi!"],
394
+ ));
358
395
  sig.cancel(&ctx);
359
396
  let _res = sig.await;
360
397
  Ok(().into())
@@ -77,6 +77,7 @@ mod machine_coverage_report {
77
77
  fail_workflow_state_machine::FailWorkflowMachine,
78
78
  local_activity_state_machine::LocalActivityMachine, patch_state_machine::PatchMachine,
79
79
  signal_external_state_machine::SignalExternalMachine, timer_state_machine::TimerMachine,
80
+ upsert_search_attributes_state_machine::UpsertSearchAttributesMachine,
80
81
  workflow_task_state_machine::WorkflowTaskMachine,
81
82
  };
82
83
  use rustfsm::StateMachine;
@@ -113,6 +114,7 @@ mod machine_coverage_report {
113
114
  let mut signal_ext = SignalExternalMachine::visualizer().to_owned();
114
115
  let mut cancel_ext = CancelExternalMachine::visualizer().to_owned();
115
116
  let mut la_mach = LocalActivityMachine::visualizer().to_owned();
117
+ let mut upsert_search_attr = UpsertSearchAttributesMachine::visualizer().to_owned();
116
118
 
117
119
  // This isn't at all efficient but doesn't need to be.
118
120
  // Replace transitions in the vizzes with green color if they are covered.
@@ -133,6 +135,9 @@ mod machine_coverage_report {
133
135
  m @ "SignalExternalMachine" => cover_transitions(m, &mut signal_ext, coverage),
134
136
  m @ "CancelExternalMachine" => cover_transitions(m, &mut cancel_ext, coverage),
135
137
  m @ "LocalActivityMachine" => cover_transitions(m, &mut la_mach, coverage),
138
+ m @ "UpsertSearchAttributesMachine" => {
139
+ cover_transitions(m, &mut upsert_search_attr, coverage)
140
+ }
136
141
  m => panic!("Unknown machine {}", m),
137
142
  }
138
143
  }