@temporalio/core-bridge 1.8.4 → 1.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@temporalio/core-bridge",
3
- "version": "1.8.4",
3
+ "version": "1.8.6",
4
4
  "description": "Temporal.io SDK Core<>Node bridge",
5
5
  "main": "index.js",
6
6
  "types": "lib/index.d.ts",
@@ -23,7 +23,7 @@
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
25
  "@opentelemetry/api": "^1.4.1",
26
- "@temporalio/common": "1.8.4",
26
+ "@temporalio/common": "1.8.6",
27
27
  "arg": "^5.0.2",
28
28
  "cargo-cp-artifact": "^0.1.6",
29
29
  "which": "^2.0.2"
@@ -53,5 +53,5 @@
53
53
  "publishConfig": {
54
54
  "access": "public"
55
55
  },
56
- "gitHead": "7e65cf816b1deef72973dc64ccbf2c93916a3eb1"
56
+ "gitHead": "1e95cf92ec5e6efffb7aedb064ea46be05df0c14"
57
57
  }
@@ -3,7 +3,7 @@ use crate::{
3
3
  replay::{default_wes_attribs, TestHistoryBuilder, DEFAULT_WORKFLOW_TYPE},
4
4
  test_help::{
5
5
  hist_to_poll_resp, mock_sdk, mock_sdk_cfg, mock_worker, single_hist_mock_sg, MockPollCfg,
6
- ResponseType,
6
+ ResponseType, WorkerExt,
7
7
  },
8
8
  worker::{client::mocks::mock_workflow_client, LEGACY_QUERY_ID},
9
9
  };
@@ -32,9 +32,7 @@ use temporal_sdk_core_protos::{
32
32
  coresdk::{
33
33
  activity_result::ActivityExecutionResult,
34
34
  workflow_activation::{workflow_activation_job, WorkflowActivationJob},
35
- workflow_commands::{
36
- ActivityCancellationType, QueryResult, QuerySuccess, ScheduleLocalActivity,
37
- },
35
+ workflow_commands::{ActivityCancellationType, ScheduleLocalActivity},
38
36
  workflow_completion::WorkflowActivationCompletion,
39
37
  ActivityTaskCompletion, AsJsonPayloadExt,
40
38
  },
@@ -47,7 +45,7 @@ use temporal_sdk_core_protos::{
47
45
  DEFAULT_ACTIVITY_TYPE,
48
46
  };
49
47
  use temporal_sdk_core_test_utils::{
50
- schedule_local_activity_cmd, start_timer_cmd, WorkerTestHelpers,
48
+ query_ok, schedule_local_activity_cmd, start_timer_cmd, WorkerTestHelpers,
51
49
  };
52
50
  use tokio::{join, select, sync::Barrier};
53
51
 
@@ -527,16 +525,7 @@ async fn query_during_wft_heartbeat_doesnt_accidentally_fail_to_continue_heartbe
527
525
  barrier.wait().await;
528
526
  core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
529
527
  task.run_id,
530
- QueryResult {
531
- query_id: query.query_id.clone(),
532
- variant: Some(
533
- QuerySuccess {
534
- response: Some("whatever".into()),
535
- }
536
- .into(),
537
- ),
538
- }
539
- .into(),
528
+ query_ok(&query.query_id, "whatev"),
540
529
  ))
541
530
  .await
542
531
  .unwrap();
@@ -582,10 +571,8 @@ async fn la_resolve_during_legacy_query_does_not_combine(#[case] impossible_quer
582
571
  t.add_timer_fired(timer_started_event_id, "1".to_string());
583
572
 
584
573
  // nonlegacy query got here & LA started here
574
+ // then next task is incremental w/ legacy query (for impossible query case)
585
575
  t.add_full_wf_task();
586
- // legacy query got here, at the same time that the LA is resolved
587
- t.add_local_activity_result_marker(1, "1", "whatever".into());
588
- t.add_workflow_execution_completed();
589
576
 
590
577
  let barr = Arc::new(Barrier::new(2));
591
578
  let barr_c = barr.clone();
@@ -612,9 +599,6 @@ async fn la_resolve_during_legacy_query_does_not_combine(#[case] impossible_quer
612
599
  ResponseType::UntilResolved(
613
600
  async move {
614
601
  barr_c.wait().await;
615
- // This sleep is the only not-incredibly-invasive way to ensure the LA
616
- // resolves & updates machines before we process this task
617
- tokio::time::sleep(Duration::from_secs(1)).await;
618
602
  }
619
603
  .boxed(),
620
604
  2,
@@ -662,42 +646,26 @@ async fn la_resolve_during_legacy_query_does_not_combine(#[case] impossible_quer
662
646
  ))
663
647
  .await
664
648
  .unwrap();
649
+
665
650
  let task = core.poll_workflow_activation().await.unwrap();
666
651
  assert_matches!(
667
652
  task.jobs.as_slice(),
668
- &[
669
- WorkflowActivationJob {
670
- variant: Some(workflow_activation_job::Variant::FireTimer(_)),
671
- },
672
- WorkflowActivationJob {
673
- variant: Some(workflow_activation_job::Variant::QueryWorkflow(_)),
674
- }
675
- ]
653
+ &[WorkflowActivationJob {
654
+ variant: Some(workflow_activation_job::Variant::FireTimer(_)),
655
+ },]
676
656
  );
677
- core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
657
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
678
658
  task.run_id,
679
- vec![
680
- schedule_local_activity_cmd(
681
- 1,
682
- "act-id",
683
- ActivityCancellationType::TryCancel,
684
- Duration::from_secs(60),
685
- ),
686
- QueryResult {
687
- query_id: "q1".to_string(),
688
- variant: Some(
689
- QuerySuccess {
690
- response: Some("whatev".into()),
691
- }
692
- .into(),
693
- ),
694
- }
695
- .into(),
696
- ],
659
+ schedule_local_activity_cmd(
660
+ 1,
661
+ "act-id",
662
+ ActivityCancellationType::TryCancel,
663
+ Duration::from_secs(60),
664
+ ),
697
665
  ))
