1#![warn(missing_docs)] #[macro_use]
61extern crate tracing;
62extern crate self as temporalio_sdk;
63
64pub mod activities;
65pub mod error;
66pub mod interceptors;
67pub mod plugins;
69pub mod runtime;
70#[cfg(feature = "testing")]
71pub mod testing;
72mod workflow_executor;
73mod workflow_future;
74pub mod workflow_interceptors;
75mod workflow_registry;
76pub mod workflow_replayer;
78#[cfg(feature = "wasm-workflows")]
79mod workflow_wasm;
80pub mod workflows;
81
82pub use crate::{
83 error::{
84 ActivityExecutionError, ApplicationFailure, ChildWorkflowExecutionError,
85 ChildWorkflowStartError, OutgoingActivityError, OutgoingError, OutgoingWorkflowError,
86 RetryState, TimeoutType, WorkerCreateError, WorkerRunError, WorkerValidationError,
87 WorkflowRegistrationError, WorkflowSignalError,
88 },
89 plugins::{
90 ClientAndWorkerPlugin, SimplePlugin, SimplePluginBuilder, SimplePluginOption, WorkerPlugin,
91 WorkflowDefinitions,
92 },
93};
94pub use runtime::Runtime;
95pub use temporalio_client::Namespace;
96pub use temporalio_workflow::{
97 ActivityCancellationType, ActivityCloseTimeouts, ActivityOptions, BaseWorkflowContext,
98 CancellableFuture, CancellableFutureWithReason, ChildWorkflowCancellationType,
99 ChildWorkflowOptions, ContinueAsNewOptions, ContinueAsNewVersioningBehavior,
100 ExternalWorkflowHandle, LocalActivityOptions, MemoValue, NexusOperationCancellationType,
101 NexusOperationOptions, ParentClosePolicy, PatchActivationCallback, SignalWorkflowOptions,
102 StartChildWorkflowExecutionFailedCause, StartChildWorkflowOutput, StartedChildWorkflow,
103 StartedNexusOperation, SyncWorkflowContext, TimerOptions, TimerResult, VersioningIntent,
104 WaitConditionOptions, WorkflowCancellationError, WorkflowCancellationToken, WorkflowContext,
105 WorkflowContextView, WorkflowIdReusePolicy, WorkflowRandomValue, WorkflowResult,
106 WorkflowTermination,
107};
108#[cfg(feature = "wasm-workflows")]
109pub use workflow_wasm::WasmWorkflowComponent;
110
111use crate::{
112 activities::{
113 ActivityContext, ActivityDefinitions, ActivityImplementer, ExecutableActivity,
114 activity_error_to_core_result,
115 },
116 interceptors::{ActivityInboundInterceptor, Next, RunWorkerInput, WorkerInterceptor},
117 workflow_executor::{TaskHandle, WorkflowExecutor},
118 workflow_future::start_workflow,
119 workflow_interceptors::WorkflowInterceptorConstructor,
120};
121use anyhow::{anyhow, bail};
122use futures_util::{FutureExt, StreamExt, TryStreamExt, future::LocalBoxFuture};
123use std::{
124 any::{Any, TypeId},
125 cell::RefCell,
126 collections::{HashMap, HashSet},
127 fmt::{Debug, Display, Formatter},
128 future::Future,
129 sync::Arc,
130 time::Duration,
131};
132use temporalio_client::{Client, ClientOptions, NamespacedClient};
133use temporalio_common::{
134 ActivityDefinition, WorkflowDefinition,
135 data_converters::{DataConverter, SerializationContext, SerializationContextData},
136 payload_visitor::{decode_payloads, encode_payloads},
137 protos::{
138 TaskToken,
139 coresdk::{
140 ActivityTaskCompletion, AsJsonPayloadExt,
141 activity_result::ActivityExecutionResult,
142 activity_task::{ActivityTask, activity_task},
143 workflow_activation::{WorkflowActivation, workflow_activation_job::Variant},
144 workflow_completion::WorkflowActivationCompletion,
145 },
146 temporal::api::{
147 common::v1::Payload, enums::v1::WorkflowTaskFailedCause, failure::v1::Failure,
148 worker::v1::PluginInfo,
149 },
150 },
151 worker::{WorkerDeploymentOptions, WorkerTaskTypes, build_id_from_current_exe},
152};
153use temporalio_sdk_core::{PollError, init_worker};
154use temporalio_workflow::runtime::entry::WorkflowImplementation;
155use tokio::sync::{
156 Notify,
157 mpsc::{UnboundedSender, unbounded_channel},
158};
159use tokio_stream::wrappers::UnboundedReceiverStream;
160use tokio_util::sync::CancellationToken;
161use tracing::{Instrument, Span, field};
162use uuid::Uuid;
163
164use crate::runtime::{
165 CoreWorker, PollerBehavior, TunerBuilder, WorkerConfig, WorkerTuner, WorkerVersioningStrategy,
166 WorkflowErrorType,
167};
168
169#[derive(bon::Builder, Clone)]
174#[builder(start_fn = new, on(String, into), state_mod(vis = "pub"))]
175#[non_exhaustive]
176pub struct WorkerOptions {
177 #[builder(start_fn)]
180 pub task_queue: String,
181
182 #[builder(field)]
183 activities: ActivityDefinitions,
184
185 #[builder(field)]
186 workflows: WorkflowDefinitions,
187
188 #[builder(field)]
189 worker_interceptors: Vec<Arc<dyn WorkerInterceptor>>,
190
191 #[builder(field)]
192 activity_inbound_interceptors: Vec<Arc<dyn ActivityInboundInterceptor>>,
193
194 #[builder(field)]
195 workflow_interceptor_constructors: Vec<WorkflowInterceptorConstructor>,
196
197 #[builder(field)]
198 worker_plugins: Vec<Arc<dyn WorkerPlugin>>,
199
200 #[builder(field)]
201 client_plugin_names: HashSet<String>,
202
203 #[cfg(feature = "wasm-workflows")]
204 #[builder(field)]
205 wasm_workflow_components: Vec<WasmWorkflowComponent>,
206
207 #[builder(default = def_build_id())]
210 pub deployment_options: WorkerDeploymentOptions,
211 pub client_identity_override: Option<String>,
215 #[builder(default = 1000)]
221 pub max_cached_workflows: usize,
222 #[builder(default = Arc::new(TunerBuilder::default().build()))]
225 pub tuner: Arc<dyn WorkerTuner + Send + Sync>,
226 pub workflow_task_poller_behavior: Option<PollerBehavior>,
233 #[builder(default = 0.2)]
241 pub nonsticky_to_sticky_poll_ratio: f32,
242 pub activity_task_poller_behavior: Option<PollerBehavior>,
247 pub nexus_task_poller_behavior: Option<PollerBehavior>,
252 #[builder(default = Duration::from_secs(10))]
255 pub sticky_queue_schedule_to_start_timeout: Duration,
256 #[builder(default = Duration::from_secs(60))]
258 pub max_heartbeat_throttle_interval: Duration,
259 #[builder(default = Duration::from_secs(30))]
264 pub default_heartbeat_throttle_interval: Duration,
265 pub max_task_queue_activities_per_second: Option<f64>,
272 pub max_worker_activities_per_second: Option<f64>,
277 #[builder(default = 3)]
280 pub max_eager_activity_reservations_per_workflow_task: usize,
281 #[builder(default)]
284 pub workflow_failure_errors: HashSet<WorkflowErrorType>,
285 #[builder(default)]
288 pub workflow_types_to_failure_errors: HashMap<String, HashSet<WorkflowErrorType>>,
289 pub graceful_shutdown_period: Option<Duration>,
292 #[builder(default = true)]
296 pub detect_nondeterministic_futures: bool,
297 #[builder(default = false)]
302 pub disable_payload_error_limit: bool,
303 pub patch_activation_callback: Option<PatchActivationCallback>,
311}
312
313impl<S: worker_options_builder::State> WorkerOptionsBuilder<S> {
314 pub(crate) fn with_workflows(mut self, workflows: WorkflowDefinitions) -> Self {
315 self.workflows = workflows;
316 self
317 }
318
319 pub(crate) fn with_worker_interceptors(
320 mut self,
321 worker_interceptors: Vec<Arc<dyn WorkerInterceptor>>,
322 ) -> Self {
323 self.worker_interceptors = worker_interceptors;
324 self
325 }
326
327 pub(crate) fn with_workflow_interceptor_constructors(
328 mut self,
329 workflow_interceptor_constructors: Vec<WorkflowInterceptorConstructor>,
330 ) -> Self {
331 self.workflow_interceptor_constructors = workflow_interceptor_constructors;
332 self
333 }
334
335 pub(crate) fn with_worker_plugins(
336 mut self,
337 worker_plugins: Vec<Arc<dyn WorkerPlugin>>,
338 ) -> Self {
339 self.worker_plugins = worker_plugins;
340 self
341 }
342
343 #[cfg(feature = "wasm-workflows")]
344 pub(crate) fn with_wasm_workflow_components(
345 mut self,
346 wasm_workflow_components: Vec<WasmWorkflowComponent>,
347 ) -> Self {
348 self.wasm_workflow_components = wasm_workflow_components;
349 self
350 }
351
352 pub fn worker_plugin<P: WorkerPlugin>(mut self, plugin: P) -> Self {
356 self.worker_plugins.push(Arc::new(plugin));
357 self
358 }
359
360 pub fn worker_interceptor<I: WorkerInterceptor + 'static>(mut self, interceptor: I) -> Self {
362 self.worker_interceptors.push(Arc::new(interceptor));
363 self
364 }
365
366 pub fn activity_inbound_interceptor<I: ActivityInboundInterceptor>(
369 mut self,
370 interceptor: I,
371 ) -> Self {
372 self.activity_inbound_interceptors
373 .push(Arc::new(interceptor));
374 self
375 }
376
377 pub fn workflow_interceptor(mut self, constructor: WorkflowInterceptorConstructor) -> Self {
379 self.workflow_interceptor_constructors.push(constructor);
380 self
381 }
382
383 pub fn register_activities<AI: ActivityImplementer>(mut self, instance: AI) -> Self {
385 self.activities.register_activities::<AI>(instance);
386 self
387 }
388 pub fn register_activity<AD>(mut self, instance: Arc<AD::Implementer>) -> Self
390 where
391 AD: ActivityDefinition + ExecutableActivity,
392 AD::Input: Send + Sync,
393 AD::Output: Send + Sync,
394 {
395 self.activities.register_activity::<AD>(instance);
396 self
397 }
398
399 pub fn register_workflow<W>(mut self) -> Result<Self, WorkflowRegistrationError>
401 where
402 W: WorkflowImplementation,
403 <W::Run as WorkflowDefinition>::Input: Send,
404 {
405 self.workflows.register_workflow::<W>()?;
406 Ok(self)
407 }
408
409 pub fn register_workflow_with_factory<W, F>(
427 mut self,
428 factory: F,
429 ) -> Result<Self, WorkflowRegistrationError>
430 where
431 W: WorkflowImplementation,
432 <W::Run as WorkflowDefinition>::Input: Send,
433 F: Fn() -> W + Send + Sync + 'static,
434 {
435 self.workflows
436 .register_workflow_run_with_factory::<W, F>(factory)?;
437 Ok(self)
438 }
439
440 pub fn register_workflow_interceptors(
444 mut self,
445 constructors: Vec<WorkflowInterceptorConstructor>,
446 ) -> Self {
447 self.workflow_interceptor_constructors = constructors;
448 self
449 }
450
451 pub fn workflow_interceptor_constructors_mut(
453 &mut self,
454 ) -> &mut Vec<WorkflowInterceptorConstructor> {
455 &mut self.workflow_interceptor_constructors
456 }
457
458 #[cfg(feature = "wasm-workflows")]
460 pub fn register_wasm_workflow(mut self, component: WasmWorkflowComponent) -> Self {
461 self.wasm_workflow_components.push(component);
462 self
463 }
464}
465
466fn def_build_id() -> WorkerDeploymentOptions {
468 WorkerDeploymentOptions::from_build_id(build_id_from_current_exe().to_owned())
469}
470
471impl WorkerOptions {
472 pub fn worker_interceptor<I: WorkerInterceptor + 'static>(
474 &mut self,
475 interceptor: I,
476 ) -> &mut Self {
477 self.worker_interceptors.push(Arc::new(interceptor));
478 self
479 }
480
481 pub fn activity_inbound_interceptor<I: ActivityInboundInterceptor>(
484 &mut self,
485 interceptor: I,
486 ) -> &mut Self {
487 self.activity_inbound_interceptors
488 .push(Arc::new(interceptor));
489 self
490 }
491
492 pub fn workflow_interceptor(
494 &mut self,
495 constructor: WorkflowInterceptorConstructor,
496 ) -> &mut Self {
497 self.workflow_interceptor_constructors.push(constructor);
498 self
499 }
500
501 pub fn register_activities<AI: ActivityImplementer>(&mut self, instance: AI) -> &mut Self {
503 self.activities.register_activities::<AI>(instance);
504 self
505 }
506 pub fn register_activity<AD>(&mut self, instance: Arc<AD::Implementer>) -> &mut Self
508 where
509 AD: ActivityDefinition + ExecutableActivity,
510 AD::Input: Send + Sync,
511 AD::Output: Send + Sync,
512 {
513 self.activities.register_activity::<AD>(instance);
514 self
515 }
516 pub fn activities(&self) -> ActivityDefinitions {
518 self.activities.clone()
519 }
520
521 pub fn register_workflow<W>(&mut self) -> Result<&mut Self, WorkflowRegistrationError>
523 where
524 W: WorkflowImplementation,
525 <W::Run as WorkflowDefinition>::Input: Send,
526 {
527 self.workflows.register_workflow::<W>()?;
528 Ok(self)
529 }
530
531 pub fn register_workflow_with_factory<W, F>(
536 &mut self,
537 factory: F,
538 ) -> Result<&mut Self, WorkflowRegistrationError>
539 where
540 W: WorkflowImplementation,
541 <W::Run as WorkflowDefinition>::Input: Send,
542 F: Fn() -> W + Send + Sync + 'static,
543 {
544 self.workflows
545 .register_workflow_run_with_factory::<W, F>(factory)?;
546 Ok(self)
547 }
548
549 pub fn register_workflow_interceptors(
553 &mut self,
554 constructors: Vec<WorkflowInterceptorConstructor>,
555 ) -> &mut Self {
556 self.workflow_interceptor_constructors = constructors;
557 self
558 }
559
560 #[cfg(feature = "wasm-workflows")]
562 pub fn register_wasm_workflow(&mut self, component: WasmWorkflowComponent) -> &mut Self {
563 self.wasm_workflow_components.push(component);
564 self
565 }
566
567 pub fn workflows(&self) -> WorkflowDefinitions {
569 self.workflows.clone()
570 }
571
572 #[doc(hidden)]
573 pub fn to_core_options(
574 &self,
575 namespace: String,
576 connection_identity: String,
577 ) -> Result<WorkerConfig, String> {
578 let workflows_registered = !self.workflows.is_empty();
579 #[cfg(feature = "wasm-workflows")]
580 let workflows_registered =
581 workflows_registered || !self.wasm_workflow_components.is_empty();
582 let activities_registered = !self.activities.is_empty();
583 if !workflows_registered && !activities_registered {
584 return Err("At least one workflow or activity must be registered".to_owned());
585 }
586
587 WorkerConfig::builder()
588 .namespace(namespace)
589 .task_queue(self.task_queue.clone())
590 .maybe_client_identity_override(self.client_identity_override.clone().or_else(|| {
591 connection_identity.is_empty().then(|| {
592 format!(
593 "{}@{}",
594 std::process::id(),
595 gethostname::gethostname().to_string_lossy()
596 )
597 })
598 }))
599 .max_cached_workflows(self.max_cached_workflows)
600 .tuner(self.tuner.clone())
601 .maybe_workflow_task_poller_behavior(self.workflow_task_poller_behavior)
602 .maybe_activity_task_poller_behavior(self.activity_task_poller_behavior)
603 .maybe_nexus_task_poller_behavior(self.nexus_task_poller_behavior)
604 .task_types(WorkerTaskTypes {
605 enable_workflows: workflows_registered,
606 enable_local_activities: workflows_registered && activities_registered,
607 enable_remote_activities: activities_registered,
608 enable_nexus: false,
609 })
610 .sticky_queue_schedule_to_start_timeout(self.sticky_queue_schedule_to_start_timeout)
611 .max_heartbeat_throttle_interval(self.max_heartbeat_throttle_interval)
612 .default_heartbeat_throttle_interval(self.default_heartbeat_throttle_interval)
613 .maybe_max_task_queue_activities_per_second(self.max_task_queue_activities_per_second)
614 .maybe_max_worker_activities_per_second(self.max_worker_activities_per_second)
615 .max_eager_activity_reservations_per_workflow_task(
616 self.max_eager_activity_reservations_per_workflow_task,
617 )
618 .maybe_graceful_shutdown_period(self.graceful_shutdown_period)
619 .versioning_strategy(WorkerVersioningStrategy::WorkerDeploymentBased(
620 self.deployment_options.clone(),
621 ))
622 .workflow_failure_errors(self.workflow_failure_errors.clone())
623 .workflow_types_to_failure_errors(self.workflow_types_to_failure_errors.clone())
624 .plugins(
625 self.client_plugin_names
626 .iter()
627 .map(|name| PluginInfo {
628 name: name.clone(),
629 version: String::new(),
630 })
631 .chain(self.worker_plugins.iter().map(|registration| PluginInfo {
632 name: registration.name().to_owned(),
633 version: String::new(),
634 }))
635 .collect(),
636 )
637 .disable_payload_error_limit(self.disable_payload_error_limit)
638 .build()
639 }
640}
641
642#[derive(Debug)]
646pub struct Worker {
647 common: CommonWorker,
648 workflow_half: WorkflowHalf,
649 activity_half: ActivityHalf,
650}
651
652#[derive(derive_more::Debug)]
653struct CommonWorker {
654 #[debug(skip)]
655 worker: Arc<CoreWorker>,
656 task_queue: String,
657 #[debug(skip)]
658 worker_interceptors: Vec<Arc<dyn WorkerInterceptor>>,
659 #[debug(skip)]
660 activity_inbound_interceptors: Vec<Arc<dyn ActivityInboundInterceptor>>,
661 #[debug(skip)]
662 workflow_interceptor_constructors: Vec<WorkflowInterceptorConstructor>,
663 client_options: ClientOptions,
664 data_converter: DataConverter,
665}
666
667#[derive(derive_more::Debug)]
668struct WorkflowHalf {
669 workflows: RefCell<HashMap<String, WorkflowData>>,
671 workflow_definitions: WorkflowDefinitions,
672 workflow_removed_from_map: Notify,
673 detect_nondeterministic_futures: bool,
674 #[debug(skip)]
675 patch_activation_callback: Option<PatchActivationCallback>,
676}
677#[derive(Debug)]
678struct WorkflowData {
679 activation_chan: UnboundedSender<WorkflowActivation>,
681}
682
683struct WorkflowFutureHandle<F: Future> {
684 join_handle: F,
685 run_id: String,
686}
687
688#[derive(Debug, Default)]
689struct ActivityHalf {
690 activities: ActivityDefinitions,
692 task_tokens_to_cancels: HashMap<TaskToken, CancellationToken>,
693}
694
695#[derive(Debug, thiserror::Error)]
696enum ActivityTaskHandlerError {
697 #[error("{source}")]
698 UnregisteredActivity {
699 source: ActivityNotRegisteredError,
700 task_token: Vec<u8>,
701 },
702 #[error(transparent)]
703 Fatal(#[from] anyhow::Error),
704}
705
706#[derive(Debug, thiserror::Error)]
707enum ActivityNotRegisteredError {
708 #[error(
709 "Activity {activity_type} is not registered on this worker, available activities: {}",
710 .available_activities.join(", ")
711 )]
712 HasAvailable {
713 activity_type: String,
714 available_activities: Vec<String>,
715 },
716 #[error("Activity {activity_type} is not registered on this worker, no available activities.")]
717 NoAvailable { activity_type: String },
718}
719
720impl ActivityNotRegisteredError {
721 fn new(activity_type: String, available_activities: Vec<String>) -> Self {
722 if available_activities.is_empty() {
723 Self::NoAvailable { activity_type }
724 } else {
725 Self::HasAvailable {
726 activity_type,
727 available_activities,
728 }
729 }
730 }
731}
732
733async fn encode_workflow_completion(
734 completion: &mut WorkflowActivationCompletion,
735 data_converter: &DataConverter,
736) {
737 let run_id = completion.run_id.clone();
738 if let Err(err) = encode_payloads(
739 completion,
740 data_converter.codec(),
741 &SerializationContextData::Workflow,
742 )
743 .await
744 {
745 error!(run_id, error = %err, "Failed encoding workflow activation completion");
746 *completion = WorkflowActivationCompletion::fail(
747 run_id,
748 Failure {
749 message: format!("Failed encoding completion: {err}"),
750 ..Default::default()
751 },
752 Some(WorkflowTaskFailedCause::WorkflowWorkerUnhandledFailure),
753 );
754 }
755}
756
757async fn encode_activity_completion(
758 completion: &mut ActivityTaskCompletion,
759 data_converter: &DataConverter,
760) {
761 if let Err(err) = encode_payloads(
762 completion,
763 data_converter.codec(),
764 &SerializationContextData::Activity,
765 )
766 .await
767 {
768 error!(error = %err, "Failed encoding activity task completion");
769 completion.result = Some(ActivityExecutionResult::fail(Failure::application_failure(
770 format!("Failed encoding activity completion: {err}"),
771 false,
772 )));
773 }
774}
775
776impl Worker {
777 pub fn new(
779 runtime: &Runtime,
780 client: Client,
781 mut options: WorkerOptions,
782 ) -> Result<Self, WorkerCreateError> {
783 plugins::apply_worker_plugins(client.options(), &mut options)?;
784 let wc = options
785 .to_core_options(client.namespace(), client.identity())
786 .map_err(|error| WorkerCreateError::Initialization(anyhow!(error)))?;
787 let core = init_worker(runtime, wc, client.connection().clone())
788 .map_err(WorkerCreateError::Initialization)?;
789 Self::new_from_core_options_prepared(Arc::new(core), client.options().clone(), options)
790 }
791
792 #[doc(hidden)]
794 pub fn new_from_core(worker: Arc<CoreWorker>, data_converter: DataConverter) -> Self {
795 let client_options = ClientOptions::new(worker.get_config().namespace.clone())
796 .data_converter(data_converter)
797 .build();
798 Self::new_from_core_definitions(
799 worker,
800 client_options,
801 Default::default(),
802 Default::default(),
803 Default::default(),
804 Default::default(),
805 Default::default(),
806 )
807 }
808
809 #[doc(hidden)]
811 pub fn new_from_core_options(
812 worker: Arc<CoreWorker>,
813 client_options: ClientOptions,
814 mut options: WorkerOptions,
815 ) -> Result<Self, WorkerCreateError> {
816 plugins::apply_worker_plugins(&client_options, &mut options)?;
817 Self::new_from_core_options_prepared(worker, client_options, options)
818 }
819
820 fn new_from_core_options_prepared(
821 worker: Arc<CoreWorker>,
822 client_options: ClientOptions,
823 mut options: WorkerOptions,
824 ) -> Result<Self, WorkerCreateError> {
825 let acts = std::mem::take(&mut options.activities);
826 let wfs = std::mem::take(&mut options.workflows);
827 let worker_interceptors = std::mem::take(&mut options.worker_interceptors);
828 let activity_inbound_interceptors =
829 std::mem::take(&mut options.activity_inbound_interceptors);
830 let workflow_interceptor_constructors =
831 std::mem::take(&mut options.workflow_interceptor_constructors);
832 #[cfg(feature = "wasm-workflows")]
833 let wasm_components = std::mem::take(&mut options.wasm_workflow_components);
834 let mut me = Self::new_from_core_definitions(
835 worker,
836 client_options,
837 acts,
838 wfs,
839 worker_interceptors,
840 activity_inbound_interceptors,
841 workflow_interceptor_constructors,
842 );
843 me.set_detect_nondeterministic_futures(options.detect_nondeterministic_futures);
844 me.workflow_half.patch_activation_callback = options.patch_activation_callback;
845 #[cfg(feature = "wasm-workflows")]
846 me.workflow_half
847 .workflow_definitions
848 .register_wasm_workflows(
849 wasm_components,
850 !me.common.workflow_interceptor_constructors.is_empty(),
851 )
852 .map_err(|error| WorkerCreateError::Initialization(anyhow!(error)))?;
853 Ok(me)
854 }
855
856 fn new_from_core_definitions(
857 worker: Arc<CoreWorker>,
858 client_options: ClientOptions,
859 activities: ActivityDefinitions,
860 workflows: WorkflowDefinitions,
861 worker_interceptors: Vec<Arc<dyn WorkerInterceptor>>,
862 activity_inbound_interceptors: Vec<Arc<dyn ActivityInboundInterceptor>>,
863 workflow_interceptor_constructors: Vec<WorkflowInterceptorConstructor>,
864 ) -> Self {
865 let data_converter = client_options.data_converter.clone();
866 Self {
867 common: CommonWorker {
868 task_queue: worker.get_config().task_queue.clone(),
869 worker,
870 worker_interceptors,
871 activity_inbound_interceptors,
872 workflow_interceptor_constructors,
873 client_options,
874 data_converter,
875 },
876 workflow_half: WorkflowHalf {
877 workflows: Default::default(),
878 workflow_definitions: workflows,
879 workflow_removed_from_map: Default::default(),
880 detect_nondeterministic_futures: false,
881 patch_activation_callback: None,
882 },
883 activity_half: ActivityHalf {
884 activities,
885 ..Default::default()
886 },
887 }
888 }
889
890 pub fn task_queue(&self) -> &str {
892 &self.common.task_queue
893 }
894
895 #[doc(hidden)]
896 pub fn set_detect_nondeterministic_futures(&mut self, enabled: bool) {
899 self.workflow_half.detect_nondeterministic_futures = enabled;
900 }
901
902 pub fn shutdown_handle(&self) -> impl Fn() + use<> {
905 let w = self.common.worker.clone();
906 move || w.initiate_shutdown()
907 }
908
909 pub async fn run(&mut self) -> Result<(), WorkerRunError> {
912 let interceptors = self.common.worker_interceptors.clone();
913 interceptors::call_run_worker(
914 &interceptors,
915 RunWorkerInput::new(self),
916 Next::new(
917 |input: RunWorkerInput<'_>| -> LocalBoxFuture<'_, Result<(), _>> {
918 Box::pin(async move { input.worker.run_inner().await })
919 },
920 ),
921 )
922 .await
923 }
924
925 pub(crate) async fn run_inner(&mut self) -> Result<(), WorkerRunError> {
926 self.common
929 .worker
930 .validate()
931 .await
932 .map_err(WorkerRunError::Validation)?;
933 let shutdown_token = CancellationToken::new();
934 let (common, wf_half, act_half) = self.split_apart();
935 let (wf_future_tx, wf_future_rx) =
936 unbounded_channel::<WorkflowFutureHandle<TaskHandle<WorkflowResult<Payload>>>>();
937 let (completions_tx, completions_rx) = unbounded_channel();
938
939 let workflow_local_set = tokio::task::LocalSet::new();
944 let executor = WorkflowExecutor::new();
945
946 let wf_future_joiner = async {
947 UnboundedReceiverStream::new(wf_future_rx)
948 .map(Result::<_, WorkerRunError>::Ok)
949 .try_for_each_concurrent(
950 None,
951 |WorkflowFutureHandle {
952 join_handle,
953 run_id,
954 }| {
955 let wf_half = &*wf_half;
956 async move {
957 let result = join_handle.await.map_err(|e| WorkerRunError::Fatal {
958 message: "workflow task dropped".into(),
959 source: e.into(),
960 })?;
961 if let Err(e) = result
964 && !matches!(e, WorkflowTermination::Evicted)
965 {
966 return Err(WorkerRunError::Fatal {
967 message: "workflow execution failed".into(),
968 source: e.into(),
969 });
970 }
971 debug!(run_id=%run_id, "Removing workflow from cache");
972 wf_half.workflows.borrow_mut().remove(&run_id);
973 wf_half.workflow_removed_from_map.notify_one();
974 Ok(())
975 }
976 },
977 )
978 .await
979 };
980 let wf_completion_processor = async {
981 UnboundedReceiverStream::new(completions_rx)
982 .map(Ok)
983 .try_for_each_concurrent(None, |mut completion| async {
984 encode_workflow_completion(&mut completion, &common.data_converter).await;
985 for i in &common.worker_interceptors {
986 i.on_workflow_activation_completion(&completion).await;
987 }
988 common.worker.complete_workflow_activation(completion).await
989 })
990 .await
991 .map_err(|source| WorkerRunError::Fatal {
992 message: "workflow completions processor encountered an error".to_owned(),
993 source: Box::new(source),
994 })
995 };
996 tokio::try_join!(
997 async {
999 workflow_local_set.run_until(async {
1000 tokio::try_join!(
1001 async {
1003 loop {
1004 let mut activation =
1005 match common.worker.poll_workflow_activation().await {
1006 Err(PollError::ShutDown) => {
1007 break;
1008 }
1009 o => o.map_err(|source| WorkerRunError::Fatal {
1010 message: "workflow polling failed".to_owned(),
1011 source: Box::new(source),
1012 })?,
1013 };
1014 if let Err(err) = decode_payloads(
1015 &mut activation,
1016 common.data_converter.codec(),
1017 &SerializationContextData::Workflow,
1018 )
1019 .await
1020 {
1021 let run_id = activation.run_id;
1022 error!(run_id, error = %err, "Failed decoding workflow activation");
1023 completions_tx
1024 .send(WorkflowActivationCompletion::fail(
1025 run_id,
1026 Failure {
1027 message: format!("Failed decoding activation: {err}"),
1028 ..Default::default()
1029 },
1030 Some(
1031 WorkflowTaskFailedCause::WorkflowWorkerUnhandledFailure,
1032 ),
1033 ))
1034 .expect("Completion channel intact");
1035 continue;
1036 }
1037 for i in &common.worker_interceptors {
1038 i.on_workflow_activation(&activation).await.map_err(|source| {
1039 WorkerRunError::Fatal {
1040 message: "workflow activation interceptor failed".to_owned(),
1041 source: source.into_boxed_dyn_error(),
1042 }
1043 })?;
1044 }
1045 if let Some(wf_fut) = wf_half
1046 .workflow_activation_handler(
1047 common,
1048 shutdown_token.clone(),
1049 activation,
1050 &completions_tx,
1051 &executor,
1052 )
1053 .await
1054 .map_err(|source| {
1055 WorkerRunError::Fatal {
1056 message: "workflow activation processing failed".to_owned(),
1057 source: source.into_boxed_dyn_error(),
1058 }
1059 })?
1060 && wf_future_tx.send(wf_fut).is_err()
1061 {
1062 panic!(
1063 "Receive half of completion processor channel cannot be dropped"
1064 );
1065 }
1066 }
1067 shutdown_token.cancel();
1069 drop(wf_future_tx);
1072 drop(completions_tx);
1073 Result::<_, WorkerRunError>::Ok(())
1074 },
1075 wf_future_joiner,
1076 async {
1077 tokio::select! {
1078 _ = executor.drive() => unreachable!("executor driver cannot finish"),
1079 _ = shutdown_token.cancelled() => {}
1080 }
1081 executor.shutdown().await;
1082 Result::<_, WorkerRunError>::Ok(())
1083 },
1084 )
1085 }).await
1086 },
1087 async {
1090 if !act_half.activities.is_empty() {
1091 loop {
1092 let activity = common.worker.poll_activity_task().await;
1093 if matches!(activity, Err(PollError::ShutDown)) {
1094 break;
1095 }
1096 let mut activity = activity.map_err(|source| WorkerRunError::Fatal {
1097 message: "activity polling failed".to_owned(),
1098 source: Box::new(source),
1099 })?;
1100 if let Err(err) = decode_payloads(
1101 &mut activity,
1102 common.data_converter.codec(),
1103 &SerializationContextData::Activity,
1104 )
1105 .await
1106 {
1107 error!(error = %err, "Failed decoding activity task");
1108 let mut completion = ActivityTaskCompletion {
1109 task_token: activity.task_token,
1110 result: Some(ActivityExecutionResult::fail(
1111 Failure::application_failure(
1112 format!("Failed decoding activity task: {err}"),
1113 false,
1114 ),
1115 )),
1116 };
1117 encode_activity_completion(&mut completion, &common.data_converter)
1118 .await;
1119 common
1120 .worker
1121 .complete_activity_task(completion)
1122 .await
1123 .map_err(|source| WorkerRunError::Fatal {
1124 message: "activity completion failed".to_owned(),
1125 source: Box::new(source),
1126 })?;
1127 continue;
1128 }
1129 match act_half.activity_task_handler(
1130 common.worker.clone(),
1131 common.client_options.clone(),
1132 common.task_queue.clone(),
1133 common.data_converter.clone(),
1134 common.activity_inbound_interceptors.clone(),
1135 activity,
1136 ) {
1137 Ok(()) => {}
1138 Err(ActivityTaskHandlerError::UnregisteredActivity {
1139 source,
1140 task_token,
1141 }) => {
1142 let failure = common.data_converter.to_failure(
1143 &SerializationContextData::Activity,
1144 OutgoingError::Activity(OutgoingActivityError::Application(
1145 ApplicationFailure::builder(source)
1146 .type_name("NotFoundError".to_owned())
1147 .build()
1148 .into(),
1149 )),
1150 );
1151 let mut completion = ActivityTaskCompletion {
1152 task_token,
1153 result: Some(ActivityExecutionResult::fail(failure)),
1154 };
1155 encode_activity_completion(&mut completion, &common.data_converter)
1156 .await;
1157 common
1158 .worker
1159 .complete_activity_task(completion)
1160 .await
1161 .map_err(|source| WorkerRunError::Fatal {
1162 message: "activity completion failed".to_owned(),
1163 source: Box::new(source),
1164 })?;
1165 }
1166 Err(ActivityTaskHandlerError::Fatal(source)) => {
1167 return Err(WorkerRunError::Fatal {
1168 message: "activity task handling failed".to_owned(),
1169 source: source.into_boxed_dyn_error(),
1170 });
1171 }
1172 };
1173 }
1174 };
1175 Result::<_, WorkerRunError>::Ok(())
1176 },
1177 wf_completion_processor,
1178 )?;
1179
1180 for i in &self.common.worker_interceptors {
1181 i.on_shutdown(self);
1182 }
1183 self.common.worker.shutdown().await;
1184 Ok(())
1185 }
1186
1187 pub(crate) fn worker_interceptors(&self) -> Vec<Arc<dyn WorkerInterceptor>> {
1188 self.common.worker_interceptors.clone()
1189 }
1190
1191 pub fn with_new_core_worker(&mut self, new_core_worker: Arc<CoreWorker>) {
1195 self.common.worker = new_core_worker;
1196 }
1197
1198 pub fn cached_workflows(&self) -> usize {
1201 self.workflow_half.workflows.borrow().len()
1202 }
1203
1204 pub fn worker_instance_key(&self) -> Uuid {
1206 self.common.worker.worker_instance_key()
1207 }
1208
1209 #[doc(hidden)]
1210 pub fn core_worker(&self) -> Arc<CoreWorker> {
1211 self.common.worker.clone()
1212 }
1213
1214 fn split_apart(&mut self) -> (&mut CommonWorker, &mut WorkflowHalf, &mut ActivityHalf) {
1215 (
1216 &mut self.common,
1217 &mut self.workflow_half,
1218 &mut self.activity_half,
1219 )
1220 }
1221}
1222
1223impl WorkflowHalf {
1224 #[allow(clippy::type_complexity)]
1225 async fn workflow_activation_handler(
1226 &self,
1227 common: &CommonWorker,
1228 shutdown_token: CancellationToken,
1229 mut activation: WorkflowActivation,
1230 completions_tx: &UnboundedSender<WorkflowActivationCompletion>,
1231 executor: &WorkflowExecutor,
1232 ) -> Result<Option<WorkflowFutureHandle<TaskHandle<WorkflowResult<Payload>>>>, anyhow::Error>
1233 {
1234 let mut res = None;
1235 let run_id = activation.run_id.clone();
1236
1237 if let Some(sw) = activation.jobs.iter_mut().find_map(|j| match j.variant {
1240 Some(Variant::InitializeWorkflow(ref mut sw)) => Some(sw),
1241 _ => None,
1242 }) {
1243 let workflow_type = sw.workflow_type.clone();
1244 let (wff, activations) = {
1245 if let Some(factory) = self.workflow_definitions.get_workflow(&workflow_type) {
1246 match start_workflow(
1247 factory,
1248 common.worker.get_config().namespace.clone(),
1249 common.task_queue.clone(),
1250 run_id.clone(),
1251 std::mem::take(sw),
1252 completions_tx.clone(),
1253 common.data_converter.clone(),
1254 self.detect_nondeterministic_futures,
1255 self.patch_activation_callback.clone(),
1256 common.workflow_interceptor_constructors.clone(),
1257 ) {
1258 Ok(result) => result,
1259 Err(e) => {
1260 warn!("Failed to create workflow {workflow_type}: {e}");
1261 completions_tx
1262 .send(WorkflowActivationCompletion::fail(
1263 run_id,
1264 format!("Failed to create workflow: {e}").into(),
1265 Some(WorkflowTaskFailedCause::WorkflowWorkerUnhandledFailure),
1266 ))
1267 .expect("Completion channel intact");
1268 return Ok(None);
1269 }
1270 }
1271 } else {
1272 warn!("Workflow type {workflow_type} not found");
1273 completions_tx
1274 .send(WorkflowActivationCompletion::fail(
1275 run_id,
1276 format!("Workflow type {workflow_type} not found").into(),
1277 Some(WorkflowTaskFailedCause::WorkflowWorkerUnhandledFailure),
1278 ))
1279 .expect("Completion channel intact");
1280 return Ok(None);
1281 }
1282 };
1283 let wff = tokio::task::coop::unconstrained(wff);
1286 let jh = executor.spawn(async move {
1288 tokio::select! {
1289 r = wff.fuse() => r,
1290 _ = shutdown_token.cancelled() => {
1293 Err(WorkflowTermination::Evicted)
1294 }
1295 }
1296 });
1297 res = Some(WorkflowFutureHandle {
1298 join_handle: jh,
1299 run_id: run_id.clone(),
1300 });
1301 loop {
1302 if self.workflows.borrow_mut().contains_key(&run_id) {
1307 self.workflow_removed_from_map.notified().await;
1308 } else {
1309 break;
1310 }
1311 }
1312 self.workflows.borrow_mut().insert(
1313 run_id.clone(),
1314 WorkflowData {
1315 activation_chan: activations,
1316 },
1317 );
1318 }
1319
1320 if let Some(dat) = self.workflows.borrow_mut().get_mut(&run_id) {
1323 dat.activation_chan
1324 .send(activation)
1325 .expect("Workflow should exist if we're sending it an activation");
1326 } else {
1327 if activation.jobs.len() == 1
1333 && matches!(
1334 activation.jobs.first().map(|j| &j.variant),
1335 Some(Some(Variant::RemoveFromCache(_)))
1336 )
1337 {
1338 completions_tx
1339 .send(WorkflowActivationCompletion::from_cmds(run_id, vec![]))
1340 .expect("Completion channel intact");
1341 return Ok(None);
1342 }
1343
1344 bail!("Got activation {activation:?} for unknown workflow {run_id}");
1347 };
1348
1349 Ok(res)
1350 }
1351}
1352
1353impl ActivityHalf {
1354 fn activity_task_handler(
1356 &mut self,
1357 worker: Arc<CoreWorker>,
1358 client_options: ClientOptions,
1359 task_queue: String,
1360 data_converter: DataConverter,
1361 activity_inbound_interceptors: Vec<Arc<dyn ActivityInboundInterceptor>>,
1362 activity: ActivityTask,
1363 ) -> Result<(), ActivityTaskHandlerError> {
1364 match activity.variant {
1365 Some(activity_task::Variant::Start(start)) => {
1366 let Some(act_fn) = self.activities.get(&start.activity_type) else {
1367 let activity_type = start.activity_type.clone();
1368 let source =
1369 ActivityNotRegisteredError::new(activity_type, self.activities.names());
1370 return Err(ActivityTaskHandlerError::UnregisteredActivity {
1371 source,
1372 task_token: activity.task_token,
1373 });
1374 };
1375 let span = info_span!(
1376 "RunActivity",
1377 "otel.name" = format!("RunActivity:{}", start.activity_type),
1378 "otel.kind" = "server",
1379 "temporalActivityID" = start.activity_id,
1380 "temporalWorkflowID" = field::Empty,
1381 "temporalRunID" = field::Empty,
1382 );
1383 let ct = CancellationToken::new();
1384 let task_token = activity.task_token;
1385 self.task_tokens_to_cancels
1386 .insert(task_token.clone().into(), ct.clone());
1387
1388 let (ctx, args) = ActivityContext::new(
1389 worker.clone(),
1390 client_options,
1391 ct,
1392 task_queue,
1393 task_token.clone(),
1394 start,
1395 );
1396 let codec_data_converter = data_converter.clone();
1397
1398 tokio::spawn(async move {
1399 let act_fut = async move {
1400 let span = Span::current();
1401 if let Some(workflow_id) = &ctx.info().workflow_id {
1402 span.record("temporalWorkflowID", workflow_id);
1403 }
1404 if let Some(workflow_run_id) = &ctx.info().workflow_run_id {
1405 span.record("temporalRunID", workflow_run_id);
1406 }
1407 (act_fn)(args, data_converter, ctx, activity_inbound_interceptors).await
1408 }
1409 .instrument(span);
1410 let result = act_fut.await;
1411 let result = match result {
1412 Ok(output) => {
1413 let pc = codec_data_converter.payload_converter();
1416 let ctx = SerializationContext {
1417 data: &SerializationContextData::Activity,
1418 converter: pc,
1419 };
1420 match output.serialize_payload(&ctx) {
1421 Ok(payload) => ActivityExecutionResult::ok(payload),
1422 Err(err) => {
1423 activity_error_to_core_result(&codec_data_converter, err.into())
1424 }
1425 }
1426 }
1427 Err(err) => activity_error_to_core_result(&codec_data_converter, err),
1428 };
1429 let mut completion = ActivityTaskCompletion {
1430 task_token,
1431 result: Some(result),
1432 };
1433 encode_activity_completion(&mut completion, &codec_data_converter).await;
1434 worker.complete_activity_task(completion).await?;
1435 Ok::<_, anyhow::Error>(())
1436 });
1437 }
1438 Some(activity_task::Variant::Cancel(_)) => {
1439 if let Some(ct) = self
1440 .task_tokens_to_cancels
1441 .get(activity.task_token.as_slice())
1442 {
1443 ct.cancel();
1444 }
1445 }
1446 None => {
1447 return Err(anyhow!("Undefined activity task variant").into());
1448 }
1449 }
1450 Ok(())
1451 }
1452}
1453
1454#[derive(Debug)]
1456pub enum ActExitValue<T> {
1457 WillCompleteAsync,
1459 Normal(T),
1461}
1462
1463impl<T: AsJsonPayloadExt> From<T> for ActExitValue<T> {
1464 fn from(t: T) -> Self {
1465 Self::Normal(t)
1466 }
1467}
1468
1469fn panic_formatter(panic: Box<dyn Any>) -> Box<dyn Display> {
1471 _panic_formatter::<&str>(panic)
1472}
1473fn _panic_formatter<T: 'static + PrintablePanicType>(panic: Box<dyn Any>) -> Box<dyn Display> {
1474 match panic.downcast::<T>() {
1475 Ok(d) => d,
1476 Err(orig) => {
1477 if TypeId::of::<<T as PrintablePanicType>::NextType>()
1478 == TypeId::of::<EndPrintingAttempts>()
1479 {
1480 return Box::new("Couldn't turn panic into a string");
1481 }
1482 _panic_formatter::<T::NextType>(orig)
1483 }
1484 }
1485}
1486trait PrintablePanicType: Display {
1487 type NextType: PrintablePanicType;
1488}
1489
1490impl PrintablePanicType for &str {
1491 type NextType = String;
1492}
1493impl PrintablePanicType for String {
1494 type NextType = EndPrintingAttempts;
1495}
1496struct EndPrintingAttempts {}
1497impl Display for EndPrintingAttempts {
1498 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1499 write!(f, "Will never be printed")
1500 }
1501}
1502impl PrintablePanicType for EndPrintingAttempts {
1503 type NextType = EndPrintingAttempts;
1504}
1505
1506#[cfg(test)]
1507mod tests {
1508 use super::*;
1509 use crate::{activities::ActivityError, workflow_interceptors::WorkflowInterceptor};
1510 use futures_util::future::BoxFuture;
1511 use std::sync::atomic::{AtomicUsize, Ordering};
1512 use temporalio_common::{
1513 data_converters::{
1514 DefaultFailureConverter, PayloadCodec, PayloadConversionError, PayloadConverter,
1515 },
1516 protos::coresdk::{
1517 activity_result::activity_execution_result,
1518 workflow_commands::{CompleteWorkflowExecution, workflow_command},
1519 workflow_completion::workflow_activation_completion,
1520 },
1521 };
1522 use temporalio_macros::{activities, activity_definitions, workflow, workflow_methods};
1523
1524 #[derive(Default)]
1525 struct FailingEncodeCodec {
1526 calls: AtomicUsize,
1527 }
1528
1529 impl PayloadCodec for FailingEncodeCodec {
1530 fn encode(
1531 &self,
1532 _: &SerializationContextData,
1533 _: Vec<Payload>,
1534 ) -> BoxFuture<'static, Result<Vec<Payload>, PayloadConversionError>> {
1535 self.calls.fetch_add(1, Ordering::SeqCst);
1536 async move {
1537 Err(PayloadConversionError::EncodingError(
1538 "codec encode failed".into(),
1539 ))
1540 }
1541 .boxed()
1542 }
1543
1544 fn decode(
1545 &self,
1546 _: &SerializationContextData,
1547 payloads: Vec<Payload>,
1548 ) -> BoxFuture<'static, Result<Vec<Payload>, PayloadConversionError>> {
1549 async move { Ok(payloads) }.boxed()
1550 }
1551 }
1552
1553 struct NoopWorkflowInterceptor;
1554
1555 impl WorkflowInterceptor for NoopWorkflowInterceptor {}
1556
1557 struct MyActivities {}
1558
1559 struct SharedActivities;
1560 #[activity_definitions]
1561 impl SharedActivities {
1562 #[activity(name = "shared-greet")]
1563 fn greet(name: String) -> Result<String, ActivityError> {
1564 unimplemented!()
1565 }
1566 }
1567
1568 #[activities]
1569 impl MyActivities {
1570 #[activity]
1571 async fn my_activity(_ctx: ActivityContext) -> Result<(), ActivityError> {
1572 Ok(())
1573 }
1574
1575 #[activity(definition = shared_activities::Greet)]
1576 async fn greet(_ctx: ActivityContext, name: String) -> Result<String, ActivityError> {
1577 Ok(name)
1578 }
1579
1580 #[activity]
1581 async fn takes_self(
1582 self: Arc<Self>,
1583 _ctx: ActivityContext,
1584 _: String,
1585 ) -> Result<(), ActivityError> {
1586 Ok(())
1587 }
1588 }
1589
1590 #[test]
1591 fn test_activity_registration() {
1592 let act_instance = MyActivities {};
1593 let _ = WorkerOptions::new("task_q").register_activities(act_instance);
1594 }
1595
1596 #[tokio::test]
1597 async fn workflow_completion_codec_error_uses_unencoded_failure() {
1598 let codec = Arc::new(FailingEncodeCodec::default());
1599 let data_converter = DataConverter::new(
1600 PayloadConverter::default(),
1601 DefaultFailureConverter,
1602 codec.clone(),
1603 );
1604 let mut completion = WorkflowActivationCompletion::from_cmd(
1605 "run-id",
1606 workflow_command::Variant::CompleteWorkflowExecution(CompleteWorkflowExecution {
1607 result: Some(Payload::default()),
1608 }),
1609 );
1610
1611 encode_workflow_completion(&mut completion, &data_converter).await;
1612
1613 let Some(workflow_activation_completion::Status::Failed(failed)) = completion.status else {
1614 panic!("expected failed workflow completion")
1615 };
1616 assert_eq!(
1617 failed.failure.unwrap().message,
1618 "Failed encoding completion: Encoding error: codec encode failed"
1619 );
1620 assert_eq!(
1621 failed.force_cause,
1622 WorkflowTaskFailedCause::WorkflowWorkerUnhandledFailure as i32
1623 );
1624 assert_eq!(codec.calls.load(Ordering::SeqCst), 1);
1625 }
1626
1627 #[tokio::test]
1628 async fn activity_completion_codec_error_uses_unencoded_failure() {
1629 let codec = Arc::new(FailingEncodeCodec::default());
1630 let data_converter = DataConverter::new(
1631 PayloadConverter::default(),
1632 DefaultFailureConverter,
1633 codec.clone(),
1634 );
1635 let mut completion = ActivityTaskCompletion {
1636 task_token: vec![],
1637 result: Some(ActivityExecutionResult::ok(Payload::default())),
1638 };
1639
1640 encode_activity_completion(&mut completion, &data_converter).await;
1641
1642 let Some(activity_execution_result::Status::Failed(failed)) =
1643 completion.result.unwrap().status
1644 else {
1645 panic!("expected failed activity completion")
1646 };
1647 let failure = failed.failure.unwrap();
1648 assert_eq!(
1649 failure.message,
1650 "Failed encoding activity completion: Encoding error: codec encode failed"
1651 );
1652 assert!(matches!(
1653 failure.failure_info,
1654 Some(
1655 temporalio_common::protos::temporal::api::failure::v1::failure::FailureInfo::ApplicationFailureInfo(info)
1656 ) if !info.non_retryable
1657 ));
1658 assert_eq!(codec.calls.load(Ordering::SeqCst), 1);
1659 }
1660
1661 #[allow(unused, clippy::diverging_sub_expression)]
1663 fn test_activity_via_workflow_context() {
1664 let wf_ctx: WorkflowContext<MyWorkflow> = unimplemented!();
1665 wf_ctx.execute_activity(
1666 MyActivities::my_activity,
1667 (),
1668 ActivityOptions::start_to_close_timeout(Duration::from_secs(5)),
1669 );
1670 wf_ctx.execute_activity(
1671 SharedActivities::greet,
1672 "Hi".to_owned(),
1673 ActivityOptions::start_to_close_timeout(Duration::from_secs(5)),
1674 );
1675 wf_ctx.execute_activity(
1676 MyActivities::greet,
1677 "Hi".to_owned(),
1678 ActivityOptions::start_to_close_timeout(Duration::from_secs(5)),
1679 );
1680 wf_ctx.execute_activity(
1681 MyActivities::takes_self,
1682 "Hi".to_owned(),
1683 ActivityOptions::start_to_close_timeout(Duration::from_secs(5)),
1684 );
1685 }
1686
1687 #[allow(dead_code, unreachable_code, unused, clippy::diverging_sub_expression)]
1689 async fn test_activity_direct_invocation() {
1690 let ctx: ActivityContext = unimplemented!();
1691 let _result = MyActivities::my_activity.run(ctx).await;
1692 }
1693
1694 #[workflow]
1695 struct MyWorkflow {
1696 counter: u32,
1697 }
1698
1699 #[allow(dead_code)]
1700 #[workflow_methods]
1701 impl MyWorkflow {
1702 #[init]
1703 fn new(_ctx: &WorkflowContextView, _input: String) -> Self {
1704 Self { counter: 0 }
1705 }
1706
1707 #[run]
1708 async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<String> {
1709 Ok(format!("Counter: {}", ctx.state(|s| s.counter)))
1710 }
1711
1712 #[signal(name = "increment")]
1713 fn increment_counter(&mut self, _ctx: &mut SyncWorkflowContext<Self>, amount: u32) {
1714 self.counter += amount;
1715 }
1716
1717 #[signal]
1718 async fn async_signal(_ctx: &mut WorkflowContext<Self>) {}
1719
1720 #[query]
1721 fn get_counter(&self, _ctx: &WorkflowContextView) -> u32 {
1722 self.counter
1723 }
1724
1725 #[update(name = "double")]
1726 fn double_counter(&mut self, _ctx: &mut SyncWorkflowContext<Self>) -> u32 {
1727 self.counter *= 2;
1728 self.counter
1729 }
1730
1731 #[update]
1732 async fn async_update(_ctx: &mut WorkflowContext<Self>, val: i32) -> i32 {
1733 val * 2
1734 }
1735 }
1736
1737 #[workflow]
1738 #[derive(Default)]
1739 struct OtherWorkflow;
1740
1741 #[workflow_methods]
1742 impl OtherWorkflow {
1743 #[run]
1744 async fn run(_ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
1745 Ok(())
1746 }
1747 }
1748
1749 #[test]
1750 fn test_workflow_registration() {
1751 let _ = WorkerOptions::new("task_q")
1752 .register_workflow::<MyWorkflow>()
1753 .unwrap();
1754 }
1755
1756 #[test]
1757 fn simple_plugin_workflow_function_merges_definitions() {
1758 let plugin = SimplePlugin::builder("simple")
1759 .workflows(|existing: Option<WorkflowDefinitions>| {
1760 assert!(existing.is_some());
1761 let mut workflows = WorkflowDefinitions::new();
1762 workflows.register_workflow::<OtherWorkflow>().unwrap();
1763 workflows
1764 })
1765 .build();
1766 let client_options = ClientOptions::new("namespace").build();
1767 let mut worker_options = WorkerOptions::new("task_q")
1768 .register_workflow::<MyWorkflow>()
1769 .unwrap()
1770 .worker_plugin(plugin)
1771 .build();
1772
1773 crate::plugins::apply_worker_plugins(&client_options, &mut worker_options).unwrap();
1774
1775 let workflows = format!("{:?}", worker_options.workflows());
1776 assert!(workflows.contains("MyWorkflow"));
1777 assert!(workflows.contains("OtherWorkflow"));
1778 }
1779
1780 #[rstest::rstest]
1781 #[case::workflow_only(true, false, Ok(WorkerTaskTypes::workflow_only()))]
1782 #[case::activity_only(false, true, Ok(WorkerTaskTypes::activity_only()))]
1783 #[case::workflow_and_activity(
1784 true,
1785 true,
1786 Ok(WorkerTaskTypes {
1787 enable_workflows: true,
1788 enable_local_activities: true,
1789 enable_remote_activities: true,
1790 enable_nexus: false,
1791 })
1792 )]
1793 #[case::empty(
1794 false,
1795 false,
1796 Err("At least one workflow or activity must be registered")
1797 )]
1798 #[test]
1799 fn task_types_are_derived_from_registrations(
1800 #[case] register_workflow: bool,
1801 #[case] register_activities: bool,
1802 #[case] expected: Result<WorkerTaskTypes, &str>,
1803 ) {
1804 let options = if register_workflow {
1805 WorkerOptions::new("task_q")
1806 .register_workflow::<MyWorkflow>()
1807 .unwrap()
1808 } else {
1809 WorkerOptions::new("task_q")
1810 };
1811 let options = if register_activities {
1812 options.register_activities(MyActivities {})
1813 } else {
1814 options
1815 };
1816
1817 let actual = options
1818 .build()
1819 .to_core_options("ns".into(), String::new())
1820 .map(|config| config.task_types);
1821 assert_eq!(
1822 actual.as_ref().map_err(String::as_str),
1823 expected.as_ref().map_err(|err| *err)
1824 );
1825 }
1826
1827 #[test]
1828 fn workflow_interceptor_registration_replaces_previous_constructors() {
1829 let mut options = WorkerOptions::new("task_q").build();
1830 options.register_workflow_interceptors(vec![
1831 WorkflowInterceptorConstructor::new(|_| NoopWorkflowInterceptor),
1832 WorkflowInterceptorConstructor::new(|_| NoopWorkflowInterceptor),
1833 ]);
1834 assert_eq!(options.workflow_interceptor_constructors.len(), 2);
1835
1836 options.register_workflow_interceptors(vec![WorkflowInterceptorConstructor::new(|_| {
1837 NoopWorkflowInterceptor
1838 })]);
1839 assert_eq!(options.workflow_interceptor_constructors.len(), 1);
1840 }
1841
1842 #[test]
1843 fn duplicate_workflow_registration_errors() {
1844 let result = WorkerOptions::new("task_q")
1845 .register_workflow::<MyWorkflow>()
1846 .unwrap()
1847 .register_workflow::<MyWorkflow>();
1848
1849 let err = match result {
1850 Ok(_) => panic!("duplicate workflow registration should error"),
1851 Err(err) => err,
1852 };
1853 assert_eq!(
1854 err,
1855 WorkflowRegistrationError::DuplicateWorkflowType {
1856 workflow_type: "MyWorkflow".to_string()
1857 }
1858 );
1859 }
1860
1861 #[test]
1862 fn factory_registration_with_init_errors() {
1863 let result = WorkerOptions::new("task_q")
1864 .register_workflow_with_factory(|| MyWorkflow { counter: 0 });
1865
1866 let err = match result {
1867 Ok(_) => panic!("factory registration with #[init] should error"),
1868 Err(err) => err,
1869 };
1870 assert_eq!(
1871 err,
1872 WorkflowRegistrationError::FactoryRegistrationWithInit {
1873 workflow_type: "MyWorkflow".to_string()
1874 }
1875 );
1876 }
1877
1878 fn default_identity() -> String {
1879 format!(
1880 "{}@{}",
1881 std::process::id(),
1882 gethostname::gethostname().to_string_lossy()
1883 )
1884 }
1885
1886 #[rstest::rstest]
1887 #[case::default_when_none_provided(None, "", Some(default_identity()))]
1888 #[case::connection_identity_preserved(None, "conn-identity", None)]
1889 #[case::worker_override_takes_precedence(
1890 Some("worker-identity"),
1891 "conn-identity",
1892 Some("worker-identity".into())
1893 )]
1894 #[case::worker_override_with_empty_connection(
1895 Some("worker-identity"),
1896 "",
1897 Some("worker-identity".into())
1898 )]
1899 #[test]
1900 fn client_identity_resolution(
1901 #[case] worker_override: Option<&str>,
1902 #[case] connection_identity: &str,
1903 #[case] expected: Option<String>,
1904 ) {
1905 let opts = WorkerOptions::new("task_q")
1906 .register_activities(MyActivities {})
1907 .maybe_client_identity_override(worker_override.map(|s| s.to_owned()))
1908 .build();
1909 let config = opts
1910 .to_core_options("ns".into(), connection_identity.into())
1911 .unwrap();
1912 assert_eq!(config.client_identity_override, expected);
1913 }
1914
1915 #[rstest::rstest]
1916 #[case::default_enforces_error_limit(None, false)]
1917 #[case::opt_out_disables_error_limit(Some(true), true)]
1918 #[case::explicit_enable_error_limit(Some(false), false)]
1919 #[test]
1920 fn disable_payload_error_limit_propagates(
1921 #[case] override_value: Option<bool>,
1922 #[case] expected: bool,
1923 ) {
1924 let config = WorkerOptions::new("task_q")
1925 .register_activities(MyActivities {})
1926 .maybe_disable_payload_error_limit(override_value)
1927 .build()
1928 .to_core_options("ns".into(), String::new())
1929 .unwrap();
1930 assert_eq!(config.disable_payload_error_limit, expected);
1931 }
1932
1933 #[test]
1934 fn max_eager_activity_reservations_per_workflow_task_propagates() {
1935 let config = WorkerOptions::new("task_q")
1936 .register_activities(MyActivities {})
1937 .max_eager_activity_reservations_per_workflow_task(7)
1938 .build()
1939 .to_core_options("ns".into(), String::new())
1940 .unwrap();
1941 assert_eq!(config.max_eager_activity_reservations_per_workflow_task, 7);
1942 }
1943}