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