698
666
  .await
699
667
  .unwrap();
700
- barr.wait().await;
668
+
701
669
  let task = core.poll_workflow_activation().await.unwrap();
702
670
  // The next task needs to be resolve, since the LA is completed immediately
703
671
  assert_matches!(
@@ -708,21 +676,30 @@ async fn la_resolve_during_legacy_query_does_not_combine(#[case] impossible_quer
708
676
  );
709
677
  // Complete workflow
710
678
  core.complete_execution(&task.run_id).await;
679
+
680
+ // Now we will get the query
681
+ let task = core.poll_workflow_activation().await.unwrap();
682
+ assert_matches!(
683
+ task.jobs.as_slice(),
684
+ &[WorkflowActivationJob {
685
+ variant: Some(workflow_activation_job::Variant::QueryWorkflow(ref q)),
686
+ }]
687
+ if q.query_id == "q1"
688
+ );
689
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
690
+ task.run_id,
691
+ query_ok("q1", "whatev"),
692
+ ))
693
+ .await
694
+ .unwrap();
695
+ barr.wait().await;
696
+
711
697
  if impossible_query_in_task {
712
698
  // finish last query
713
699
  let task = core.poll_workflow_activation().await.unwrap();
714
- core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
700
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
715
701
  task.run_id,
716
- vec![QueryResult {
717
- query_id: LEGACY_QUERY_ID.to_string(),
718
- variant: Some(
719
- QuerySuccess {
720
- response: Some("whatev".into()),
721
- }
722
- .into(),
723
- ),
724
- }
725
- .into()],
702
+ query_ok(LEGACY_QUERY_ID, "whatev"),
726
703
  ))
727
704
  .await
728
705
  .unwrap();
@@ -738,8 +715,8 @@ async fn la_resolve_during_legacy_query_does_not_combine(#[case] impossible_quer
738
715
  .unwrap();
739
716
  };
740
717
 
741
- tokio::join!(wf_fut, act_fut);
742
- core.shutdown().await;
718
+ join!(wf_fut, act_fut);
719
+ core.drain_pollers_and_shutdown().await;
743
720
  }
744
721
 
745
722
  #[tokio::test]
@@ -1215,3 +1192,106 @@ async fn local_activities_can_be_delivered_during_shutdown() {
1215
1192
  assert_matches!(wf_r.unwrap_err(), PollWfError::ShutDown);
1216
1193
  assert_matches!(act_r.unwrap_err(), PollActivityError::ShutDown);
1217
1194
  }
