@temporalio/core-bridge 1.9.0 → 1.9.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 (52) hide show
  1. package/Cargo.lock +2 -33
  2. package/package.json +3 -3
  3. package/releases/aarch64-apple-darwin/index.node +0 -0
  4. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  5. package/releases/x86_64-apple-darwin/index.node +0 -0
  6. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  7. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  8. package/sdk-core/.github/workflows/per-pr.yml +1 -1
  9. package/sdk-core/Cargo.toml +1 -0
  10. package/sdk-core/README.md +1 -1
  11. package/sdk-core/client/src/lib.rs +40 -11
  12. package/sdk-core/client/src/workflow_handle/mod.rs +4 -0
  13. package/sdk-core/core/Cargo.toml +3 -2
  14. package/sdk-core/core/src/core_tests/activity_tasks.rs +69 -2
  15. package/sdk-core/core/src/core_tests/local_activities.rs +99 -4
  16. package/sdk-core/core/src/core_tests/queries.rs +90 -1
  17. package/sdk-core/core/src/core_tests/workflow_tasks.rs +8 -11
  18. package/sdk-core/core/src/telemetry/metrics.rs +4 -4
  19. package/sdk-core/core/src/telemetry/mod.rs +1 -3
  20. package/sdk-core/core/src/test_help/mod.rs +9 -0
  21. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +1 -2
  22. package/sdk-core/core/src/worker/activities/local_activities.rs +1 -1
  23. package/sdk-core/core/src/worker/activities.rs +11 -4
  24. package/sdk-core/core/src/worker/mod.rs +6 -1
  25. package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +0 -1
  26. package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +28 -6
  27. package/sdk-core/core/src/worker/workflow/machines/workflow_machines/local_acts.rs +15 -0
  28. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +19 -15
  29. package/sdk-core/core/src/worker/workflow/mod.rs +89 -59
  30. package/sdk-core/core/src/worker/workflow/workflow_stream/saved_wf_inputs.rs +1 -1
  31. package/sdk-core/core-api/Cargo.toml +2 -2
  32. package/sdk-core/fsm/rustfsm_procmacro/Cargo.toml +1 -1
  33. package/sdk-core/sdk/Cargo.toml +2 -2
  34. package/sdk-core/sdk/src/lib.rs +13 -8
  35. package/sdk-core/sdk/src/workflow_context.rs +2 -2
  36. package/sdk-core/sdk/src/workflow_future.rs +1 -1
  37. package/sdk-core/sdk-core-protos/Cargo.toml +1 -1
  38. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/event_type.proto +4 -0
  39. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/reset.proto +16 -3
  40. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/update.proto +11 -0
  41. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/history/v1/message.proto +10 -0
  42. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +4 -1
  43. package/sdk-core/sdk-core-protos/protos/testsrv_upstream/Makefile +3 -10
  44. package/sdk-core/sdk-core-protos/protos/testsrv_upstream/api-linter.yaml +0 -5
  45. package/sdk-core/sdk-core-protos/protos/testsrv_upstream/temporal/api/testservice/v1/request_response.proto +3 -4
  46. package/sdk-core/sdk-core-protos/src/history_info.rs +2 -2
  47. package/sdk-core/sdk-core-protos/src/lib.rs +1 -0
  48. package/sdk-core/tests/integ_tests/queries_tests.rs +12 -12
  49. package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +48 -0
  50. package/src/conversions.rs +19 -17
  51. package/src/runtime.rs +32 -4
  52. package/sdk-core/sdk-core-protos/protos/testsrv_upstream/dependencies/gogoproto/gogo.proto +0 -141
@@ -25,6 +25,7 @@
25
25
  //! let worker_config = WorkerConfigBuilder::default()
26
26
  //! .namespace("default")
27
27
  //! .task_queue("task_queue")
28
+ //! .worker_build_id("rust-sdk")
28
29
  //! .build()?;
29
30
  //!
30
31
  //! let core_worker = init_worker(&runtime, worker_config, client)?;
@@ -710,7 +711,7 @@ impl<F, Fut, O> From<F> for WorkflowFunction
710
711
  where
711
712
  F: Fn(WfContext) -> Fut + Send + Sync + 'static,
712
713
  Fut: Future<Output = Result<WfExitValue<O>, anyhow::Error>> + Send + 'static,
