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