1195
+
1196
+ #[tokio::test]
1197
+ async fn queries_can_be_received_while_heartbeating() {
1198
+ let wfid = "fake_wf_id";
1199
+ let mut t = TestHistoryBuilder::default();
1200
+ t.add_wfe_started_with_wft_timeout(Duration::from_millis(200));
1201
+ t.add_full_wf_task();
1202
+ t.add_full_wf_task();
1203
+ t.add_full_wf_task();
1204
+
1205
+ let tasks = [
1206
+ hist_to_poll_resp(&t, wfid.to_owned(), ResponseType::ToTaskNum(1)),
1207
+ {
1208
+ let mut pr = hist_to_poll_resp(&t, wfid.to_owned(), ResponseType::OneTask(2));
1209
+ pr.queries = HashMap::new();
1210
+ pr.queries.insert(
1211
+ "q1".to_string(),
1212
+ WorkflowQuery {
1213
+ query_type: "query-type".to_string(),
1214
+ query_args: Some(b"hi".into()),
1215
+ header: None,
1216
+ },
1217
+ );
1218
+ pr
1219
+ },
1220
+ {
1221
+ let mut pr = hist_to_poll_resp(&t, wfid.to_owned(), ResponseType::OneTask(3));
1222
+ pr.query = Some(WorkflowQuery {
1223
+ query_type: "query-type".to_string(),
1224
+ query_args: Some(b"hi".into()),
1225
+ header: None,
1226
+ });
1227
+ pr
1228
+ },
1229
+ ];
1230
+ let mut mock = mock_workflow_client();
1231
+ mock.expect_respond_legacy_query()
1232
+ .times(1)
1233
+ .returning(move |_, _| Ok(Default::default()));
1234
+ let mut mock = single_hist_mock_sg(wfid, t, tasks, mock, true);
1235
+ mock.worker_cfg(|wc| wc.max_cached_workflows = 1);
1236
+ let core = mock_worker(mock);
1237
+
1238
+ let task = core.poll_workflow_activation().await.unwrap();
1239
+ assert_matches!(
1240
+ task.jobs.as_slice(),
1241
+ &[WorkflowActivationJob {
1242
+ variant: Some(workflow_activation_job::Variant::StartWorkflow(_)),
1243
+ },]
1244
+ );
1245
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
1246
+ task.run_id,
1247
+ schedule_local_activity_cmd(
1248
+ 1,
1249
+ "act-id",
1250
+ ActivityCancellationType::TryCancel,
1251
+ Duration::from_secs(60),
1252
+ ),
1253
+ ))
1254
+ .await
1255
+ .unwrap();
1256
+
1257
+ let task = core.poll_workflow_activation().await.unwrap();
1258
+ assert_matches!(
1259
+ task.jobs.as_slice(),
1260
+ &[WorkflowActivationJob {
1261
+ variant: Some(workflow_activation_job::Variant::QueryWorkflow(ref q)),
1262
+ }]
1263
+ if q.query_id == "q1"
1264
+ );
1265
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
1266
+ task.run_id,
1267
+ query_ok("q1", "whatev"),
1268
+ ))
1269
+ .await
1270
+ .unwrap();
1271
+
1272
+ let task = core.poll_workflow_activation().await.unwrap();
1273
+ assert_matches!(
1274
+ task.jobs.as_slice(),
1275
+ &[WorkflowActivationJob {
1276
+ variant: Some(workflow_activation_job::Variant::QueryWorkflow(ref q)),
1277
+ }]
1278
+ if q.query_id == LEGACY_QUERY_ID
1279
+ );
1280
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
1281
+ task.run_id,
1282
+ query_ok(LEGACY_QUERY_ID, "whatev"),
1283
+ ))
1284
+ .await
1285
+ .unwrap();
1286
+
1287
+ // Handle the activity so we can shut down cleanly
1288
+ let act_task = core.poll_activity_task().await.unwrap();
1289
+ core.complete_activity_task(ActivityTaskCompletion {
1290
+ task_token: act_task.task_token,
1291
+ result: Some(ActivityExecutionResult::ok(vec![1].into())),
1292
+ })
1293
+ .await
1294
+ .unwrap();
1295
+
1296
+ core.drain_pollers_and_shutdown().await;
1297
+ }
@@ -17,7 +17,7 @@ use temporal_sdk_core_protos::{
17
17
  },
18
18
  workflow_commands::{
19
19
  query_result, ActivityCancellationType, CompleteWorkflowExecution,
20
- ContinueAsNewWorkflowExecution, QueryResult, QuerySuccess, RequestCancelActivity,
20
+ ContinueAsNewWorkflowExecution, QueryResult, RequestCancelActivity,
21
21
  },
22
22
  workflow_completion::WorkflowActivationCompletion,
23
23
  },
@@ -33,7 +33,9 @@ use temporal_sdk_core_protos::{
33
33
  },
34
34
  TestHistoryBuilder,
35
35
  };
36
- use temporal_sdk_core_test_utils::{schedule_activity_cmd, start_timer_cmd, WorkerTestHelpers};
36
+ use temporal_sdk_core_test_utils::{
37
+ query_ok, schedule_activity_cmd, start_timer_cmd, WorkerTestHelpers,
38
+ };
37
39
 
38
40
  #[rstest::rstest]
39
41
  #[case::with_history(true)]
@@ -111,16 +113,7 @@ async fn legacy_query(#[case] include_history: bool) {
111
113
  worker
112
114
  .complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
113
115
  task.run_id,
114
- QueryResult {
115
- query_id: query.query_id.clone(),
116
- variant: Some(
117
- QuerySuccess {
118
- response: Some(query_resp.into()),
119
- }
120
- .into(),
121
- ),
122
- }
123
- .into(),
116
+ query_ok(&query.query_id, query_resp),
124
117
  ))
125
118
  .await
126
119
  .unwrap();