713
- O: Serialize + Debug,
714
+ O: Serialize,
714
715
  {
715
716
  fn from(wf_func: F) -> Self {
716
717
  Self::new(wf_func)
@@ -723,7 +724,7 @@ impl WorkflowFunction {
723
724
  where
724
725
  F: Fn(WfContext) -> Fut + Send + Sync + 'static,
725
726
  Fut: Future<Output = Result<WfExitValue<O>, anyhow::Error>> + Send + 'static,
726
- O: Serialize + Debug,
727
+ O: Serialize,
727
728
  {
728
729
  Self {
729
730
  wf_func: Box::new(move |ctx: WfContext| {
@@ -749,7 +750,7 @@ pub type WorkflowResult<T> = Result<WfExitValue<T>, anyhow::Error>;
749
750
 
750
751
  /// Workflow functions may return these values when exiting
751
752
  #[derive(Debug, derive_more::From)]
752
- pub enum WfExitValue<T: Debug> {
753
+ pub enum WfExitValue<T> {
753
754
  /// Continue the workflow as a new execution
754
755
  #[from(ignore)]
755
756
  ContinueAsNew(Box<ContinueAsNewWorkflowExecution>),
@@ -763,7 +764,7 @@ pub enum WfExitValue<T: Debug> {
763
764
  Normal(T),
764
765
  }
765
766
 
766
- impl<T: Debug> WfExitValue<T> {
767
+ impl<T> WfExitValue<T> {
767
768
  /// Construct a [WfExitValue::ContinueAsNew] variant (handles boxing)
768
769
  pub fn continue_as_new(can: ContinueAsNewWorkflowExecution) -> Self {
769
770
  Self::ContinueAsNew(Box::new(can))
@@ -771,15 +772,19 @@ impl<T: Debug> WfExitValue<T> {
771
772
  }
772
773
 
773
774
  /// Activity functions may return these values when exiting
774
- #[derive(derive_more::From)]
775
- pub enum ActExitValue<T: Debug> {
775
+ pub enum ActExitValue<T> {
776
776
  /// Completion requires an asynchronous callback
777
- #[from(ignore)]
778
777
  WillCompleteAsync,
779
778
  /// Finish with a result
780
779
  Normal(T),
781
780
  }
782
781
 
782
+ impl<T: AsJsonPayloadExt> From<T> for ActExitValue<T> {
783
+ fn from(t: T) -> Self {
784
+ Self::Normal(t)
785
+ }
786
+ }
787
+
783
788
  type BoxActFn = Arc<
784
789
  dyn Fn(ActContext, Payload) -> BoxFuture<'static, Result<ActExitValue<Payload>, anyhow::Error>>
785
790
  + Send
@@ -834,7 +839,7 @@ where
834
839
  A: FromJsonPayloadExt + Send,
835
840
  Rf: Future<Output = Result<R, anyhow::Error>> + Send + 'static,
836
841
  R: Into<ActExitValue<O>>,
837
- O: AsJsonPayloadExt + Debug,
842
+ O: AsJsonPayloadExt,
838
843
  {
839
844
  fn into_activity_fn(self) -> BoxActFn {
840
845
  let wrapper = move |ctx: ActContext, input: Payload| {
@@ -11,7 +11,7 @@ use crate::{
11
11
  IntoUpdateValidatorFunc, RustWfCmd, SignalExternalWfResult, TimerResult, UnblockEvent,
12
12
  Unblockable, UpdateFunctions,
13
13
  };
14
- use crossbeam::channel::{Receiver, Sender};
14
+ use crossbeam_channel::{Receiver, Sender};
15
15
  use futures::{task::Context, FutureExt, Stream, StreamExt};
16
16
  use parking_lot::RwLock;
17
17
  use std::{
@@ -117,7 +117,7 @@ impl WfContext {
117
117
  am_cancelled: watch::Receiver<bool>,
118
118
  ) -> (Self, Receiver<RustWfCmd>) {
119
119
  // We need to use a normal std channel since our receiving side is non-async
120
- let (chan, rx) = crossbeam::channel::unbounded();
120
+ let (chan, rx) = crossbeam_channel::unbounded();
121
121
  (
122
122
  Self {
123
123
  namespace,
@@ -4,7 +4,7 @@ use crate::{
4
4
  WorkflowResult,
5
5
  };
6
6
  use anyhow::{anyhow, bail, Context as AnyhowContext, Error};
7
- use crossbeam::channel::Receiver;
7
+ use crossbeam_channel::Receiver;
8
8
  use futures::{future::BoxFuture, FutureExt};
9
9
  use std::{
10
10
  collections::{hash_map::Entry, HashMap},
@@ -17,7 +17,7 @@ serde_serialize = []
17
17
  [dependencies]
18
18
  anyhow = "1.0"
19
19
  base64 = "0.21"
20
- derive_more = "0.99"
20
+ derive_more = { workspace = true }
21
21
  prost = "0.11"
22
22
  prost-wkt = "0.4"
23
23
  prost-wkt-types = "0.4"
@@ -167,4 +167,8 @@ enum EventType {
167
167
  EVENT_TYPE_ACTIVITY_PROPERTIES_MODIFIED_EXTERNALLY = 45;
168
168
  // Workflow properties modified by user workflow code
169
169
  EVENT_TYPE_WORKFLOW_PROPERTIES_MODIFIED = 46;
170
+ // An update was requested. Note that not all update requests result in this
171
+ // event. See UpdateRequestedEventOrigin for situations in which this event
172
+ // is created.
173
+ EVENT_TYPE_WORKFLOW_EXECUTION_UPDATE_REQUESTED = 47;
170
174
  }
@@ -31,13 +31,26 @@ option java_outer_classname = "ResetProto";
31
31
  option ruby_package = "Temporalio::Api::Enums::V1";
32
32
  option csharp_namespace = "Temporalio.Api.Enums.V1";
33
33
 
34
- // Reset reapply (replay) options
35
- // * RESET_REAPPLY_TYPE_SIGNAL (default) - Signals are reapplied when workflow is reset
36
- // * RESET_REAPPLY_TYPE_NONE - nothing is reapplied
34
+ // Event types to exclude when reapplying events.
35
+ enum ResetReapplyExcludeType {
36
+ RESET_REAPPLY_EXCLUDE_TYPE_UNSPECIFIED = 0;
37
+ // Exclude signals when reapplying events.
38
+ RESET_REAPPLY_EXCLUDE_TYPE_SIGNAL = 1;
39
+ // Exclude updates when reapplying events.
40
+ RESET_REAPPLY_EXCLUDE_TYPE_UPDATE = 2;
41
+ }
42
+
43
+ // Event types to include when reapplying events. Deprecated: applications
44
+ // should use ResetReapplyExcludeType to specify exclusions from this set, and
45
+ // new event types should be added to ResetReapplyExcludeType instead of here.
37
46
  enum ResetReapplyType {
38
47
  RESET_REAPPLY_TYPE_UNSPECIFIED = 0;
48
+ // Signals are reapplied when workflow is reset.
39
49
  RESET_REAPPLY_TYPE_SIGNAL = 1;
50
+ // No events are reapplied when workflow is reset.
40
51
  RESET_REAPPLY_TYPE_NONE = 2;
52
+ // All eligible events are reapplied when workflow is reset.
53
+ RESET_REAPPLY_TYPE_ALL_ELIGIBLE = 3;
41
54
  }
42
55
 
43
56
  // Reset type options. Deprecated, see temporal.api.common.v1.ResetOptions.
@@ -54,3 +54,14 @@ enum UpdateWorkflowExecutionLifecycleStage {
54
54
  // on a worker and has either been rejected or returned a value or an error.
55
55
  UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_COMPLETED = 3;
56
56
  }
57
+
58
+ // UpdateRequestedEventOrigin records why an
59
+ // WorkflowExecutionUpdateRequestedEvent was written to history. Note that not
60
+ // all update requests result in a WorkflowExecutionUpdateRequestedEvent.
61
+ enum UpdateRequestedEventOrigin {
62
+ UPDATE_REQUESTED_EVENT_ORIGIN_UNSPECIFIED = 0;
63
+ // The UpdateRequested event was created when reapplying events during reset
64
+ // or replication. I.e. an accepted update on one branch of workflow history
65
+ // was converted into a requested update on a different branch.
66
+ UPDATE_REQUESTED_EVENT_ORIGIN_REAPPLY = 1;
67
+ }
@@ -36,6 +36,7 @@ import "google/protobuf/timestamp.proto";
36
36
 
37
37
  import "temporal/api/enums/v1/event_type.proto";
38
38
  import "temporal/api/enums/v1/failed_cause.proto";
39
+ import "temporal/api/enums/v1/update.proto";
39
40
  import "temporal/api/enums/v1/workflow.proto";
40
41
  import "temporal/api/common/v1/message.proto";
41
42
  import "temporal/api/failure/v1/message.proto";
@@ -434,6 +435,8 @@ message WorkflowExecutionSignaledEventAttributes {
434
435
  temporal.api.common.v1.Header header = 4;
435
436
  // Indicates the signal did not generate a new workflow task when received.
436
437
  bool skip_generate_workflow_task = 5;
438
+ // When signal origin is a workflow execution, this field is set.
439
+ temporal.api.common.v1.WorkflowExecution external_workflow_execution = 6;
437
440
  }
438
441
 
439
442
  message WorkflowExecutionTerminatedEventAttributes {
@@ -747,6 +750,12 @@ message WorkflowExecutionUpdateRejectedEventAttributes {
747
750
  temporal.api.failure.v1.Failure failure = 5;
748
751
  }
749
752
 
753
+ message WorkflowExecutionUpdateRequestedEventAttributes {
754
+ // The update request associated with this event.
755
+ temporal.api.update.v1.Request request = 1;
756
+ // A record of why this event was written to history.
757
+ temporal.api.enums.v1.UpdateRequestedEventOrigin origin = 2;
758
+ }
750
759
 
751
760
  // History events are the method by which Temporal SDKs advance (or recreate) workflow state.
752
761
  // See the `EventType` enum for more info about what each event is for.
@@ -812,6 +821,7 @@ message HistoryEvent {
812
821
  WorkflowPropertiesModifiedExternallyEventAttributes workflow_properties_modified_externally_event_attributes = 49;
813
822
  ActivityPropertiesModifiedExternallyEventAttributes activity_properties_modified_externally_event_attributes = 50;
814
823
  WorkflowPropertiesModifiedEventAttributes workflow_properties_modified_event_attributes = 51;
824
+ WorkflowExecutionUpdateRequestedEventAttributes workflow_execution_update_requested_event_attributes = 52;
815
825
  }
816
826
  }
817
827
 
@@ -669,8 +669,11 @@ message ResetWorkflowExecutionRequest {
669
669
  int64 workflow_task_finish_event_id = 4;
670
670
  // Used to de-dupe reset requests
671
671
  string request_id = 5;
672
- // Reset reapply (replay) options.
672
+ // Event types to be reapplied (deprecated)
673
+ // Default: RESET_REAPPLY_TYPE_ALL_ELIGIBLE
673
674
  temporal.api.enums.v1.ResetReapplyType reset_reapply_type = 6;
675
+ // Event types not to be reapplied
676
+ repeated temporal.api.enums.v1.ResetReapplyExcludeType reset_reapply_exclude_types = 7;
674
677
  }
675
678
 
676
679
  message ResetWorkflowExecutionResponse {
@@ -23,33 +23,26 @@ PROTO_ROOT := .
23
23
  PROTO_FILES = $(shell find $(PROTO_ROOT) -name "*.proto")
24
24
  PROTO_DIRS = $(sort $(dir $(PROTO_FILES)))
25
25
  PROTO_OUT := .gen
26
- PROTO_IMPORTS := -I=$(PROTO_ROOT) -I=$(GOPATH)/src/github.com/temporalio/gogo-protobuf/protobuf
26
+ PROTO_IMPORTS := -I=$(PROTO_ROOT)
27
27
 
28
28
  $(PROTO_OUT):
29
29
  mkdir $(PROTO_OUT)
30
30
 
31
31
  ##### Compile proto files for go #####
32
- grpc: buf-lint api-linter buf-breaking gogo-grpc fix-path
32
+ grpc: buf-lint api-linter buf-breaking fix-path
33
33
 
34
34
  go-grpc: clean $(PROTO_OUT)
35
35
  printf $(COLOR) "Compile for go-gRPC..."
36
36
  $(foreach PROTO_DIR,$(PROTO_DIRS),protoc $(PROTO_IMPORTS) --go_out=plugins=grpc,paths=source_relative:$(PROTO_OUT) $(PROTO_DIR)*.proto;)
37
37
 
38
- gogo-grpc: clean $(PROTO_OUT)
39
- printf $(COLOR) "Compile for gogo-gRPC..."
40
- $(foreach PROTO_DIR,$(PROTO_DIRS),protoc $(PROTO_IMPORTS) --gogoslick_out=Mgoogle/protobuf/wrappers.proto=github.com/gogo/protobuf/types,Mgoogle/protobuf/duration.proto=github.com/gogo/protobuf/types,Mgoogle/protobuf/descriptor.proto=github.com/golang/protobuf/protoc-gen-go/descriptor,Mgoogle/protobuf/timestamp.proto=github.com/gogo/protobuf/types,plugins=grpc,paths=source_relative:$(PROTO_OUT) $(PROTO_DIR)*.proto;)
41
-
42
38
  fix-path:
43
39
  mv -f $(PROTO_OUT)/temporal/api/* $(PROTO_OUT) && rm -rf $(PROTO_OUT)/temporal
44
40
 
45
41
  ##### Plugins & tools #####
46
- grpc-install: gogo-protobuf-install
42
+ grpc-install:
47
43
  printf $(COLOR) "Install/update gRPC plugins..."
48
44
  go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0
49
45
 
50
- gogo-protobuf-install: go-protobuf-install
51
- GO111MODULE=off go get github.com/temporalio/gogo-protobuf/protoc-gen-gogoslick
52
-
53
46
  go-protobuf-install:
54
47
  go install github.com/golang/protobuf/protoc-gen-go@v1.4.3
55
48
 
@@ -31,8 +31,3 @@
31
31
  - 'core::0127::http-annotation'
32
32
  - 'core::0131::method-signature'
33
33
  - 'core::0131::response-message-name'
34
-
35
- - included_paths:
36
- - 'dependencies/gogoproto/gogo.proto'
37
- disabled_rules:
38
- - 'all'
@@ -33,7 +33,6 @@ option csharp_namespace = "Temporalio.Api.TestService.V1";
33
33
 
34
34
  import "google/protobuf/duration.proto";
35
35
  import "google/protobuf/timestamp.proto";
36
- import "dependencies/gogoproto/gogo.proto";
37
36
 
38
37
  message LockTimeSkippingRequest {
39
38
  }
@@ -48,16 +47,16 @@ message UnlockTimeSkippingResponse {
48
47
  }
49
48
 
50
49
  message SleepUntilRequest {
51
- google.protobuf.Timestamp timestamp = 1 [(gogoproto.stdtime) = true];
50
+ google.protobuf.Timestamp timestamp = 1;
52
51
  }
53
52
 
54
53
  message SleepRequest {
55
- google.protobuf.Duration duration = 1 [(gogoproto.stdduration) = true];
54
+ google.protobuf.Duration duration = 1;
56
55
  }
57
56
 
58
57
  message SleepResponse {
59
58
  }
60
59
 
61
60
  message GetCurrentTimeResponse {
62
- google.protobuf.Timestamp time = 1 [(gogoproto.stdtime) = true];
61
+ google.protobuf.Timestamp time = 1;
63
62
  }
@@ -6,7 +6,7 @@ use crate::temporal::api::{
6
6
  workflowservice::v1::{GetWorkflowExecutionHistoryResponse, PollWorkflowTaskQueueResponse},
7
7
  };
8
8
  use anyhow::{anyhow, bail};
9
- use rand::{thread_rng, Rng};
9
+ use rand::random;
10
10
 
11
11
  /// Contains information about a validated history. Used for replay and other testing.
12
12
  #[derive(Clone, Debug, PartialEq)]
@@ -154,7 +154,7 @@ impl HistoryInfo {
154
154
  /// randomly generated task token. Caller should attach a meaningful `workflow_execution` if
155
155
  /// needed.
156
156
  pub fn as_poll_wft_response(&self) -> PollWorkflowTaskQueueResponse {
157
- let task_token: [u8; 16] = thread_rng().gen();
157
+ let task_token: [u8; 16] = random();
158
158
  PollWorkflowTaskQueueResponse {
159
159
  history: Some(History {
160
160
  events: self.events.clone(),
@@ -1981,6 +1981,7 @@ pub mod temporal {
1981
1981
  Attributes::WorkflowExecutionUpdateRejectedEventAttributes(_) => {EventType::WorkflowExecutionUpdateRejected}
1982
1982
  Attributes::WorkflowExecutionUpdateAcceptedEventAttributes(_) => {EventType::WorkflowExecutionUpdateAccepted}
1983
1983
  Attributes::WorkflowExecutionUpdateCompletedEventAttributes(_) => {EventType::WorkflowExecutionUpdateCompleted}
1984
+ Attributes::WorkflowExecutionUpdateRequestedEventAttributes(_) => {EventType::WorkflowExecutionUpdateRequested}
1984
1985
  Attributes::WorkflowPropertiesModifiedExternallyEventAttributes(_) => {EventType::WorkflowPropertiesModifiedExternally}
1985
1986
  Attributes::ActivityPropertiesModifiedExternallyEventAttributes(_) => {EventType::ActivityPropertiesModifiedExternally}
1986
1987
  Attributes::WorkflowPropertiesModifiedEventAttributes(_) => {EventType::WorkflowPropertiesModified}
@@ -384,8 +384,8 @@ async fn multiple_concurrent_queries_no_new_history() {
384
384
  }
385
385
 
386
386
  #[tokio::test]
387
- async fn query_superseded_by_newer_wft_is_discarded() {
388
- let mut starter = init_core_and_create_wf("query_superseded_by_newer_wft_is_discarded").await;
387
+ async fn queries_handled_before_next_wft() {
388
+ let mut starter = init_core_and_create_wf("queries_handled_before_next_wft").await;
389
389
  let core = starter.get_worker().await;
390
390
  let workflow_id = starter.get_task_queue().to_string();
391
391
  let task = core.poll_workflow_activation().await.unwrap();
@@ -447,16 +447,7 @@ async fn query_superseded_by_newer_wft_is_discarded() {
447
447
  ))
448
448
  .await
449
449
  .unwrap();
450
- // We should get the signal activation since the in-buffer query should've been failed
451
- let task = core.poll_workflow_activation().await.unwrap();
452
- assert_matches!(
453
- task.jobs.as_slice(),
454
- [WorkflowActivationJob {
455
- variant: Some(workflow_activation_job::Variant::SignalWorkflow(_)),
456
- }]
457
- );
458
- core.complete_execution(&task.run_id).await;
459
- // Query will get retried by server since we fail the task w/ the stale query
450
+ // We now get the second query
460
451
  let task = core.poll_workflow_activation().await.unwrap();
461
452
  let query = assert_matches!(
462
453
  task.jobs.as_slice(),
@@ -479,6 +470,15 @@ async fn query_superseded_by_newer_wft_is_discarded() {
479
470
  ))
480
471
  .await
481
472
  .unwrap();
473
+ // Then the signal afterward
474
+ let task = core.poll_workflow_activation().await.unwrap();
475
+ assert_matches!(
476
+ task.jobs.as_slice(),
477
+ [WorkflowActivationJob {
478
+ variant: Some(workflow_activation_job::Variant::SignalWorkflow(_)),
479
+ }]
480
+ );
481
+ core.complete_execution(&task.run_id).await;
482
482
  };
483
483
  join!(join_all(query_futs), complete_fut);
484
484
  drain_pollers_and_shutdown(&core).await;
@@ -5,6 +5,9 @@ use std::{
5
5
  },
6
6
  time::Duration,
7
7
  };
8
+ use temporal_client::WorkflowClientTrait;
9
+ use tokio::{join, sync::Notify};
10
+ use tokio_stream::StreamExt;
8
11
 
9
12
  use temporal_sdk::{WfContext, WorkflowResult};
10
13
  use temporal_sdk_core_test_utils::CoreWfStarter;
@@ -151,3 +154,48 @@ async fn can_remove_deprecated_patch_near_other_patch() {
151
154
  starter.start_with_worker(wf_name, &mut worker).await;
152
155
  worker.run_until_done().await.unwrap();
153
156
  }
157
+
158
+ #[tokio::test]
159
+ async fn deprecated_patch_removal() {
160
+ let wf_name = "deprecated_patch_removal";
161
+ let mut starter = CoreWfStarter::new(wf_name);
162
+ starter.no_remote_activities();
163
+ let mut worker = starter.worker().await;
164
+ let client = starter.get_client().await;
165
+ let wf_id = starter.get_task_queue().to_string();
166
+ let did_die = Arc::new(AtomicBool::new(false));
167
+ let send_sig = Arc::new(Notify::new());
168
+ let send_sig_c = send_sig.clone();
169
+ worker.register_wf(wf_name, move |ctx: WfContext| {
170
+ let did_die = did_die.clone();
171
+ let send_sig_c = send_sig_c.clone();
172
+ async move {
173
+ if !did_die.load(Ordering::Acquire) {
174
+ assert!(ctx.deprecate_patch("getting-deprecated"));
175
+ }
176
+ send_sig_c.notify_one();
177
+ ctx.make_signal_channel("sig").next().await;
178
+
179
+ ctx.timer(Duration::from_millis(1)).await;
180
+
181
+ if !did_die.load(Ordering::Acquire) {
182
+ did_die.store(true, Ordering::Release);
183
+ ctx.force_task_fail(anyhow::anyhow!("i'm ded"));
184
+ }
185
+ Ok(().into())
186
+ }
187
+ });
188
+
189
+ starter.start_with_worker(wf_name, &mut worker).await;
190
+ let sig_fut = async {
191
+ send_sig.notified().await;
192
+ client
193
+ .signal_workflow_execution(wf_id, "".to_string(), "sig".to_string(), None, None)
194
+ .await
195
+ .unwrap()
196
+ };
197
+ let run_fut = async {
198
+ worker.run_until_done().await.unwrap();
199
+ };
200
+ join!(sig_fut, run_fut);
201
+ }
@@ -44,10 +44,9 @@ impl ArrayHandleConversionsExt for Handle<'_, JsArray> {
44
44
  }
45
45
  }
46
46
 
47
- pub(crate) type TelemOptsRes = (
48
- TelemetryOptions,
49
- Option<Box<dyn FnOnce() -> Arc<dyn CoreMeter> + Send>>,
50
- );
47
+ type BoxedMeterMaker = Box<dyn FnOnce() -> Result<Arc<dyn CoreMeter>, String> + Send + Sync>;
48
+
49
+ pub(crate) type TelemOptsRes = (TelemetryOptions, Option<BoxedMeterMaker>);
51
50
 
52
51
  pub trait ObjectHandleConversionsExt {
53
52
  fn set_default(&self, cx: &mut FunctionContext, key: &str, value: &str) -> NeonResult<()>;
@@ -220,20 +219,24 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> {
220
219
  .unwrap_err()
221
220
  })?;
222
221
 
223
- meter_maker = Some(Box::new(move || {
224
- let prom_info = start_prometheus_metric_exporter(options)
225
- .expect("Failed creating prometheus exporter");
226
- prom_info.meter as Arc<dyn CoreMeter>
227
- })
228
- as Box<dyn FnOnce() -> Arc<dyn CoreMeter> + Send>);
222
+ meter_maker =
223
+ Some(
224
+ Box::new(move || match start_prometheus_metric_exporter(options) {
225
+ Ok(prom_info) => Ok(prom_info.meter as Arc<dyn CoreMeter>),
226
+ Err(e) => Err(format!("Failed to start prometheus exporter: {}", e)),
227
+ }) as BoxedMeterMaker,
228
+ );
229
229
  } else if let Some(ref otel) = js_optional_getter!(cx, metrics, "otel", JsObject) {
230
230
  let mut options = OtelCollectorOptionsBuilder::default();
231
231
 
232
232
  let url = js_value_getter!(cx, otel, "url", JsString);
233
233
  match Url::parse(&url) {
234
234
  Ok(url) => options.url(url),
235
- Err(_) => {
236
- return cx.throw_type_error("Invalid telemetryOptions.metrics.otel.url");
235
+ Err(e) => {
236
+ return cx.throw_type_error(format!(
237
+ "Invalid telemetryOptions.metrics.otel.url: {}",
238
+ e
239
+ ))?;
237
240
  }
238
241
  };
239
242
 
@@ -269,11 +272,10 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> {
269
272
  .unwrap_err()
270
273
  })?;
271
274
 
272
- meter_maker = Some(Box::new(move || {
273
- let otlp_exporter =
274
- build_otlp_metric_exporter(options).expect("Failed to build otlp exporter");
275
- Arc::new(otlp_exporter) as Arc<dyn CoreMeter>
276
- }));
275
+ meter_maker = Some(Box::new(move || match build_otlp_metric_exporter(options) {
276
+ Ok(otlp_exporter) => Ok(Arc::new(otlp_exporter) as Arc<dyn CoreMeter>),
277
+ Err(e) => Err(format!("Failed to start otlp exporter: {}", e)),
278
+ }) as BoxedMeterMaker);
277
279
  } else {
278
280
  cx.throw_type_error(
279
281
  "Invalid telemetryOptions.metrics, missing `prometheus` or `otel` option",
package/src/runtime.rs CHANGED
@@ -19,6 +19,7 @@ use temporal_sdk_core::{
19
19
  replay::{HistoryForReplay, ReplayWorkerInput},
20
20
  ClientOptions, RetryClient, WorkerConfig,
21
21
  };
22
+ use tokio::sync::oneshot;
22
23
  use tokio::sync::{
23
24
  mpsc::{channel, unbounded_channel, Sender, UnboundedReceiver, UnboundedSender},
24
25
  Mutex,
@@ -119,6 +120,7 @@ pub fn start_bridge_loop(
119
120
  telemetry_options: TelemOptsRes,
120
121
  channel: Arc<Channel>,
121
122
  receiver: &mut UnboundedReceiver<RuntimeRequest>,
123
+ result_sender: oneshot::Sender<Result<(), String>>,
122
124
  ) {
123
125
  let mut tokio_builder = tokio::runtime::Builder::new_multi_thread();
124
126
  tokio_builder.enable_all().thread_name("core");
@@ -129,10 +131,24 @@ pub fn start_bridge_loop(
129
131
 
130
132
  core_runtime.tokio_handle().block_on(async {
131
133
  if let Some(meter_maker) = meter_maker {
132
- core_runtime
133
- .telemetry_mut()
134
- .attach_late_init_metrics(meter_maker());
134
+ match meter_maker() {
135
+ Ok(meter) => {
136
+ core_runtime.telemetry_mut().attach_late_init_metrics(meter);
137
+ }
138
+ Err(err) => {
139
+ result_sender
140
+ .send(Err(format!("Failed to create meter: {}", err)))
141
+ .unwrap_or_else(|_| {
142
+ panic!("Failed to report runtime start error: {}", err)
143
+ });
144
+ return;
145
+ }
146
+ }
135
147
  }
148
+ result_sender
149
+ .send(Ok(()))
150
+ .expect("Failed to report runtime start success");
151
+
136
152
  loop {
137
153
  let request_option = receiver.recv().await;
138
154
  let request = match request_option {
@@ -386,7 +402,19 @@ pub fn runtime_new(mut cx: FunctionContext) -> JsResult<BoxedRuntime> {
386
402
  let channel = Arc::new(cx.channel());
387
403
  let (sender, mut receiver) = unbounded_channel::<RuntimeRequest>();
388
404
 
389
- std::thread::spawn(move || start_bridge_loop(telemetry_options, channel, &mut receiver));
405
+ // FIXME: This is a temporary fix to get sync notifications of errors while initializing the runtime.
406
+ // The proper fix would be to avoid spawning a new thread here, so that start_bridge_loop
407
+ // can simply yeild back a Result. But early attempts to do just that caused panics
408
+ // on runtime shutdown, so let's use this hack until we can dig deeper.
409
+ let (result_sender, result_receiver) = oneshot::channel::<Result<(), String>>();
410
+
411
+ std::thread::spawn(move || {
412
+ start_bridge_loop(telemetry_options, channel, &mut receiver, result_sender)
413
+ });
414
+
415
+ if let Ok(Err(e)) = result_receiver.blocking_recv() {
416
+ Err(cx.throw_error::<_, String>(e).unwrap_err())?;
417
+ }
390
418
 
391
419
  Ok(cx.boxed(Arc::new(RuntimeHandle { sender })))
392
420
  }