@@ -149,10 +142,7 @@ async fn legacy_query(#[case] include_history: bool) {
149
142
 
150
143
  #[rstest::rstest]
151
144
  #[tokio::test]
152
- async fn new_queries(
153
- #[values(1, 3)] num_queries: usize,
154
- #[values(false, true)] query_results_after_complete: bool,
155
- ) {
145
+ async fn new_queries(#[values(1, 3)] num_queries: usize) {
156
146
  let wfid = "fake_wf_id";
157
147
  let query_resp = "response";
158
148
  let t = canned_histories::single_timer("1");
@@ -200,12 +190,20 @@ async fn new_queries(
200
190
 
201
191
  let task = core.poll_workflow_activation().await.unwrap();
202
192
  assert_matches!(
203
- task.jobs[0],
204
- WorkflowActivationJob {
193
+ task.jobs.as_slice(),
194
+ &[WorkflowActivationJob {
205
195
  variant: Some(workflow_activation_job::Variant::FireTimer(_)),
206
- }
196
+ }]
207
197
  );
208
- for i in 1..=num_queries {
198
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
199
+ task.run_id,
200
+ CompleteWorkflowExecution { result: None }.into(),
201
+ ))
202
+ .await
203
+ .unwrap();
204
+
205
+ let task = core.poll_workflow_activation().await.unwrap();
206
+ for i in 0..num_queries {
209
207
  assert_matches!(
210
208
  task.jobs[i],
211
209
  WorkflowActivationJob {
@@ -217,25 +215,8 @@ async fn new_queries(
217
215
  }
218
216
 
219
217
  let mut commands = vec![];
220
- if query_results_after_complete {
221
- commands.push(CompleteWorkflowExecution { result: None }.into());
222
- }
223
218
  for i in 1..=num_queries {
224
- commands.push(
225
- QueryResult {
226
- query_id: format!("q{i}"),
227
- variant: Some(
228
- QuerySuccess {
229
- response: Some(query_resp.into()),
230
- }
231
- .into(),
232
- ),
233
- }
234
- .into(),
235
- );
236
- }
237
- if !query_results_after_complete {
238
- commands.push(CompleteWorkflowExecution { result: None }.into());
219
+ commands.push(query_ok(format!("q{i}"), query_resp));
239
220
  }
240
221
  core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
241
222
  task.run_id,
@@ -411,16 +392,7 @@ async fn legacy_query_after_complete(#[values(false, true)] full_history: bool)
411
392
  );
412
393
  core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
413
394
  task.run_id,
414
- QueryResult {
415
- query_id: query.query_id.clone(),
416
- variant: Some(
417
- QuerySuccess {
418
- response: Some("whatever".into()),
419
- }
420
- .into(),
421
- ),
422
- }
423
- .into(),
395
+ query_ok(query.query_id.clone(), "whatever"),
424
396
  ))
425
397
  .await
426
398
  .unwrap();
@@ -534,16 +506,7 @@ async fn query_cache_miss_causes_page_fetch_dont_reply_wft_too_early(
534
506
  );
535
507
  core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
536
508
  task.run_id,
537
- QueryResult {
538
- query_id: "the-query".to_string(),
539
- variant: Some(
540
- QuerySuccess {
541
- response: Some(query_resp.into()),
542
- }
543
- .into(),
544
- ),
545
- }
546
- .into(),
509
+ query_ok("the-query".to_string(), query_resp),
547
510
  ))
548
511
  .await
549
512
  .unwrap();
@@ -632,16 +595,7 @@ async fn query_replay_with_continue_as_new_doesnt_reply_empty_command() {
632
595
 
633
596
  core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
634
597
  task.run_id,
635
- QueryResult {
636
- query_id: query.query_id.clone(),
637
- variant: Some(
638
- QuerySuccess {
639
- response: Some("whatever".into()),
640
- }
641
- .into(),
642
- ),
643
- }
644
- .into(),
598
+ query_ok(query.query_id.clone(), "whatever"),
645
599
  ))
646
600
  .await
647
601
  .unwrap();
@@ -690,16 +644,7 @@ async fn legacy_query_response_gets_not_found_not_fatal() {
690
644
  // Fail wft which should result in query being failed
691
645
  core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
692
646
  task.run_id,
693
- QueryResult {
694
- query_id: LEGACY_QUERY_ID.to_string(),
695
- variant: Some(
696
- QuerySuccess {
697
- response: Some("hi".into()),
698
- }
699
- .into(),
700
- ),
701
- }
702
- .into(),
647
+ query_ok(LEGACY_QUERY_ID.to_string(), "hi"),
703
648
  ))
704
649
  .await
705
650
  .unwrap();
@@ -856,21 +801,9 @@ async fn legacy_query_combined_with_timer_fire_repro() {
856
801
  .unwrap();
857
802
 
858
803
  let task = core.poll_workflow_activation().await.unwrap();
859
- core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
804
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
860
805
  task.run_id,
861
- vec![
862
- RequestCancelActivity { seq: 1 }.into(),
863
- QueryResult {
864
- query_id: "the-query".to_string(),
865
- variant: Some(
866
- QuerySuccess {
867
- response: Some("whatever".into()),
868
- }
869
- .into(),
870
- ),
871
- }
872
- .into(),
873
- ],
806
+ RequestCancelActivity { seq: 1 }.into(),
874
807
  ))
875
808
  .await
876
809
  .unwrap();
@@ -885,26 +818,33 @@ async fn legacy_query_combined_with_timer_fire_repro() {
885
818
  );
886
819
  core.complete_execution(&task.run_id).await;
887
820
 
888
- // Then the query
821
+ // Then the queries
889
822
  let task = core.poll_workflow_activation().await.unwrap();
890
823
  assert_matches!(
891
824
  task.jobs.as_slice(),
892
825
  [WorkflowActivationJob {
893
- variant: Some(workflow_activation_job::Variant::QueryWorkflow(_)),
826
+ variant: Some(workflow_activation_job::Variant::QueryWorkflow(ref q)),
894
827
  }]
828
+ if q.query_id == "the-query"
895
829
  );
896
830
  core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
897
831
  task.run_id,
898
- QueryResult {
899
- query_id: LEGACY_QUERY_ID.to_string(),
900
- variant: Some(
901
- QuerySuccess {
902
- response: Some("whatever".into()),
903
- }
904
- .into(),
905
- ),
906
- }
907
- .into(),
832
+ query_ok("the-query".to_string(), "whatever"),
833
+ ))
834
+ .await
835
+ .unwrap();
836
+
837
+ let task = core.poll_workflow_activation().await.unwrap();
838
+ assert_matches!(
839
+ task.jobs.as_slice(),
840
+ [WorkflowActivationJob {
841
+ variant: Some(workflow_activation_job::Variant::QueryWorkflow(ref q)),
842
+ }]
843
+ if q.query_id == LEGACY_QUERY_ID
844
+ );
845
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
846
+ task.run_id,
847
+ query_ok(LEGACY_QUERY_ID.to_string(), "whatever"),
908
848
  ))
909
849
  .await
910
850
  .unwrap();
@@ -1,6 +1,7 @@
1
1
  use crate::{
2
2
  test_help::{
3
3
  build_mock_pollers, canned_histories, hist_to_poll_resp, mock_worker, MockPollCfg,
4
+ ResponseType,
4
5
  },
5
6
  worker::{client::mocks::mock_workflow_client, ManagedWFFunc, LEGACY_QUERY_ID},
6
7
  };
@@ -10,12 +11,16 @@ use temporal_sdk::{WfContext, WorkflowFunction};
10
11
  use temporal_sdk_core_api::Worker;
11
12
  use temporal_sdk_core_protos::{
12
13
  coresdk::{
13
- workflow_commands::{workflow_command::Variant::RespondToQuery, QueryResult, QuerySuccess},
14
+ workflow_activation::{workflow_activation_job, WorkflowActivationJob},
14
15
  workflow_completion::WorkflowActivationCompletion,
15
16
  },
16
- temporal::api::{enums::v1::CommandType, query::v1::WorkflowQuery},
17
+ temporal::api::{
18
+ enums::v1::{CommandType, EventType},
19
+ query::v1::WorkflowQuery,
20
+ },
21
+ TestHistoryBuilder,
17
22
  };
18
- use temporal_sdk_core_test_utils::start_timer_cmd;
23
+ use temporal_sdk_core_test_utils::{query_ok, start_timer_cmd};
19
24
 
20
25
  fn timers_wf(num_timers: u32) -> WorkflowFunction {
21
26
  WorkflowFunction::new(move |command_sink: WfContext| async move {
@@ -117,15 +122,7 @@ async fn replay_flag_correct_with_query() {
117
122
  assert!(task.is_replaying);
118
123
  core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
119
124
  task.run_id,
120
- RespondToQuery(QueryResult {
121
- query_id: LEGACY_QUERY_ID.to_string(),
122
- variant: Some(
123
- QuerySuccess {
124
- response: Some("hi".into()),
125
- }
126
- .into(),
127
- ),
128
- }),
125
+ query_ok(LEGACY_QUERY_ID, "hi"),
129
126
  ))
130
127
  .await
131
128
  .unwrap();
@@ -133,3 +130,60 @@ async fn replay_flag_correct_with_query() {
133
130
  let task = core.poll_workflow_activation().await.unwrap();
134
131
  assert!(!task.is_replaying);
135
132
  }
133
+
134
+ #[tokio::test]
135
+ async fn replay_flag_correct_signal_before_query_ending_on_wft_completed() {
136
+ let wfid = "fake_wf_id";
137
+ let mut t = TestHistoryBuilder::default();
138
+ t.add_by_type(EventType::WorkflowExecutionStarted);
139
+ t.add_full_wf_task();
140
+ t.add_we_signaled("signal", vec![]);
141
+ t.add_full_wf_task();
142
+ let task = {
143
+ let mut pr = hist_to_poll_resp(&t, wfid.to_owned(), ResponseType::AllHistory);
144
+ pr.query = Some(WorkflowQuery {
145
+ query_type: "query-type".to_string(),
146
+ query_args: Some(b"hi".into()),
147
+ header: None,
148
+ });
149
+ pr
150
+ };
151
+
152
+ let mut mock = MockPollCfg::from_resp_batches(wfid, t, [task], mock_workflow_client());
153
+ mock.num_expected_legacy_query_resps = 1;
154
+ let mut mock = build_mock_pollers(mock);
155
+ mock.worker_cfg(|wc| wc.max_cached_workflows = 10);
156
+ let core = mock_worker(mock);
157
+
158
+ let task = core.poll_workflow_activation().await.unwrap();
159
+ core.complete_workflow_activation(WorkflowActivationCompletion::empty(task.run_id))
160
+ .await
161
+ .unwrap();
162
+
163
+ let task = core.poll_workflow_activation().await.unwrap();
164
+ assert!(task.is_replaying);
165
+ assert_matches!(
166
+ task.jobs.as_slice(),
167
+ [WorkflowActivationJob {
168
+ variant: Some(workflow_activation_job::Variant::SignalWorkflow(_)),
169
+ }]
170
+ );
171
+ core.complete_workflow_activation(WorkflowActivationCompletion::empty(task.run_id))
172
+ .await
173
+ .unwrap();
174
+
175
+ let task = core.poll_workflow_activation().await.unwrap();
176
+ assert!(task.is_replaying);
177
+ assert_matches!(
178
+ task.jobs.as_slice(),
179
+ [WorkflowActivationJob {
180
+ variant: Some(workflow_activation_job::Variant::QueryWorkflow(_)),
181
+ }]
182
+ );
183
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
184
+ task.run_id,
185
+ query_ok(LEGACY_QUERY_ID, "hi"),
186
+ ))
187
+ .await
188
+ .unwrap();
189
+ }
@@ -520,6 +520,7 @@ impl WorkflowMachines {
520
520
  // them.
521
521
  if self.replaying
522
522
  && has_final_event
523
+ && event.event_id > self.last_history_from_server.previous_wft_started_id
523
524
  && event.event_type() != EventType::WorkflowTaskCompleted
524
525
  && !event.is_command_event()
525
526
  {
@@ -148,7 +148,7 @@ impl ManagedRun {
148
148
  /// Called whenever a new workflow task is obtained for this run
149
149
  pub(super) fn incoming_wft(&mut self, pwft: PermittedWFT) -> RunUpdateAct {
150
150
  let res = self._incoming_wft(pwft);
151
- self.update_to_acts(res.map(Into::into), true)
151
+ self.update_to_acts(res.map(Into::into))
152
152
  }
153
153
 
154
154
  fn _incoming_wft(
@@ -161,7 +161,6 @@ impl ManagedRun {
161
161
  let start_time = Instant::now();
162
162
 
163
163
  let work = pwft.work;
164
- let did_miss_cache = !work.is_incremental() || !work.update.is_real();
165
164
  debug!(
166
165
  run_id = %work.execution.run_id,
167
166
  task_token = %&work.task_token,
@@ -200,7 +199,6 @@ impl ManagedRun {
200
199
  self.paginator = Some(pwft.paginator);
201
200
  self.wft = Some(OutstandingTask {
202
201
  info: wft_info,
203
- hit_cache: !did_miss_cache,
204
202
  pending_queries,
205
203
  start_time,
206
204
  permit: pwft.permit,
@@ -284,7 +282,7 @@ impl ManagedRun {
284
282
  /// Checks if any further activations need to go out for this run and produces them if so.
285
283
  pub(super) fn check_more_activations(&mut self) -> RunUpdateAct {
286
284
  let res = self._check_more_activations();
287
- self.update_to_acts(res.map(Into::into), false)
285
+ self.update_to_acts(res.map(Into::into))
288
286
  }
289
287
 
290
288
  fn _check_more_activations(&mut self) -> Result<Option<ActivationOrAuto>, RunUpdateErr> {
@@ -445,17 +443,14 @@ impl ManagedRun {
445
443
  span: Span::current(),
446
444
  })
447
445
  } else {
448
- Ok(self.update_to_acts(
449
- Err(RunUpdateErr {
450
- source: WFMachinesError::Fatal(
451
- "Run's paginator was absent when attempting to fetch next history \
446
+ Ok(self.update_to_acts(Err(RunUpdateErr {
447
+ source: WFMachinesError::Fatal(
448
+ "Run's paginator was absent when attempting to fetch next history \
452
449
  page. This is a Core SDK bug."
453
- .to_string(),
454
- ),
455
- complete_resp_chan: rac.resp_chan,
456
- }),
457
- false,
458
- ))
450
+ .to_string(),
451
+ ),
452
+ complete_resp_chan: rac.resp_chan,
453
+ })))
459
454
  };
460
455
  }
461
456
 
@@ -473,7 +468,7 @@ impl ManagedRun {
473
468
  paginator: HistoryPaginator,
474
469
  ) -> RunUpdateAct {
475
470
  let res = self._fetched_page_completion(update, paginator);
476
- self.update_to_acts(res.map(Into::into), false)
471
+ self.update_to_acts(res.map(Into::into))
477
472
  }
478
473
  fn _fetched_page_completion(
479
474
  &mut self,
@@ -564,12 +559,12 @@ impl ManagedRun {
564
559
  /// Called when local activities resolve
565
560
  pub(super) fn local_resolution(&mut self, res: LocalResolution) -> RunUpdateAct {
566
561
  let res = self._local_resolution(res);
567
- self.update_to_acts(res.map(Into::into), false)
562
+ self.update_to_acts(res.map(Into::into))
568
563
  }
569
564
 
570
565
  fn process_completion(&mut self, completion: RunActivationCompletion) -> RunUpdateAct {
571
566
  let res = self._process_completion(completion, None);
572
- self.update_to_acts(res.map(Into::into), false)
567
+ self.update_to_acts(res.map(Into::into))
573
568
  }
574
569
 
575
570
  fn _process_completion(
@@ -688,7 +683,7 @@ impl ManagedRun {
688
683
  } else {
689
684
  None
690
685
  };
691
- self.update_to_acts(Ok(maybe_act).map(Into::into), false)
686
+ self.update_to_acts(Ok(maybe_act).map(Into::into))
692
687
  }
693
688
  /// Returns `true` if autocompletion should be issued, which will actually cause us to end up
694
689
  /// in [completion] again, at which point we'll start a new heartbeat timeout, which will
@@ -812,11 +807,7 @@ impl ManagedRun {
812
807
 
813
808
  /// Take the result of some update to ourselves and turn it into a return value of zero or more
814
809
  /// actions
815
- fn update_to_acts(
816
- &mut self,
817
- outcome: Result<ActOrFulfill, RunUpdateErr>,
818
- in_response_to_wft: bool,
819
- ) -> RunUpdateAct {
810
+ fn update_to_acts(&mut self, outcome: Result<ActOrFulfill, RunUpdateErr>) -> RunUpdateAct {
820
811
  match outcome {
821
812
  Ok(act_or_fulfill) => {
822
813
  let (mut maybe_act, maybe_fulfill) = match act_or_fulfill {
@@ -828,25 +819,12 @@ impl ManagedRun {
828
819
  match self._check_more_activations() {
829
820
  Ok(oa) => maybe_act = oa,
830
821
  Err(e) => {
831
- return self.update_to_acts(Err(e), in_response_to_wft);
822
+ return self.update_to_acts(Err(e));
832
823
  }
833
824
  }
834
825
  }
835
826
  let r = match maybe_act {
836
- Some(ActivationOrAuto::LangActivation(mut activation)) => {
837
- if in_response_to_wft {
838
- let wft = self
839
- .wft
840
- .as_mut()
841
- .expect("WFT must exist for run just updated with one");
842
- // If there are in-poll queries, insert jobs for those queries into the
843
- // activation, but only if we hit the cache. If we didn't, those queries
844
- // will need to be dealt with once replay is over
845
- if wft.hit_cache {
846
- put_queries_in_act(&mut activation, wft);
847
- }
848
- }
849
-
827
+ Some(ActivationOrAuto::LangActivation(activation)) => {
850
828
  if activation.jobs.is_empty() {
851
829
  dbg_panic!("Should not send lang activation with no jobs");
852
830
  }
@@ -270,7 +270,7 @@ impl Workflows {
270
270
  match al {
271
271
  ActivationOrAuto::LangActivation(mut act)
272
272
  | ActivationOrAuto::ReadyForQueries(mut act) => {
273
- sort_act_jobs(&mut act);
273
+ prepare_to_ship_activation(&mut act);
274
274
  debug!(activation=%act, "Sending activation to lang");
275
275
  break Ok(act);
276
276
  }
@@ -527,7 +527,10 @@ impl Workflows {
527
527
  r
528
528
  })
529
529
  );
530
- jh_res?.map_err(|e| anyhow!("Error joining workflow processing thread: {e:?}"))?;
530
+ jh_res?.map_err(|e| {
531
+ let as_str = e.downcast::<&str>();
532
+ anyhow!("Error joining workflow processing thread: {as_str:?}")
533
+ })?;
531
534
  }
532
535
  Ok(())
533
536
  }
@@ -795,7 +798,6 @@ impl PreparedWFT {
795
798
  #[derive(Debug)]
796
799
  pub(crate) struct OutstandingTask {
797
800
  pub info: WorkflowTaskInfo,
798
- pub hit_cache: bool,
799
801
  /// Set if the outstanding task has quer(ies) which must be fulfilled upon finishing replay
800
802
  pub pending_queries: Vec<QueryWorkflow>,
801
803
  pub start_time: Instant,
@@ -1334,9 +1336,33 @@ impl LocalActivityRequestSink for LAReqSink {
1334
1336
  }
1335
1337
  }
1336
1338
 
1337
- /// Sorts jobs in an activation to be in the order lang expects:
1338
- /// `patches -> signals -> other -> queries`
1339
- fn sort_act_jobs(wfa: &mut WorkflowActivation) {
1339
+ /// Sorts jobs in an activation to be in the order lang expects, and confirms any invariants
1340
+ /// activations must uphold.
1341
+ ///
1342
+ /// ## Ordering
1343
+ /// `patches -> signals -> other -X-> queries`
1344
+ ///
1345
+ /// ## Invariants:
1346
+ /// * Queries always go in their own activation
1347
+ fn prepare_to_ship_activation(wfa: &mut WorkflowActivation) {
1348
+ let any_job_is_query = wfa.jobs.iter().any(|j| {
1349
+ matches!(
1350
+ j.variant,
1351
+ Some(workflow_activation_job::Variant::QueryWorkflow(_))
1352
+ )
1353
+ });
1354
+ let all_jobs_are_query = wfa.jobs.iter().all(|j| {
1355
+ matches!(
1356
+ j.variant,
1357
+ Some(workflow_activation_job::Variant::QueryWorkflow(_))
1358
+ )
1359
+ });
1360
+ if any_job_is_query && !all_jobs_are_query {
1361
+ dbg_panic!(
1362
+ "About to issue an activation that contains query jobs with non-query jobs: {:?}",
1363
+ &wfa
1364
+ );
1365
+ }
1340
1366
  wfa.jobs.sort_by(|j1, j2| {
1341
1367
  // Unwrapping is fine here since we'll never issue empty variants
1342
1368
  let j1v = j1.variant.as_ref().unwrap();
@@ -1348,6 +1374,9 @@ fn sort_act_jobs(wfa: &mut WorkflowActivation) {
1348
1374
  match v {
1349
1375
  workflow_activation_job::Variant::NotifyHasPatch(_) => 1,
1350
1376
  workflow_activation_job::Variant::SignalWorkflow(_) => 2,
1377
+ // In principle we should never actually need to sort these with the others, since
1378
+ // queries always get their own activation, but, maintaining the semantic is
1379
+ // reasonable.
1351
1380
  workflow_activation_job::Variant::QueryWorkflow(_) => 4,
1352
1381
  _ => 3,
1353
1382
  }
@@ -1375,11 +1404,6 @@ mod tests {
1375
1404
  Default::default(),
1376
1405
  )),
1377
1406
  },
1378
- WorkflowActivationJob {
1379
- variant: Some(workflow_activation_job::Variant::QueryWorkflow(
1380
- Default::default(),
1381
- )),
1382
- },
1383
1407
  WorkflowActivationJob {
1384
1408
  variant: Some(workflow_activation_job::Variant::FireTimer(
1385
1409
  Default::default(),
@@ -1393,7 +1417,7 @@ mod tests {
1393
1417
  ],
1394
1418
  ..Default::default()
1395
1419
  };
1396
- sort_act_jobs(&mut act);
1420
+ prepare_to_ship_activation(&mut act);
1397
1421
  let variants = act
1398
1422
  .jobs
1399
1423
  .into_iter()
@@ -1406,8 +1430,28 @@ mod tests {
1406
1430
  workflow_activation_job::Variant::SignalWorkflow(_),
1407
1431
  workflow_activation_job::Variant::FireTimer(_),
1408
1432
  workflow_activation_job::Variant::ResolveActivity(_),
1409
- workflow_activation_job::Variant::QueryWorkflow(_)
1410
1433
  ]
1411
1434
  )
1412
1435
  }
1436
+
1437
+ #[test]
1438
+ #[should_panic]
1439
+ fn queries_cannot_go_with_other_jobs() {
1440
+ let mut act = WorkflowActivation {
1441
+ jobs: vec![
1442
+ WorkflowActivationJob {
1443
+ variant: Some(workflow_activation_job::Variant::SignalWorkflow(
1444
+ Default::default(),
1445
+ )),
1446
+ },
1447
+ WorkflowActivationJob {
1448
+ variant: Some(workflow_activation_job::Variant::QueryWorkflow(
1449
+ Default::default(),
1450
+ )),
1451
+ },
1452
+ ],
1453
+ ..Default::default()
1454
+ };
1455
+ prepare_to_ship_activation(&mut act);
1456
+ }
1413
1457
  }
@@ -46,8 +46,8 @@ use temporal_sdk_core_api::{
46
46
  use temporal_sdk_core_protos::{
47
47
  coresdk::{
48
48
  workflow_commands::{
49
- workflow_command, ActivityCancellationType, CompleteWorkflowExecution,
50
- ScheduleActivity, ScheduleLocalActivity, StartTimer,
49
+ workflow_command, ActivityCancellationType, CompleteWorkflowExecution, QueryResult,
50
+ QuerySuccess, ScheduleActivity, ScheduleLocalActivity, StartTimer,
51
51
  },
52
52
  workflow_completion::WorkflowActivationCompletion,
53
53
  },
@@ -669,6 +669,19 @@ pub fn start_timer_cmd(seq: u32, duration: Duration) -> workflow_command::Varian
669
669
  .into()
670
670
  }
671
671
 
672
+ pub fn query_ok(id: impl Into<String>, response: impl Into<Payload>) -> workflow_command::Variant {
673
+ QueryResult {
674
+ query_id: id.into(),
675
+ variant: Some(
676
+ QuerySuccess {
677
+ response: Some(response.into()),
678
+ }
679
+ .into(),
680
+ ),
681
+ }
682
+ .into()
683
+ }
684
+
672
685
  /// Given a desired number of concurrent executions and a provided function that produces a future,
673
686
  /// run that many instances of the future concurrently.
674
687
  ///
@@ -22,8 +22,7 @@ use temporal_sdk_core_protos::{
22
22
  TestHistoryBuilder,
23
23
  };
24
24
  use temporal_sdk_core_test_utils::{
25
- history_from_proto_binary, init_integ_telem, replay_sdk_worker, workflows::la_problem_workflow,
26
- CoreWfStarter,
25
+ history_from_proto_binary, replay_sdk_worker, workflows::la_problem_workflow, CoreWfStarter,
27
26
  };
28
27
  use tokio_util::sync::CancellationToken;
29
28
 
@@ -589,7 +588,6 @@ async fn repro_nondeterminism_with_timer_bug() {
589
588
  #[rstest::rstest]
590
589
  #[tokio::test]
591
590
  async fn weird_la_nondeterminism_repro(#[values(true, false)] fix_hist: bool) {
592
- init_integ_telem();
593
591
  let mut hist = history_from_proto_binary(
594
592
  "histories/evict_while_la_running_no_interference-85_history.bin",
595
593
  )
@@ -618,7 +616,6 @@ async fn weird_la_nondeterminism_repro(#[values(true, false)] fix_hist: bool) {
618
616
 
619
617
  #[tokio::test]
620
618
  async fn second_weird_la_nondeterminism_repro() {
621
- init_integ_telem();
622
619
  let mut hist = history_from_proto_binary(
623
620
  "histories/evict_while_la_running_no_interference-23_history.bin",
624
621
  )
@@ -644,7 +641,6 @@ async fn second_weird_la_nondeterminism_repro() {
644
641
 
645
642
  #[tokio::test]
646
643
  async fn third_weird_la_nondeterminism_repro() {
647
- init_integ_telem();
648
644
  let mut hist = history_from_proto_binary(
649
645
  "histories/evict_while_la_running_no_interference-16_history.bin",
650
646
  )