From 7a7eb99b65ecb0a461aa55870756349bfe6e6926 Mon Sep 17 00:00:00 2001 From: Andrew Yuan Date: Thu, 13 Nov 2025 12:48:08 -0800 Subject: [PATCH 1/8] Add new task_types --- crates/common/Cargo.toml | 1 + crates/common/src/worker.rs | 93 +++- crates/common/tests/worker_task_types_test.rs | 174 +++++++ crates/sdk-core-c-bridge/src/worker.rs | 8 + .../sdk-core/src/test_help/integ_helpers.rs | 12 +- crates/sdk-core/src/worker/mod.rs | 461 ++++++++++-------- 6 files changed, 550 insertions(+), 199 deletions(-) create mode 100644 crates/common/tests/worker_task_types_test.rs diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 5d013587e..d3f55dd34 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -23,6 +23,7 @@ test-utilities = ["history_builders"] anyhow = "1.0" async-trait = "0.1" base64 = "0.22" +bitflags = "2.6" dirs = { version = "6.0", optional = true } derive_builder = { workspace = true } derive_more = { workspace = true } diff --git a/crates/common/src/worker.rs b/crates/common/src/worker.rs index 8af1c7c99..5b395655e 100644 --- a/crates/common/src/worker.rs +++ b/crates/common/src/worker.rs @@ -8,6 +8,7 @@ use crate::{ }, telemetry::metrics::TemporalMeter, }; +use bitflags::bitflags; use std::{ any::Any, collections::{HashMap, HashSet}, @@ -16,6 +17,39 @@ use std::{ time::Duration, }; +bitflags! { + /// Specifies which task types a worker will poll for. + /// + /// This allows fine-grained control over what kinds of work this worker handles. + /// Workers can be configured to handle any combination of workflows, activities, and nexus operations. + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct WorkerTaskTypes: u8 { + /// Poll for workflow tasks + const WORKFLOWS = 0b001; + /// Poll for activity tasks (remote activities) + const ACTIVITIES = 0b010; + /// Poll for nexus tasks + const NEXUS = 0b100; + } +} + +impl WorkerTaskTypes { + /// Returns true if this worker should poll for workflow tasks + pub fn polls_workflows(&self) -> bool { + self.contains(Self::WORKFLOWS) + } + + /// Returns true if this worker should poll for activity tasks + pub fn polls_activities(&self) -> bool { + self.contains(Self::ACTIVITIES) + } + + /// Returns true if this worker should poll for nexus tasks + pub fn polls_nexus(&self) -> bool { + self.contains(Self::NEXUS) + } +} + /// Defines per-worker configuration options #[derive(Clone, derive_builder::Builder)] #[builder(setter(into), build_fn(validate = "Self::validate"))] @@ -64,8 +98,24 @@ pub struct WorkerConfig { /// worker's task queue #[builder(default = "PollerBehavior::SimpleMaximum(5)")] pub nexus_task_poller_behavior: PollerBehavior, + /// Specifies which task types this worker will poll for. + /// + /// By default, workers poll for all task types (workflows, activities, and nexus). + /// You can restrict this to any combination. For example, a workflow-only worker would use + /// `WorkerTaskTypes::WORKFLOWS`, while a worker handling activities and nexus but not workflows + /// would use `WorkerTaskTypes::ACTIVITIES | WorkerTaskTypes::NEXUS`. If using in combination + /// with `no_remote_activites`, it must be ensured that `ACTIVITIES` is not selected, as + /// validation will fail. + /// + /// Note: At least one task type must be specified or the worker will fail validation. + #[builder(default = "WorkerTaskTypes::all()")] + pub task_types: WorkerTaskTypes, /// If set to true this worker will only handle workflow tasks and local activities, it will not - /// poll for activity tasks. + /// poll for activity tasks. If this is used with `task_types`, users must ensure + /// `WorkerTaskTypes::ACTIVITIES` is not set. + /// + /// This is equivalent to setting + /// `task_types = WorkerTaskTypes::WORKFLOWS | WorkerTaskTypes::NEXUS`. #[builder(default = "false")] pub no_remote_activities: bool, /// How long a workflow task is allowed to sit on the sticky queue before it is timed out @@ -175,6 +225,18 @@ pub struct WorkerConfig { } impl WorkerConfig { + /// Returns the effective task types this worker should poll for, taking into account + /// `no_remote_activities` field. + /// + /// If `no_remote_activities` is true, activities are excluded from the task types. + pub fn effective_task_types(&self) -> WorkerTaskTypes { + if self.no_remote_activities { + self.task_types - WorkerTaskTypes::ACTIVITIES + } else { + self.task_types + } + } + /// Returns true if the configuration specifies we should fail a workflow on a certain error /// type rather than failing the workflow task. pub fn should_fail_workflow( @@ -218,6 +280,24 @@ impl WorkerConfigBuilder { } fn validate(&self) -> Result<(), String> { + let task_types = self.task_types.unwrap_or_else(WorkerTaskTypes::all); + if task_types.is_empty() { + return Err("At least one task type must be enabled in `task_types`".to_owned()); + } + + // Handle backward compatibility with no_remote_activities + if let Some(true) = self.no_remote_activities { + // If no_remote_activities is set to true, warn if task_types was also explicitly set + // and includes activities + if self.task_types.is_some() && task_types.contains(WorkerTaskTypes::ACTIVITIES) { + return Err( + "Conflicting configuration: `no_remote_activities` is true but `task_types` includes ACTIVITIES. \ + Please update `task_types` to not allow for ACTIVITIES." + .to_owned(), + ); + } + } + if let Some(b) = self.workflow_task_poller_behavior.as_ref() { b.validate()? } @@ -249,6 +329,17 @@ impl WorkerConfigBuilder { return Err("`max_outstanding_nexus_tasks` must be > 0".to_owned()); } + // Validate workflow cache is consistent with task_types + if !task_types.contains(WorkerTaskTypes::WORKFLOWS) + && let Some(cache) = self.max_cached_workflows.as_ref() + && *cache > 0 + { + return Err( + "Cannot have `max_cached_workflows` > 0 when workflows are not enabled in `task_types`" + .to_owned(), + ); + } + if let Some(cache) = self.max_cached_workflows.as_ref() && *cache > 0 { diff --git a/crates/common/tests/worker_task_types_test.rs b/crates/common/tests/worker_task_types_test.rs new file mode 100644 index 000000000..39068ed54 --- /dev/null +++ b/crates/common/tests/worker_task_types_test.rs @@ -0,0 +1,174 @@ +use temporalio_common::worker::{WorkerConfigBuilder, WorkerTaskTypes, WorkerVersioningStrategy}; + +fn default_versioning_strategy() -> WorkerVersioningStrategy { + WorkerVersioningStrategy::None { + build_id: String::new(), + } +} + +#[test] +fn test_default_configuration_polls_all_types() { + let config = WorkerConfigBuilder::default() + .namespace("default") + .task_queue("test-queue") + .versioning_strategy(default_versioning_strategy()) + .build() + .expect("Failed to build default config"); + + let effective = config.effective_task_types(); + assert!( + effective.polls_workflows(), + "Should poll workflows by default" + ); + assert!( + effective.polls_activities(), + "Should poll activities by default" + ); + assert!(effective.polls_nexus(), "Should poll nexus by default"); +} + +#[test] +fn test_workflow_only_worker() { + let config = WorkerConfigBuilder::default() + .namespace("default") + .task_queue("test-queue") + .versioning_strategy(default_versioning_strategy()) + .task_types(WorkerTaskTypes::WORKFLOWS) + .max_cached_workflows(0usize) + .build() + .expect("Failed to build workflow-only config"); + + let effective = config.effective_task_types(); + assert!(effective.polls_workflows(), "Should poll workflows"); + assert!(!effective.polls_activities(), "Should NOT poll activities"); + assert!(!effective.polls_nexus(), "Should NOT poll nexus"); +} + +#[test] +fn test_activity_and_nexus_worker() { + let config = WorkerConfigBuilder::default() + .namespace("default") + .task_queue("test-queue") + .versioning_strategy(default_versioning_strategy()) + .task_types(WorkerTaskTypes::ACTIVITIES | WorkerTaskTypes::NEXUS) + .max_cached_workflows(0usize) + .build() + .expect("Failed to build activity+nexus config"); + + let effective = config.effective_task_types(); + assert!(!effective.polls_workflows(), "Should NOT poll workflows"); + assert!(effective.polls_activities(), "Should poll activities"); + assert!(effective.polls_nexus(), "Should poll nexus"); +} + +#[test] +fn test_backward_compatibility_with_no_remote_activities() { + let config = WorkerConfigBuilder::default() + .namespace("default") + .task_queue("test-queue") + .versioning_strategy(default_versioning_strategy()) + .no_remote_activities(true) + .build() + .expect("Failed to build config with no_remote_activities"); + + let effective = config.effective_task_types(); + assert!(effective.polls_workflows(), "Should poll workflows"); + assert!(!effective.polls_activities(), "Should NOT poll activities"); + assert!(effective.polls_nexus(), "Should poll nexus"); +} + +#[test] +fn test_empty_task_types_fails_validation() { + let result = WorkerConfigBuilder::default() + .namespace("default") + .task_queue("test-queue") + .versioning_strategy(default_versioning_strategy()) + .task_types(WorkerTaskTypes::empty()) + .build(); + + assert!(result.is_err(), "Empty task_types should fail validation"); + let err = result.err().unwrap().to_string(); + assert!( + err.contains("At least one task type"), + "Error should mention task types: {err}", + ); +} + +#[test] +fn test_workflow_cache_without_workflows_fails() { + let result = WorkerConfigBuilder::default() + .namespace("default") + .task_queue("test-queue") + .versioning_strategy(default_versioning_strategy()) + .task_types(WorkerTaskTypes::ACTIVITIES) + .max_cached_workflows(10usize) + .build(); + + assert!( + result.is_err(), + "Workflow cache > 0 without workflows should fail" + ); + let err = result.err().unwrap().to_string(); + assert!( + err.contains("max_cached_workflows"), + "Error should mention max_cached_workflows: {err}", + ); +} + +#[test] +fn test_conflicting_no_remote_activities_and_task_types_fails() { + #[allow(deprecated)] + let result = WorkerConfigBuilder::default() + .namespace("default") + .task_queue("test-queue") + .versioning_strategy(default_versioning_strategy()) + .task_types(WorkerTaskTypes::ACTIVITIES) + .no_remote_activities(true) + .build(); + + assert!(result.is_err(), "Conflicting settings should fail"); + + let err = result.err().unwrap().to_string(); + assert!( + err.contains("Conflicting configuration"), + "Error should mention conflict: {err}", + ); +} + +#[test] +fn test_all_combinations() { + let combinations = [ + (WorkerTaskTypes::WORKFLOWS, "workflows only"), + (WorkerTaskTypes::ACTIVITIES, "activities only"), + (WorkerTaskTypes::NEXUS, "nexus only"), + ( + WorkerTaskTypes::WORKFLOWS | WorkerTaskTypes::ACTIVITIES, + "workflows + activities", + ), + ( + WorkerTaskTypes::WORKFLOWS | WorkerTaskTypes::NEXUS, + "workflows + nexus", + ), + ( + WorkerTaskTypes::ACTIVITIES | WorkerTaskTypes::NEXUS, + "activities + nexus", + ), + (WorkerTaskTypes::all(), "all types"), + ]; + + for (task_types, description) in combinations { + let config = WorkerConfigBuilder::default() + .namespace("default") + .task_queue("test-queue") + .versioning_strategy(default_versioning_strategy()) + .task_types(task_types) + .build() + .unwrap_or_else(|e| panic!("Failed to build config for {description}: {e:?}")); + + let effective = config.effective_task_types(); + assert_eq!( + effective, task_types, + "Effective types should match for {description}", + ); + } +} diff --git a/crates/sdk-core-c-bridge/src/worker.rs b/crates/sdk-core-c-bridge/src/worker.rs index 542d06d99..ac015996d 100644 --- a/crates/sdk-core-c-bridge/src/worker.rs +++ b/crates/sdk-core-c-bridge/src/worker.rs @@ -43,6 +43,7 @@ pub struct WorkerOptions { pub identity_override: ByteArrayRef, pub max_cached_workflows: u32, pub tuner: TunerHolder, + pub task_types: u8, pub no_remote_activities: bool, pub sticky_queue_schedule_to_start_timeout_millis: u64, pub max_heartbeat_throttle_interval_millis: u64, @@ -1183,6 +1184,13 @@ impl TryFrom<&WorkerOptions> for temporalio_sdk_core::WorkerConfig { .client_identity_override(opt.identity_override.to_option_string()) .max_cached_workflows(opt.max_cached_workflows as usize) .tuner(Arc::new(converted_tuner)) + .task_types({ + if opt.task_types == 0 { + temporalio_common::worker::WorkerTaskTypes::all() + } else { + temporalio_common::worker::WorkerTaskTypes::from_bits_truncate(opt.task_types) + } + }) .no_remote_activities(opt.no_remote_activities) .sticky_queue_schedule_to_start_timeout(Duration::from_millis( opt.sticky_queue_schedule_to_start_timeout_millis, diff --git a/crates/sdk-core/src/test_help/integ_helpers.rs b/crates/sdk-core/src/test_help/integ_helpers.rs index e27a92c9f..f20f564f5 100644 --- a/crates/sdk-core/src/test_help/integ_helpers.rs +++ b/crates/sdk-core/src/test_help/integ_helpers.rs @@ -194,12 +194,14 @@ pub fn mock_worker(mocks: MocksHolder) -> Worker { sticky_q, mocks.client, TaskPollers::Mocked { - wft_stream: mocks.inputs.wft_stream, + wft_stream: Some(mocks.inputs.wft_stream), act_poller, - nexus_poller: mocks - .inputs - .nexus_poller - .unwrap_or_else(|| mock_poller_from_resps([])), + nexus_poller: Some( + mocks + .inputs + .nexus_poller + .unwrap_or_else(|| mock_poller_from_resps([])), + ), }, None, None, diff --git a/crates/sdk-core/src/worker/mod.rs b/crates/sdk-core/src/worker/mod.rs index 9825db89f..bcdc26b31 100644 --- a/crates/sdk-core/src/worker/mod.rs +++ b/crates/sdk-core/src/worker/mod.rs @@ -115,14 +115,14 @@ pub struct Worker { client: Arc, /// Worker instance key, unique identifier for this worker worker_instance_key: Uuid, - /// Manages all workflows and WFT processing - workflows: Workflows, + /// Manages all workflows and WFT processing. None if workflow polling is disabled. + workflows: Option, /// Manages activity tasks for this worker/task queue at_task_mgr: Option, - /// Manages local activities - local_act_mgr: Arc, - /// Manages Nexus tasks - nexus_mgr: NexusManager, + /// Manages local activities. None if workflow polling is disabled (local activities require workflows). + local_act_mgr: Option>, + /// Manages Nexus tasks. None if nexus polling is disabled. + nexus_mgr: Option, /// Has shutdown been called? shutdown_token: CancellationToken, /// Will be called at the end of each activation completion @@ -186,7 +186,10 @@ impl WorkerTrait for Worker { #[instrument(skip(self))] async fn poll_nexus_task(&self) -> Result { - self.nexus_mgr.next_nexus_task().await + match &self.nexus_mgr { + Some(mgr) => mgr.next_nexus_task().await, + None => Err(PollError::ShutDown), + } } async fn complete_workflow_activation( @@ -268,7 +271,9 @@ impl WorkerTrait for Worker { // Push a BumpStream message to the workflow activation queue. This ensures that // any pending workflow activation polls will resolve, even if there are no other inputs. - self.workflows.bump_stream(); + if let Some(workflows) = &self.workflows { + workflows.bump_stream(); + } // Second, we want to stop polling of both activity and workflow tasks if let Some(atm) = self.at_task_mgr.as_ref() { @@ -276,13 +281,15 @@ impl WorkerTrait for Worker { } // Let the manager know that shutdown has been initiated to try to unblock the local // activity poll in case this worker is an activity-only worker. - self.local_act_mgr.shutdown_initiated(); - - // If workflows have never been polled, immediately tell the local activity manager - // that workflows have shut down, so it can proceed with shutdown without waiting. - // This is particularly important for activity-only workers. - if !self.workflows.ever_polled() { - self.local_act_mgr.workflows_have_shutdown(); + if let Some(la_mgr) = &self.local_act_mgr { + la_mgr.shutdown_initiated(); + + // If workflows have never been polled, immediately tell the local activity manager + // that workflows have shut down, so it can proceed with shutdown without waiting. + // This is particularly important for activity-only workers. + if self.workflows.as_ref().is_none_or(|w| !w.ever_polled()) { + la_mgr.workflows_have_shutdown(); + } } } @@ -444,30 +451,35 @@ impl Worker { slot_context_data.clone(), meter.clone(), ); + let effective_task_types = config.effective_task_types(); let (wft_stream, act_poller, nexus_poller) = match task_pollers { TaskPollers::Real => { - let wft_stream = make_wft_poller( - &config, - &sticky_queue_name, - &client, - &metrics, - &shutdown_token, - &wft_slots, - wf_last_suc_poll_time.clone(), - wf_sticky_last_suc_poll_time.clone(), - ); - let wft_stream = if !client.is_mock() { - // Some replay tests combine a mock client with real pollers, - // and they don't need to use the external stream - stream::select(wft_stream, UnboundedReceiverStream::new(external_wft_rx)) - .left_stream() + let wft_stream = if effective_task_types.polls_workflows() { + let stream = make_wft_poller( + &config, + &sticky_queue_name, + &client, + &metrics, + &shutdown_token, + &wft_slots, + wf_last_suc_poll_time.clone(), + wf_sticky_last_suc_poll_time.clone(), + ) + .boxed(); + let stream = if !client.is_mock() { + // Some replay tests combine a mock client with real pollers, + // and they don't need to use the external stream + stream::select(stream, UnboundedReceiverStream::new(external_wft_rx)) + .left_stream() + } else { + stream.right_stream() + }; + Some(stream) } else { - wft_stream.right_stream() + None }; - let act_poll_buffer = if config.no_remote_activities { - None - } else { + let act_poll_buffer = if effective_task_types.polls_activities() { let act_metrics = metrics.with_new_attrs([activity_poller()]); let ap = LongPollBuffer::new_activity_task( client.clone(), @@ -483,23 +495,28 @@ impl Worker { act_last_suc_poll_time.clone(), ); Some(Box::from(ap) as BoxedActPoller) + } else { + None }; - let np_metrics = metrics.with_new_attrs([nexus_poller()]); - - let nexus_poll_buffer = Box::new(LongPollBuffer::new_nexus_task( - client.clone(), - config.task_queue.clone(), - config.nexus_task_poller_behavior, - nexus_slots.clone(), - shutdown_token.child_token(), - Some(move |np| np_metrics.record_num_pollers(np)), - nexus_last_suc_poll_time.clone(), - shared_namespace_worker, - )) as BoxedNexusPoller; + let nexus_poll_buffer = if effective_task_types.polls_nexus() { + let np_metrics = metrics.with_new_attrs([nexus_poller()]); + Some(Box::new(LongPollBuffer::new_nexus_task( + client.clone(), + config.task_queue.clone(), + config.nexus_task_poller_behavior, + nexus_slots.clone(), + shutdown_token.child_token(), + Some(move |np| np_metrics.record_num_pollers(np)), + nexus_last_suc_poll_time.clone(), + shared_namespace_worker, + )) as BoxedNexusPoller) + } else { + None + }; #[cfg(any(feature = "test-utilities", test))] - let wft_stream = wft_stream.left_stream(); + let wft_stream = wft_stream.map(|s| s.left_stream()); (wft_stream, act_poll_buffer, nexus_poll_buffer) } #[cfg(any(feature = "test-utilities", test))] @@ -510,25 +527,27 @@ impl Worker { } => { let ap = act_poller .map(|ap| MockPermittedPollBuffer::new(Arc::new(act_slots.clone()), ap)); - let np = MockPermittedPollBuffer::new(Arc::new(nexus_slots.clone()), nexus_poller); - let wft_semaphore = wft_slots.clone(); - let wfs = wft_stream.then(move |s| { - let wft_semaphore = wft_semaphore.clone(); - async move { - let permit = wft_semaphore.acquire_owned().await; - s.map(|s| (s, permit)) - } + let np = nexus_poller + .map(|np| MockPermittedPollBuffer::new(Arc::new(nexus_slots.clone()), np)); + let wfs = wft_stream.map(|stream| { + let wft_semaphore = wft_slots.clone(); + let wfs = stream.then(move |s| { + let wft_semaphore = wft_semaphore.clone(); + async move { + let permit = wft_semaphore.acquire_owned().await; + s.map(|s| (s, permit)) + } + }); + wfs.right_stream() }); - let wfs = wfs.right_stream(); ( wfs, ap.map(|ap| Box::new(ap) as BoxedActPoller), - Box::new(np) as BoxedNexusPoller, + np.map(|np| Box::new(np) as BoxedNexusPoller), ) } }; - let (hb_tx, hb_rx) = unbounded_channel(); let la_permit_dealer = MeteredPermitDealer::new( tuner.local_activity_slot_supplier(), metrics.with_new_attrs([local_activity_worker_type()]), @@ -537,12 +556,21 @@ impl Worker { meter.clone(), ); let la_permits = la_permit_dealer.get_extant_count_rcv(); - let local_act_mgr = Arc::new(LocalActivityManager::new( - config.namespace.clone(), - la_permit_dealer.clone(), - hb_tx, - metrics.clone(), - )); + + let (local_act_mgr, la_sink, hb_rx) = if effective_task_types.polls_workflows() { + let (hb_tx, hb_rx) = unbounded_channel(); + let local_act_mgr = Arc::new(LocalActivityManager::new( + config.namespace.clone(), + la_permit_dealer.clone(), + hb_tx, + metrics.clone(), + )); + let la_sink = LAReqSink::new(local_act_mgr.clone()); + (Some(local_act_mgr), Some(la_sink), Some(hb_rx)) + } else { + (None, None, None) + }; + let at_task_mgr = act_poller.map(|ap| { WorkerActivityTasks::new( act_slots.clone(), @@ -559,14 +587,15 @@ impl Worker { if !poll_on_non_local_activities && !shared_namespace_worker { info!("Activity polling is disabled for this worker"); }; - let la_sink = LAReqSink::new(local_act_mgr.clone()); - let nexus_mgr = NexusManager::new( - nexus_poller, - metrics.clone(), - config.graceful_shutdown_period, - shutdown_token.child_token(), - ); + let nexus_mgr = nexus_poller.map(|poller| { + NexusManager::new( + poller, + metrics.clone(), + config.graceful_shutdown_period, + shutdown_token.child_token(), + ) + }); let deployment_options = match &config.versioning_strategy { temporalio_common::worker::WorkerVersioningStrategy::WorkerDeploymentBased(opts) => { @@ -626,47 +655,51 @@ impl Worker { Ok(Self { worker_instance_key, client: client.clone(), - workflows: Workflows::new( - WorkflowBasics { - worker_config: Arc::new(config.clone()), - shutdown_token: shutdown_token.child_token(), - metrics, - server_capabilities: client.capabilities().unwrap_or_default(), - sdk_name: sdk_name_and_ver.0, - sdk_version: sdk_name_and_ver.1, - default_versioning_behavior: config - .versioning_strategy - .default_versioning_behavior(), - }, - sticky_queue_name.map(|sq| StickyExecutionAttributes { - worker_task_queue: Some(TaskQueue { - name: sq, - kind: TaskQueueKind::Sticky as i32, - normal_name: config.task_queue.clone(), + workflows: wft_stream.map(|stream| { + Workflows::new( + WorkflowBasics { + worker_config: Arc::new(config.clone()), + shutdown_token: shutdown_token.child_token(), + metrics, + server_capabilities: client.capabilities().unwrap_or_default(), + sdk_name: sdk_name_and_ver.0, + sdk_version: sdk_name_and_ver.1, + default_versioning_behavior: config + .versioning_strategy + .default_versioning_behavior(), + }, + sticky_queue_name.map(|sq| StickyExecutionAttributes { + worker_task_queue: Some(TaskQueue { + name: sq, + kind: TaskQueueKind::Sticky as i32, + normal_name: config.task_queue.clone(), + }), + schedule_to_start_timeout: Some( + config + .sticky_queue_schedule_to_start_timeout + .try_into() + .expect("timeout fits into proto"), + ), }), - schedule_to_start_timeout: Some( - config - .sticky_queue_schedule_to_start_timeout - .try_into() - .expect("timeout fits into proto"), - ), - }), - client, - wft_slots, - wft_stream, - la_sink, - local_act_mgr.clone(), - hb_rx, - at_task_mgr.as_ref().and_then(|mgr| { - match config.max_task_queue_activities_per_second { - Some(persec) if persec > 0.0 => None, - _ => Some(mgr.get_handle_for_workflows()), - } - }), - worker_telemetry - .as_ref() - .and_then(|telem| telem.trace_subscriber.clone()), - ), + client, + wft_slots, + stream, + la_sink.expect("LA sink must exist when workflows enabled"), + local_act_mgr + .clone() + .expect("LA manager must exist when workflows enabled"), + hb_rx.expect("Heartbeat channel must exist when workflows enabled"), + at_task_mgr.as_ref().and_then(|mgr| { + match config.max_task_queue_activities_per_second { + Some(persec) if persec > 0.0 => None, + _ => Some(mgr.get_handle_for_workflows()), + } + }), + worker_telemetry + .as_ref() + .and_then(|telem| telem.trace_subscriber.clone()), + ) + }), at_task_mgr, local_act_mgr, config, @@ -690,45 +723,51 @@ impl Worker { /// completed async fn shutdown(&self) { self.initiate_shutdown(); - if let Some(name) = self.workflows.get_sticky_queue_name() { - let heartbeat = self - .client_worker_registrator - .heartbeat_manager - .as_ref() - .map(|hm| hm.heartbeat_callback.clone()()); - - // This is a best effort call and we can still shutdown the worker if it fails - match self.client.shutdown_worker(name, heartbeat).await { - Err(err) - if !matches!( - err.code(), - tonic::Code::Unimplemented | tonic::Code::Unavailable - ) => - { - warn!( - "shutdown_worker rpc errored during worker shutdown: {:?}", - err - ); + if let Some(workflows) = &self.workflows { + if let Some(name) = workflows.get_sticky_queue_name() { + let heartbeat = self + .client_worker_registrator + .heartbeat_manager + .as_ref() + .map(|hm| hm.heartbeat_callback.clone()()); + + // This is a best effort call and we can still shutdown the worker if it fails + match self.client.shutdown_worker(name, heartbeat).await { + Err(err) + if !matches!( + err.code(), + tonic::Code::Unimplemented | tonic::Code::Unavailable + ) => + { + warn!( + "shutdown_worker rpc errored during worker shutdown: {:?}", + err + ); + } + _ => {} } - _ => {} } } // We need to wait for all local activities to finish so no more workflow task heartbeats // will be generated - self.local_act_mgr - .wait_all_outstanding_tasks_finished() - .await; + if let Some(la_mgr) = &self.local_act_mgr { + la_mgr.wait_all_outstanding_tasks_finished().await; + } // Wait for workflows to finish - self.workflows - .shutdown() - .await - .expect("Workflow processing terminates cleanly"); + if let Some(workflows) = &self.workflows { + workflows + .shutdown() + .await + .expect("Workflow processing terminates cleanly"); + } // Wait for activities to finish if let Some(acts) = self.at_task_mgr.as_ref() { acts.shutdown().await; } // Wait for nexus tasks to finish - self.nexus_mgr.shutdown().await; + if let Some(nexus) = &self.nexus_mgr { + nexus.shutdown().await; + } // Wait for all permits to be released, but don't totally hang real-world shutdown. tokio::select! { _ = async { self.all_permits_tracker.lock().await.all_done().await } => {}, @@ -752,30 +791,38 @@ impl Worker { /// Returns number of currently cached workflows pub async fn cached_workflows(&self) -> usize { - self.workflows - .get_state_info() - .await - .map(|r| r.cached_workflows) - .unwrap_or_default() + match &self.workflows { + Some(workflows) => workflows + .get_state_info() + .await + .map(|r| r.cached_workflows) + .unwrap_or_default(), + None => 0, + } } /// Returns number of currently outstanding workflow tasks #[cfg(test)] pub(crate) async fn outstanding_workflow_tasks(&self) -> usize { - self.workflows - .get_state_info() - .await - .map(|r| r.outstanding_wft) - .unwrap_or_default() + match &self.workflows { + Some(workflows) => workflows + .get_state_info() + .await + .map(|r| r.outstanding_wft) + .unwrap_or_default(), + None => 0, + } } #[allow(unused)] pub(crate) fn available_wft_permits(&self) -> Option { - self.workflows.available_wft_permits() + self.workflows + .as_ref() + .and_then(|w| w.available_wft_permits()) } #[cfg(test)] pub(crate) fn unused_wft_permits(&self) -> Option { - self.workflows.unused_wft_permits() + self.workflows.as_ref().and_then(|w| w.unused_wft_permits()) } /// Get new activity tasks (may be local or nonlocal). Local activities are returned first @@ -817,17 +864,23 @@ impl Worker { future::pending::<()>().await; unreachable!() } - match self.local_act_mgr.next_pending().await { - Some(NextPendingLAAction::Dispatch(r)) => Ok(Some(r)), - Some(NextPendingLAAction::Autocomplete(action)) => { - Ok(self.handle_la_complete_action(action)) - } - None => { - if self.shutdown_token.is_cancelled() { - self.local_activities_complete - .store(true, Ordering::Relaxed); + match &self.local_act_mgr { + Some(la_mgr) => match la_mgr.next_pending().await { + Some(NextPendingLAAction::Dispatch(r)) => Ok(Some(r)), + Some(NextPendingLAAction::Autocomplete(action)) => { + Ok(self.handle_la_complete_action(action)) + } + None => { + if self.shutdown_token.is_cancelled() { + self.local_activities_complete + .store(true, Ordering::Relaxed); + } + Ok(None) } - Ok(None) + }, + None => { + future::pending::<()>().await; + unreachable!() } } }; @@ -884,18 +937,25 @@ impl Worker { #[instrument(skip(self), fields(run_id, workflow_id, task_queue=%self.config.task_queue))] pub(crate) async fn next_workflow_activation(&self) -> Result { - let r = self.workflows.next_workflow_activation().await; - // In the event workflows are shutdown or erroring, begin shutdown of everything else. Once - // they are shut down, tell the local activity manager that, so that it can know to cancel - // any remaining outstanding LAs and shutdown. - if let Err(ref e) = r { - // This is covering the situation where WFT pollers dying is the reason for shutdown - self.initiate_shutdown(); - if matches!(e, PollError::ShutDown) { - self.local_act_mgr.workflows_have_shutdown(); + match &self.workflows { + Some(workflows) => { + let r = workflows.next_workflow_activation().await; + // In the event workflows are shutdown or erroring, begin shutdown of everything else. Once + // they are shut down, tell the local activity manager that, so that it can know to cancel + // any remaining outstanding LAs and shutdown. + if let Err(ref e) = r { + // This is covering the situation where WFT pollers dying is the reason for shutdown + self.initiate_shutdown(); + if matches!(e, PollError::ShutDown) + && let Some(la_mgr) = &self.local_act_mgr + { + la_mgr.workflows_have_shutdown(); + } + } + r } + None => Err(PollError::ShutDown), // Workflow polling is disabled } - r } #[instrument(skip(self, completion), @@ -905,16 +965,24 @@ impl Worker { &self, completion: WorkflowActivationCompletion, ) -> Result<(), CompleteWfError> { - self.workflows - .activation_completed( - completion, - false, - self.post_activate_hook - .as_ref() - .map(|h| |data: PostActivateHookData| h(self, data)), - ) - .await?; - Ok(()) + match &self.workflows { + Some(workflows) => { + workflows + .activation_completed( + completion, + false, + self.post_activate_hook + .as_ref() + .map(|h| |data: PostActivateHookData| h(self, data)), + ) + .await?; + Ok(()) + } + None => Err(CompleteWfError::MalformedWorkflowCompletion { + reason: "Workflow polling is disabled for this worker".to_string(), + run_id: completion.run_id, + }), + } } #[instrument( @@ -926,9 +994,10 @@ impl Worker { tt: TaskToken, status: nexus_task_completion::Status, ) -> Result<(), CompleteNexusError> { - self.nexus_mgr - .complete_task(tt, status, &*self.client) - .await + match &self.nexus_mgr { + Some(mgr) => mgr.complete_task(tt, status, &*self.client).await, + None => Err(CompleteNexusError::NexusNotEnabled), + } } /// Request a workflow eviction @@ -938,7 +1007,9 @@ impl Worker { message: impl Into, reason: EvictionReason, ) { - self.workflows.request_eviction(run_id, message, reason); + if let Some(workflows) = &self.workflows { + workflows.request_eviction(run_id, message, reason); + } } /// Sets a function to be called at the end of each activation completion @@ -950,11 +1021,13 @@ impl Worker { } fn complete_local_act(&self, task_token: TaskToken, la_res: LocalActivityExecutionResult) { - if self - .handle_la_complete_action(self.local_act_mgr.complete(&task_token, la_res)) - .is_some() - { - dbg_panic!("Should never be a task from direct completion"); + if let Some(la_mgr) = &self.local_act_mgr { + if self + .handle_la_complete_action(la_mgr.complete(&task_token, la_res)) + .is_some() + { + dbg_panic!("Should never be a task from direct completion"); + } } } @@ -974,7 +1047,9 @@ impl Worker { } fn notify_local_result(&self, run_id: &str, res: LocalResolution) { - self.workflows.notify_of_local_result(run_id, res); + if let Some(workflows) = &self.workflows { + workflows.notify_of_local_result(run_id, res); + } } async fn verify_namespace_exists(&self) -> Result<(), WorkerValidationError> { @@ -1220,9 +1295,9 @@ pub(crate) enum TaskPollers { Real, #[cfg(any(feature = "test-utilities", test))] Mocked { - wft_stream: BoxStream<'static, Result>, + wft_stream: Option>>, act_poller: Option>, - nexus_poller: BoxedPoller, + nexus_poller: Option>, }, } From 07538f40278d4b9d97c334aad2de02154b8affc7 Mon Sep 17 00:00:00 2001 From: Andrew Yuan Date: Thu, 13 Nov 2025 14:45:30 -0800 Subject: [PATCH 2/8] remove no_remote_activities --- crates/common/src/lib.rs | 4 +- crates/common/src/worker.rs | 37 +--------------- crates/common/tests/worker_task_types_test.rs | 44 ++----------------- crates/sdk-core-c-bridge/src/worker.rs | 2 - .../sdk-core/src/core_tests/activity_tasks.rs | 4 +- crates/sdk-core/src/core_tests/workers.rs | 4 +- .../sdk-core/src/core_tests/workflow_tasks.rs | 9 ++-- crates/sdk-core/src/replay/mod.rs | 3 +- .../sdk-core/src/test_help/integ_helpers.rs | 12 +++-- crates/sdk-core/src/worker/heartbeat.rs | 3 +- crates/sdk-core/src/worker/mod.rs | 27 +++++++++--- crates/sdk-core/tests/heavy_tests.rs | 3 +- .../tests/integ_tests/metrics_tests.rs | 13 +++--- .../tests/integ_tests/update_tests.rs | 11 ++--- .../tests/integ_tests/worker_tests.rs | 5 ++- .../integ_tests/worker_versioning_tests.rs | 3 +- .../tests/integ_tests/workflow_tests.rs | 7 +-- .../workflow_tests/cancel_external.rs | 3 +- .../integ_tests/workflow_tests/cancel_wf.rs | 3 +- .../workflow_tests/child_workflows.rs | 9 ++-- .../workflow_tests/continue_as_new.rs | 5 ++- .../integ_tests/workflow_tests/determinism.rs | 3 +- .../tests/integ_tests/workflow_tests/eager.rs | 5 ++- .../workflow_tests/modify_wf_properties.rs | 3 +- .../tests/integ_tests/workflow_tests/nexus.rs | 21 ++++++--- .../integ_tests/workflow_tests/patches.rs | 13 +++--- .../integ_tests/workflow_tests/resets.rs | 5 ++- .../integ_tests/workflow_tests/signals.rs | 9 ++-- .../integ_tests/workflow_tests/stickyness.rs | 8 ++-- .../integ_tests/workflow_tests/timers.rs | 9 ++-- .../workflow_tests/upsert_search_attrs.rs | 3 +- crates/sdk-core/tests/manual_tests.rs | 3 +- crates/sdk-core/tests/shared_tests/mod.rs | 3 +- 33 files changed, 141 insertions(+), 155 deletions(-) diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 5ec5c2068..bd68ba7f0 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -129,8 +129,8 @@ pub trait Worker: Send + Sync { /// [Worker::complete_workflow_activation] and [Worker::complete_activity_task] for those /// workflows & activities until they are done. At that point, the lang SDK can end the process, /// or drop the [Worker] instance via [Worker::finalize_shutdown], which will close the - /// connection and free resources. If you have set [WorkerConfig::no_remote_activities], you may - /// skip calling [Worker::poll_activity_task]. + /// connection and free resources. If you have set [WorkerConfig::task_types] to exclude + /// [WorkerTaskTypes::ACTIVITIES], you may skip calling [Worker::poll_activity_task]. /// /// Lang implementations should use [Worker::initiate_shutdown] followed by /// [Worker::finalize_shutdown]. diff --git a/crates/common/src/worker.rs b/crates/common/src/worker.rs index 5b395655e..d0a682a28 100644 --- a/crates/common/src/worker.rs +++ b/crates/common/src/worker.rs @@ -103,21 +103,11 @@ pub struct WorkerConfig { /// By default, workers poll for all task types (workflows, activities, and nexus). /// You can restrict this to any combination. For example, a workflow-only worker would use /// `WorkerTaskTypes::WORKFLOWS`, while a worker handling activities and nexus but not workflows - /// would use `WorkerTaskTypes::ACTIVITIES | WorkerTaskTypes::NEXUS`. If using in combination - /// with `no_remote_activites`, it must be ensured that `ACTIVITIES` is not selected, as - /// validation will fail. + /// would use `WorkerTaskTypes::ACTIVITIES | WorkerTaskTypes::NEXUS`. /// /// Note: At least one task type must be specified or the worker will fail validation. #[builder(default = "WorkerTaskTypes::all()")] pub task_types: WorkerTaskTypes, - /// If set to true this worker will only handle workflow tasks and local activities, it will not - /// poll for activity tasks. If this is used with `task_types`, users must ensure - /// `WorkerTaskTypes::ACTIVITIES` is not set. - /// - /// This is equivalent to setting - /// `task_types = WorkerTaskTypes::WORKFLOWS | WorkerTaskTypes::NEXUS`. - #[builder(default = "false")] - pub no_remote_activities: bool, /// How long a workflow task is allowed to sit on the sticky queue before it is timed out /// and moved to the non-sticky queue where it may be picked up by any worker. #[builder(default = "Duration::from_secs(10)")] @@ -225,18 +215,6 @@ pub struct WorkerConfig { } impl WorkerConfig { - /// Returns the effective task types this worker should poll for, taking into account - /// `no_remote_activities` field. - /// - /// If `no_remote_activities` is true, activities are excluded from the task types. - pub fn effective_task_types(&self) -> WorkerTaskTypes { - if self.no_remote_activities { - self.task_types - WorkerTaskTypes::ACTIVITIES - } else { - self.task_types - } - } - /// Returns true if the configuration specifies we should fail a workflow on a certain error /// type rather than failing the workflow task. pub fn should_fail_workflow( @@ -285,19 +263,6 @@ impl WorkerConfigBuilder { return Err("At least one task type must be enabled in `task_types`".to_owned()); } - // Handle backward compatibility with no_remote_activities - if let Some(true) = self.no_remote_activities { - // If no_remote_activities is set to true, warn if task_types was also explicitly set - // and includes activities - if self.task_types.is_some() && task_types.contains(WorkerTaskTypes::ACTIVITIES) { - return Err( - "Conflicting configuration: `no_remote_activities` is true but `task_types` includes ACTIVITIES. \ - Please update `task_types` to not allow for ACTIVITIES." - .to_owned(), - ); - } - } - if let Some(b) = self.workflow_task_poller_behavior.as_ref() { b.validate()? } diff --git a/crates/common/tests/worker_task_types_test.rs b/crates/common/tests/worker_task_types_test.rs index 39068ed54..a66471e70 100644 --- a/crates/common/tests/worker_task_types_test.rs +++ b/crates/common/tests/worker_task_types_test.rs @@ -15,7 +15,7 @@ fn test_default_configuration_polls_all_types() { .build() .expect("Failed to build default config"); - let effective = config.effective_task_types(); + let effective = config.task_types; assert!( effective.polls_workflows(), "Should poll workflows by default" @@ -38,7 +38,7 @@ fn test_workflow_only_worker() { .build() .expect("Failed to build workflow-only config"); - let effective = config.effective_task_types(); + let effective = config.task_types; assert!(effective.polls_workflows(), "Should poll workflows"); assert!(!effective.polls_activities(), "Should NOT poll activities"); assert!(!effective.polls_nexus(), "Should NOT poll nexus"); @@ -55,28 +55,12 @@ fn test_activity_and_nexus_worker() { .build() .expect("Failed to build activity+nexus config"); - let effective = config.effective_task_types(); + let effective = config.task_types; assert!(!effective.polls_workflows(), "Should NOT poll workflows"); assert!(effective.polls_activities(), "Should poll activities"); assert!(effective.polls_nexus(), "Should poll nexus"); } -#[test] -fn test_backward_compatibility_with_no_remote_activities() { - let config = WorkerConfigBuilder::default() - .namespace("default") - .task_queue("test-queue") - .versioning_strategy(default_versioning_strategy()) - .no_remote_activities(true) - .build() - .expect("Failed to build config with no_remote_activities"); - - let effective = config.effective_task_types(); - assert!(effective.polls_workflows(), "Should poll workflows"); - assert!(!effective.polls_activities(), "Should NOT poll activities"); - assert!(effective.polls_nexus(), "Should poll nexus"); -} - #[test] fn test_empty_task_types_fails_validation() { let result = WorkerConfigBuilder::default() @@ -115,26 +99,6 @@ fn test_workflow_cache_without_workflows_fails() { ); } -#[test] -fn test_conflicting_no_remote_activities_and_task_types_fails() { - #[allow(deprecated)] - let result = WorkerConfigBuilder::default() - .namespace("default") - .task_queue("test-queue") - .versioning_strategy(default_versioning_strategy()) - .task_types(WorkerTaskTypes::ACTIVITIES) - .no_remote_activities(true) - .build(); - - assert!(result.is_err(), "Conflicting settings should fail"); - - let err = result.err().unwrap().to_string(); - assert!( - err.contains("Conflicting configuration"), - "Error should mention conflict: {err}", - ); -} - #[test] fn test_all_combinations() { let combinations = [ @@ -165,7 +129,7 @@ fn test_all_combinations() { .build() .unwrap_or_else(|e| panic!("Failed to build config for {description}: {e:?}")); - let effective = config.effective_task_types(); + let effective = config.task_types; assert_eq!( effective, task_types, "Effective types should match for {description}", diff --git a/crates/sdk-core-c-bridge/src/worker.rs b/crates/sdk-core-c-bridge/src/worker.rs index ac015996d..75757deb6 100644 --- a/crates/sdk-core-c-bridge/src/worker.rs +++ b/crates/sdk-core-c-bridge/src/worker.rs @@ -44,7 +44,6 @@ pub struct WorkerOptions { pub max_cached_workflows: u32, pub tuner: TunerHolder, pub task_types: u8, - pub no_remote_activities: bool, pub sticky_queue_schedule_to_start_timeout_millis: u64, pub max_heartbeat_throttle_interval_millis: u64, pub default_heartbeat_throttle_interval_millis: u64, @@ -1191,7 +1190,6 @@ impl TryFrom<&WorkerOptions> for temporalio_sdk_core::WorkerConfig { temporalio_common::worker::WorkerTaskTypes::from_bits_truncate(opt.task_types) } }) - .no_remote_activities(opt.no_remote_activities) .sticky_queue_schedule_to_start_timeout(Duration::from_millis( opt.sticky_queue_schedule_to_start_timeout_millis, )) diff --git a/crates/sdk-core/src/core_tests/activity_tasks.rs b/crates/sdk-core/src/core_tests/activity_tasks.rs index 56bdd57a0..25fbc73e5 100644 --- a/crates/sdk-core/src/core_tests/activity_tasks.rs +++ b/crates/sdk-core/src/core_tests/activity_tasks.rs @@ -53,7 +53,7 @@ use temporalio_common::{ }, test_utils::start_timer_cmd, }, - worker::PollerBehavior, + worker::{PollerBehavior, WorkerTaskTypes}, }; use tokio::{join, time::sleep}; use tokio_util::sync::CancellationToken; @@ -725,7 +725,7 @@ async fn no_eager_activities_requested_when_worker_options_disable_it( mock.worker_cfg(|wc| { wc.max_cached_workflows = 2; if reason == "no_remote" { - wc.no_remote_activities = true; + wc.task_types = WorkerTaskTypes::WORKFLOWS; } else { wc.max_task_queue_activities_per_second = Some(1.0); } diff --git a/crates/sdk-core/src/core_tests/workers.rs b/crates/sdk-core/src/core_tests/workers.rs index 7171b963d..df16313e7 100644 --- a/crates/sdk-core/src/core_tests/workers.rs +++ b/crates/sdk-core/src/core_tests/workers.rs @@ -29,7 +29,7 @@ use temporalio_common::{ }, test_utils::start_timer_cmd, }, - worker::PollerBehavior, + worker::{PollerBehavior, WorkerTaskTypes}, }; use tokio::sync::{Barrier, watch}; @@ -135,7 +135,7 @@ async fn can_shutdown_local_act_only_worker_when_act_polling() { let mh = MockPollCfg::from_resp_batches("fakeid", t, [1], mock); let mut mock = build_mock_pollers(mh); mock.worker_cfg(|w| { - w.no_remote_activities = true; + w.task_types = WorkerTaskTypes::WORKFLOWS; w.max_cached_workflows = 1; }); let worker = mock_worker(mock); diff --git a/crates/sdk-core/src/core_tests/workflow_tasks.rs b/crates/sdk-core/src/core_tests/workflow_tasks.rs index 90b84e8c5..ba78ac026 100644 --- a/crates/sdk-core/src/core_tests/workflow_tasks.rs +++ b/crates/sdk-core/src/core_tests/workflow_tasks.rs @@ -28,6 +28,7 @@ use std::{ time::Duration, }; use temporalio_client::MESSAGE_TOO_LARGE_KEY; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ Worker as WorkerTrait, errors::PollError, @@ -2671,7 +2672,7 @@ async fn poller_wont_run_ahead_of_task_slots() { .max_cached_workflows(10_usize) .max_outstanding_workflow_tasks(10_usize) .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(10_usize)) - .no_remote_activities(true) + .task_types(WorkerTaskTypes::WORKFLOWS) .build() .unwrap(), mock_client, @@ -2731,7 +2732,7 @@ async fn poller_wont_poll_until_lang_polls() { let worker = Worker::new_test( test_worker_cfg() - .no_remote_activities(true) + .task_types(WorkerTaskTypes::WORKFLOWS) .build() .unwrap(), mock_client, @@ -2876,7 +2877,7 @@ async fn slot_provider_cant_hand_out_more_permits_than_cache_size() { .build(), )) .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(10_usize)) - .no_remote_activities(true) + .task_types(WorkerTaskTypes::WORKFLOWS) .build() .unwrap(), mock_client, @@ -3024,7 +3025,7 @@ async fn both_normal_and_sticky_pollers_poll_concurrently() { .max_outstanding_workflow_tasks(2_usize) .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(2_usize)) .nonsticky_to_sticky_poll_ratio(0.2) - .no_remote_activities(true) + .task_types(WorkerTaskTypes::WORKFLOWS) .build() .unwrap(), Some("stickytq".to_string()), diff --git a/crates/sdk-core/src/replay/mod.rs b/crates/sdk-core/src/replay/mod.rs index cd088f0e7..125d969a7 100644 --- a/crates/sdk-core/src/replay/mod.rs +++ b/crates/sdk-core/src/replay/mod.rs @@ -19,6 +19,7 @@ use std::{ pub use temporalio_common::protos::{ DEFAULT_WORKFLOW_TYPE, HistoryInfo, TestHistoryBuilder, default_wes_attribs, }; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ protos::{ coresdk::workflow_activation::remove_from_cache::EvictionReason, @@ -62,7 +63,7 @@ where pub(crate) fn into_core_worker(mut self) -> Result { self.config.max_cached_workflows = 1; self.config.workflow_task_poller_behavior = PollerBehavior::SimpleMaximum(1); - self.config.no_remote_activities = true; + self.config.task_types = WorkerTaskTypes::WORKFLOWS; self.config.skip_client_worker_set_check = true; let historator = Historator::new(self.history_stream); let post_activate = historator.get_post_activate_hook(); diff --git a/crates/sdk-core/src/test_help/integ_helpers.rs b/crates/sdk-core/src/test_help/integ_helpers.rs index f20f564f5..9d880019a 100644 --- a/crates/sdk-core/src/test_help/integ_helpers.rs +++ b/crates/sdk-core/src/test_help/integ_helpers.rs @@ -36,6 +36,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ Worker as WorkerTrait, errors::PollError, @@ -184,10 +185,15 @@ pub fn build_fake_worker( pub fn mock_worker(mocks: MocksHolder) -> Worker { let sticky_q = sticky_q_name_for_worker("unit-test", mocks.inputs.config.max_cached_workflows); - let act_poller = if mocks.inputs.config.no_remote_activities { - None - } else { + let act_poller = if mocks + .inputs + .config + .task_types + .contains(WorkerTaskTypes::ACTIVITIES) + { mocks.inputs.act_poller + } else { + None }; Worker::new_with_pollers( mocks.inputs.config, diff --git a/crates/sdk-core/src/worker/heartbeat.rs b/crates/sdk-core/src/worker/heartbeat.rs index 400c202c3..6373bf4cb 100644 --- a/crates/sdk-core/src/worker/heartbeat.rs +++ b/crates/sdk-core/src/worker/heartbeat.rs @@ -5,6 +5,7 @@ use crate::{ use parking_lot::RwLock; use std::{collections::HashMap, sync::Arc, time::Duration}; use temporalio_client::SharedNamespaceWorkerTrait; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ protos::temporal::api::worker::v1::WorkerHeartbeat, worker::{PollerBehavior, WorkerConfigBuilder, WorkerVersioningStrategy}, @@ -38,7 +39,7 @@ impl SharedNamespaceWorker { "temporal-sys/worker-commands/{namespace}/{}", client.worker_grouping_key(), )) - .no_remote_activities(true) + .task_types(WorkerTaskTypes::NEXUS) .max_outstanding_nexus_tasks(5_usize) .versioning_strategy(WorkerVersioningStrategy::None { build_id: "1.0".to_owned(), diff --git a/crates/sdk-core/src/worker/mod.rs b/crates/sdk-core/src/worker/mod.rs index bcdc26b31..c40e42f89 100644 --- a/crates/sdk-core/src/worker/mod.rs +++ b/crates/sdk-core/src/worker/mod.rs @@ -451,10 +451,9 @@ impl Worker { slot_context_data.clone(), meter.clone(), ); - let effective_task_types = config.effective_task_types(); let (wft_stream, act_poller, nexus_poller) = match task_pollers { TaskPollers::Real => { - let wft_stream = if effective_task_types.polls_workflows() { + let wft_stream = if config.task_types.polls_workflows() { let stream = make_wft_poller( &config, &sticky_queue_name, @@ -479,7 +478,7 @@ impl Worker { None }; - let act_poll_buffer = if effective_task_types.polls_activities() { + let act_poll_buffer = if config.task_types.polls_activities() { let act_metrics = metrics.with_new_attrs([activity_poller()]); let ap = LongPollBuffer::new_activity_task( client.clone(), @@ -499,7 +498,7 @@ impl Worker { None }; - let nexus_poll_buffer = if effective_task_types.polls_nexus() { + let nexus_poll_buffer = if config.task_types.polls_nexus() { let np_metrics = metrics.with_new_attrs([nexus_poller()]); Some(Box::new(LongPollBuffer::new_nexus_task( client.clone(), @@ -525,6 +524,24 @@ impl Worker { act_poller, nexus_poller, } => { + use temporalio_common::worker::WorkerTaskTypes; + + let wft_stream = config + .task_types + .contains(WorkerTaskTypes::WORKFLOWS) + .then_some(wft_stream) + .flatten(); + let act_poller = config + .task_types + .contains(WorkerTaskTypes::ACTIVITIES) + .then_some(act_poller) + .flatten(); + let nexus_poller = config + .task_types + .contains(WorkerTaskTypes::NEXUS) + .then_some(nexus_poller) + .flatten(); + let ap = act_poller .map(|ap| MockPermittedPollBuffer::new(Arc::new(act_slots.clone()), ap)); let np = nexus_poller @@ -557,7 +574,7 @@ impl Worker { ); let la_permits = la_permit_dealer.get_extant_count_rcv(); - let (local_act_mgr, la_sink, hb_rx) = if effective_task_types.polls_workflows() { + let (local_act_mgr, la_sink, hb_rx) = if config.task_types.polls_workflows() { let (hb_tx, hb_rx) = unbounded_channel(); let local_act_mgr = Arc::new(LocalActivityManager::new( config.namespace.clone(), diff --git a/crates/sdk-core/tests/heavy_tests.rs b/crates/sdk-core/tests/heavy_tests.rs index 561e99340..c8b38abdd 100644 --- a/crates/sdk-core/tests/heavy_tests.rs +++ b/crates/sdk-core/tests/heavy_tests.rs @@ -22,6 +22,7 @@ use std::{ time::{Duration, Instant}, }; use temporalio_client::{GetWorkflowResultOpts, WfClientExt, WorkflowClientTrait, WorkflowOptions}; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ protos::{ coresdk::{AsJsonPayloadExt, workflow_commands::ActivityCancellationType}, @@ -349,7 +350,7 @@ async fn can_paginate_long_history() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .no_remote_activities(true) + .task_types(WorkerTaskTypes::WORKFLOWS) // Do not use sticky queues so we are forced to paginate once history gets long .max_cached_workflows(0_usize); diff --git a/crates/sdk-core/tests/integ_tests/metrics_tests.rs b/crates/sdk-core/tests/integ_tests/metrics_tests.rs index abdfdca7f..26f09c5a7 100644 --- a/crates/sdk-core/tests/integ_tests/metrics_tests.rs +++ b/crates/sdk-core/tests/integ_tests/metrics_tests.rs @@ -17,6 +17,7 @@ use std::{ use temporalio_client::{ REQUEST_LATENCY_HISTOGRAM_NAME, WorkflowClientTrait, WorkflowOptions, WorkflowService, }; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ Worker, errors::PollError, @@ -919,7 +920,9 @@ async fn nexus_metrics() { let rt = CoreRuntime::new_assume_tokio(get_integ_runtime_options(telemopts)).unwrap(); let wf_name = "nexus_metrics"; let mut starter = CoreWfStarter::new_with_runtime(wf_name, rt); - starter.worker_config.no_remote_activities(true); + starter + .worker_config + .task_types(WorkerTaskTypes::WORKFLOWS | WorkerTaskTypes::NEXUS); let task_queue = starter.get_task_queue().to_owned(); let mut worker = starter.worker().await; let core_worker = starter.get_worker().await; @@ -1096,7 +1099,7 @@ async fn evict_on_complete_does_not_count_as_forced_eviction() { let rt = CoreRuntime::new_assume_tokio(get_integ_runtime_options(telemopts)).unwrap(); let wf_name = "evict_on_complete_does_not_count_as_forced_eviction"; let mut starter = CoreWfStarter::new_with_runtime(wf_name, rt); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.register_wf( @@ -1179,7 +1182,7 @@ async fn metrics_available_from_custom_slot_supplier() { let rt = CoreRuntime::new_assume_tokio(get_integ_runtime_options(telemopts)).unwrap(); let mut starter = CoreWfStarter::new_with_runtime("metrics_available_from_custom_slot_supplier", rt); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); starter.worker_config.clear_max_outstanding_opts(); let mut tb = TunerBuilder::default(); tb.workflow_slot_supplier(Arc::new(MetricRecordingSlotSupplier:: { @@ -1348,7 +1351,7 @@ async fn sticky_queue_label_strategy( let mut starter = CoreWfStarter::new_with_runtime(&wf_name, rt); // Enable sticky queues by setting a reasonable cache size starter.worker_config.max_cached_workflows(10_usize); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let task_queue = starter.get_task_queue().to_owned(); let mut worker = starter.worker().await; @@ -1424,7 +1427,7 @@ async fn resource_based_tuner_metrics() { let rt = CoreRuntime::new_assume_tokio(get_integ_runtime_options(telemopts)).unwrap(); let wf_name = "resource_based_tuner_metrics"; let mut starter = CoreWfStarter::new_with_runtime(wf_name, rt); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); starter.worker_config.clear_max_outstanding_opts(); // Create a resource-based tuner with reasonable thresholds diff --git a/crates/sdk-core/tests/integ_tests/update_tests.rs b/crates/sdk-core/tests/integ_tests/update_tests.rs index 7265ff39a..df7ee2eb2 100644 --- a/crates/sdk-core/tests/integ_tests/update_tests.rs +++ b/crates/sdk-core/tests/integ_tests/update_tests.rs @@ -14,6 +14,7 @@ use std::{ use temporalio_client::{ Client, NamespacedClient, RetryClient, WorkflowClientTrait, WorkflowService, }; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ Worker, prost_dur, protos::{ @@ -723,7 +724,7 @@ async fn update_with_local_acts() { async fn update_rejection_sdk() { let wf_name = "update_rejection_sdk"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; let client = starter.get_client().await; worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move { @@ -767,7 +768,7 @@ async fn update_rejection_sdk() { async fn update_fail_sdk() { let wf_name = "update_fail_sdk"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; let client = starter.get_client().await; worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move { @@ -811,7 +812,7 @@ async fn update_fail_sdk() { async fn update_timer_sequence() { let wf_name = "update_timer_sequence"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; let client = starter.get_client().await; worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move { @@ -859,7 +860,7 @@ async fn update_timer_sequence() { async fn task_failure_during_validation() { let wf_name = "task_failure_during_validation"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); starter.workflow_options.task_timeout = Some(Duration::from_secs(1)); let mut worker = starter.worker().await; let client = starter.get_client().await; @@ -920,7 +921,7 @@ async fn task_failure_during_validation() { async fn task_failure_after_update() { let wf_name = "task_failure_after_update"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); starter.workflow_options.task_timeout = Some(Duration::from_secs(1)); let mut worker = starter.worker().await; let client = starter.get_client().await; diff --git a/crates/sdk-core/tests/integ_tests/worker_tests.rs b/crates/sdk-core/tests/integ_tests/worker_tests.rs index 097b4e088..2d4746b80 100644 --- a/crates/sdk-core/tests/integ_tests/worker_tests.rs +++ b/crates/sdk-core/tests/integ_tests/worker_tests.rs @@ -19,6 +19,7 @@ use std::{ time::Duration, }; use temporalio_client::WorkflowOptions; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ Worker, errors::WorkerValidationError, @@ -177,7 +178,7 @@ async fn resource_based_few_pollers_guarantees_non_sticky_poll() { starter .worker_config .clear_max_outstanding_opts() - .no_remote_activities(true) + .task_types(WorkerTaskTypes::WORKFLOWS) // 3 pollers so the minimum slots of 2 can both be handed out to a sticky poller .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(3_usize)); // Set the limits to zero so it's essentially unwilling to hand out slots @@ -210,7 +211,7 @@ async fn resource_based_few_pollers_guarantees_non_sticky_poll() { async fn oversize_grpc_message() { let wf_name = "oversize_grpc_message"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut core = starter.worker().await; static OVERSIZE_GRPC_MESSAGE_RUN: AtomicBool = AtomicBool::new(false); diff --git a/crates/sdk-core/tests/integ_tests/worker_versioning_tests.rs b/crates/sdk-core/tests/integ_tests/worker_versioning_tests.rs index 6154cca6e..49f70bb7a 100644 --- a/crates/sdk-core/tests/integ_tests/worker_versioning_tests.rs +++ b/crates/sdk-core/tests/integ_tests/worker_versioning_tests.rs @@ -4,6 +4,7 @@ use crate::{ }; use std::time::Duration; use temporalio_client::{NamespacedClient, WorkflowOptions, WorkflowService}; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ protos::{ coresdk::{ @@ -44,7 +45,7 @@ async fn sets_deployment_info_on_task_responses(#[values(true, false)] use_defau default_versioning_behavior: VersioningBehavior::AutoUpgrade.into(), }, )) - .no_remote_activities(true); + .task_types(WorkerTaskTypes::WORKFLOWS); let core = starter.get_worker().await; let client = starter.get_client().await; diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests.rs b/crates/sdk-core/tests/integ_tests/workflow_tests.rs index 81bdb1fed..f8d04e30b 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests.rs @@ -33,6 +33,7 @@ use std::{ use temporalio_client::{ WfClientExt, WorkflowClientTrait, WorkflowExecutionResult, WorkflowOptions, }; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ errors::{PollError, WorkflowErrorType}, prost_dur, @@ -77,7 +78,7 @@ use tokio::{join, sync::Notify, time::sleep}; async fn parallel_workflows_same_queue() { let wf_name = "parallel_workflows_same_queue"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut core = starter.worker().await; core.register_wf(wf_name.to_owned(), |ctx: WfContext| async move { @@ -544,7 +545,7 @@ async fn deployment_version_correct_in_wf_info(#[values(true, false)] use_only_b starter .worker_config .versioning_strategy(version_strat) - .no_remote_activities(true); + .task_types(WorkerTaskTypes::WORKFLOWS); let core = starter.get_worker().await; starter.start_wf().await; let client = starter.get_client().await; @@ -767,7 +768,7 @@ async fn nondeterminism_errors_fail_workflow_when_configured_to( let rt = CoreRuntime::new_assume_tokio(get_integ_runtime_options(telemopts)).unwrap(); let wf_name = "nondeterminism_errors_fail_workflow_when_configured_to"; let mut starter = CoreWfStarter::new_with_runtime(wf_name, rt); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let typeset = HashSet::from([WorkflowErrorType::Nondeterminism]); if whole_worker { starter.worker_config.workflow_failure_errors(typeset); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs index 6bf9ff3d1..51b7414bb 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs @@ -5,6 +5,7 @@ use temporalio_common::protos::{ coresdk::{FromJsonPayloadExt, common::NamespacedWorkflowExecution}, temporal::api::enums::v1::{CommandType, EventType}, }; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::{WfContext, WorkflowResult}; use temporalio_sdk_core::test_help::MockPollCfg; @@ -39,7 +40,7 @@ async fn cancel_receiver(ctx: WfContext) -> WorkflowResult { #[tokio::test] async fn sends_cancel_to_other_wf() { let mut starter = CoreWfStarter::new("sends_cancel_to_other_wf"); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.register_wf("sender", cancel_sender); worker.register_wf("receiver", cancel_receiver); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs index 348f9c2df..1c9038b44 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs @@ -6,6 +6,7 @@ use temporalio_common::protos::{ coresdk::workflow_activation::{WorkflowActivationJob, workflow_activation_job}, temporal::api::enums::v1::{CommandType, WorkflowExecutionStatus}, }; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::{WfContext, WfExitValue, WorkflowResult}; use temporalio_sdk_core::test_help::MockPollCfg; @@ -32,7 +33,7 @@ async fn cancelled_wf(ctx: WfContext) -> WorkflowResult<()> { async fn cancel_during_timer() { let wf_name = "cancel_during_timer"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; let client = starter.get_client().await; worker.register_wf(wf_name.to_string(), cancelled_wf); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs index 3a2fdace3..f5c2b878e 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs @@ -3,6 +3,7 @@ use anyhow::anyhow; use assert_matches::assert_matches; use std::time::Duration; use temporalio_client::{WorkflowClientTrait, WorkflowOptions}; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ Worker, protos::{ @@ -85,7 +86,7 @@ async fn happy_parent(ctx: WfContext) -> WorkflowResult<()> { #[tokio::test] async fn child_workflow_happy_path() { let mut starter = CoreWfStarter::new("child-workflows"); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.register_wf(PARENT_WF_TYPE.to_string(), happy_parent); @@ -106,7 +107,7 @@ async fn child_workflow_happy_path() { #[tokio::test] async fn abandoned_child_bug_repro() { let mut starter = CoreWfStarter::new("child-workflow-abandon-bug"); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; let barr: &'static Barrier = Box::leak(Box::new(Barrier::new(2))); @@ -177,7 +178,7 @@ async fn abandoned_child_bug_repro() { #[tokio::test] async fn abandoned_child_resolves_post_cancel() { let mut starter = CoreWfStarter::new("child-workflow-resolves-post-cancel"); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; let barr: &'static Barrier = Box::leak(Box::new(Barrier::new(2))); @@ -244,7 +245,7 @@ async fn abandoned_child_resolves_post_cancel() { async fn cancelled_child_gets_reason() { let wf_name = "cancelled-child-gets-reason"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_string(), move |ctx: WfContext| async move { diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs index daa142330..d87d01bfc 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs @@ -6,6 +6,7 @@ use temporalio_common::protos::{ coresdk::workflow_commands::ContinueAsNewWorkflowExecution, temporal::api::enums::v1::CommandType, }; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::{WfContext, WfExitValue, WorkflowResult}; use temporalio_sdk_core::test_help::MockPollCfg; @@ -26,7 +27,7 @@ async fn continue_as_new_wf(ctx: WfContext) -> WorkflowResult<()> { async fn continue_as_new_happy_path() { let wf_name = "continue_as_new_happy_path"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_string(), continue_as_new_wf); @@ -48,7 +49,7 @@ async fn continue_as_new_multiple_concurrent() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .no_remote_activities(true) + .task_types(WorkerTaskTypes::WORKFLOWS) .max_cached_workflows(5_usize) .max_outstanding_workflow_tasks(5_usize); let mut worker = starter.worker().await; diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/determinism.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/determinism.rs index 6abc1d3a2..6e8b7a5d5 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/determinism.rs @@ -12,6 +12,7 @@ use temporalio_common::protos::{ failure::v1::Failure, }, }; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::{ ActContext, ActivityOptions, ChildWorkflowOptions, LocalActivityOptions, WfContext, WorkflowResult, @@ -52,7 +53,7 @@ pub(crate) async fn timer_wf_nondeterministic(ctx: WfContext) -> WorkflowResult< async fn test_determinism_error_then_recovers() { let wf_name = "test_determinism_error_then_recovers"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), timer_wf_nondeterministic); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/eager.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/eager.rs index 1a3ca56c2..58148ddc9 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/eager.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/eager.rs @@ -1,6 +1,7 @@ use crate::common::{CoreWfStarter, NAMESPACE, get_integ_server_options}; use std::time::Duration; use temporalio_client::WorkflowClientTrait; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::{WfContext, WorkflowResult}; pub(crate) async fn eager_wf(_context: WfContext) -> WorkflowResult<()> { @@ -14,7 +15,7 @@ async fn eager_wf_start() { starter.workflow_options.enable_eager_workflow_start = true; // hang the test if eager task dispatch failed starter.workflow_options.task_timeout = Some(Duration::from_secs(1500)); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), eager_wf); starter.eager_start_with_worker(wf_name, &mut worker).await; @@ -28,7 +29,7 @@ async fn eager_wf_start_different_clients() { starter.workflow_options.enable_eager_workflow_start = true; // hang the test if wf task needs retry starter.workflow_options.task_timeout = Some(Duration::from_secs(1500)); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), eager_wf); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs index e1ee71c2e..24727e28b 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs @@ -9,6 +9,7 @@ use temporalio_common::protos::{ enums::v1::EventType, }, }; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::{WfContext, WorkflowResult}; use temporalio_sdk_core::test_help::MockPollCfg; use uuid::Uuid; @@ -29,7 +30,7 @@ async fn sends_modify_wf_props() { let wf_name = "can_upsert_memo"; let wf_id = Uuid::new_v4(); let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.register_wf(wf_name, memo_upserter); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/nexus.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/nexus.rs index a9b6a0b58..06566e636 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/nexus.rs @@ -12,6 +12,7 @@ use std::{ time::Duration, }; use temporalio_client::{WfClientExt, WorkflowClientTrait, WorkflowOptions}; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ errors::PollError, protos::{ @@ -57,7 +58,9 @@ async fn nexus_basic( ) { let wf_name = "nexus_basic"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter + .worker_config + .task_types(WorkerTaskTypes::WORKFLOWS | WorkerTaskTypes::NEXUS); let mut worker = starter.worker().await; let core_worker = starter.get_worker().await; @@ -202,7 +205,9 @@ async fn nexus_async( ) { let wf_name = "nexus_async"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter + .worker_config + .task_types(WorkerTaskTypes::WORKFLOWS | WorkerTaskTypes::NEXUS); let mut worker = starter.worker().await; let core_worker = starter.get_worker().await; @@ -429,7 +434,9 @@ async fn nexus_async( async fn nexus_cancel_before_start() { let wf_name = "nexus_cancel_before_start"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter + .worker_config + .task_types(WorkerTaskTypes::WORKFLOWS | WorkerTaskTypes::NEXUS); let mut worker = starter.worker().await; let endpoint = mk_nexus_endpoint(&mut starter).await; @@ -471,7 +478,9 @@ async fn nexus_cancel_before_start() { async fn nexus_must_complete_task_to_shutdown(#[values(true, false)] use_grace_period: bool) { let wf_name = "nexus_must_complete_task_to_shutdown"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter + .worker_config + .task_types(WorkerTaskTypes::WORKFLOWS | WorkerTaskTypes::NEXUS); if use_grace_period { starter .worker_config @@ -571,7 +580,9 @@ async fn nexus_cancellation_types( ) { let wf_name = "nexus_cancellation_types"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter + .worker_config + .task_types(WorkerTaskTypes::WORKFLOWS | WorkerTaskTypes::NEXUS); let mut worker = starter.worker().await; let core_worker = starter.get_worker().await; diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/patches.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/patches.rs index 96371f014..2b1ad842f 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/patches.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/patches.rs @@ -29,6 +29,7 @@ use temporalio_common::protos::{ }, }, }; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::{ActivityOptions, WfContext, WorkflowResult}; use temporalio_sdk_core::test_help::{CoreInternalFlags, MockPollCfg, ResponseType}; use tokio::{join, sync::Notify}; @@ -55,7 +56,7 @@ pub(crate) async fn changes_wf(ctx: WfContext) -> WorkflowResult<()> { async fn writes_change_markers() { let wf_name = "writes_change_markers"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), changes_wf); @@ -89,7 +90,7 @@ pub(crate) async fn no_change_then_change_wf(ctx: WfContext) -> WorkflowResult<( async fn can_add_change_markers() { let wf_name = "can_add_change_markers"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), no_change_then_change_wf); @@ -113,7 +114,7 @@ pub(crate) async fn replay_with_change_marker_wf(ctx: WfContext) -> WorkflowResu async fn replaying_with_patch_marker() { let wf_name = "replaying_with_patch_marker"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), replay_with_change_marker_wf); @@ -132,7 +133,7 @@ async fn patched_on_second_workflow_task_is_deterministic() { starter .worker_config .max_cached_workflows(0_usize) - .no_remote_activities(true); + .task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; // Include a task failure as well to make sure that works static FAIL_ONCE: AtomicBool = AtomicBool::new(true); @@ -155,7 +156,7 @@ async fn patched_on_second_workflow_task_is_deterministic() { async fn can_remove_deprecated_patch_near_other_patch() { let wf_name = "can_add_change_markers"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; let did_die = Arc::new(AtomicBool::new(false)); worker.register_wf(wf_name.to_owned(), move |ctx: WfContext| { @@ -186,7 +187,7 @@ async fn can_remove_deprecated_patch_near_other_patch() { async fn deprecated_patch_removal() { let wf_name = "deprecated_patch_removal"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; let client = starter.get_client().await; let wf_id = starter.get_task_queue().to_string(); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/resets.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/resets.rs index b6ad44a74..819fa762e 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/resets.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/resets.rs @@ -17,6 +17,7 @@ use temporalio_common::protos::{ common::v1::WorkflowExecution, workflowservice::v1::ResetWorkflowExecutionRequest, }, }; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::{LocalActivityOptions, WfContext}; use tokio::sync::Notify; use tonic::IntoRequest; @@ -27,7 +28,7 @@ const POST_RESET_SIG: &str = "post-reset"; async fn reset_workflow() { let wf_name = "reset_me_wf"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.fetch_results = false; let notify = Arc::new(Notify::new()); @@ -113,7 +114,7 @@ async fn reset_workflow() { async fn reset_randomseed() { let wf_name = "reset_randomseed"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.fetch_results = false; let notify = Arc::new(Notify::new()); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/signals.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/signals.rs index df49fa4ce..83efef01d 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/signals.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/signals.rs @@ -16,6 +16,7 @@ use temporalio_common::protos::{ enums::v1::{CommandType, EventType}, }, }; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::{ CancellableFuture, ChildWorkflowOptions, Signal, SignalWorkflowOptions, WfContext, WorkflowResult, @@ -46,7 +47,7 @@ async fn signal_sender(ctx: WfContext) -> WorkflowResult<()> { async fn sends_signal_to_missing_wf() { let wf_name = "sends_signal_to_missing_wf"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), signal_sender); @@ -85,7 +86,7 @@ async fn signal_with_create_wf_receiver(ctx: WfContext) -> WorkflowResult<()> { #[tokio::test] async fn sends_signal_to_other_wf() { let mut starter = CoreWfStarter::new("sends_signal_to_other_wf"); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.register_wf("sender", signal_sender); worker.register_wf("receiver", signal_receiver); @@ -114,7 +115,7 @@ async fn sends_signal_to_other_wf() { #[tokio::test] async fn sends_signal_with_create_wf() { let mut starter = CoreWfStarter::new("sends_signal_with_create_wf"); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.register_wf("receiver_signal", signal_with_create_wf_receiver); @@ -160,7 +161,7 @@ async fn signals_child(ctx: WfContext) -> WorkflowResult<()> { #[tokio::test] async fn sends_signal_to_child() { let mut starter = CoreWfStarter::new("sends_signal_to_child"); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.register_wf("child_signaler", signals_child); worker.register_wf("child_receiver", signal_receiver); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs index ee65b07d3..310af469b 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs @@ -4,7 +4,7 @@ use std::{ time::Duration, }; use temporalio_client::WorkflowOptions; -use temporalio_common::worker::PollerBehavior; +use temporalio_common::worker::{PollerBehavior, WorkerTaskTypes}; use temporalio_sdk::{WfContext, WorkflowResult}; use tokio::sync::Barrier; @@ -14,7 +14,7 @@ async fn timer_workflow_not_sticky() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .no_remote_activities(true) + .task_types(WorkerTaskTypes::WORKFLOWS) .max_cached_workflows(0_usize); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), timer_wf); @@ -42,7 +42,7 @@ async fn timer_workflow_timeout_on_sticky() { // on a not-sticky queue let wf_name = "timer_workflow_timeout_on_sticky"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); starter.workflow_options.task_timeout = Some(Duration::from_secs(2)); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), timer_timeout_wf); @@ -59,7 +59,7 @@ async fn cache_miss_ok() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .no_remote_activities(true) + .task_types(WorkerTaskTypes::WORKFLOWS) .max_outstanding_workflow_tasks(2_usize) .max_cached_workflows(0_usize) .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(1_usize)); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/timers.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/timers.rs index 1a991a23a..482a46c8c 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/timers.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/timers.rs @@ -1,6 +1,7 @@ use std::time::Duration; use crate::common::{CoreWfStarter, build_fake_sdk, init_core_and_create_wf}; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ prost_dur, protos::{ @@ -28,7 +29,7 @@ pub(crate) async fn timer_wf(command_sink: WfContext) -> WorkflowResult<()> { async fn timer_workflow_workflow_driver() { let wf_name = "timer_wf_new"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), timer_wf); @@ -40,7 +41,7 @@ async fn timer_workflow_workflow_driver() { async fn timer_workflow_manual() { let mut starter = init_core_and_create_wf("timer_workflow").await; let core = starter.get_worker().await; - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let task = core.poll_workflow_activation().await.unwrap(); core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds( task.run_id, @@ -64,7 +65,7 @@ async fn timer_workflow_manual() { async fn timer_cancel_workflow() { let mut starter = init_core_and_create_wf("timer_cancel_workflow").await; let core = starter.get_worker().await; - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let task = core.poll_workflow_activation().await.unwrap(); core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds( task.run_id, @@ -123,7 +124,7 @@ async fn parallel_timer_wf(command_sink: WfContext) -> WorkflowResult<()> { async fn parallel_timers() { let wf_name = "parallel_timers"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), parallel_timer_wf); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs index e839895e2..3bea9ef2c 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs @@ -14,6 +14,7 @@ use temporalio_common::protos::{ enums::v1::EventType, }, }; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::{WfContext, WfExitValue, WorkflowResult}; use temporalio_sdk_core::test_help::MockPollCfg; use uuid::Uuid; @@ -44,7 +45,7 @@ async fn sends_upsert() { let wf_name = "sends_upsert_search_attrs"; let wf_id = Uuid::new_v4(); let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.register_wf(wf_name, search_attr_updater); diff --git a/crates/sdk-core/tests/manual_tests.rs b/crates/sdk-core/tests/manual_tests.rs index 86fb86a53..a44159584 100644 --- a/crates/sdk-core/tests/manual_tests.rs +++ b/crates/sdk-core/tests/manual_tests.rs @@ -20,6 +20,7 @@ use std::{ time::{Duration, Instant}, }; use temporalio_client::{GetWorkflowResultOpts, WfClientExt, WorkflowClientTrait, WorkflowOptions}; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ protos::coresdk::AsJsonPayloadExt, telemetry::PrometheusExporterOptionsBuilder, worker::PollerBehavior, @@ -214,7 +215,7 @@ async fn poller_load_sustained() { maximum: 200, initial: 5, }) - .no_remote_activities(true); + .task_types(WorkerTaskTypes::WORKFLOWS); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move { let sigchan = ctx.make_signal_channel(SIGNAME).map(Ok); diff --git a/crates/sdk-core/tests/shared_tests/mod.rs b/crates/sdk-core/tests/shared_tests/mod.rs index 16485d964..e55149d28 100644 --- a/crates/sdk-core/tests/shared_tests/mod.rs +++ b/crates/sdk-core/tests/shared_tests/mod.rs @@ -6,6 +6,7 @@ use temporalio_common::protos::temporal::api::{ enums::v1::{EventType, WorkflowTaskFailedCause::GrpcMessageTooLarge}, history::v1::history_event::Attributes::WorkflowTaskFailedEventAttributes, }; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::WfContext; pub(crate) mod priority; @@ -15,7 +16,7 @@ pub(crate) async fn grpc_message_too_large() { let mut starter = CoreWfStarter::new_cloud_or_local(wf_name, "") .await .unwrap(); - starter.worker_config.no_remote_activities(true); + starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); let mut core = starter.worker().await; static OVERSIZE_GRPC_MESSAGE_RUN: AtomicBool = AtomicBool::new(false); From 1c94eeb12a08cd5fcab1d7a55ff66103c0745058 Mon Sep 17 00:00:00 2001 From: Andrew Yuan Date: Thu, 13 Nov 2025 17:00:34 -0800 Subject: [PATCH 3/8] Remove use of bitflags --- crates/common/Cargo.toml | 1 - crates/common/src/errors.rs | 7 ++ crates/common/src/lib.rs | 2 +- crates/common/src/worker.rs | 97 +++++++++++----- crates/common/tests/worker_task_types_test.rs | 84 ++++++++++---- crates/sdk-core-c-bridge/src/worker.rs | 10 +- .../sdk-core/src/core_tests/activity_tasks.rs | 4 +- crates/sdk-core/src/core_tests/workers.rs | 54 ++++++++- .../sdk-core/src/core_tests/workflow_tasks.rs | 11 +- crates/sdk-core/src/replay/mod.rs | 4 +- .../sdk-core/src/test_help/integ_helpers.rs | 4 +- crates/sdk-core/src/worker/heartbeat.rs | 4 +- crates/sdk-core/src/worker/mod.rs | 109 +++++++++--------- crates/sdk-core/tests/heavy_tests.rs | 5 +- .../tests/integ_tests/metrics_tests.rs | 26 +++-- .../tests/integ_tests/update_tests.rs | 23 +++- .../tests/integ_tests/worker_tests.rs | 59 +++++++++- .../integ_tests/worker_versioning_tests.rs | 5 +- .../tests/integ_tests/workflow_tests.rs | 12 +- .../workflow_tests/cancel_external.rs | 7 +- .../integ_tests/workflow_tests/cancel_wf.rs | 7 +- .../workflow_tests/child_workflows.rs | 19 ++- .../workflow_tests/continue_as_new.rs | 9 +- .../integ_tests/workflow_tests/determinism.rs | 7 +- .../tests/integ_tests/workflow_tests/eager.rs | 11 +- .../workflow_tests/modify_wf_properties.rs | 7 +- .../tests/integ_tests/workflow_tests/nexus.rs | 38 +++--- .../integ_tests/workflow_tests/patches.rs | 24 ++-- .../integ_tests/workflow_tests/resets.rs | 11 +- .../integ_tests/workflow_tests/signals.rs | 20 +++- .../integ_tests/workflow_tests/stickyness.rs | 11 +- .../integ_tests/workflow_tests/timers.rs | 19 ++- .../workflow_tests/upsert_search_attrs.rs | 7 +- crates/sdk-core/tests/manual_tests.rs | 5 +- crates/sdk-core/tests/shared_tests/mod.rs | 7 +- 35 files changed, 503 insertions(+), 227 deletions(-) diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index d3f55dd34..5d013587e 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -23,7 +23,6 @@ test-utilities = ["history_builders"] anyhow = "1.0" async-trait = "0.1" base64 = "0.22" -bitflags = "2.6" dirs = { version = "6.0", optional = true } derive_builder = { workspace = true } derive_more = { workspace = true } diff --git a/crates/common/src/errors.rs b/crates/common/src/errors.rs index d2fb69739..b505c9345 100644 --- a/crates/common/src/errors.rs +++ b/crates/common/src/errors.rs @@ -40,10 +40,14 @@ pub enum CompleteWfError { /// The run associated with the completion run_id: String, }, + /// Workflows have not been enabled on this worker. + #[error("Workflows are not enabled on this worker")] + WorkflowNotEnabled, } /// Errors thrown by [crate::Worker::complete_activity_task] #[derive(thiserror::Error, Debug)] +#[allow(clippy::large_enum_variant)] pub enum CompleteActivityError { /// Lang SDK sent us a malformed activity completion. This likely means a bug in the lang sdk. #[error("Lang SDK sent us a malformed activity completion ({reason}): {completion:?}")] @@ -53,6 +57,9 @@ pub enum CompleteActivityError { /// The completion, which may not be included to avoid unnecessary copies. completion: Option, }, + /// Activities have not been enabled on this worker. + #[error("Activities are not enabled on this worker")] + ActivityNotEnabled, } /// Errors thrown by [crate::Worker::complete_nexus_task] diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index bd68ba7f0..7e2a04cbb 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -130,7 +130,7 @@ pub trait Worker: Send + Sync { /// workflows & activities until they are done. At that point, the lang SDK can end the process, /// or drop the [Worker] instance via [Worker::finalize_shutdown], which will close the /// connection and free resources. If you have set [WorkerConfig::task_types] to exclude - /// [WorkerTaskTypes::ACTIVITIES], you may skip calling [Worker::poll_activity_task]. + /// [worker_task_types::activities()], you may skip calling [Worker::poll_activity_task]. /// /// Lang implementations should use [Worker::initiate_shutdown] followed by /// [Worker::finalize_shutdown]. diff --git a/crates/common/src/worker.rs b/crates/common/src/worker.rs index d0a682a28..db36afa38 100644 --- a/crates/common/src/worker.rs +++ b/crates/common/src/worker.rs @@ -8,7 +8,6 @@ use crate::{ }, telemetry::metrics::TemporalMeter, }; -use bitflags::bitflags; use std::{ any::Any, collections::{HashMap, HashSet}, @@ -17,36 +16,70 @@ use std::{ time::Duration, }; -bitflags! { - /// Specifies which task types a worker will poll for. - /// - /// This allows fine-grained control over what kinds of work this worker handles. - /// Workers can be configured to handle any combination of workflows, activities, and nexus operations. - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] - pub struct WorkerTaskTypes: u8 { - /// Poll for workflow tasks - const WORKFLOWS = 0b001; - /// Poll for activity tasks (remote activities) - const ACTIVITIES = 0b010; - /// Poll for nexus tasks - const NEXUS = 0b100; - } +/// Represents a single task type that a worker can poll for +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum WorkerTaskType { + /// Poll for workflow tasks + Workflows, + /// Poll for activity tasks (remote activities) + Activities, + /// Poll for nexus tasks + Nexus, } -impl WorkerTaskTypes { - /// Returns true if this worker should poll for workflow tasks - pub fn polls_workflows(&self) -> bool { - self.contains(Self::WORKFLOWS) +/// Specifies which task types a worker will poll for. +/// +/// This is a set of [WorkerTaskType] values. Workers can be configured to handle +/// any combination of workflows, activities, and nexus operations. +pub type WorkerTaskTypes = HashSet; + +/// Helper functions for working with WorkerTaskTypes +pub mod worker_task_types { + use super::{WorkerTaskType, WorkerTaskTypes}; + + /// Create a set with all task types enabled + pub fn all() -> WorkerTaskTypes { + [ + WorkerTaskType::Workflows, + WorkerTaskType::Activities, + WorkerTaskType::Nexus, + ] + .into_iter() + .collect() + } + + /// Create a set with only workflow tasks enabled + pub fn workflows() -> WorkerTaskTypes { + [WorkerTaskType::Workflows].into_iter().collect() + } + + /// Create a set with only activity tasks enabled + pub fn activities() -> WorkerTaskTypes { + [WorkerTaskType::Activities].into_iter().collect() } - /// Returns true if this worker should poll for activity tasks - pub fn polls_activities(&self) -> bool { - self.contains(Self::ACTIVITIES) + /// Create a set with only nexus tasks enabled + pub fn nexus() -> WorkerTaskTypes { + [WorkerTaskType::Nexus].into_iter().collect() } - /// Returns true if this worker should poll for nexus tasks - pub fn polls_nexus(&self) -> bool { - self.contains(Self::NEXUS) + /// Create a set from a bitmask (for C FFI compatibility) + /// 0x01 = Workflows, 0x02 = Activities, 0x04 = Nexus + pub fn from_bits(bits: u8) -> WorkerTaskTypes { + if bits == 0 { + return all(); + } + let mut set = WorkerTaskTypes::new(); + if bits & 0x01 != 0 { + set.insert(WorkerTaskType::Workflows); + } + if bits & 0x02 != 0 { + set.insert(WorkerTaskType::Activities); + } + if bits & 0x04 != 0 { + set.insert(WorkerTaskType::Nexus); + } + set } } @@ -101,12 +134,10 @@ pub struct WorkerConfig { /// Specifies which task types this worker will poll for. /// /// By default, workers poll for all task types (workflows, activities, and nexus). - /// You can restrict this to any combination. For example, a workflow-only worker would use - /// `WorkerTaskTypes::WORKFLOWS`, while a worker handling activities and nexus but not workflows - /// would use `WorkerTaskTypes::ACTIVITIES | WorkerTaskTypes::NEXUS`. + /// You can restrict this to any combination. /// /// Note: At least one task type must be specified or the worker will fail validation. - #[builder(default = "WorkerTaskTypes::all()")] + #[builder(default = "worker_task_types::all()")] pub task_types: WorkerTaskTypes, /// How long a workflow task is allowed to sit on the sticky queue before it is timed out /// and moved to the non-sticky queue where it may be picked up by any worker. @@ -258,7 +289,11 @@ impl WorkerConfigBuilder { } fn validate(&self) -> Result<(), String> { - let task_types = self.task_types.unwrap_or_else(WorkerTaskTypes::all); + let task_types = self + .task_types + .as_ref() + .cloned() + .unwrap_or_else(worker_task_types::all); if task_types.is_empty() { return Err("At least one task type must be enabled in `task_types`".to_owned()); } @@ -295,7 +330,7 @@ impl WorkerConfigBuilder { } // Validate workflow cache is consistent with task_types - if !task_types.contains(WorkerTaskTypes::WORKFLOWS) + if !task_types.contains(&WorkerTaskType::Workflows) && let Some(cache) = self.max_cached_workflows.as_ref() && *cache > 0 { diff --git a/crates/common/tests/worker_task_types_test.rs b/crates/common/tests/worker_task_types_test.rs index a66471e70..050d92ac2 100644 --- a/crates/common/tests/worker_task_types_test.rs +++ b/crates/common/tests/worker_task_types_test.rs @@ -1,4 +1,8 @@ -use temporalio_common::worker::{WorkerConfigBuilder, WorkerTaskTypes, WorkerVersioningStrategy}; +use std::collections::HashSet; +use temporalio_common::worker::{ + WorkerConfigBuilder, WorkerTaskType, WorkerTaskTypes, WorkerVersioningStrategy, + worker_task_types, +}; fn default_versioning_strategy() -> WorkerVersioningStrategy { WorkerVersioningStrategy::None { @@ -15,16 +19,19 @@ fn test_default_configuration_polls_all_types() { .build() .expect("Failed to build default config"); - let effective = config.task_types; + let effective = &config.task_types; assert!( - effective.polls_workflows(), + effective.contains(&WorkerTaskType::Workflows), "Should poll workflows by default" ); assert!( - effective.polls_activities(), + effective.contains(&WorkerTaskType::Activities), "Should poll activities by default" ); - assert!(effective.polls_nexus(), "Should poll nexus by default"); + assert!( + effective.contains(&WorkerTaskType::Nexus), + "Should poll nexus by default" + ); } #[test] @@ -33,32 +40,53 @@ fn test_workflow_only_worker() { .namespace("default") .task_queue("test-queue") .versioning_strategy(default_versioning_strategy()) - .task_types(WorkerTaskTypes::WORKFLOWS) + .task_types(HashSet::from([WorkerTaskType::Workflows])) .max_cached_workflows(0usize) .build() .expect("Failed to build workflow-only config"); - let effective = config.task_types; - assert!(effective.polls_workflows(), "Should poll workflows"); - assert!(!effective.polls_activities(), "Should NOT poll activities"); - assert!(!effective.polls_nexus(), "Should NOT poll nexus"); + let effective = &config.task_types; + assert!( + effective.contains(&WorkerTaskType::Workflows), + "Should poll workflows" + ); + assert!( + !effective.contains(&WorkerTaskType::Activities), + "Should NOT poll activities" + ); + assert!( + !effective.contains(&WorkerTaskType::Nexus), + "Should NOT poll nexus" + ); } #[test] fn test_activity_and_nexus_worker() { + let types: WorkerTaskTypes = [WorkerTaskType::Activities, WorkerTaskType::Nexus] + .into_iter() + .collect(); let config = WorkerConfigBuilder::default() .namespace("default") .task_queue("test-queue") .versioning_strategy(default_versioning_strategy()) - .task_types(WorkerTaskTypes::ACTIVITIES | WorkerTaskTypes::NEXUS) + .task_types(types) .max_cached_workflows(0usize) .build() .expect("Failed to build activity+nexus config"); - let effective = config.task_types; - assert!(!effective.polls_workflows(), "Should NOT poll workflows"); - assert!(effective.polls_activities(), "Should poll activities"); - assert!(effective.polls_nexus(), "Should poll nexus"); + let effective = &config.task_types; + assert!( + !effective.contains(&WorkerTaskType::Workflows), + "Should NOT poll workflows" + ); + assert!( + effective.contains(&WorkerTaskType::Activities), + "Should poll activities" + ); + assert!( + effective.contains(&WorkerTaskType::Nexus), + "Should poll nexus" + ); } #[test] @@ -67,7 +95,7 @@ fn test_empty_task_types_fails_validation() { .namespace("default") .task_queue("test-queue") .versioning_strategy(default_versioning_strategy()) - .task_types(WorkerTaskTypes::empty()) + .task_types(WorkerTaskTypes::new()) .build(); assert!(result.is_err(), "Empty task_types should fail validation"); @@ -84,7 +112,7 @@ fn test_workflow_cache_without_workflows_fails() { .namespace("default") .task_queue("test-queue") .versioning_strategy(default_versioning_strategy()) - .task_types(WorkerTaskTypes::ACTIVITIES) + .task_types(worker_task_types::activities()) .max_cached_workflows(10usize) .build(); @@ -102,22 +130,28 @@ fn test_workflow_cache_without_workflows_fails() { #[test] fn test_all_combinations() { let combinations = [ - (WorkerTaskTypes::WORKFLOWS, "workflows only"), - (WorkerTaskTypes::ACTIVITIES, "activities only"), - (WorkerTaskTypes::NEXUS, "nexus only"), + (worker_task_types::workflows(), "workflows only"), + (worker_task_types::activities(), "activities only"), + (worker_task_types::nexus(), "nexus only"), ( - WorkerTaskTypes::WORKFLOWS | WorkerTaskTypes::ACTIVITIES, + [WorkerTaskType::Workflows, WorkerTaskType::Activities] + .into_iter() + .collect(), "workflows + activities", ), ( - WorkerTaskTypes::WORKFLOWS | WorkerTaskTypes::NEXUS, + [WorkerTaskType::Workflows, WorkerTaskType::Nexus] + .into_iter() + .collect(), "workflows + nexus", ), ( - WorkerTaskTypes::ACTIVITIES | WorkerTaskTypes::NEXUS, + [WorkerTaskType::Activities, WorkerTaskType::Nexus] + .into_iter() + .collect(), "activities + nexus", ), - (WorkerTaskTypes::all(), "all types"), + (worker_task_types::all(), "all types"), ]; for (task_types, description) in combinations { @@ -125,7 +159,7 @@ fn test_all_combinations() { .namespace("default") .task_queue("test-queue") .versioning_strategy(default_versioning_strategy()) - .task_types(task_types) + .task_types(task_types.clone()) .build() .unwrap_or_else(|e| panic!("Failed to build config for {description}: {e:?}")); diff --git a/crates/sdk-core-c-bridge/src/worker.rs b/crates/sdk-core-c-bridge/src/worker.rs index 75757deb6..8d4ce37f5 100644 --- a/crates/sdk-core-c-bridge/src/worker.rs +++ b/crates/sdk-core-c-bridge/src/worker.rs @@ -1183,13 +1183,9 @@ impl TryFrom<&WorkerOptions> for temporalio_sdk_core::WorkerConfig { .client_identity_override(opt.identity_override.to_option_string()) .max_cached_workflows(opt.max_cached_workflows as usize) .tuner(Arc::new(converted_tuner)) - .task_types({ - if opt.task_types == 0 { - temporalio_common::worker::WorkerTaskTypes::all() - } else { - temporalio_common::worker::WorkerTaskTypes::from_bits_truncate(opt.task_types) - } - }) + .task_types(temporalio_common::worker::worker_task_types::from_bits( + opt.task_types, + )) .sticky_queue_schedule_to_start_timeout(Duration::from_millis( opt.sticky_queue_schedule_to_start_timeout_millis, )) diff --git a/crates/sdk-core/src/core_tests/activity_tasks.rs b/crates/sdk-core/src/core_tests/activity_tasks.rs index 25fbc73e5..4b1ed1ef8 100644 --- a/crates/sdk-core/src/core_tests/activity_tasks.rs +++ b/crates/sdk-core/src/core_tests/activity_tasks.rs @@ -53,7 +53,7 @@ use temporalio_common::{ }, test_utils::start_timer_cmd, }, - worker::{PollerBehavior, WorkerTaskTypes}, + worker::{PollerBehavior, worker_task_types}, }; use tokio::{join, time::sleep}; use tokio_util::sync::CancellationToken; @@ -725,7 +725,7 @@ async fn no_eager_activities_requested_when_worker_options_disable_it( mock.worker_cfg(|wc| { wc.max_cached_workflows = 2; if reason == "no_remote" { - wc.task_types = WorkerTaskTypes::WORKFLOWS; + wc.task_types = worker_task_types::workflows(); } else { wc.max_task_queue_activities_per_second = Some(1.0); } diff --git a/crates/sdk-core/src/core_tests/workers.rs b/crates/sdk-core/src/core_tests/workers.rs index df16313e7..c9a186cfe 100644 --- a/crates/sdk-core/src/core_tests/workers.rs +++ b/crates/sdk-core/src/core_tests/workers.rs @@ -14,6 +14,7 @@ use crate::{ }; use futures_util::{stream, stream::StreamExt}; use std::{cell::RefCell, time::Duration}; +use temporalio_common::worker::WorkerTaskType; use temporalio_common::{ Worker, protos::{ @@ -29,9 +30,10 @@ use temporalio_common::{ }, test_utils::start_timer_cmd, }, - worker::{PollerBehavior, WorkerTaskTypes}, + worker::{PollerBehavior, worker_task_types}, }; use tokio::sync::{Barrier, watch}; +use tokio::time::timeout; #[tokio::test] async fn after_shutdown_of_worker_get_shutdown_err() { @@ -135,7 +137,7 @@ async fn can_shutdown_local_act_only_worker_when_act_polling() { let mh = MockPollCfg::from_resp_batches("fakeid", t, [1], mock); let mut mock = build_mock_pollers(mh); mock.worker_cfg(|w| { - w.task_types = WorkerTaskTypes::WORKFLOWS; + w.task_types = worker_task_types::workflows(); w.max_cached_workflows = 1; }); let worker = mock_worker(mock); @@ -368,3 +370,51 @@ async fn worker_shutdown_api(#[case] use_cache: bool, #[case] api_success: bool) ); }); } + +#[tokio::test] +async fn test_worker_type_shutdown_all_combinations() { + let combinations = [ + (worker_task_types::workflows(), "workflows only"), + (worker_task_types::activities(), "activities only"), + (worker_task_types::nexus(), "nexus only"), + ( + [WorkerTaskType::Workflows, WorkerTaskType::Activities] + .into_iter() + .collect(), + "workflows + activities", + ), + ( + [WorkerTaskType::Workflows, WorkerTaskType::Nexus] + .into_iter() + .collect(), + "workflows + nexus", + ), + ( + [WorkerTaskType::Activities, WorkerTaskType::Nexus] + .into_iter() + .collect(), + "activities + nexus", + ), + (worker_task_types::all(), "all types"), + ]; + + for (task_types, description) in combinations { + let mut cfg = test_worker_cfg(); + cfg.task_types(task_types); + + let mock_inputs = MockWorkerInputs { + config: cfg.build().unwrap(), + ..Default::default() + }; + let worker = mock_worker(MocksHolder::from_mock_worker( + mock_worker_client(), + mock_inputs, + )); + + let shutdown_result = timeout(Duration::from_secs(1), worker.shutdown()).await; + assert!( + shutdown_result.is_ok(), + "worker shutdown should not hang for {description}" + ); + } +} diff --git a/crates/sdk-core/src/core_tests/workflow_tasks.rs b/crates/sdk-core/src/core_tests/workflow_tasks.rs index ba78ac026..247d72ff6 100644 --- a/crates/sdk-core/src/core_tests/workflow_tasks.rs +++ b/crates/sdk-core/src/core_tests/workflow_tasks.rs @@ -28,7 +28,6 @@ use std::{ time::Duration, }; use temporalio_client::MESSAGE_TOO_LARGE_KEY; -use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ Worker as WorkerTrait, errors::PollError, @@ -67,7 +66,7 @@ use temporalio_common::{ }, worker::{ PollerBehavior, SlotMarkUsedContext, SlotReleaseContext, SlotReservationContext, - SlotSupplier, SlotSupplierPermit, WorkflowSlotKind, + SlotSupplier, SlotSupplierPermit, WorkerTaskType, WorkflowSlotKind, }, }; use tokio::{ @@ -2672,7 +2671,7 @@ async fn poller_wont_run_ahead_of_task_slots() { .max_cached_workflows(10_usize) .max_outstanding_workflow_tasks(10_usize) .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(10_usize)) - .task_types(WorkerTaskTypes::WORKFLOWS) + .task_types(HashSet::from([WorkerTaskType::Workflows])) .build() .unwrap(), mock_client, @@ -2732,7 +2731,7 @@ async fn poller_wont_poll_until_lang_polls() { let worker = Worker::new_test( test_worker_cfg() - .task_types(WorkerTaskTypes::WORKFLOWS) + .task_types(HashSet::from([WorkerTaskType::Workflows])) .build() .unwrap(), mock_client, @@ -2877,7 +2876,7 @@ async fn slot_provider_cant_hand_out_more_permits_than_cache_size() { .build(), )) .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(10_usize)) - .task_types(WorkerTaskTypes::WORKFLOWS) + .task_types(HashSet::from([WorkerTaskType::Workflows])) .build() .unwrap(), mock_client, @@ -3025,7 +3024,7 @@ async fn both_normal_and_sticky_pollers_poll_concurrently() { .max_outstanding_workflow_tasks(2_usize) .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(2_usize)) .nonsticky_to_sticky_poll_ratio(0.2) - .task_types(WorkerTaskTypes::WORKFLOWS) + .task_types(HashSet::from([WorkerTaskType::Workflows])) .build() .unwrap(), Some("stickytq".to_string()), diff --git a/crates/sdk-core/src/replay/mod.rs b/crates/sdk-core/src/replay/mod.rs index 125d969a7..d1de88145 100644 --- a/crates/sdk-core/src/replay/mod.rs +++ b/crates/sdk-core/src/replay/mod.rs @@ -19,7 +19,7 @@ use std::{ pub use temporalio_common::protos::{ DEFAULT_WORKFLOW_TYPE, HistoryInfo, TestHistoryBuilder, default_wes_attribs, }; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::worker_task_types; use temporalio_common::{ protos::{ coresdk::workflow_activation::remove_from_cache::EvictionReason, @@ -63,7 +63,7 @@ where pub(crate) fn into_core_worker(mut self) -> Result { self.config.max_cached_workflows = 1; self.config.workflow_task_poller_behavior = PollerBehavior::SimpleMaximum(1); - self.config.task_types = WorkerTaskTypes::WORKFLOWS; + self.config.task_types = worker_task_types::workflows(); self.config.skip_client_worker_set_check = true; let historator = Historator::new(self.history_stream); let post_activate = historator.get_post_activate_hook(); diff --git a/crates/sdk-core/src/test_help/integ_helpers.rs b/crates/sdk-core/src/test_help/integ_helpers.rs index 9d880019a..08f94e110 100644 --- a/crates/sdk-core/src/test_help/integ_helpers.rs +++ b/crates/sdk-core/src/test_help/integ_helpers.rs @@ -36,7 +36,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_common::{ Worker as WorkerTrait, errors::PollError, @@ -189,7 +189,7 @@ pub fn mock_worker(mocks: MocksHolder) -> Worker { .inputs .config .task_types - .contains(WorkerTaskTypes::ACTIVITIES) + .contains(&WorkerTaskType::Activities) { mocks.inputs.act_poller } else { diff --git a/crates/sdk-core/src/worker/heartbeat.rs b/crates/sdk-core/src/worker/heartbeat.rs index 6373bf4cb..02ac91907 100644 --- a/crates/sdk-core/src/worker/heartbeat.rs +++ b/crates/sdk-core/src/worker/heartbeat.rs @@ -5,7 +5,7 @@ use crate::{ use parking_lot::RwLock; use std::{collections::HashMap, sync::Arc, time::Duration}; use temporalio_client::SharedNamespaceWorkerTrait; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::worker_task_types; use temporalio_common::{ protos::temporal::api::worker::v1::WorkerHeartbeat, worker::{PollerBehavior, WorkerConfigBuilder, WorkerVersioningStrategy}, @@ -39,7 +39,7 @@ impl SharedNamespaceWorker { "temporal-sys/worker-commands/{namespace}/{}", client.worker_grouping_key(), )) - .task_types(WorkerTaskTypes::NEXUS) + .task_types(worker_task_types::nexus()) .max_outstanding_nexus_tasks(5_usize) .versioning_strategy(WorkerVersioningStrategy::None { build_id: "1.0".to_owned(), diff --git a/crates/sdk-core/src/worker/mod.rs b/crates/sdk-core/src/worker/mod.rs index c40e42f89..c33f7d727 100644 --- a/crates/sdk-core/src/worker/mod.rs +++ b/crates/sdk-core/src/worker/mod.rs @@ -6,6 +6,7 @@ mod slot_provider; pub(crate) mod tuner; mod workflow; +pub(crate) use temporalio_common::worker::WorkerTaskType; pub use temporalio_common::worker::{WorkerConfig, WorkerConfigBuilder}; pub use tuner::{ FixedSizeSlotSupplier, ResourceBasedSlotsOptions, ResourceBasedSlotsOptionsBuilder, @@ -453,7 +454,7 @@ impl Worker { ); let (wft_stream, act_poller, nexus_poller) = match task_pollers { TaskPollers::Real => { - let wft_stream = if config.task_types.polls_workflows() { + let wft_stream = if config.task_types.contains(&WorkerTaskType::Workflows) { let stream = make_wft_poller( &config, &sticky_queue_name, @@ -478,7 +479,7 @@ impl Worker { None }; - let act_poll_buffer = if config.task_types.polls_activities() { + let act_poll_buffer = if config.task_types.contains(&WorkerTaskType::Activities) { let act_metrics = metrics.with_new_attrs([activity_poller()]); let ap = LongPollBuffer::new_activity_task( client.clone(), @@ -498,7 +499,7 @@ impl Worker { None }; - let nexus_poll_buffer = if config.task_types.polls_nexus() { + let nexus_poll_buffer = if config.task_types.contains(&WorkerTaskType::Nexus) { let np_metrics = metrics.with_new_attrs([nexus_poller()]); Some(Box::new(LongPollBuffer::new_nexus_task( client.clone(), @@ -524,21 +525,19 @@ impl Worker { act_poller, nexus_poller, } => { - use temporalio_common::worker::WorkerTaskTypes; - let wft_stream = config .task_types - .contains(WorkerTaskTypes::WORKFLOWS) + .contains(&WorkerTaskType::Workflows) .then_some(wft_stream) .flatten(); let act_poller = config .task_types - .contains(WorkerTaskTypes::ACTIVITIES) + .contains(&WorkerTaskType::Activities) .then_some(act_poller) .flatten(); let nexus_poller = config .task_types - .contains(WorkerTaskTypes::NEXUS) + .contains(&WorkerTaskType::Nexus) .then_some(nexus_poller) .flatten(); @@ -574,19 +573,20 @@ impl Worker { ); let la_permits = la_permit_dealer.get_extant_count_rcv(); - let (local_act_mgr, la_sink, hb_rx) = if config.task_types.polls_workflows() { - let (hb_tx, hb_rx) = unbounded_channel(); - let local_act_mgr = Arc::new(LocalActivityManager::new( - config.namespace.clone(), - la_permit_dealer.clone(), - hb_tx, - metrics.clone(), - )); - let la_sink = LAReqSink::new(local_act_mgr.clone()); - (Some(local_act_mgr), Some(la_sink), Some(hb_rx)) - } else { - (None, None, None) - }; + let (local_act_mgr, la_sink, hb_rx) = + if config.task_types.contains(&WorkerTaskType::Workflows) { + let (hb_tx, hb_rx) = unbounded_channel(); + let local_act_mgr = Arc::new(LocalActivityManager::new( + config.namespace.clone(), + la_permit_dealer.clone(), + hb_tx, + metrics.clone(), + )); + let la_sink = LAReqSink::new(local_act_mgr.clone()); + (Some(local_act_mgr), Some(la_sink), Some(hb_rx)) + } else { + (None, None, None) + }; let at_task_mgr = act_poller.map(|ap| { WorkerActivityTasks::new( @@ -740,29 +740,29 @@ impl Worker { /// completed async fn shutdown(&self) { self.initiate_shutdown(); - if let Some(workflows) = &self.workflows { - if let Some(name) = workflows.get_sticky_queue_name() { - let heartbeat = self - .client_worker_registrator - .heartbeat_manager - .as_ref() - .map(|hm| hm.heartbeat_callback.clone()()); - - // This is a best effort call and we can still shutdown the worker if it fails - match self.client.shutdown_worker(name, heartbeat).await { - Err(err) - if !matches!( - err.code(), - tonic::Code::Unimplemented | tonic::Code::Unavailable - ) => - { - warn!( - "shutdown_worker rpc errored during worker shutdown: {:?}", - err - ); - } - _ => {} + if let Some(workflows) = &self.workflows + && let Some(name) = workflows.get_sticky_queue_name() + { + let heartbeat = self + .client_worker_registrator + .heartbeat_manager + .as_ref() + .map(|hm| hm.heartbeat_callback.clone()()); + + // This is a best effort call and we can still shutdown the worker if it fails + match self.client.shutdown_worker(name, heartbeat).await { + Err(err) + if !matches!( + err.code(), + tonic::Code::Unimplemented | tonic::Code::Unavailable + ) => + { + warn!( + "shutdown_worker rpc errored during worker shutdown: {:?}", + err + ); } + _ => {} } } // We need to wait for all local activities to finish so no more workflow task heartbeats @@ -896,8 +896,9 @@ impl Worker { } }, None => { - future::pending::<()>().await; - unreachable!() + self.local_activities_complete + .store(true, Ordering::Relaxed); + Ok(None) } } }; @@ -943,13 +944,10 @@ impl Worker { if let Some(atm) = &self.at_task_mgr { atm.complete(task_token, status, &*self.client).await; + Ok(()) } else { - error!( - "Tried to complete activity {} on a worker that does not have an activity manager", - task_token - ); + Err(CompleteActivityError::ActivityNotEnabled) } - Ok(()) } #[instrument(skip(self), fields(run_id, workflow_id, task_queue=%self.config.task_queue))] @@ -971,7 +969,7 @@ impl Worker { } r } - None => Err(PollError::ShutDown), // Workflow polling is disabled + None => Err(PollError::ShutDown), } } @@ -995,10 +993,7 @@ impl Worker { .await?; Ok(()) } - None => Err(CompleteWfError::MalformedWorkflowCompletion { - reason: "Workflow polling is disabled for this worker".to_string(), - run_id: completion.run_id, - }), + None => Err(CompleteWfError::WorkflowNotEnabled), } } @@ -1026,6 +1021,8 @@ impl Worker { ) { if let Some(workflows) = &self.workflows { workflows.request_eviction(run_id, message, reason); + } else { + dbg_panic!("trying to request wf eviction when workflows not enabled for this worker"); } } @@ -1066,6 +1063,8 @@ impl Worker { fn notify_local_result(&self, run_id: &str, res: LocalResolution) { if let Some(workflows) = &self.workflows { workflows.notify_of_local_result(run_id, res); + } else { + dbg_panic!("trying to notify local result when workflows not enabled for this worker"); } } diff --git a/crates/sdk-core/tests/heavy_tests.rs b/crates/sdk-core/tests/heavy_tests.rs index c8b38abdd..f7a6df737 100644 --- a/crates/sdk-core/tests/heavy_tests.rs +++ b/crates/sdk-core/tests/heavy_tests.rs @@ -16,13 +16,14 @@ use futures_util::{ stream::FuturesUnordered, }; use rand::Rng; +use std::collections::HashSet; use std::{ mem, sync::Arc, time::{Duration, Instant}, }; use temporalio_client::{GetWorkflowResultOpts, WfClientExt, WorkflowClientTrait, WorkflowOptions}; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_common::{ protos::{ coresdk::{AsJsonPayloadExt, workflow_commands::ActivityCancellationType}, @@ -350,7 +351,7 @@ async fn can_paginate_long_history() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(WorkerTaskTypes::WORKFLOWS) + .task_types(HashSet::from([WorkerTaskType::Workflows])) // Do not use sticky queues so we are forced to paginate once history gets long .max_cached_workflows(0_usize); diff --git a/crates/sdk-core/tests/integ_tests/metrics_tests.rs b/crates/sdk-core/tests/integ_tests/metrics_tests.rs index 26f09c5a7..499bda515 100644 --- a/crates/sdk-core/tests/integ_tests/metrics_tests.rs +++ b/crates/sdk-core/tests/integ_tests/metrics_tests.rs @@ -7,6 +7,7 @@ use crate::{ }; use anyhow::anyhow; use assert_matches::assert_matches; +use std::collections::HashSet; use std::{ collections::HashMap, env, @@ -17,7 +18,7 @@ use std::{ use temporalio_client::{ REQUEST_LATENCY_HISTOGRAM_NAME, WorkflowClientTrait, WorkflowOptions, WorkflowService, }; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_common::{ Worker, errors::PollError, @@ -920,9 +921,10 @@ async fn nexus_metrics() { let rt = CoreRuntime::new_assume_tokio(get_integ_runtime_options(telemopts)).unwrap(); let wf_name = "nexus_metrics"; let mut starter = CoreWfStarter::new_with_runtime(wf_name, rt); - starter - .worker_config - .task_types(WorkerTaskTypes::WORKFLOWS | WorkerTaskTypes::NEXUS); + starter.worker_config.task_types(HashSet::from([ + WorkerTaskType::Workflows, + WorkerTaskType::Nexus, + ])); let task_queue = starter.get_task_queue().to_owned(); let mut worker = starter.worker().await; let core_worker = starter.get_worker().await; @@ -1099,7 +1101,9 @@ async fn evict_on_complete_does_not_count_as_forced_eviction() { let rt = CoreRuntime::new_assume_tokio(get_integ_runtime_options(telemopts)).unwrap(); let wf_name = "evict_on_complete_does_not_count_as_forced_eviction"; let mut starter = CoreWfStarter::new_with_runtime(wf_name, rt); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.register_wf( @@ -1182,7 +1186,9 @@ async fn metrics_available_from_custom_slot_supplier() { let rt = CoreRuntime::new_assume_tokio(get_integ_runtime_options(telemopts)).unwrap(); let mut starter = CoreWfStarter::new_with_runtime("metrics_available_from_custom_slot_supplier", rt); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); starter.worker_config.clear_max_outstanding_opts(); let mut tb = TunerBuilder::default(); tb.workflow_slot_supplier(Arc::new(MetricRecordingSlotSupplier:: { @@ -1351,7 +1357,9 @@ async fn sticky_queue_label_strategy( let mut starter = CoreWfStarter::new_with_runtime(&wf_name, rt); // Enable sticky queues by setting a reasonable cache size starter.worker_config.max_cached_workflows(10_usize); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let task_queue = starter.get_task_queue().to_owned(); let mut worker = starter.worker().await; @@ -1427,7 +1435,9 @@ async fn resource_based_tuner_metrics() { let rt = CoreRuntime::new_assume_tokio(get_integ_runtime_options(telemopts)).unwrap(); let wf_name = "resource_based_tuner_metrics"; let mut starter = CoreWfStarter::new_with_runtime(wf_name, rt); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); starter.worker_config.clear_max_outstanding_opts(); // Create a resource-based tuner with reasonable thresholds diff --git a/crates/sdk-core/tests/integ_tests/update_tests.rs b/crates/sdk-core/tests/integ_tests/update_tests.rs index df7ee2eb2..60461e708 100644 --- a/crates/sdk-core/tests/integ_tests/update_tests.rs +++ b/crates/sdk-core/tests/integ_tests/update_tests.rs @@ -4,6 +4,7 @@ use crate::common::{ use anyhow::anyhow; use assert_matches::assert_matches; use futures_util::{StreamExt, future, future::join_all}; +use std::collections::HashSet; use std::{ sync::{ Arc, LazyLock, @@ -14,7 +15,7 @@ use std::{ use temporalio_client::{ Client, NamespacedClient, RetryClient, WorkflowClientTrait, WorkflowService, }; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_common::{ Worker, prost_dur, protos::{ @@ -724,7 +725,9 @@ async fn update_with_local_acts() { async fn update_rejection_sdk() { let wf_name = "update_rejection_sdk"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; let client = starter.get_client().await; worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move { @@ -768,7 +771,9 @@ async fn update_rejection_sdk() { async fn update_fail_sdk() { let wf_name = "update_fail_sdk"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; let client = starter.get_client().await; worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move { @@ -812,7 +817,9 @@ async fn update_fail_sdk() { async fn update_timer_sequence() { let wf_name = "update_timer_sequence"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; let client = starter.get_client().await; worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move { @@ -860,7 +867,9 @@ async fn update_timer_sequence() { async fn task_failure_during_validation() { let wf_name = "task_failure_during_validation"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); starter.workflow_options.task_timeout = Some(Duration::from_secs(1)); let mut worker = starter.worker().await; let client = starter.get_client().await; @@ -921,7 +930,9 @@ async fn task_failure_during_validation() { async fn task_failure_after_update() { let wf_name = "task_failure_after_update"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); starter.workflow_options.task_timeout = Some(Duration::from_secs(1)); let mut worker = starter.worker().await; let client = starter.get_client().await; diff --git a/crates/sdk-core/tests/integ_tests/worker_tests.rs b/crates/sdk-core/tests/integ_tests/worker_tests.rs index 2d4746b80..aa964fbb2 100644 --- a/crates/sdk-core/tests/integ_tests/worker_tests.rs +++ b/crates/sdk-core/tests/integ_tests/worker_tests.rs @@ -7,6 +7,7 @@ use crate::{ }; use assert_matches::assert_matches; use futures_util::FutureExt; +use std::collections::HashSet; use std::{ cell::Cell, sync::{ @@ -19,7 +20,7 @@ use std::{ time::Duration, }; use temporalio_client::WorkflowOptions; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::{WorkerTaskType, worker_task_types}; use temporalio_common::{ Worker, errors::WorkerValidationError, @@ -68,6 +69,7 @@ use temporalio_sdk_core::{ }, }; use tokio::sync::{Barrier, Notify, Semaphore}; +use tokio::time::timeout; use tokio_util::sync::CancellationToken; use uuid::Uuid; @@ -178,7 +180,7 @@ async fn resource_based_few_pollers_guarantees_non_sticky_poll() { starter .worker_config .clear_max_outstanding_opts() - .task_types(WorkerTaskTypes::WORKFLOWS) + .task_types(HashSet::from([WorkerTaskType::Workflows])) // 3 pollers so the minimum slots of 2 can both be handed out to a sticky poller .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(3_usize)); // Set the limits to zero so it's essentially unwilling to hand out slots @@ -211,7 +213,9 @@ async fn resource_based_few_pollers_guarantees_non_sticky_poll() { async fn oversize_grpc_message() { let wf_name = "oversize_grpc_message"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut core = starter.worker().await; static OVERSIZE_GRPC_MESSAGE_RUN: AtomicBool = AtomicBool::new(false); @@ -893,3 +897,52 @@ async fn shutdown_worker_not_retried() { drain_pollers_and_shutdown(&worker).await; assert_eq!(shutdown_call_count.load(Ordering::Relaxed), 1); } + +#[tokio::test] +async fn worker_type_shutdown_all_combinations() { + let combinations = [ + (worker_task_types::workflows(), "workflows only"), + (worker_task_types::activities(), "activities only"), + (worker_task_types::nexus(), "nexus only"), + ( + [WorkerTaskType::Workflows, WorkerTaskType::Activities] + .into_iter() + .collect(), + "workflows + activities", + ), + ( + [WorkerTaskType::Workflows, WorkerTaskType::Nexus] + .into_iter() + .collect(), + "workflows + nexus", + ), + ( + [WorkerTaskType::Activities, WorkerTaskType::Nexus] + .into_iter() + .collect(), + "activities + nexus", + ), + (worker_task_types::all(), "all types"), + ]; + + for (task_types, description) in combinations { + let wf_type = format!("worker_type_shutdown_{}", description.replace(" ", "_")); + let mut starter = CoreWfStarter::new(&wf_type); + if !task_types.contains(&WorkerTaskType::Workflows) { + starter.worker_config.max_cached_workflows(0usize); + } + starter.worker_config.task_types(task_types); + + let worker = starter.get_worker().await; + + let shutdown_result = timeout(Duration::from_secs(1), async { + drain_pollers_and_shutdown(&worker).await; + }) + .await; + + assert!( + shutdown_result.is_ok(), + "Worker shutdown should not hang for {description}" + ); + } +} diff --git a/crates/sdk-core/tests/integ_tests/worker_versioning_tests.rs b/crates/sdk-core/tests/integ_tests/worker_versioning_tests.rs index 49f70bb7a..53a26d889 100644 --- a/crates/sdk-core/tests/integ_tests/worker_versioning_tests.rs +++ b/crates/sdk-core/tests/integ_tests/worker_versioning_tests.rs @@ -2,9 +2,10 @@ use crate::{ common::{CoreWfStarter, eventually}, integ_tests::activity_functions::echo, }; +use std::collections::HashSet; use std::time::Duration; use temporalio_client::{NamespacedClient, WorkflowOptions, WorkflowService}; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_common::{ protos::{ coresdk::{ @@ -45,7 +46,7 @@ async fn sets_deployment_info_on_task_responses(#[values(true, false)] use_defau default_versioning_behavior: VersioningBehavior::AutoUpgrade.into(), }, )) - .task_types(WorkerTaskTypes::WORKFLOWS); + .task_types(HashSet::from([WorkerTaskType::Workflows])); let core = starter.get_worker().await; let client = starter.get_client().await; diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests.rs b/crates/sdk-core/tests/integ_tests/workflow_tests.rs index f8d04e30b..0c66887e1 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests.rs @@ -33,7 +33,7 @@ use std::{ use temporalio_client::{ WfClientExt, WorkflowClientTrait, WorkflowExecutionResult, WorkflowOptions, }; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_common::{ errors::{PollError, WorkflowErrorType}, prost_dur, @@ -78,7 +78,9 @@ use tokio::{join, sync::Notify, time::sleep}; async fn parallel_workflows_same_queue() { let wf_name = "parallel_workflows_same_queue"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut core = starter.worker().await; core.register_wf(wf_name.to_owned(), |ctx: WfContext| async move { @@ -545,7 +547,7 @@ async fn deployment_version_correct_in_wf_info(#[values(true, false)] use_only_b starter .worker_config .versioning_strategy(version_strat) - .task_types(WorkerTaskTypes::WORKFLOWS); + .task_types(HashSet::from([WorkerTaskType::Workflows])); let core = starter.get_worker().await; starter.start_wf().await; let client = starter.get_client().await; @@ -768,7 +770,9 @@ async fn nondeterminism_errors_fail_workflow_when_configured_to( let rt = CoreRuntime::new_assume_tokio(get_integ_runtime_options(telemopts)).unwrap(); let wf_name = "nondeterminism_errors_fail_workflow_when_configured_to"; let mut starter = CoreWfStarter::new_with_runtime(wf_name, rt); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let typeset = HashSet::from([WorkflowErrorType::Nondeterminism]); if whole_worker { starter.worker_config.workflow_failure_errors(typeset); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs index 51b7414bb..4ffc235d3 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs @@ -1,11 +1,12 @@ use crate::common::{CoreWfStarter, build_fake_sdk}; +use std::collections::HashSet; use temporalio_client::{GetWorkflowResultOpts, WfClientExt, WorkflowOptions}; use temporalio_common::protos::{ DEFAULT_WORKFLOW_TYPE, TestHistoryBuilder, coresdk::{FromJsonPayloadExt, common::NamespacedWorkflowExecution}, temporal::api::enums::v1::{CommandType, EventType}, }; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_sdk::{WfContext, WorkflowResult}; use temporalio_sdk_core::test_help::MockPollCfg; @@ -40,7 +41,9 @@ async fn cancel_receiver(ctx: WfContext) -> WorkflowResult { #[tokio::test] async fn sends_cancel_to_other_wf() { let mut starter = CoreWfStarter::new("sends_cancel_to_other_wf"); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.register_wf("sender", cancel_sender); worker.register_wf("receiver", cancel_receiver); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs index 1c9038b44..63a4ff230 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs @@ -1,4 +1,5 @@ use crate::common::{ActivationAssertionsInterceptor, CoreWfStarter, build_fake_sdk}; +use std::collections::HashSet; use std::time::Duration; use temporalio_client::WorkflowClientTrait; use temporalio_common::protos::{ @@ -6,7 +7,7 @@ use temporalio_common::protos::{ coresdk::workflow_activation::{WorkflowActivationJob, workflow_activation_job}, temporal::api::enums::v1::{CommandType, WorkflowExecutionStatus}, }; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_sdk::{WfContext, WfExitValue, WorkflowResult}; use temporalio_sdk_core::test_help::MockPollCfg; @@ -33,7 +34,9 @@ async fn cancelled_wf(ctx: WfContext) -> WorkflowResult<()> { async fn cancel_during_timer() { let wf_name = "cancel_during_timer"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; let client = starter.get_client().await; worker.register_wf(wf_name.to_string(), cancelled_wf); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs index f5c2b878e..4f7b6f8c0 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs @@ -1,9 +1,10 @@ use crate::common::{CoreWfStarter, build_fake_sdk, mock_sdk, mock_sdk_cfg}; use anyhow::anyhow; use assert_matches::assert_matches; +use std::collections::HashSet; use std::time::Duration; use temporalio_client::{WorkflowClientTrait, WorkflowOptions}; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_common::{ Worker, protos::{ @@ -86,7 +87,9 @@ async fn happy_parent(ctx: WfContext) -> WorkflowResult<()> { #[tokio::test] async fn child_workflow_happy_path() { let mut starter = CoreWfStarter::new("child-workflows"); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.register_wf(PARENT_WF_TYPE.to_string(), happy_parent); @@ -107,7 +110,9 @@ async fn child_workflow_happy_path() { #[tokio::test] async fn abandoned_child_bug_repro() { let mut starter = CoreWfStarter::new("child-workflow-abandon-bug"); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; let barr: &'static Barrier = Box::leak(Box::new(Barrier::new(2))); @@ -178,7 +183,9 @@ async fn abandoned_child_bug_repro() { #[tokio::test] async fn abandoned_child_resolves_post_cancel() { let mut starter = CoreWfStarter::new("child-workflow-resolves-post-cancel"); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; let barr: &'static Barrier = Box::leak(Box::new(Barrier::new(2))); @@ -245,7 +252,9 @@ async fn abandoned_child_resolves_post_cancel() { async fn cancelled_child_gets_reason() { let wf_name = "cancelled-child-gets-reason"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_string(), move |ctx: WfContext| async move { diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs index d87d01bfc..761128ec9 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs @@ -1,4 +1,5 @@ use crate::common::{CoreWfStarter, build_fake_sdk}; +use std::collections::HashSet; use std::time::Duration; use temporalio_client::WorkflowOptions; use temporalio_common::protos::{ @@ -6,7 +7,7 @@ use temporalio_common::protos::{ coresdk::workflow_commands::ContinueAsNewWorkflowExecution, temporal::api::enums::v1::CommandType, }; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_sdk::{WfContext, WfExitValue, WorkflowResult}; use temporalio_sdk_core::test_help::MockPollCfg; @@ -27,7 +28,9 @@ async fn continue_as_new_wf(ctx: WfContext) -> WorkflowResult<()> { async fn continue_as_new_happy_path() { let wf_name = "continue_as_new_happy_path"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_string(), continue_as_new_wf); @@ -49,7 +52,7 @@ async fn continue_as_new_multiple_concurrent() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(WorkerTaskTypes::WORKFLOWS) + .task_types(HashSet::from([WorkerTaskType::Workflows])) .max_cached_workflows(5_usize) .max_outstanding_workflow_tasks(5_usize); let mut worker = starter.worker().await; diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/determinism.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/determinism.rs index 6e8b7a5d5..69fd1e069 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/determinism.rs @@ -1,4 +1,5 @@ use crate::common::{CoreWfStarter, WorkflowHandleExt, mock_sdk, mock_sdk_cfg}; +use std::collections::HashSet; use std::{ sync::atomic::{AtomicBool, AtomicUsize, Ordering}, time::Duration, @@ -12,7 +13,7 @@ use temporalio_common::protos::{ failure::v1::Failure, }, }; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_sdk::{ ActContext, ActivityOptions, ChildWorkflowOptions, LocalActivityOptions, WfContext, WorkflowResult, @@ -53,7 +54,9 @@ pub(crate) async fn timer_wf_nondeterministic(ctx: WfContext) -> WorkflowResult< async fn test_determinism_error_then_recovers() { let wf_name = "test_determinism_error_then_recovers"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), timer_wf_nondeterministic); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/eager.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/eager.rs index 58148ddc9..cc6956144 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/eager.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/eager.rs @@ -1,7 +1,8 @@ use crate::common::{CoreWfStarter, NAMESPACE, get_integ_server_options}; +use std::collections::HashSet; use std::time::Duration; use temporalio_client::WorkflowClientTrait; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_sdk::{WfContext, WorkflowResult}; pub(crate) async fn eager_wf(_context: WfContext) -> WorkflowResult<()> { @@ -15,7 +16,9 @@ async fn eager_wf_start() { starter.workflow_options.enable_eager_workflow_start = true; // hang the test if eager task dispatch failed starter.workflow_options.task_timeout = Some(Duration::from_secs(1500)); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), eager_wf); starter.eager_start_with_worker(wf_name, &mut worker).await; @@ -29,7 +32,9 @@ async fn eager_wf_start_different_clients() { starter.workflow_options.enable_eager_workflow_start = true; // hang the test if wf task needs retry starter.workflow_options.task_timeout = Some(Duration::from_secs(1500)); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), eager_wf); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs index 24727e28b..8acf77d32 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs @@ -1,4 +1,5 @@ use crate::common::{CoreWfStarter, build_fake_sdk}; +use std::collections::HashSet; use temporalio_client::WorkflowClientTrait; use temporalio_common::protos::{ DEFAULT_WORKFLOW_TYPE, TestHistoryBuilder, @@ -9,7 +10,7 @@ use temporalio_common::protos::{ enums::v1::EventType, }, }; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_sdk::{WfContext, WorkflowResult}; use temporalio_sdk_core::test_help::MockPollCfg; use uuid::Uuid; @@ -30,7 +31,9 @@ async fn sends_modify_wf_props() { let wf_name = "can_upsert_memo"; let wf_id = Uuid::new_v4(); let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.register_wf(wf_name, memo_upserter); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/nexus.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/nexus.rs index 06566e636..ebac6509f 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/nexus.rs @@ -4,6 +4,7 @@ use crate::{ }; use anyhow::bail; use assert_matches::assert_matches; +use std::collections::HashSet; use std::{ sync::{ Arc, @@ -12,7 +13,7 @@ use std::{ time::Duration, }; use temporalio_client::{WfClientExt, WorkflowClientTrait, WorkflowOptions}; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_common::{ errors::PollError, protos::{ @@ -58,9 +59,10 @@ async fn nexus_basic( ) { let wf_name = "nexus_basic"; let mut starter = CoreWfStarter::new(wf_name); - starter - .worker_config - .task_types(WorkerTaskTypes::WORKFLOWS | WorkerTaskTypes::NEXUS); + starter.worker_config.task_types(HashSet::from([ + WorkerTaskType::Workflows, + WorkerTaskType::Nexus, + ])); let mut worker = starter.worker().await; let core_worker = starter.get_worker().await; @@ -205,9 +207,10 @@ async fn nexus_async( ) { let wf_name = "nexus_async"; let mut starter = CoreWfStarter::new(wf_name); - starter - .worker_config - .task_types(WorkerTaskTypes::WORKFLOWS | WorkerTaskTypes::NEXUS); + starter.worker_config.task_types(HashSet::from([ + WorkerTaskType::Workflows, + WorkerTaskType::Nexus, + ])); let mut worker = starter.worker().await; let core_worker = starter.get_worker().await; @@ -434,9 +437,10 @@ async fn nexus_async( async fn nexus_cancel_before_start() { let wf_name = "nexus_cancel_before_start"; let mut starter = CoreWfStarter::new(wf_name); - starter - .worker_config - .task_types(WorkerTaskTypes::WORKFLOWS | WorkerTaskTypes::NEXUS); + starter.worker_config.task_types(HashSet::from([ + WorkerTaskType::Workflows, + WorkerTaskType::Nexus, + ])); let mut worker = starter.worker().await; let endpoint = mk_nexus_endpoint(&mut starter).await; @@ -478,9 +482,10 @@ async fn nexus_cancel_before_start() { async fn nexus_must_complete_task_to_shutdown(#[values(true, false)] use_grace_period: bool) { let wf_name = "nexus_must_complete_task_to_shutdown"; let mut starter = CoreWfStarter::new(wf_name); - starter - .worker_config - .task_types(WorkerTaskTypes::WORKFLOWS | WorkerTaskTypes::NEXUS); + starter.worker_config.task_types(HashSet::from([ + WorkerTaskType::Workflows, + WorkerTaskType::Nexus, + ])); if use_grace_period { starter .worker_config @@ -580,9 +585,10 @@ async fn nexus_cancellation_types( ) { let wf_name = "nexus_cancellation_types"; let mut starter = CoreWfStarter::new(wf_name); - starter - .worker_config - .task_types(WorkerTaskTypes::WORKFLOWS | WorkerTaskTypes::NEXUS); + starter.worker_config.task_types(HashSet::from([ + WorkerTaskType::Workflows, + WorkerTaskType::Nexus, + ])); let mut worker = starter.worker().await; let core_worker = starter.get_worker().await; diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/patches.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/patches.rs index 2b1ad842f..e4a0c9310 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/patches.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/patches.rs @@ -29,7 +29,7 @@ use temporalio_common::protos::{ }, }, }; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_sdk::{ActivityOptions, WfContext, WorkflowResult}; use temporalio_sdk_core::test_help::{CoreInternalFlags, MockPollCfg, ResponseType}; use tokio::{join, sync::Notify}; @@ -56,7 +56,9 @@ pub(crate) async fn changes_wf(ctx: WfContext) -> WorkflowResult<()> { async fn writes_change_markers() { let wf_name = "writes_change_markers"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), changes_wf); @@ -90,7 +92,9 @@ pub(crate) async fn no_change_then_change_wf(ctx: WfContext) -> WorkflowResult<( async fn can_add_change_markers() { let wf_name = "can_add_change_markers"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), no_change_then_change_wf); @@ -114,7 +118,9 @@ pub(crate) async fn replay_with_change_marker_wf(ctx: WfContext) -> WorkflowResu async fn replaying_with_patch_marker() { let wf_name = "replaying_with_patch_marker"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), replay_with_change_marker_wf); @@ -133,7 +139,7 @@ async fn patched_on_second_workflow_task_is_deterministic() { starter .worker_config .max_cached_workflows(0_usize) - .task_types(WorkerTaskTypes::WORKFLOWS); + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; // Include a task failure as well to make sure that works static FAIL_ONCE: AtomicBool = AtomicBool::new(true); @@ -156,7 +162,9 @@ async fn patched_on_second_workflow_task_is_deterministic() { async fn can_remove_deprecated_patch_near_other_patch() { let wf_name = "can_add_change_markers"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; let did_die = Arc::new(AtomicBool::new(false)); worker.register_wf(wf_name.to_owned(), move |ctx: WfContext| { @@ -187,7 +195,9 @@ async fn can_remove_deprecated_patch_near_other_patch() { async fn deprecated_patch_removal() { let wf_name = "deprecated_patch_removal"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; let client = starter.get_client().await; let wf_id = starter.get_task_queue().to_string(); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/resets.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/resets.rs index 819fa762e..7b28f468a 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/resets.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/resets.rs @@ -3,6 +3,7 @@ use crate::{ integ_tests::activity_functions::echo, }; use futures_util::StreamExt; +use std::collections::HashSet; use std::{ sync::{ Arc, @@ -17,7 +18,7 @@ use temporalio_common::protos::{ common::v1::WorkflowExecution, workflowservice::v1::ResetWorkflowExecutionRequest, }, }; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_sdk::{LocalActivityOptions, WfContext}; use tokio::sync::Notify; use tonic::IntoRequest; @@ -28,7 +29,9 @@ const POST_RESET_SIG: &str = "post-reset"; async fn reset_workflow() { let wf_name = "reset_me_wf"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.fetch_results = false; let notify = Arc::new(Notify::new()); @@ -114,7 +117,9 @@ async fn reset_workflow() { async fn reset_randomseed() { let wf_name = "reset_randomseed"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.fetch_results = false; let notify = Arc::new(Notify::new()); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/signals.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/signals.rs index 83efef01d..659c27a7d 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/signals.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/signals.rs @@ -1,6 +1,6 @@ use crate::common::{ActivationAssertionsInterceptor, CoreWfStarter, build_fake_sdk}; use futures_util::StreamExt; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use temporalio_client::{SignalWithStartOptions, WorkflowClientTrait, WorkflowOptions}; use temporalio_common::protos::{ DEFAULT_WORKFLOW_TYPE, TestHistoryBuilder, @@ -16,7 +16,7 @@ use temporalio_common::protos::{ enums::v1::{CommandType, EventType}, }, }; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_sdk::{ CancellableFuture, ChildWorkflowOptions, Signal, SignalWorkflowOptions, WfContext, WorkflowResult, @@ -47,7 +47,9 @@ async fn signal_sender(ctx: WfContext) -> WorkflowResult<()> { async fn sends_signal_to_missing_wf() { let wf_name = "sends_signal_to_missing_wf"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), signal_sender); @@ -86,7 +88,9 @@ async fn signal_with_create_wf_receiver(ctx: WfContext) -> WorkflowResult<()> { #[tokio::test] async fn sends_signal_to_other_wf() { let mut starter = CoreWfStarter::new("sends_signal_to_other_wf"); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.register_wf("sender", signal_sender); worker.register_wf("receiver", signal_receiver); @@ -115,7 +119,9 @@ async fn sends_signal_to_other_wf() { #[tokio::test] async fn sends_signal_with_create_wf() { let mut starter = CoreWfStarter::new("sends_signal_with_create_wf"); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.register_wf("receiver_signal", signal_with_create_wf_receiver); @@ -161,7 +167,9 @@ async fn signals_child(ctx: WfContext) -> WorkflowResult<()> { #[tokio::test] async fn sends_signal_to_child() { let mut starter = CoreWfStarter::new("sends_signal_to_child"); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.register_wf("child_signaler", signals_child); worker.register_wf("child_receiver", signal_receiver); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs index 310af469b..d7d5feaab 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs @@ -1,10 +1,11 @@ use crate::{common::CoreWfStarter, integ_tests::workflow_tests::timers::timer_wf}; +use std::collections::HashSet; use std::{ sync::atomic::{AtomicBool, AtomicUsize, Ordering}, time::Duration, }; use temporalio_client::WorkflowOptions; -use temporalio_common::worker::{PollerBehavior, WorkerTaskTypes}; +use temporalio_common::worker::{PollerBehavior, WorkerTaskType}; use temporalio_sdk::{WfContext, WorkflowResult}; use tokio::sync::Barrier; @@ -14,7 +15,7 @@ async fn timer_workflow_not_sticky() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(WorkerTaskTypes::WORKFLOWS) + .task_types(HashSet::from([WorkerTaskType::Workflows])) .max_cached_workflows(0_usize); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), timer_wf); @@ -42,7 +43,9 @@ async fn timer_workflow_timeout_on_sticky() { // on a not-sticky queue let wf_name = "timer_workflow_timeout_on_sticky"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); starter.workflow_options.task_timeout = Some(Duration::from_secs(2)); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), timer_timeout_wf); @@ -59,7 +62,7 @@ async fn cache_miss_ok() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(WorkerTaskTypes::WORKFLOWS) + .task_types(HashSet::from([WorkerTaskType::Workflows])) .max_outstanding_workflow_tasks(2_usize) .max_cached_workflows(0_usize) .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(1_usize)); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/timers.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/timers.rs index 482a46c8c..17aef0835 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/timers.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/timers.rs @@ -1,7 +1,8 @@ +use std::collections::HashSet; use std::time::Duration; use crate::common::{CoreWfStarter, build_fake_sdk, init_core_and_create_wf}; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_common::{ prost_dur, protos::{ @@ -29,7 +30,9 @@ pub(crate) async fn timer_wf(command_sink: WfContext) -> WorkflowResult<()> { async fn timer_workflow_workflow_driver() { let wf_name = "timer_wf_new"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), timer_wf); @@ -41,7 +44,9 @@ async fn timer_workflow_workflow_driver() { async fn timer_workflow_manual() { let mut starter = init_core_and_create_wf("timer_workflow").await; let core = starter.get_worker().await; - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let task = core.poll_workflow_activation().await.unwrap(); core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds( task.run_id, @@ -65,7 +70,9 @@ async fn timer_workflow_manual() { async fn timer_cancel_workflow() { let mut starter = init_core_and_create_wf("timer_cancel_workflow").await; let core = starter.get_worker().await; - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let task = core.poll_workflow_activation().await.unwrap(); core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds( task.run_id, @@ -124,7 +131,9 @@ async fn parallel_timer_wf(command_sink: WfContext) -> WorkflowResult<()> { async fn parallel_timers() { let wf_name = "parallel_timers"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), parallel_timer_wf); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs index 3bea9ef2c..8fab9154f 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs @@ -1,5 +1,6 @@ use crate::common::{CoreWfStarter, SEARCH_ATTR_INT, SEARCH_ATTR_TXT, build_fake_sdk}; use assert_matches::assert_matches; +use std::collections::HashSet; use std::{collections::HashMap, time::Duration}; use temporalio_client::{ GetWorkflowResultOpts, WfClientExt, WorkflowClientTrait, WorkflowExecutionResult, @@ -14,7 +15,7 @@ use temporalio_common::protos::{ enums::v1::EventType, }, }; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_sdk::{WfContext, WfExitValue, WorkflowResult}; use temporalio_sdk_core::test_help::MockPollCfg; use uuid::Uuid; @@ -45,7 +46,9 @@ async fn sends_upsert() { let wf_name = "sends_upsert_search_attrs"; let wf_id = Uuid::new_v4(); let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.register_wf(wf_name, search_attr_updater); diff --git a/crates/sdk-core/tests/manual_tests.rs b/crates/sdk-core/tests/manual_tests.rs index a44159584..3602544be 100644 --- a/crates/sdk-core/tests/manual_tests.rs +++ b/crates/sdk-core/tests/manual_tests.rs @@ -14,13 +14,14 @@ use futures_util::{ stream::FuturesUnordered, }; use rand::{Rng, SeedableRng}; +use std::collections::HashSet; use std::{ mem, net::SocketAddr, time::{Duration, Instant}, }; use temporalio_client::{GetWorkflowResultOpts, WfClientExt, WorkflowClientTrait, WorkflowOptions}; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_common::{ protos::coresdk::AsJsonPayloadExt, telemetry::PrometheusExporterOptionsBuilder, worker::PollerBehavior, @@ -215,7 +216,7 @@ async fn poller_load_sustained() { maximum: 200, initial: 5, }) - .task_types(WorkerTaskTypes::WORKFLOWS); + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move { let sigchan = ctx.make_signal_channel(SIGNAME).map(Ok); diff --git a/crates/sdk-core/tests/shared_tests/mod.rs b/crates/sdk-core/tests/shared_tests/mod.rs index e55149d28..e1a7a9b57 100644 --- a/crates/sdk-core/tests/shared_tests/mod.rs +++ b/crates/sdk-core/tests/shared_tests/mod.rs @@ -1,12 +1,13 @@ //! Shared tests that are meant to be run against both local dev server and cloud use crate::common::CoreWfStarter; +use std::collections::HashSet; use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; use temporalio_common::protos::temporal::api::{ enums::v1::{EventType, WorkflowTaskFailedCause::GrpcMessageTooLarge}, history::v1::history_event::Attributes::WorkflowTaskFailedEventAttributes, }; -use temporalio_common::worker::WorkerTaskTypes; +use temporalio_common::worker::WorkerTaskType; use temporalio_sdk::WfContext; pub(crate) mod priority; @@ -16,7 +17,9 @@ pub(crate) async fn grpc_message_too_large() { let mut starter = CoreWfStarter::new_cloud_or_local(wf_name, "") .await .unwrap(); - starter.worker_config.task_types(WorkerTaskTypes::WORKFLOWS); + starter + .worker_config + .task_types(HashSet::from([WorkerTaskType::Workflows])); let mut core = starter.worker().await; static OVERSIZE_GRPC_MESSAGE_RUN: AtomicBool = AtomicBool::new(false); From 85ca74b29e126bdd1490d5fcc8c31d48efbae039 Mon Sep 17 00:00:00 2001 From: Andrew Yuan Date: Thu, 13 Nov 2025 17:43:25 -0800 Subject: [PATCH 4/8] Missed a change --- crates/sdk-core/src/worker/mod.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/sdk-core/src/worker/mod.rs b/crates/sdk-core/src/worker/mod.rs index c33f7d727..c6808fe4b 100644 --- a/crates/sdk-core/src/worker/mod.rs +++ b/crates/sdk-core/src/worker/mod.rs @@ -1035,13 +1035,12 @@ impl Worker { } fn complete_local_act(&self, task_token: TaskToken, la_res: LocalActivityExecutionResult) { - if let Some(la_mgr) = &self.local_act_mgr { - if self + if let Some(la_mgr) = &self.local_act_mgr + && self .handle_la_complete_action(la_mgr.complete(&task_token, la_res)) .is_some() - { - dbg_panic!("Should never be a task from direct completion"); - } + { + dbg_panic!("Should never be a task from direct completion"); } } From 9ceb6d831d141944731ce93dbd55d149ba3af11e Mon Sep 17 00:00:00 2001 From: Andrew Yuan Date: Thu, 13 Nov 2025 17:48:25 -0800 Subject: [PATCH 5/8] update temporal-sdk-core-c-bridge.h --- crates/sdk-core-c-bridge/include/temporal-sdk-core-c-bridge.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sdk-core-c-bridge/include/temporal-sdk-core-c-bridge.h b/crates/sdk-core-c-bridge/include/temporal-sdk-core-c-bridge.h index d17de11a7..f626aa417 100644 --- a/crates/sdk-core-c-bridge/include/temporal-sdk-core-c-bridge.h +++ b/crates/sdk-core-c-bridge/include/temporal-sdk-core-c-bridge.h @@ -765,7 +765,7 @@ typedef struct TemporalCoreWorkerOptions { struct TemporalCoreByteArrayRef identity_override; uint32_t max_cached_workflows; struct TemporalCoreTunerHolder tuner; - bool no_remote_activities; + uint8_t task_types; uint64_t sticky_queue_schedule_to_start_timeout_millis; uint64_t max_heartbeat_throttle_interval_millis; uint64_t default_heartbeat_throttle_interval_millis; From 98b0c9c53250d42edea8a68eb419b5a8f2e2bbd7 Mon Sep 17 00:00:00 2001 From: Andrew Yuan Date: Fri, 14 Nov 2025 12:29:33 -0800 Subject: [PATCH 6/8] Create working test --- crates/common/src/lib.rs | 2 +- crates/common/src/worker.rs | 96 +-- crates/common/tests/worker_task_types_test.rs | 112 +-- .../include/temporal-sdk-core-c-bridge.h | 8 +- crates/sdk-core-c-bridge/src/worker.rs | 23 +- .../sdk-core/src/core_tests/activity_tasks.rs | 4 +- crates/sdk-core/src/core_tests/workers.rs | 738 +++++++++++++++++- .../sdk-core/src/core_tests/workflow_tasks.rs | 10 +- crates/sdk-core/src/replay/mod.rs | 5 +- .../sdk-core/src/test_help/integ_helpers.rs | 118 ++- crates/sdk-core/src/worker/heartbeat.rs | 5 +- crates/sdk-core/src/worker/mod.rs | 46 +- crates/sdk-core/tests/heavy_tests.rs | 6 +- .../tests/integ_tests/metrics_tests.rs | 20 +- .../tests/integ_tests/update_tests.rs | 13 +- .../tests/integ_tests/worker_tests.rs | 159 +++- .../integ_tests/worker_versioning_tests.rs | 8 +- .../tests/integ_tests/workflow_tests.rs | 8 +- .../workflow_tests/cancel_external.rs | 5 +- .../integ_tests/workflow_tests/cancel_wf.rs | 5 +- .../workflow_tests/child_workflows.rs | 11 +- .../workflow_tests/continue_as_new.rs | 7 +- .../integ_tests/workflow_tests/determinism.rs | 5 +- .../tests/integ_tests/workflow_tests/eager.rs | 7 +- .../workflow_tests/modify_wf_properties.rs | 5 +- .../tests/integ_tests/workflow_tests/nexus.rs | 48 +- .../integ_tests/workflow_tests/patches.rs | 15 +- .../integ_tests/workflow_tests/resets.rs | 8 +- .../integ_tests/workflow_tests/signals.rs | 13 +- .../integ_tests/workflow_tests/stickyness.rs | 9 +- .../integ_tests/workflow_tests/timers.rs | 14 +- .../workflow_tests/upsert_search_attrs.rs | 5 +- crates/sdk-core/tests/manual_tests.rs | 5 +- crates/sdk-core/tests/shared_tests/mod.rs | 5 +- 34 files changed, 1199 insertions(+), 349 deletions(-) diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 7e2a04cbb..d44da2924 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -130,7 +130,7 @@ pub trait Worker: Send + Sync { /// workflows & activities until they are done. At that point, the lang SDK can end the process, /// or drop the [Worker] instance via [Worker::finalize_shutdown], which will close the /// connection and free resources. If you have set [WorkerConfig::task_types] to exclude - /// [worker_task_types::activities()], you may skip calling [Worker::poll_activity_task]. + /// [WorkerTaskTypes::activity_only()], you may skip calling [Worker::poll_activity_task]. /// /// Lang implementations should use [Worker::initiate_shutdown] followed by /// [Worker::finalize_shutdown]. diff --git a/crates/common/src/worker.rs b/crates/common/src/worker.rs index db36afa38..7c27a3b05 100644 --- a/crates/common/src/worker.rs +++ b/crates/common/src/worker.rs @@ -16,70 +16,56 @@ use std::{ time::Duration, }; -/// Represents a single task type that a worker can poll for -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum WorkerTaskType { - /// Poll for workflow tasks - Workflows, - /// Poll for activity tasks (remote activities) - Activities, - /// Poll for nexus tasks - Nexus, -} - /// Specifies which task types a worker will poll for. /// -/// This is a set of [WorkerTaskType] values. Workers can be configured to handle -/// any combination of workflows, activities, and nexus operations. -pub type WorkerTaskTypes = HashSet; - -/// Helper functions for working with WorkerTaskTypes -pub mod worker_task_types { - use super::{WorkerTaskType, WorkerTaskTypes}; +/// Workers can be configured to handle any combination of workflows, activities, and nexus operations. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct WorkerTaskTypes { + pub enable_workflows: bool, + pub enable_activities: bool, + pub enable_nexus: bool, +} - /// Create a set with all task types enabled - pub fn all() -> WorkerTaskTypes { - [ - WorkerTaskType::Workflows, - WorkerTaskType::Activities, - WorkerTaskType::Nexus, - ] - .into_iter() - .collect() +impl WorkerTaskTypes { + /// Check if no task types are enabled + pub fn is_empty(&self) -> bool { + !self.enable_workflows && !self.enable_activities && !self.enable_nexus } - /// Create a set with only workflow tasks enabled - pub fn workflows() -> WorkerTaskTypes { - [WorkerTaskType::Workflows].into_iter().collect() + /// Create a config with all task types enabled + pub fn all() -> WorkerTaskTypes { + WorkerTaskTypes { + enable_workflows: true, + enable_activities: true, + enable_nexus: true, + } } - /// Create a set with only activity tasks enabled - pub fn activities() -> WorkerTaskTypes { - [WorkerTaskType::Activities].into_iter().collect() + /// Create a config with only workflow tasks enabled + pub fn workflow_only() -> WorkerTaskTypes { + WorkerTaskTypes { + enable_workflows: true, + enable_activities: false, + enable_nexus: false, + } } - /// Create a set with only nexus tasks enabled - pub fn nexus() -> WorkerTaskTypes { - [WorkerTaskType::Nexus].into_iter().collect() + /// Create a config with only activity tasks enabled + pub fn activity_only() -> WorkerTaskTypes { + WorkerTaskTypes { + enable_workflows: false, + enable_activities: true, + enable_nexus: false, + } } - /// Create a set from a bitmask (for C FFI compatibility) - /// 0x01 = Workflows, 0x02 = Activities, 0x04 = Nexus - pub fn from_bits(bits: u8) -> WorkerTaskTypes { - if bits == 0 { - return all(); - } - let mut set = WorkerTaskTypes::new(); - if bits & 0x01 != 0 { - set.insert(WorkerTaskType::Workflows); - } - if bits & 0x02 != 0 { - set.insert(WorkerTaskType::Activities); - } - if bits & 0x04 != 0 { - set.insert(WorkerTaskType::Nexus); + /// Create a config with only nexus tasks enabled + pub fn nexus_only() -> WorkerTaskTypes { + WorkerTaskTypes { + enable_workflows: false, + enable_activities: false, + enable_nexus: true, } - set } } @@ -137,7 +123,7 @@ pub struct WorkerConfig { /// You can restrict this to any combination. /// /// Note: At least one task type must be specified or the worker will fail validation. - #[builder(default = "worker_task_types::all()")] + #[builder(default = "WorkerTaskTypes::all()")] pub task_types: WorkerTaskTypes, /// How long a workflow task is allowed to sit on the sticky queue before it is timed out /// and moved to the non-sticky queue where it may be picked up by any worker. @@ -293,7 +279,7 @@ impl WorkerConfigBuilder { .task_types .as_ref() .cloned() - .unwrap_or_else(worker_task_types::all); + .unwrap_or_else(WorkerTaskTypes::all); if task_types.is_empty() { return Err("At least one task type must be enabled in `task_types`".to_owned()); } @@ -330,7 +316,7 @@ impl WorkerConfigBuilder { } // Validate workflow cache is consistent with task_types - if !task_types.contains(&WorkerTaskType::Workflows) + if !task_types.enable_workflows && let Some(cache) = self.max_cached_workflows.as_ref() && *cache > 0 { diff --git a/crates/common/tests/worker_task_types_test.rs b/crates/common/tests/worker_task_types_test.rs index 050d92ac2..44b50b5cb 100644 --- a/crates/common/tests/worker_task_types_test.rs +++ b/crates/common/tests/worker_task_types_test.rs @@ -1,8 +1,4 @@ -use std::collections::HashSet; -use temporalio_common::worker::{ - WorkerConfigBuilder, WorkerTaskType, WorkerTaskTypes, WorkerVersioningStrategy, - worker_task_types, -}; +use temporalio_common::worker::{WorkerConfigBuilder, WorkerTaskTypes, WorkerVersioningStrategy}; fn default_versioning_strategy() -> WorkerVersioningStrategy { WorkerVersioningStrategy::None { @@ -21,72 +17,14 @@ fn test_default_configuration_polls_all_types() { let effective = &config.task_types; assert!( - effective.contains(&WorkerTaskType::Workflows), + effective.enable_workflows, "Should poll workflows by default" ); assert!( - effective.contains(&WorkerTaskType::Activities), + effective.enable_activities, "Should poll activities by default" ); - assert!( - effective.contains(&WorkerTaskType::Nexus), - "Should poll nexus by default" - ); -} - -#[test] -fn test_workflow_only_worker() { - let config = WorkerConfigBuilder::default() - .namespace("default") - .task_queue("test-queue") - .versioning_strategy(default_versioning_strategy()) - .task_types(HashSet::from([WorkerTaskType::Workflows])) - .max_cached_workflows(0usize) - .build() - .expect("Failed to build workflow-only config"); - - let effective = &config.task_types; - assert!( - effective.contains(&WorkerTaskType::Workflows), - "Should poll workflows" - ); - assert!( - !effective.contains(&WorkerTaskType::Activities), - "Should NOT poll activities" - ); - assert!( - !effective.contains(&WorkerTaskType::Nexus), - "Should NOT poll nexus" - ); -} - -#[test] -fn test_activity_and_nexus_worker() { - let types: WorkerTaskTypes = [WorkerTaskType::Activities, WorkerTaskType::Nexus] - .into_iter() - .collect(); - let config = WorkerConfigBuilder::default() - .namespace("default") - .task_queue("test-queue") - .versioning_strategy(default_versioning_strategy()) - .task_types(types) - .max_cached_workflows(0usize) - .build() - .expect("Failed to build activity+nexus config"); - - let effective = &config.task_types; - assert!( - !effective.contains(&WorkerTaskType::Workflows), - "Should NOT poll workflows" - ); - assert!( - effective.contains(&WorkerTaskType::Activities), - "Should poll activities" - ); - assert!( - effective.contains(&WorkerTaskType::Nexus), - "Should poll nexus" - ); + assert!(effective.enable_nexus, "Should poll nexus by default"); } #[test] @@ -95,7 +33,11 @@ fn test_empty_task_types_fails_validation() { .namespace("default") .task_queue("test-queue") .versioning_strategy(default_versioning_strategy()) - .task_types(WorkerTaskTypes::new()) + .task_types(WorkerTaskTypes { + enable_workflows: false, + enable_activities: false, + enable_nexus: false, + }) .build(); assert!(result.is_err(), "Empty task_types should fail validation"); @@ -112,7 +54,7 @@ fn test_workflow_cache_without_workflows_fails() { .namespace("default") .task_queue("test-queue") .versioning_strategy(default_versioning_strategy()) - .task_types(worker_task_types::activities()) + .task_types(WorkerTaskTypes::activity_only()) .max_cached_workflows(10usize) .build(); @@ -130,28 +72,34 @@ fn test_workflow_cache_without_workflows_fails() { #[test] fn test_all_combinations() { let combinations = [ - (worker_task_types::workflows(), "workflows only"), - (worker_task_types::activities(), "activities only"), - (worker_task_types::nexus(), "nexus only"), + (WorkerTaskTypes::workflow_only(), "workflows only"), + (WorkerTaskTypes::activity_only(), "activities only"), + (WorkerTaskTypes::nexus_only(), "nexus only"), ( - [WorkerTaskType::Workflows, WorkerTaskType::Activities] - .into_iter() - .collect(), + WorkerTaskTypes { + enable_workflows: true, + enable_activities: true, + enable_nexus: false, + }, "workflows + activities", ), ( - [WorkerTaskType::Workflows, WorkerTaskType::Nexus] - .into_iter() - .collect(), + WorkerTaskTypes { + enable_workflows: true, + enable_activities: false, + enable_nexus: true, + }, "workflows + nexus", ), ( - [WorkerTaskType::Activities, WorkerTaskType::Nexus] - .into_iter() - .collect(), + WorkerTaskTypes { + enable_workflows: false, + enable_activities: true, + enable_nexus: true, + }, "activities + nexus", ), - (worker_task_types::all(), "all types"), + (WorkerTaskTypes::all(), "all types"), ]; for (task_types, description) in combinations { @@ -159,7 +107,7 @@ fn test_all_combinations() { .namespace("default") .task_queue("test-queue") .versioning_strategy(default_versioning_strategy()) - .task_types(task_types.clone()) + .task_types(task_types) .build() .unwrap_or_else(|e| panic!("Failed to build config for {description}: {e:?}")); diff --git a/crates/sdk-core-c-bridge/include/temporal-sdk-core-c-bridge.h b/crates/sdk-core-c-bridge/include/temporal-sdk-core-c-bridge.h index f626aa417..050119ee0 100644 --- a/crates/sdk-core-c-bridge/include/temporal-sdk-core-c-bridge.h +++ b/crates/sdk-core-c-bridge/include/temporal-sdk-core-c-bridge.h @@ -738,6 +738,12 @@ typedef struct TemporalCoreTunerHolder { struct TemporalCoreSlotSupplier nexus_task_slot_supplier; } TemporalCoreTunerHolder; +typedef struct TemporalCoreWorkerTaskTypes { + bool enable_workflows; + bool enable_activities; + bool enable_nexus; +} TemporalCoreWorkerTaskTypes; + typedef struct TemporalCorePollerBehaviorSimpleMaximum { uintptr_t simple_maximum; } TemporalCorePollerBehaviorSimpleMaximum; @@ -765,7 +771,7 @@ typedef struct TemporalCoreWorkerOptions { struct TemporalCoreByteArrayRef identity_override; uint32_t max_cached_workflows; struct TemporalCoreTunerHolder tuner; - uint8_t task_types; + struct TemporalCoreWorkerTaskTypes task_types; uint64_t sticky_queue_schedule_to_start_timeout_millis; uint64_t max_heartbeat_throttle_interval_millis; uint64_t default_heartbeat_throttle_interval_millis; diff --git a/crates/sdk-core-c-bridge/src/worker.rs b/crates/sdk-core-c-bridge/src/worker.rs index 8d4ce37f5..022761ca1 100644 --- a/crates/sdk-core-c-bridge/src/worker.rs +++ b/crates/sdk-core-c-bridge/src/worker.rs @@ -43,7 +43,7 @@ pub struct WorkerOptions { pub identity_override: ByteArrayRef, pub max_cached_workflows: u32, pub tuner: TunerHolder, - pub task_types: u8, + pub task_types: WorkerTaskTypes, pub sticky_queue_schedule_to_start_timeout_millis: u64, pub max_heartbeat_throttle_interval_millis: u64, pub default_heartbeat_throttle_interval_millis: u64, @@ -58,6 +58,23 @@ pub struct WorkerOptions { pub nondeterminism_as_workflow_fail_for_types: ByteArrayRefArray, } +#[repr(C)] +pub struct WorkerTaskTypes { + pub enable_workflows: bool, + pub enable_activities: bool, + pub enable_nexus: bool, +} + +impl From<&WorkerTaskTypes> for temporalio_common::worker::WorkerTaskTypes { + fn from(t: &WorkerTaskTypes) -> Self { + Self { + enable_workflows: t.enable_workflows, + enable_activities: t.enable_activities, + enable_nexus: t.enable_nexus, + } + } +} + #[repr(C)] pub struct PollerBehaviorSimpleMaximum { pub simple_maximum: usize, @@ -1183,8 +1200,8 @@ impl TryFrom<&WorkerOptions> for temporalio_sdk_core::WorkerConfig { .client_identity_override(opt.identity_override.to_option_string()) .max_cached_workflows(opt.max_cached_workflows as usize) .tuner(Arc::new(converted_tuner)) - .task_types(temporalio_common::worker::worker_task_types::from_bits( - opt.task_types, + .task_types(temporalio_common::worker::WorkerTaskTypes::from( + &opt.task_types, )) .sticky_queue_schedule_to_start_timeout(Duration::from_millis( opt.sticky_queue_schedule_to_start_timeout_millis, diff --git a/crates/sdk-core/src/core_tests/activity_tasks.rs b/crates/sdk-core/src/core_tests/activity_tasks.rs index 4b1ed1ef8..3e0ad2240 100644 --- a/crates/sdk-core/src/core_tests/activity_tasks.rs +++ b/crates/sdk-core/src/core_tests/activity_tasks.rs @@ -53,7 +53,7 @@ use temporalio_common::{ }, test_utils::start_timer_cmd, }, - worker::{PollerBehavior, worker_task_types}, + worker::{PollerBehavior, WorkerTaskTypes}, }; use tokio::{join, time::sleep}; use tokio_util::sync::CancellationToken; @@ -725,7 +725,7 @@ async fn no_eager_activities_requested_when_worker_options_disable_it( mock.worker_cfg(|wc| { wc.max_cached_workflows = 2; if reason == "no_remote" { - wc.task_types = worker_task_types::workflows(); + wc.task_types = WorkerTaskTypes::workflow_only(); } else { wc.max_task_queue_activities_per_second = Some(1.0); } diff --git a/crates/sdk-core/src/core_tests/workers.rs b/crates/sdk-core/src/core_tests/workers.rs index c9a186cfe..0477d362e 100644 --- a/crates/sdk-core/src/core_tests/workers.rs +++ b/crates/sdk-core/src/core_tests/workers.rs @@ -1,3 +1,4 @@ +use temporalio_common::protos::temporal::api::workflowservice::v1::RespondNexusTaskCompletedResponse; use crate::{ PollError, prost_dur, test_help::{ @@ -14,7 +15,6 @@ use crate::{ }; use futures_util::{stream, stream::StreamExt}; use std::{cell::RefCell, time::Duration}; -use temporalio_common::worker::WorkerTaskType; use temporalio_common::{ Worker, protos::{ @@ -25,15 +25,22 @@ use temporalio_common::{ workflow_completion::WorkflowActivationCompletion, }, temporal::api::workflowservice::v1::{ - PollWorkflowTaskQueueResponse, RespondWorkflowTaskCompletedResponse, + PollWorkflowTaskQueueResponse, PollNexusTaskQueueResponse, RespondWorkflowTaskCompletedResponse, ShutdownWorkerResponse, }, + temporal::api::common::v1::{ActivityType, WorkflowExecution}, test_utils::start_timer_cmd, }, - worker::{PollerBehavior, worker_task_types}, + worker::{PollerBehavior, WorkerTaskTypes}, }; use tokio::sync::{Barrier, watch}; use tokio::time::timeout; +use temporalio_common::protos::coresdk::activity_result::ActivityExecutionResult; +use temporalio_common::protos::coresdk::ActivityTaskCompletion; +use temporalio_common::protos::coresdk::nexus::NexusTaskCompletion; +use temporalio_common::protos::temporal::api::nexus::v1::{Request as NexusRequest, Response as NexusResponse, StartOperationRequest, StartOperationResponse, start_operation_response}; +use temporalio_common::protos::temporal::api::workflowservice::v1::{PollActivityTaskQueueResponse, RespondActivityTaskCompletedResponse}; +use crate::test_help::QueueResponse; #[tokio::test] async fn after_shutdown_of_worker_get_shutdown_err() { @@ -137,7 +144,7 @@ async fn can_shutdown_local_act_only_worker_when_act_polling() { let mh = MockPollCfg::from_resp_batches("fakeid", t, [1], mock); let mut mock = build_mock_pollers(mh); mock.worker_cfg(|w| { - w.task_types = worker_task_types::workflows(); + w.task_types = WorkerTaskTypes::workflow_only(); w.max_cached_workflows = 1; }); let worker = mock_worker(mock); @@ -374,28 +381,34 @@ async fn worker_shutdown_api(#[case] use_cache: bool, #[case] api_success: bool) #[tokio::test] async fn test_worker_type_shutdown_all_combinations() { let combinations = [ - (worker_task_types::workflows(), "workflows only"), - (worker_task_types::activities(), "activities only"), - (worker_task_types::nexus(), "nexus only"), + (WorkerTaskTypes::workflow_only(), "workflows only"), + (WorkerTaskTypes::activity_only(), "activities only"), + (WorkerTaskTypes::nexus_only(), "nexus only"), ( - [WorkerTaskType::Workflows, WorkerTaskType::Activities] - .into_iter() - .collect(), + WorkerTaskTypes { + enable_workflows: true, + enable_activities: true, + enable_nexus: false, + }, "workflows + activities", ), ( - [WorkerTaskType::Workflows, WorkerTaskType::Nexus] - .into_iter() - .collect(), + WorkerTaskTypes { + enable_workflows: true, + enable_activities: false, + enable_nexus: true, + }, "workflows + nexus", ), ( - [WorkerTaskType::Activities, WorkerTaskType::Nexus] - .into_iter() - .collect(), + WorkerTaskTypes { + enable_workflows: false, + enable_activities: true, + enable_nexus: true, + }, "activities + nexus", ), - (worker_task_types::all(), "all types"), + (WorkerTaskTypes::all(), "all types"), ]; for (task_types, description) in combinations { @@ -418,3 +431,694 @@ async fn test_worker_type_shutdown_all_combinations() { ); } } + +// #[tokio::test] +// async fn test_type_shutdown_with_tasks2() { +// telemetry_init_fallback(); +// let combinations = [ +// // (WorkerTaskTypes::workflow_only(), "workflows only"), +// (WorkerTaskTypes::activity_only(), "activities only"), +// // (WorkerTaskTypes::nexus_only(), "nexus only"), +// // ( +// // WorkerTaskTypes { +// // enable_workflows: true, +// // enable_activities: true, +// // enable_nexus: false, +// // }, +// // "workflows + activities", +// // ), +// // ( +// // WorkerTaskTypes { +// // enable_workflows: true, +// // enable_activities: false, +// // enable_nexus: true, +// // }, +// // "workflows + nexus", +// // ), +// // ( +// // WorkerTaskTypes { +// // enable_workflows: false, +// // enable_activities: true, +// // enable_nexus: true, +// // }, +// // "activities + nexus", +// // ), +// // (WorkerTaskTypes::all(), "all types"), +// ]; +// +// for (task_types, description) in combinations { +// eprintln!("\nTesting: {description}"); +// +// let mut mock_client = mock_worker_client(); +// if task_types.enable_workflows { +// mock_client +// .expect_complete_workflow_task() +// .times(1) +// .returning(|_| Ok(RespondWorkflowTaskCompletedResponse::default())); +// // mock_client.expect_poll_workflow_task().times(1).returning(|_, _| Ok(PollWorkflowTaskQueueResponse::default())); +// } +// if task_types.enable_activities { +// mock_client.expect_poll_activity_task().times(1).returning(|_, _| Ok(PollActivityTaskQueueResponse { +// task_token: vec![1], +// ..Default::default() +// })); +// mock_client.expect_complete_activity_task().times(1).returning(|_, _| Ok(RespondActivityTaskCompletedResponse::default())); +// } +// if task_types.enable_nexus { +// mock_client.expect_poll_nexus_task().times(1).returning(|_, _| Ok(Default::default())); +// mock_client.expect_complete_nexus_task().times(1).returning(|_, _| Ok(RespondNexusTaskCompletedResponse::default())); +// } +// +// let t = canned_histories::single_timer("1"); +// let mut mh = MockPollCfg::from_resp_batches("fakeid", t, [1], mock_client); +// mh.enforce_correct_number_of_polls = false; +// let act_tasks: Vec> = vec![QueueResponse::from(PollActivityTaskQueueResponse::default())]; +// mh.activity_responses = Some(act_tasks); +// let mut mock = build_mock_pollers(mh); +// mock.worker_cfg(|w| { +// w.task_types = task_types; +// w.max_cached_workflows = 1; +// }); +// let worker = mock_worker(mock); +// +// println!("a"); +// if task_types.enable_workflows { +// let activation = worker.poll_workflow_activation().await.unwrap(); +// let _ = worker +// .complete_workflow_activation(WorkflowActivationCompletion::from_cmds( +// activation.run_id, +// vec![], +// )) +// .await; +// } +// +// println!("a"); +// if task_types.enable_activities { +// let task = worker.poll_activity_task().await.unwrap(); +// worker.complete_activity_task(ActivityTaskCompletion { +// task_token: task.task_token, +// result: Some(ActivityExecutionResult::ok(Default::default())), +// }) +// .await.unwrap(); +// } +// +// println!("a"); +// if task_types.enable_nexus { +// let task = worker.poll_nexus_task().await.unwrap(); +// worker +// .complete_nexus_task(NexusTaskCompletion { +// task_token: task.task_token().to_vec(), +// status: None, +// }) +// .await.unwrap(); +// +// } +// } +// } + +#[tokio::test] +async fn test_task_type_activity_only() { + let queue = "activity-only-queue"; + + let mut client = mock_worker_client(); + client + .expect_complete_activity_task() + .returning(|_, _| Ok(RespondActivityTaskCompletedResponse::default())); + + let mut act_task = PollActivityTaskQueueResponse::default(); + act_task.task_token = b"act-task".to_vec(); + act_task.workflow_execution = Some(WorkflowExecution { + workflow_id: "activity-only".to_string(), + run_id: "run-id".to_string(), + }); + act_task.activity_id = "activity".to_string(); + act_task.activity_type = Some(ActivityType { + name: "activity".to_string(), + }); + + let mut mocks = MocksHolder::from_client_with_activities( + client, + vec![QueueResponse::from(act_task)] + ); + mocks.worker_cfg(|w| { + w.task_queue = queue.to_string(); + w.task_types = WorkerTaskTypes::activity_only(); + w.skip_client_worker_set_check = true; + }); + let worker = mock_worker(mocks); + + let activity_task = worker.poll_activity_task().await.unwrap(); + worker + .complete_activity_task(ActivityTaskCompletion { + task_token: activity_task.task_token, + result: Some(ActivityExecutionResult::ok(vec![1].into())), + }) + .await + .unwrap(); + + worker.initiate_shutdown(); + assert_matches!( + worker.poll_activity_task().await.unwrap_err(), + PollError::ShutDown + ); + worker.shutdown().await; + worker.finalize_shutdown().await; +} + +#[tokio::test] +async fn test_task_type_nexus_only() { + let shared_queue = "shared-nexus-queue"; + + let t = canned_histories::single_timer("wf-only"); + let wf_cfg = MockPollCfg::from_resp_batches("wf-only", t, [1], mock_worker_client()); + let mut wf_mocks = build_mock_pollers(wf_cfg); + wf_mocks.worker_cfg(|w| { + w.task_queue = shared_queue.to_string(); + w.task_types = WorkerTaskTypes::workflow_only(); + w.skip_client_worker_set_check = true; + }); + let workflow_worker = mock_worker(wf_mocks); + + let mut nex_client = mock_worker_client(); + nex_client + .expect_complete_nexus_task() + .returning(|_, _| Ok(RespondNexusTaskCompletedResponse::default())); + let nex_task = PollNexusTaskQueueResponse { + task_token: b"nex-task".to_vec(), + request: Some(NexusRequest { + header: Default::default(), + scheduled_time: None, + variant: Some(temporalio_common::protos::temporal::api::nexus::v1::request::Variant::StartOperation( + StartOperationRequest { + service: "test-service".to_string(), + operation: "test-operation".to_string(), + request_id: "test-request-id".to_string(), + callback: "".to_string(), + payload: None, + callback_header: Default::default(), + links: vec![], + } + )), + }), + poller_scaling_decision: None, + }; + let mut nex_mocks = + MocksHolder::from_client_with_nexus(nex_client, vec![QueueResponse::from(nex_task)]); + nex_mocks.worker_cfg(|w| { + w.task_queue = shared_queue.to_string(); + w.task_types = WorkerTaskTypes::nexus_only(); + w.skip_client_worker_set_check = true; + }); + let nexus_worker = mock_worker(nex_mocks); + + let activation = workflow_worker.poll_workflow_activation().await.unwrap(); + workflow_worker + .complete_workflow_activation(WorkflowActivationCompletion::from_cmds( + activation.run_id.clone(), + vec![CompleteWorkflowExecution::default().into()], + )) + .await + .unwrap(); + + let nexus_task = nexus_worker.poll_nexus_task().await.unwrap(); + nexus_worker + .complete_nexus_task(NexusTaskCompletion { + task_token: nexus_task.task_token().to_vec(), + status: Some(temporalio_common::protos::coresdk::nexus::nexus_task_completion::Status::Completed( + NexusResponse { + variant: Some(temporalio_common::protos::temporal::api::nexus::v1::response::Variant::StartOperation( + StartOperationResponse { + variant: Some(start_operation_response::Variant::SyncSuccess( + start_operation_response::Sync { + payload: None, + links: vec![], + } + )), + } + )), + } + )), + }) + .await + .unwrap(); + + let workflow_shutdown = workflow_worker.drain_pollers_and_shutdown(); + let nexus_shutdown = async move { + nexus_worker.initiate_shutdown(); + assert_matches!( + nexus_worker.poll_nexus_task().await.unwrap_err(), + PollError::ShutDown + ); + nexus_worker.shutdown().await; + nexus_worker.finalize_shutdown().await; + }; + + timeout(Duration::from_secs(5), async { + tokio::join!(workflow_shutdown, nexus_shutdown); + }) + .await + .expect("workers failed to shutdown"); +} + +#[tokio::test] +async fn test_task_type_workflow_and_activity() { + let shared_queue = "shared-wf-act-queue"; + + let mut client = mock_worker_client(); + client + .expect_complete_activity_task() + .returning(|_, _| Ok(RespondActivityTaskCompletedResponse::default())); + + let t = canned_histories::single_timer("wf-act"); + let wf_cfg = MockPollCfg::from_resp_batches("wf-act", t, [1], client); + let mut act_task = PollActivityTaskQueueResponse::default(); + act_task.task_token = b"act-task".to_vec(); + act_task.workflow_execution = Some(WorkflowExecution { + workflow_id: "wf-act".to_string(), + run_id: "run-id".to_string(), + }); + act_task.activity_id = "activity".to_string(); + act_task.activity_type = Some(ActivityType { + name: "activity".to_string(), + }); + let mut mocks = build_mock_pollers(wf_cfg); + mocks.set_act_poller_from_resps(vec![QueueResponse::from(act_task)]); + mocks.worker_cfg(|w| { + w.task_queue = shared_queue.to_string(); + w.task_types = WorkerTaskTypes { + enable_workflows: true, + enable_activities: true, + enable_nexus: false, + }; + w.skip_client_worker_set_check = true; + }); + let worker = mock_worker(mocks); + + let activation = worker.poll_workflow_activation().await.unwrap(); + worker + .complete_workflow_activation(WorkflowActivationCompletion::from_cmds( + activation.run_id.clone(), + vec![CompleteWorkflowExecution::default().into()], + )) + .await + .unwrap(); + + let activity_task = worker.poll_activity_task().await.unwrap(); + worker + .complete_activity_task(ActivityTaskCompletion { + task_token: activity_task.task_token, + result: Some(ActivityExecutionResult::ok(vec![1].into())), + }) + .await + .unwrap(); + + let shutdown = worker.drain_pollers_and_shutdown(); + timeout(Duration::from_secs(5), shutdown) + .await + .expect("worker failed to shutdown"); +} + +#[tokio::test] +async fn test_task_type_workflow_and_nexus() { + let shared_queue = "shared-wf-nex-queue"; + + let mut client = mock_worker_client(); + client + .expect_complete_nexus_task() + .returning(|_, _| Ok(RespondNexusTaskCompletedResponse::default())); + + let t = canned_histories::single_timer("wf-nex"); + let wf_cfg = MockPollCfg::from_resp_batches("wf-nex", t, [1], client); + let nex_task = PollNexusTaskQueueResponse { + task_token: b"nex-task".to_vec(), + request: Some(NexusRequest { + header: Default::default(), + scheduled_time: None, + variant: Some(temporalio_common::protos::temporal::api::nexus::v1::request::Variant::StartOperation( + StartOperationRequest { + service: "test-service".to_string(), + operation: "test-operation".to_string(), + request_id: "test-request-id".to_string(), + callback: "".to_string(), + payload: None, + callback_header: Default::default(), + links: vec![], + } + )), + }), + poller_scaling_decision: None, + }; + let mut mocks = build_mock_pollers(wf_cfg); + mocks.set_nexus_poller_from_resps(vec![QueueResponse::from(nex_task)]); + mocks.worker_cfg(|w| { + w.task_queue = shared_queue.to_string(); + w.task_types = WorkerTaskTypes { + enable_workflows: true, + enable_activities: false, + enable_nexus: true, + }; + w.skip_client_worker_set_check = true; + }); + let worker = mock_worker(mocks); + + let activation = worker.poll_workflow_activation().await.unwrap(); + worker + .complete_workflow_activation(WorkflowActivationCompletion::from_cmds( + activation.run_id.clone(), + vec![CompleteWorkflowExecution::default().into()], + )) + .await + .unwrap(); + + let nexus_task = worker.poll_nexus_task().await.unwrap(); + worker + .complete_nexus_task(NexusTaskCompletion { + task_token: nexus_task.task_token().to_vec(), + status: Some(temporalio_common::protos::coresdk::nexus::nexus_task_completion::Status::Completed( + NexusResponse { + variant: Some(temporalio_common::protos::temporal::api::nexus::v1::response::Variant::StartOperation( + StartOperationResponse { + variant: Some(start_operation_response::Variant::SyncSuccess( + start_operation_response::Sync { + payload: None, + links: vec![], + } + )), + } + )), + } + )), + }) + .await + .unwrap(); + + worker.initiate_shutdown(); + // Drain nexus poller + assert_matches!( + worker.poll_nexus_task().await.unwrap_err(), + PollError::ShutDown + ); + worker.shutdown().await; + worker.finalize_shutdown().await; +} + +#[tokio::test] +async fn test_task_type_activity_and_nexus() { + let shared_queue = "shared-act-nex-queue"; + + let mut client = mock_worker_client(); + client + .expect_complete_activity_task() + .returning(|_, _| Ok(RespondActivityTaskCompletedResponse::default())); + client + .expect_complete_nexus_task() + .returning(|_, _| Ok(RespondNexusTaskCompletedResponse::default())); + + let mut act_task = PollActivityTaskQueueResponse::default(); + act_task.task_token = b"act-task".to_vec(); + act_task.workflow_execution = Some(WorkflowExecution { + workflow_id: "act-nex".to_string(), + run_id: "run-id".to_string(), + }); + act_task.activity_id = "activity".to_string(); + act_task.activity_type = Some(ActivityType { + name: "activity".to_string(), + }); + + let nex_task = PollNexusTaskQueueResponse { + task_token: b"nex-task".to_vec(), + request: Some(NexusRequest { + header: Default::default(), + scheduled_time: None, + variant: Some(temporalio_common::protos::temporal::api::nexus::v1::request::Variant::StartOperation( + StartOperationRequest { + service: "test-service".to_string(), + operation: "test-operation".to_string(), + request_id: "test-request-id".to_string(), + callback: "".to_string(), + payload: None, + callback_header: Default::default(), + links: vec![], + } + )), + }), + poller_scaling_decision: None, + }; + + let mut mocks = MocksHolder::from_client_with_activities(client, vec![QueueResponse::from(act_task)]); + mocks.set_nexus_poller_from_resps(vec![QueueResponse::from(nex_task)]); + mocks.worker_cfg(|w| { + w.task_queue = shared_queue.to_string(); + w.task_types = WorkerTaskTypes { + enable_workflows: false, + enable_activities: true, + enable_nexus: true, + }; + w.skip_client_worker_set_check = true; + }); + let worker = mock_worker(mocks); + + let activity_task = worker.poll_activity_task().await.unwrap(); + worker + .complete_activity_task(ActivityTaskCompletion { + task_token: activity_task.task_token, + result: Some(ActivityExecutionResult::ok(vec![1].into())), + }) + .await + .unwrap(); + + let nexus_task = worker.poll_nexus_task().await.unwrap(); + worker + .complete_nexus_task(NexusTaskCompletion { + task_token: nexus_task.task_token().to_vec(), + status: Some(temporalio_common::protos::coresdk::nexus::nexus_task_completion::Status::Completed( + NexusResponse { + variant: Some(temporalio_common::protos::temporal::api::nexus::v1::response::Variant::StartOperation( + StartOperationResponse { + variant: Some(start_operation_response::Variant::SyncSuccess( + start_operation_response::Sync { + payload: None, + links: vec![], + } + )), + } + )), + } + )), + }) + .await + .unwrap(); + + worker.initiate_shutdown(); + // Drain activity poller + assert_matches!( + worker.poll_activity_task().await.unwrap_err(), + PollError::ShutDown + ); + // Drain nexus poller + assert_matches!( + worker.poll_nexus_task().await.unwrap_err(), + PollError::ShutDown + ); + worker.shutdown().await; + worker.finalize_shutdown().await; +} + +// Helper functions for creating consistent test data +fn create_test_activity_task() -> PollActivityTaskQueueResponse { + PollActivityTaskQueueResponse { + task_token: b"act-task".to_vec(), + workflow_execution: Some(WorkflowExecution { + workflow_id: "test".to_string(), + run_id: "run-id".to_string(), + }), + activity_id: "activity".to_string(), + activity_type: Some(ActivityType { + name: "activity".to_string(), + }), + ..Default::default() + } +} + +fn create_test_nexus_task() -> PollNexusTaskQueueResponse { + PollNexusTaskQueueResponse { + task_token: b"nex-task".to_vec(), + request: Some(NexusRequest { + header: Default::default(), + scheduled_time: None, + variant: Some(temporalio_common::protos::temporal::api::nexus::v1::request::Variant::StartOperation( + StartOperationRequest { + service: "test-service".to_string(), + operation: "test-operation".to_string(), + request_id: "test-request-id".to_string(), + callback: "".to_string(), + payload: None, + callback_header: Default::default(), + links: vec![], + } + )), + }), + poller_scaling_decision: None, + } +} + +fn create_test_nexus_completion(task_token: &[u8]) -> NexusTaskCompletion { + NexusTaskCompletion { + task_token: task_token.to_vec(), + status: Some(temporalio_common::protos::coresdk::nexus::nexus_task_completion::Status::Completed( + NexusResponse { + variant: Some(temporalio_common::protos::temporal::api::nexus::v1::response::Variant::StartOperation( + StartOperationResponse { + variant: Some(start_operation_response::Variant::SyncSuccess( + start_operation_response::Sync { + payload: None, + links: vec![], + } + )), + } + )), + } + )), + } +} + +#[rstest::rstest] +// With tasks +#[case::activity_only_with_task(false, true, false, true, "activity-only")] +#[case::nexus_only_with_task(false, false, true, true, "nexus-only")] +#[case::workflow_only_with_task(true, false, false, true, "workflow-only")] +#[case::workflow_and_activity_with_task(true, true, false, true, "workflow-activity")] +#[case::workflow_and_nexus_with_task(true, false, true, true, "workflow-nexus")] +#[case::activity_and_nexus_with_task(false, true, true, true, "activity-nexus")] +// Without tasks (idle worker shutdown) +#[case::activity_only_idle(false, true, false, false, "activity-only-idle")] +#[case::nexus_only_idle(false, false, true, false, "nexus-only-idle")] +#[case::workflow_only_idle(true, false, false, false, "workflow-only-idle")] +#[case::workflow_and_activity_idle(true, true, false, false, "workflow-activity-idle")] +#[case::workflow_and_nexus_idle(true, false, true, false, "workflow-nexus-idle")] +#[case::activity_and_nexus_idle(false, true, true, false, "activity-nexus-idle")] +#[tokio::test] +async fn test_task_type_combinations_unified( + #[case] enable_workflows: bool, + #[case] enable_activities: bool, + #[case] enable_nexus: bool, + #[case] with_task: bool, + #[case] queue_name: &str, +) { + let mut client = mock_worker_client(); + + // Setup expectations based on enabled types (only if with_task is true) + if enable_activities && with_task { + client + .expect_complete_activity_task() + .returning(|_, _| Ok(RespondActivityTaskCompletedResponse::default())); + } + if enable_nexus && with_task { + client + .expect_complete_nexus_task() + .returning(|_, _| Ok(RespondNexusTaskCompletedResponse::default())); + } + + // Build worker based on enabled task types + let mut mocks = if enable_workflows && with_task { + // When workflows are enabled AND we have tasks, use build_mock_pollers as the base + let t = canned_histories::single_timer(queue_name); + let wf_cfg = MockPollCfg::from_resp_batches(queue_name, t, [1], client); + let mut mocks = build_mock_pollers(wf_cfg); + if enable_activities { + mocks.set_act_poller_from_resps(vec![QueueResponse::from(create_test_activity_task())]); + } + if enable_nexus { + mocks.set_nexus_poller_from_resps(vec![QueueResponse::from(create_test_nexus_task())]); + } + mocks + } else { + // When workflows are disabled OR idle (no tasks), use from_client_with_custom + // Provide workflow stream if workflows enabled (but no tasks in it) + let wft_stream = if enable_workflows { + Some(stream::empty()) + } else { + None + }; + let activity_tasks = if enable_activities && with_task { + Some(vec![QueueResponse::from(create_test_activity_task())]) + } else { + None + }; + let nexus_tasks = if enable_nexus && with_task { + Some(vec![QueueResponse::from(create_test_nexus_task())]) + } else { + None + }; + MocksHolder::from_client_with_custom(client, wft_stream, activity_tasks, nexus_tasks) + }; + + mocks.worker_cfg(|w| { + w.task_queue = queue_name.to_string(); + w.task_types = WorkerTaskTypes { + enable_workflows, + enable_activities, + enable_nexus, + }; + w.skip_client_worker_set_check = true; + }); + let worker = mock_worker(mocks); + + // Poll and complete tasks ONLY if with_task is true + if with_task { + if enable_workflows { + let activation = worker.poll_workflow_activation().await.unwrap(); + worker + .complete_workflow_activation(WorkflowActivationCompletion::from_cmds( + activation.run_id.clone(), + vec![CompleteWorkflowExecution::default().into()], + )) + .await + .unwrap(); + } + + if enable_activities { + let activity_task = worker.poll_activity_task().await.unwrap(); + worker + .complete_activity_task(ActivityTaskCompletion { + task_token: activity_task.task_token, + result: Some(ActivityExecutionResult::ok(vec![1].into())), + }) + .await + .unwrap(); + } + + if enable_nexus { + let nexus_task = worker.poll_nexus_task().await.unwrap(); + worker + .complete_nexus_task(create_test_nexus_completion(nexus_task.task_token())) + .await + .unwrap(); + } + } + + // Shutdown (works whether tasks were processed or not) + worker.initiate_shutdown(); + if enable_workflows { + assert_matches!( + worker.poll_workflow_activation().await.unwrap_err(), + PollError::ShutDown + ); + } + if enable_activities { + assert_matches!( + worker.poll_activity_task().await.unwrap_err(), + PollError::ShutDown + ); + } + if enable_nexus { + assert_matches!( + worker.poll_nexus_task().await.unwrap_err(), + PollError::ShutDown + ); + } + worker.shutdown().await; + worker.finalize_shutdown().await; +} + + diff --git a/crates/sdk-core/src/core_tests/workflow_tasks.rs b/crates/sdk-core/src/core_tests/workflow_tasks.rs index 247d72ff6..ffa7444f6 100644 --- a/crates/sdk-core/src/core_tests/workflow_tasks.rs +++ b/crates/sdk-core/src/core_tests/workflow_tasks.rs @@ -66,7 +66,7 @@ use temporalio_common::{ }, worker::{ PollerBehavior, SlotMarkUsedContext, SlotReleaseContext, SlotReservationContext, - SlotSupplier, SlotSupplierPermit, WorkerTaskType, WorkflowSlotKind, + SlotSupplier, SlotSupplierPermit, WorkerTaskTypes, WorkflowSlotKind, }, }; use tokio::{ @@ -2671,7 +2671,7 @@ async fn poller_wont_run_ahead_of_task_slots() { .max_cached_workflows(10_usize) .max_outstanding_workflow_tasks(10_usize) .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(10_usize)) - .task_types(HashSet::from([WorkerTaskType::Workflows])) + .task_types(WorkerTaskTypes::workflow_only()) .build() .unwrap(), mock_client, @@ -2731,7 +2731,7 @@ async fn poller_wont_poll_until_lang_polls() { let worker = Worker::new_test( test_worker_cfg() - .task_types(HashSet::from([WorkerTaskType::Workflows])) + .task_types(WorkerTaskTypes::workflow_only()) .build() .unwrap(), mock_client, @@ -2876,7 +2876,7 @@ async fn slot_provider_cant_hand_out_more_permits_than_cache_size() { .build(), )) .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(10_usize)) - .task_types(HashSet::from([WorkerTaskType::Workflows])) + .task_types(WorkerTaskTypes::workflow_only()) .build() .unwrap(), mock_client, @@ -3024,7 +3024,7 @@ async fn both_normal_and_sticky_pollers_poll_concurrently() { .max_outstanding_workflow_tasks(2_usize) .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(2_usize)) .nonsticky_to_sticky_poll_ratio(0.2) - .task_types(HashSet::from([WorkerTaskType::Workflows])) + .task_types(WorkerTaskTypes::workflow_only()) .build() .unwrap(), Some("stickytq".to_string()), diff --git a/crates/sdk-core/src/replay/mod.rs b/crates/sdk-core/src/replay/mod.rs index d1de88145..eb870a4c0 100644 --- a/crates/sdk-core/src/replay/mod.rs +++ b/crates/sdk-core/src/replay/mod.rs @@ -19,7 +19,6 @@ use std::{ pub use temporalio_common::protos::{ DEFAULT_WORKFLOW_TYPE, HistoryInfo, TestHistoryBuilder, default_wes_attribs, }; -use temporalio_common::worker::worker_task_types; use temporalio_common::{ protos::{ coresdk::workflow_activation::remove_from_cache::EvictionReason, @@ -31,7 +30,7 @@ use temporalio_common::{ }, }, }, - worker::{PollerBehavior, WorkerConfig}, + worker::{PollerBehavior, WorkerConfig, WorkerTaskTypes}, }; use tokio::sync::{Mutex as TokioMutex, mpsc, mpsc::UnboundedSender}; use tokio_stream::wrappers::UnboundedReceiverStream; @@ -63,7 +62,7 @@ where pub(crate) fn into_core_worker(mut self) -> Result { self.config.max_cached_workflows = 1; self.config.workflow_task_poller_behavior = PollerBehavior::SimpleMaximum(1); - self.config.task_types = worker_task_types::workflows(); + self.config.task_types = WorkerTaskTypes::workflow_only(); self.config.skip_client_worker_set_check = true; let historator = Historator::new(self.history_stream); let post_activate = historator.get_post_activate_hook(); diff --git a/crates/sdk-core/src/test_help/integ_helpers.rs b/crates/sdk-core/src/test_help/integ_helpers.rs index 08f94e110..bfede55a1 100644 --- a/crates/sdk-core/src/test_help/integ_helpers.rs +++ b/crates/sdk-core/src/test_help/integ_helpers.rs @@ -36,7 +36,6 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use temporalio_common::worker::WorkerTaskType; use temporalio_common::{ Worker as WorkerTrait, errors::PollError, @@ -185,29 +184,24 @@ pub fn build_fake_worker( pub fn mock_worker(mocks: MocksHolder) -> Worker { let sticky_q = sticky_q_name_for_worker("unit-test", mocks.inputs.config.max_cached_workflows); - let act_poller = if mocks - .inputs - .config - .task_types - .contains(&WorkerTaskType::Activities) - { + let act_poller = if mocks.inputs.config.task_types.enable_activities { mocks.inputs.act_poller } else { None }; + let nexus_poller = if mocks.inputs.config.task_types.enable_nexus { + mocks.inputs.nexus_poller + } else { + None + }; Worker::new_with_pollers( mocks.inputs.config, sticky_q, mocks.client, TaskPollers::Mocked { - wft_stream: Some(mocks.inputs.wft_stream), + wft_stream: mocks.inputs.wft_stream, act_poller, - nexus_poller: Some( - mocks - .inputs - .nexus_poller - .unwrap_or_else(|| mock_poller_from_resps([])), - ), + nexus_poller, }, None, None, @@ -238,15 +232,36 @@ impl MocksHolder { self.inputs.act_poller = Some(poller); } + /// Helper to create and set an activity poller from responses + pub(crate) fn set_act_poller_from_resps(&mut self, act_tasks: ACT) + where + ACT: IntoIterator>, + ::IntoIter: Send + 'static, + { + let act_poller = mock_poller_from_resps(act_tasks); + self.set_act_poller(act_poller); + } + + /// Helper to create and set a nexus poller from responses + pub(crate) fn set_nexus_poller_from_resps(&mut self, nexus_tasks: NEX) + where + NEX: IntoIterator>, + ::IntoIter: Send + 'static, + { + let nexus_poller = mock_poller_from_resps(nexus_tasks); + self.inputs.nexus_poller = Some(nexus_poller); + } + /// Can be used for tests that need to avoid auto-shutdown due to running out of mock responses pub fn make_wft_stream_interminable(&mut self) { - let old_stream = std::mem::replace(&mut self.inputs.wft_stream, stream::pending().boxed()); - self.inputs.wft_stream = old_stream.chain(stream::pending()).boxed(); + if let Some(old_stream) = self.inputs.wft_stream.take() { + self.inputs.wft_stream = Some(old_stream.chain(stream::pending()).boxed()); + } } } pub struct MockWorkerInputs { - pub(crate) wft_stream: BoxStream<'static, Result>, + pub(crate) wft_stream: Option>>, pub(crate) act_poller: Option>, pub(crate) nexus_poller: Option>, pub(crate) config: WorkerConfig, @@ -263,7 +278,7 @@ impl MockWorkerInputs { wft_stream: BoxStream<'static, Result>, ) -> Self { Self { - wft_stream, + wft_stream: Some(wft_stream), act_poller: None, nexus_poller: None, config: test_worker_cfg().build().unwrap(), @@ -292,10 +307,9 @@ impl MocksHolder { ACT: IntoIterator>, ::IntoIter: Send + 'static, { - let wft_stream = stream::pending().boxed(); let mock_act_poller = mock_poller_from_resps(act_tasks); let mock_worker = MockWorkerInputs { - wft_stream, + wft_stream: None, act_poller: Some(mock_act_poller), nexus_poller: None, config: test_worker_cfg().build().unwrap(), @@ -307,6 +321,66 @@ impl MocksHolder { } } + /// Uses the provided list of tasks to create a mock poller with a randomly generated task queue + pub fn from_client_with_nexus( + client: impl WorkerClient + 'static, + nexus_tasks: NEX, + ) -> Self + where + NEX: IntoIterator>, + ::IntoIter: Send + 'static, + { + let mock_nexus_poller = mock_poller_from_resps(nexus_tasks); + let mock_worker = MockWorkerInputs { + wft_stream: None, + act_poller: None, + nexus_poller: Some(mock_nexus_poller), + config: test_worker_cfg().build().unwrap(), + }; + Self { + client: Arc::new(client), + inputs: mock_worker, + outstanding_task_map: None, + } + } + + /// Create a MocksHolder with custom combination of task pollers. + /// Allows any combination of workflow, activity, and nexus tasks. + pub fn from_client_with_custom( + client: impl WorkerClient + 'static, + wft_stream: Option, + activity_tasks: Option, + nexus_tasks: Option, + ) -> Self + where + WFT: Stream + Send + 'static, + ACT: IntoIterator>, + ::IntoIter: Send + 'static, + NEX: IntoIterator>, + ::IntoIter: Send + 'static, + { + let wft_stream = wft_stream.map(|s| { + s.map(|r| Ok(r.try_into().expect("Mock responses must be valid work"))) + .boxed() + }); + + let act_poller = activity_tasks.map(|tasks| mock_poller_from_resps(tasks)); + let nexus_poller = nexus_tasks.map(|tasks| mock_poller_from_resps(tasks)); + + let mock_worker = MockWorkerInputs { + wft_stream, + act_poller, + nexus_poller, + config: test_worker_cfg().build().unwrap(), + }; + + Self { + client: Arc::new(client), + inputs: mock_worker, + outstanding_task_map: None, + } + } + /// Uses the provided task responses and delivers them as quickly as possible when polled. /// This is only useful to test buffering, as typically you do not want to pretend that /// the server is delivering WFTs super fast for the same run. @@ -314,9 +388,9 @@ impl MocksHolder { client: impl WorkerClient + 'static, stream: impl Stream + Send + 'static, ) -> Self { - let wft_stream = stream + let wft_stream = Some(stream .map(|r| Ok(r.try_into().expect("Mock responses must be valid work"))) - .boxed(); + .boxed()); let mock_worker = MockWorkerInputs { wft_stream, act_poller: None, diff --git a/crates/sdk-core/src/worker/heartbeat.rs b/crates/sdk-core/src/worker/heartbeat.rs index 02ac91907..8cbd6c42d 100644 --- a/crates/sdk-core/src/worker/heartbeat.rs +++ b/crates/sdk-core/src/worker/heartbeat.rs @@ -5,10 +5,9 @@ use crate::{ use parking_lot::RwLock; use std::{collections::HashMap, sync::Arc, time::Duration}; use temporalio_client::SharedNamespaceWorkerTrait; -use temporalio_common::worker::worker_task_types; use temporalio_common::{ protos::temporal::api::worker::v1::WorkerHeartbeat, - worker::{PollerBehavior, WorkerConfigBuilder, WorkerVersioningStrategy}, + worker::{PollerBehavior, WorkerConfigBuilder, WorkerTaskTypes, WorkerVersioningStrategy}, }; use tokio::sync::Notify; use tokio_util::sync::CancellationToken; @@ -39,7 +38,7 @@ impl SharedNamespaceWorker { "temporal-sys/worker-commands/{namespace}/{}", client.worker_grouping_key(), )) - .task_types(worker_task_types::nexus()) + .task_types(WorkerTaskTypes::nexus_only()) .max_outstanding_nexus_tasks(5_usize) .versioning_strategy(WorkerVersioningStrategy::None { build_id: "1.0".to_owned(), diff --git a/crates/sdk-core/src/worker/mod.rs b/crates/sdk-core/src/worker/mod.rs index c6808fe4b..ce215997c 100644 --- a/crates/sdk-core/src/worker/mod.rs +++ b/crates/sdk-core/src/worker/mod.rs @@ -6,7 +6,6 @@ mod slot_provider; pub(crate) mod tuner; mod workflow; -pub(crate) use temporalio_common::worker::WorkerTaskType; pub use temporalio_common::worker::{WorkerConfig, WorkerConfigBuilder}; pub use tuner::{ FixedSizeSlotSupplier, ResourceBasedSlotsOptions, ResourceBasedSlotsOptionsBuilder, @@ -454,7 +453,7 @@ impl Worker { ); let (wft_stream, act_poller, nexus_poller) = match task_pollers { TaskPollers::Real => { - let wft_stream = if config.task_types.contains(&WorkerTaskType::Workflows) { + let wft_stream = if config.task_types.enable_workflows { let stream = make_wft_poller( &config, &sticky_queue_name, @@ -479,7 +478,7 @@ impl Worker { None }; - let act_poll_buffer = if config.task_types.contains(&WorkerTaskType::Activities) { + let act_poll_buffer = if config.task_types.enable_activities { let act_metrics = metrics.with_new_attrs([activity_poller()]); let ap = LongPollBuffer::new_activity_task( client.clone(), @@ -499,7 +498,7 @@ impl Worker { None }; - let nexus_poll_buffer = if config.task_types.contains(&WorkerTaskType::Nexus) { + let nexus_poll_buffer = if config.task_types.enable_nexus { let np_metrics = metrics.with_new_attrs([nexus_poller()]); Some(Box::new(LongPollBuffer::new_nexus_task( client.clone(), @@ -525,21 +524,27 @@ impl Worker { act_poller, nexus_poller, } => { + println!("wft_stream {:?}", wft_stream.is_some()); let wft_stream = config .task_types - .contains(&WorkerTaskType::Workflows) + .enable_workflows .then_some(wft_stream) .flatten(); + println!("wft_stream after {:?}", wft_stream.is_some()); + println!("act_poller {:?}", act_poller.is_some()); let act_poller = config .task_types - .contains(&WorkerTaskType::Activities) + .enable_activities .then_some(act_poller) .flatten(); + println!("act_poller after {:?}", act_poller.is_some()); + println!("nexus_poller {:?}", nexus_poller.is_some()); let nexus_poller = config .task_types - .contains(&WorkerTaskType::Nexus) + .enable_nexus .then_some(nexus_poller) .flatten(); + println!("nexus_poller after {:?}", nexus_poller.is_some()); let ap = act_poller .map(|ap| MockPermittedPollBuffer::new(Arc::new(act_slots.clone()), ap)); @@ -573,20 +578,19 @@ impl Worker { ); let la_permits = la_permit_dealer.get_extant_count_rcv(); - let (local_act_mgr, la_sink, hb_rx) = - if config.task_types.contains(&WorkerTaskType::Workflows) { - let (hb_tx, hb_rx) = unbounded_channel(); - let local_act_mgr = Arc::new(LocalActivityManager::new( - config.namespace.clone(), - la_permit_dealer.clone(), - hb_tx, - metrics.clone(), - )); - let la_sink = LAReqSink::new(local_act_mgr.clone()); - (Some(local_act_mgr), Some(la_sink), Some(hb_rx)) - } else { - (None, None, None) - }; + let (local_act_mgr, la_sink, hb_rx) = if config.task_types.enable_workflows { + let (hb_tx, hb_rx) = unbounded_channel(); + let local_act_mgr = Arc::new(LocalActivityManager::new( + config.namespace.clone(), + la_permit_dealer.clone(), + hb_tx, + metrics.clone(), + )); + let la_sink = LAReqSink::new(local_act_mgr.clone()); + (Some(local_act_mgr), Some(la_sink), Some(hb_rx)) + } else { + (None, None, None) + }; let at_task_mgr = act_poller.map(|ap| { WorkerActivityTasks::new( diff --git a/crates/sdk-core/tests/heavy_tests.rs b/crates/sdk-core/tests/heavy_tests.rs index f7a6df737..6d7bd1d6c 100644 --- a/crates/sdk-core/tests/heavy_tests.rs +++ b/crates/sdk-core/tests/heavy_tests.rs @@ -16,14 +16,14 @@ use futures_util::{ stream::FuturesUnordered, }; use rand::Rng; -use std::collections::HashSet; use std::{ mem, sync::Arc, time::{Duration, Instant}, }; use temporalio_client::{GetWorkflowResultOpts, WfClientExt, WorkflowClientTrait, WorkflowOptions}; -use temporalio_common::worker::WorkerTaskType; + +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ protos::{ coresdk::{AsJsonPayloadExt, workflow_commands::ActivityCancellationType}, @@ -351,7 +351,7 @@ async fn can_paginate_long_history() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])) + .task_types(WorkerTaskTypes::workflow_only()) // Do not use sticky queues so we are forced to paginate once history gets long .max_cached_workflows(0_usize); diff --git a/crates/sdk-core/tests/integ_tests/metrics_tests.rs b/crates/sdk-core/tests/integ_tests/metrics_tests.rs index 499bda515..a63c07747 100644 --- a/crates/sdk-core/tests/integ_tests/metrics_tests.rs +++ b/crates/sdk-core/tests/integ_tests/metrics_tests.rs @@ -7,7 +7,6 @@ use crate::{ }; use anyhow::anyhow; use assert_matches::assert_matches; -use std::collections::HashSet; use std::{ collections::HashMap, env, @@ -18,7 +17,7 @@ use std::{ use temporalio_client::{ REQUEST_LATENCY_HISTOGRAM_NAME, WorkflowClientTrait, WorkflowOptions, WorkflowService, }; -use temporalio_common::worker::WorkerTaskType; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ Worker, errors::PollError, @@ -921,10 +920,11 @@ async fn nexus_metrics() { let rt = CoreRuntime::new_assume_tokio(get_integ_runtime_options(telemopts)).unwrap(); let wf_name = "nexus_metrics"; let mut starter = CoreWfStarter::new_with_runtime(wf_name, rt); - starter.worker_config.task_types(HashSet::from([ - WorkerTaskType::Workflows, - WorkerTaskType::Nexus, - ])); + starter.worker_config.task_types(WorkerTaskTypes { + enable_workflows: true, + enable_activities: false, + enable_nexus: true, + }); let task_queue = starter.get_task_queue().to_owned(); let mut worker = starter.worker().await; let core_worker = starter.get_worker().await; @@ -1103,7 +1103,7 @@ async fn evict_on_complete_does_not_count_as_forced_eviction() { let mut starter = CoreWfStarter::new_with_runtime(wf_name, rt); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.register_wf( @@ -1188,7 +1188,7 @@ async fn metrics_available_from_custom_slot_supplier() { CoreWfStarter::new_with_runtime("metrics_available_from_custom_slot_supplier", rt); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); starter.worker_config.clear_max_outstanding_opts(); let mut tb = TunerBuilder::default(); tb.workflow_slot_supplier(Arc::new(MetricRecordingSlotSupplier:: { @@ -1359,7 +1359,7 @@ async fn sticky_queue_label_strategy( starter.worker_config.max_cached_workflows(10_usize); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let task_queue = starter.get_task_queue().to_owned(); let mut worker = starter.worker().await; @@ -1437,7 +1437,7 @@ async fn resource_based_tuner_metrics() { let mut starter = CoreWfStarter::new_with_runtime(wf_name, rt); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); starter.worker_config.clear_max_outstanding_opts(); // Create a resource-based tuner with reasonable thresholds diff --git a/crates/sdk-core/tests/integ_tests/update_tests.rs b/crates/sdk-core/tests/integ_tests/update_tests.rs index 60461e708..872a79303 100644 --- a/crates/sdk-core/tests/integ_tests/update_tests.rs +++ b/crates/sdk-core/tests/integ_tests/update_tests.rs @@ -4,7 +4,6 @@ use crate::common::{ use anyhow::anyhow; use assert_matches::assert_matches; use futures_util::{StreamExt, future, future::join_all}; -use std::collections::HashSet; use std::{ sync::{ Arc, LazyLock, @@ -15,7 +14,7 @@ use std::{ use temporalio_client::{ Client, NamespacedClient, RetryClient, WorkflowClientTrait, WorkflowService, }; -use temporalio_common::worker::WorkerTaskType; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ Worker, prost_dur, protos::{ @@ -727,7 +726,7 @@ async fn update_rejection_sdk() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; let client = starter.get_client().await; worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move { @@ -773,7 +772,7 @@ async fn update_fail_sdk() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; let client = starter.get_client().await; worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move { @@ -819,7 +818,7 @@ async fn update_timer_sequence() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; let client = starter.get_client().await; worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move { @@ -869,7 +868,7 @@ async fn task_failure_during_validation() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); starter.workflow_options.task_timeout = Some(Duration::from_secs(1)); let mut worker = starter.worker().await; let client = starter.get_client().await; @@ -932,7 +931,7 @@ async fn task_failure_after_update() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); starter.workflow_options.task_timeout = Some(Duration::from_secs(1)); let mut worker = starter.worker().await; let client = starter.get_client().await; diff --git a/crates/sdk-core/tests/integ_tests/worker_tests.rs b/crates/sdk-core/tests/integ_tests/worker_tests.rs index aa964fbb2..9cf825adf 100644 --- a/crates/sdk-core/tests/integ_tests/worker_tests.rs +++ b/crates/sdk-core/tests/integ_tests/worker_tests.rs @@ -3,11 +3,11 @@ use crate::{ CoreWfStarter, fake_grpc_server::fake_server, get_integ_runtime_options, get_integ_server_options, get_integ_telem_options, mock_sdk_cfg, }, + integ_tests::mk_nexus_endpoint, shared_tests, }; use assert_matches::assert_matches; use futures_util::FutureExt; -use std::collections::HashSet; use std::{ cell::Cell, sync::{ @@ -20,7 +20,8 @@ use std::{ time::Duration, }; use temporalio_client::WorkflowOptions; -use temporalio_common::worker::{WorkerTaskType, worker_task_types}; +use temporalio_common::protos::coresdk::nexus::NexusOperationCancellationType; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ Worker, errors::WorkerValidationError, @@ -29,6 +30,7 @@ use temporalio_common::{ coresdk::{ ActivityTaskCompletion, activity_result::ActivityExecutionResult, + nexus::{NexusTaskCompletion, nexus_task_completion}, workflow_completion::{ Failure, WorkflowActivationCompletion, workflow_activation_completion::Status, }, @@ -46,6 +48,10 @@ use temporalio_common::{ self as EventAttributes, WorkflowTaskFailedEventAttributes, }, }, + nexus::v1::{ + Response as NexusResponse, StartOperationResponse, response, + start_operation_response, + }, workflowservice::v1::{ GetWorkflowExecutionHistoryResponse, PollActivityTaskQueueResponse, RespondActivityTaskCompletedResponse, @@ -59,7 +65,8 @@ use temporalio_common::{ }, }; use temporalio_sdk::{ - ActivityOptions, LocalActivityOptions, WfContext, interceptors::WorkerInterceptor, + ActivityOptions, LocalActivityOptions, NexusOperationOptions, WfContext, + interceptors::WorkerInterceptor, }; use temporalio_sdk_core::{ CoreRuntime, ResourceBasedTuner, ResourceSlotOptions, TunerBuilder, init_worker, @@ -68,6 +75,7 @@ use temporalio_sdk_core::{ hist_to_poll_resp, mock_worker, mock_worker_client, }, }; +use tokio::join; use tokio::sync::{Barrier, Notify, Semaphore}; use tokio::time::timeout; use tokio_util::sync::CancellationToken; @@ -180,7 +188,7 @@ async fn resource_based_few_pollers_guarantees_non_sticky_poll() { starter .worker_config .clear_max_outstanding_opts() - .task_types(HashSet::from([WorkerTaskType::Workflows])) + .task_types(WorkerTaskTypes::workflow_only()) // 3 pollers so the minimum slots of 2 can both be handed out to a sticky poller .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(3_usize)); // Set the limits to zero so it's essentially unwilling to hand out slots @@ -215,7 +223,7 @@ async fn oversize_grpc_message() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut core = starter.worker().await; static OVERSIZE_GRPC_MESSAGE_RUN: AtomicBool = AtomicBool::new(false); @@ -901,34 +909,40 @@ async fn shutdown_worker_not_retried() { #[tokio::test] async fn worker_type_shutdown_all_combinations() { let combinations = [ - (worker_task_types::workflows(), "workflows only"), - (worker_task_types::activities(), "activities only"), - (worker_task_types::nexus(), "nexus only"), + (WorkerTaskTypes::workflow_only(), "workflows only"), + (WorkerTaskTypes::activity_only(), "activities only"), + (WorkerTaskTypes::nexus_only(), "nexus only"), ( - [WorkerTaskType::Workflows, WorkerTaskType::Activities] - .into_iter() - .collect(), + WorkerTaskTypes { + enable_workflows: true, + enable_activities: true, + enable_nexus: false, + }, "workflows + activities", ), ( - [WorkerTaskType::Workflows, WorkerTaskType::Nexus] - .into_iter() - .collect(), + WorkerTaskTypes { + enable_workflows: true, + enable_activities: false, + enable_nexus: true, + }, "workflows + nexus", ), ( - [WorkerTaskType::Activities, WorkerTaskType::Nexus] - .into_iter() - .collect(), + WorkerTaskTypes { + enable_workflows: false, + enable_activities: true, + enable_nexus: true, + }, "activities + nexus", ), - (worker_task_types::all(), "all types"), + (WorkerTaskTypes::all(), "all types"), ]; for (task_types, description) in combinations { let wf_type = format!("worker_type_shutdown_{}", description.replace(" ", "_")); let mut starter = CoreWfStarter::new(&wf_type); - if !task_types.contains(&WorkerTaskType::Workflows) { + if !task_types.enable_workflows { starter.worker_config.max_cached_workflows(0usize); } starter.worker_config.task_types(task_types); @@ -946,3 +960,110 @@ async fn worker_type_shutdown_all_combinations() { ); } } + +#[tokio::test] +async fn test_type_shutdown_with_tasks1() { + let combinations = [ + (WorkerTaskTypes::workflow_only(), "workflows only"), + (WorkerTaskTypes::activity_only(), "activities only"), + (WorkerTaskTypes::nexus_only(), "nexus only"), + ( + WorkerTaskTypes { + enable_workflows: true, + enable_activities: true, + enable_nexus: false, + }, + "workflows + activities", + ), + ( + WorkerTaskTypes { + enable_workflows: true, + enable_activities: false, + enable_nexus: true, + }, + "workflows + nexus", + ), + ( + WorkerTaskTypes { + enable_workflows: false, + enable_activities: true, + enable_nexus: true, + }, + "activities + nexus", + ), + (WorkerTaskTypes::all(), "all types"), + ]; + + for (task_types, description) in combinations { + eprintln!("\nTesting: {description}"); + let wf_type = format!( + "test_type_shutdown_with_tasks_{}", + description.replace(" ", "_") + ); + let mut starter = CoreWfStarter::new(&wf_type); + if !task_types.enable_workflows { + starter.worker_config.max_cached_workflows(0usize); + } + starter.worker_config.task_types(task_types); + let core_worker = starter.get_worker().await; + + // Poll and complete one task of each enabled type directly + if task_types.enable_workflows { + starter.start_wf().await; + if let Ok(Ok(activation)) = timeout( + Duration::from_secs(1), + core_worker.poll_workflow_activation(), + ) + .await + { + println!("AAAAA workflow activation: {:?}", activation); + let _ = core_worker + .complete_workflow_activation(WorkflowActivationCompletion::from_cmds( + activation.run_id, + vec![], + )) + .await; + } + } + + if task_types.enable_activities { + let task = core_worker.poll_activity_task().await.unwrap(); + println!("AAAAA activity task: {:?}", task); + let _ = core_worker + .complete_activity_task(ActivityTaskCompletion { + task_token: task.task_token, + result: Some(ActivityExecutionResult::ok(Default::default())), + }) + .await; + + } + + if task_types.enable_nexus { + let task = core_worker.poll_nexus_task().await.unwrap(); + println!("AAAAA nexus task: {:?}", task); + let _ = core_worker + .complete_nexus_task(NexusTaskCompletion { + task_token: task.task_token().to_vec(), + status: Some(nexus_task_completion::Status::Completed(NexusResponse { + variant: Some(response::Variant::StartOperation( + StartOperationResponse { + variant: Some(start_operation_response::Variant::SyncSuccess( + start_operation_response::Sync { + payload: None, + links: vec![], + }, + )), + }, + )), + })), + }) + .await; + } + + // Test shutdown + timeout(Duration::from_secs(10), async { + drain_pollers_and_shutdown(&core_worker).await; + }) + .await.expect(&format!("shutdown must not hang for {description}")); + } +} diff --git a/crates/sdk-core/tests/integ_tests/worker_versioning_tests.rs b/crates/sdk-core/tests/integ_tests/worker_versioning_tests.rs index 53a26d889..e7ac54f09 100644 --- a/crates/sdk-core/tests/integ_tests/worker_versioning_tests.rs +++ b/crates/sdk-core/tests/integ_tests/worker_versioning_tests.rs @@ -2,10 +2,8 @@ use crate::{ common::{CoreWfStarter, eventually}, integ_tests::activity_functions::echo, }; -use std::collections::HashSet; use std::time::Duration; use temporalio_client::{NamespacedClient, WorkflowOptions, WorkflowService}; -use temporalio_common::worker::WorkerTaskType; use temporalio_common::{ protos::{ coresdk::{ @@ -20,7 +18,9 @@ use temporalio_common::{ }, }, }, - worker::{WorkerDeploymentOptions, WorkerDeploymentVersion, WorkerVersioningStrategy}, + worker::{ + WorkerDeploymentOptions, WorkerDeploymentVersion, WorkerTaskTypes, WorkerVersioningStrategy, + }, }; use temporalio_sdk::{ActivityOptions, WfContext}; use temporalio_sdk_core::test_help::WorkerTestHelpers; @@ -46,7 +46,7 @@ async fn sets_deployment_info_on_task_responses(#[values(true, false)] use_defau default_versioning_behavior: VersioningBehavior::AutoUpgrade.into(), }, )) - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let core = starter.get_worker().await; let client = starter.get_client().await; diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests.rs b/crates/sdk-core/tests/integ_tests/workflow_tests.rs index 0c66887e1..aa783aa09 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests.rs @@ -33,7 +33,7 @@ use std::{ use temporalio_client::{ WfClientExt, WorkflowClientTrait, WorkflowExecutionResult, WorkflowOptions, }; -use temporalio_common::worker::WorkerTaskType; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ errors::{PollError, WorkflowErrorType}, prost_dur, @@ -80,7 +80,7 @@ async fn parallel_workflows_same_queue() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut core = starter.worker().await; core.register_wf(wf_name.to_owned(), |ctx: WfContext| async move { @@ -547,7 +547,7 @@ async fn deployment_version_correct_in_wf_info(#[values(true, false)] use_only_b starter .worker_config .versioning_strategy(version_strat) - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let core = starter.get_worker().await; starter.start_wf().await; let client = starter.get_client().await; @@ -772,7 +772,7 @@ async fn nondeterminism_errors_fail_workflow_when_configured_to( let mut starter = CoreWfStarter::new_with_runtime(wf_name, rt); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let typeset = HashSet::from([WorkflowErrorType::Nondeterminism]); if whole_worker { starter.worker_config.workflow_failure_errors(typeset); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs index 4ffc235d3..942a5a75c 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs @@ -1,12 +1,11 @@ use crate::common::{CoreWfStarter, build_fake_sdk}; -use std::collections::HashSet; use temporalio_client::{GetWorkflowResultOpts, WfClientExt, WorkflowOptions}; use temporalio_common::protos::{ DEFAULT_WORKFLOW_TYPE, TestHistoryBuilder, coresdk::{FromJsonPayloadExt, common::NamespacedWorkflowExecution}, temporal::api::enums::v1::{CommandType, EventType}, }; -use temporalio_common::worker::WorkerTaskType; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::{WfContext, WorkflowResult}; use temporalio_sdk_core::test_help::MockPollCfg; @@ -43,7 +42,7 @@ async fn sends_cancel_to_other_wf() { let mut starter = CoreWfStarter::new("sends_cancel_to_other_wf"); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.register_wf("sender", cancel_sender); worker.register_wf("receiver", cancel_receiver); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs index 63a4ff230..747ad7bb2 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs @@ -1,5 +1,4 @@ use crate::common::{ActivationAssertionsInterceptor, CoreWfStarter, build_fake_sdk}; -use std::collections::HashSet; use std::time::Duration; use temporalio_client::WorkflowClientTrait; use temporalio_common::protos::{ @@ -7,7 +6,7 @@ use temporalio_common::protos::{ coresdk::workflow_activation::{WorkflowActivationJob, workflow_activation_job}, temporal::api::enums::v1::{CommandType, WorkflowExecutionStatus}, }; -use temporalio_common::worker::WorkerTaskType; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::{WfContext, WfExitValue, WorkflowResult}; use temporalio_sdk_core::test_help::MockPollCfg; @@ -36,7 +35,7 @@ async fn cancel_during_timer() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; let client = starter.get_client().await; worker.register_wf(wf_name.to_string(), cancelled_wf); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs index 4f7b6f8c0..e9029b7c1 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs @@ -1,10 +1,9 @@ use crate::common::{CoreWfStarter, build_fake_sdk, mock_sdk, mock_sdk_cfg}; use anyhow::anyhow; use assert_matches::assert_matches; -use std::collections::HashSet; use std::time::Duration; use temporalio_client::{WorkflowClientTrait, WorkflowOptions}; -use temporalio_common::worker::WorkerTaskType; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ Worker, protos::{ @@ -89,7 +88,7 @@ async fn child_workflow_happy_path() { let mut starter = CoreWfStarter::new("child-workflows"); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.register_wf(PARENT_WF_TYPE.to_string(), happy_parent); @@ -112,7 +111,7 @@ async fn abandoned_child_bug_repro() { let mut starter = CoreWfStarter::new("child-workflow-abandon-bug"); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; let barr: &'static Barrier = Box::leak(Box::new(Barrier::new(2))); @@ -185,7 +184,7 @@ async fn abandoned_child_resolves_post_cancel() { let mut starter = CoreWfStarter::new("child-workflow-resolves-post-cancel"); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; let barr: &'static Barrier = Box::leak(Box::new(Barrier::new(2))); @@ -254,7 +253,7 @@ async fn cancelled_child_gets_reason() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_string(), move |ctx: WfContext| async move { diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs index 761128ec9..1785adb47 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs @@ -1,5 +1,4 @@ use crate::common::{CoreWfStarter, build_fake_sdk}; -use std::collections::HashSet; use std::time::Duration; use temporalio_client::WorkflowOptions; use temporalio_common::protos::{ @@ -7,7 +6,7 @@ use temporalio_common::protos::{ coresdk::workflow_commands::ContinueAsNewWorkflowExecution, temporal::api::enums::v1::CommandType, }; -use temporalio_common::worker::WorkerTaskType; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::{WfContext, WfExitValue, WorkflowResult}; use temporalio_sdk_core::test_help::MockPollCfg; @@ -30,7 +29,7 @@ async fn continue_as_new_happy_path() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_string(), continue_as_new_wf); @@ -52,7 +51,7 @@ async fn continue_as_new_multiple_concurrent() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])) + .task_types(WorkerTaskTypes::workflow_only()) .max_cached_workflows(5_usize) .max_outstanding_workflow_tasks(5_usize); let mut worker = starter.worker().await; diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/determinism.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/determinism.rs index 69fd1e069..4e435992b 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/determinism.rs @@ -1,5 +1,4 @@ use crate::common::{CoreWfStarter, WorkflowHandleExt, mock_sdk, mock_sdk_cfg}; -use std::collections::HashSet; use std::{ sync::atomic::{AtomicBool, AtomicUsize, Ordering}, time::Duration, @@ -13,7 +12,7 @@ use temporalio_common::protos::{ failure::v1::Failure, }, }; -use temporalio_common::worker::WorkerTaskType; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::{ ActContext, ActivityOptions, ChildWorkflowOptions, LocalActivityOptions, WfContext, WorkflowResult, @@ -56,7 +55,7 @@ async fn test_determinism_error_then_recovers() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), timer_wf_nondeterministic); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/eager.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/eager.rs index cc6956144..6d3c3c1cb 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/eager.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/eager.rs @@ -1,8 +1,7 @@ use crate::common::{CoreWfStarter, NAMESPACE, get_integ_server_options}; -use std::collections::HashSet; use std::time::Duration; use temporalio_client::WorkflowClientTrait; -use temporalio_common::worker::WorkerTaskType; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::{WfContext, WorkflowResult}; pub(crate) async fn eager_wf(_context: WfContext) -> WorkflowResult<()> { @@ -18,7 +17,7 @@ async fn eager_wf_start() { starter.workflow_options.task_timeout = Some(Duration::from_secs(1500)); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), eager_wf); starter.eager_start_with_worker(wf_name, &mut worker).await; @@ -34,7 +33,7 @@ async fn eager_wf_start_different_clients() { starter.workflow_options.task_timeout = Some(Duration::from_secs(1500)); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), eager_wf); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs index 8acf77d32..01d29f6b1 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs @@ -1,5 +1,4 @@ use crate::common::{CoreWfStarter, build_fake_sdk}; -use std::collections::HashSet; use temporalio_client::WorkflowClientTrait; use temporalio_common::protos::{ DEFAULT_WORKFLOW_TYPE, TestHistoryBuilder, @@ -10,7 +9,7 @@ use temporalio_common::protos::{ enums::v1::EventType, }, }; -use temporalio_common::worker::WorkerTaskType; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::{WfContext, WorkflowResult}; use temporalio_sdk_core::test_help::MockPollCfg; use uuid::Uuid; @@ -33,7 +32,7 @@ async fn sends_modify_wf_props() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.register_wf(wf_name, memo_upserter); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/nexus.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/nexus.rs index ebac6509f..42ec1216c 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/nexus.rs @@ -4,7 +4,6 @@ use crate::{ }; use anyhow::bail; use assert_matches::assert_matches; -use std::collections::HashSet; use std::{ sync::{ Arc, @@ -13,7 +12,7 @@ use std::{ time::Duration, }; use temporalio_client::{WfClientExt, WorkflowClientTrait, WorkflowOptions}; -use temporalio_common::worker::WorkerTaskType; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ errors::PollError, protos::{ @@ -59,10 +58,11 @@ async fn nexus_basic( ) { let wf_name = "nexus_basic"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(HashSet::from([ - WorkerTaskType::Workflows, - WorkerTaskType::Nexus, - ])); + starter.worker_config.task_types(WorkerTaskTypes { + enable_workflows: true, + enable_activities: false, + enable_nexus: true, + }); let mut worker = starter.worker().await; let core_worker = starter.get_worker().await; @@ -207,10 +207,11 @@ async fn nexus_async( ) { let wf_name = "nexus_async"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(HashSet::from([ - WorkerTaskType::Workflows, - WorkerTaskType::Nexus, - ])); + starter.worker_config.task_types(WorkerTaskTypes { + enable_workflows: true, + enable_activities: false, + enable_nexus: true, + }); let mut worker = starter.worker().await; let core_worker = starter.get_worker().await; @@ -437,10 +438,11 @@ async fn nexus_async( async fn nexus_cancel_before_start() { let wf_name = "nexus_cancel_before_start"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(HashSet::from([ - WorkerTaskType::Workflows, - WorkerTaskType::Nexus, - ])); + starter.worker_config.task_types(WorkerTaskTypes { + enable_workflows: true, + enable_activities: false, + enable_nexus: true, + }); let mut worker = starter.worker().await; let endpoint = mk_nexus_endpoint(&mut starter).await; @@ -482,10 +484,11 @@ async fn nexus_cancel_before_start() { async fn nexus_must_complete_task_to_shutdown(#[values(true, false)] use_grace_period: bool) { let wf_name = "nexus_must_complete_task_to_shutdown"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(HashSet::from([ - WorkerTaskType::Workflows, - WorkerTaskType::Nexus, - ])); + starter.worker_config.task_types(WorkerTaskTypes { + enable_workflows: true, + enable_activities: false, + enable_nexus: true, + }); if use_grace_period { starter .worker_config @@ -585,10 +588,11 @@ async fn nexus_cancellation_types( ) { let wf_name = "nexus_cancellation_types"; let mut starter = CoreWfStarter::new(wf_name); - starter.worker_config.task_types(HashSet::from([ - WorkerTaskType::Workflows, - WorkerTaskType::Nexus, - ])); + starter.worker_config.task_types(WorkerTaskTypes { + enable_workflows: true, + enable_activities: false, + enable_nexus: true, + }); let mut worker = starter.worker().await; let core_worker = starter.get_worker().await; diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/patches.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/patches.rs index e4a0c9310..4b700c2e1 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/patches.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/patches.rs @@ -29,7 +29,8 @@ use temporalio_common::protos::{ }, }, }; -use temporalio_common::worker::WorkerTaskType; + +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::{ActivityOptions, WfContext, WorkflowResult}; use temporalio_sdk_core::test_help::{CoreInternalFlags, MockPollCfg, ResponseType}; use tokio::{join, sync::Notify}; @@ -58,7 +59,7 @@ async fn writes_change_markers() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), changes_wf); @@ -94,7 +95,7 @@ async fn can_add_change_markers() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), no_change_then_change_wf); @@ -120,7 +121,7 @@ async fn replaying_with_patch_marker() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), replay_with_change_marker_wf); @@ -139,7 +140,7 @@ async fn patched_on_second_workflow_task_is_deterministic() { starter .worker_config .max_cached_workflows(0_usize) - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; // Include a task failure as well to make sure that works static FAIL_ONCE: AtomicBool = AtomicBool::new(true); @@ -164,7 +165,7 @@ async fn can_remove_deprecated_patch_near_other_patch() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; let did_die = Arc::new(AtomicBool::new(false)); worker.register_wf(wf_name.to_owned(), move |ctx: WfContext| { @@ -197,7 +198,7 @@ async fn deprecated_patch_removal() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; let client = starter.get_client().await; let wf_id = starter.get_task_queue().to_string(); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/resets.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/resets.rs index 7b28f468a..2b31d9fd0 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/resets.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/resets.rs @@ -3,7 +3,6 @@ use crate::{ integ_tests::activity_functions::echo, }; use futures_util::StreamExt; -use std::collections::HashSet; use std::{ sync::{ Arc, @@ -18,7 +17,8 @@ use temporalio_common::protos::{ common::v1::WorkflowExecution, workflowservice::v1::ResetWorkflowExecutionRequest, }, }; -use temporalio_common::worker::WorkerTaskType; + +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::{LocalActivityOptions, WfContext}; use tokio::sync::Notify; use tonic::IntoRequest; @@ -31,7 +31,7 @@ async fn reset_workflow() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.fetch_results = false; let notify = Arc::new(Notify::new()); @@ -119,7 +119,7 @@ async fn reset_randomseed() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.fetch_results = false; let notify = Arc::new(Notify::new()); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/signals.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/signals.rs index 659c27a7d..fe6ee4c10 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/signals.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/signals.rs @@ -1,6 +1,6 @@ use crate::common::{ActivationAssertionsInterceptor, CoreWfStarter, build_fake_sdk}; use futures_util::StreamExt; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use temporalio_client::{SignalWithStartOptions, WorkflowClientTrait, WorkflowOptions}; use temporalio_common::protos::{ DEFAULT_WORKFLOW_TYPE, TestHistoryBuilder, @@ -16,7 +16,8 @@ use temporalio_common::protos::{ enums::v1::{CommandType, EventType}, }, }; -use temporalio_common::worker::WorkerTaskType; + +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::{ CancellableFuture, ChildWorkflowOptions, Signal, SignalWorkflowOptions, WfContext, WorkflowResult, @@ -49,7 +50,7 @@ async fn sends_signal_to_missing_wf() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), signal_sender); @@ -90,7 +91,7 @@ async fn sends_signal_to_other_wf() { let mut starter = CoreWfStarter::new("sends_signal_to_other_wf"); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.register_wf("sender", signal_sender); worker.register_wf("receiver", signal_receiver); @@ -121,7 +122,7 @@ async fn sends_signal_with_create_wf() { let mut starter = CoreWfStarter::new("sends_signal_with_create_wf"); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.register_wf("receiver_signal", signal_with_create_wf_receiver); @@ -169,7 +170,7 @@ async fn sends_signal_to_child() { let mut starter = CoreWfStarter::new("sends_signal_to_child"); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.register_wf("child_signaler", signals_child); worker.register_wf("child_receiver", signal_receiver); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs index d7d5feaab..e00c976c2 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs @@ -1,11 +1,10 @@ use crate::{common::CoreWfStarter, integ_tests::workflow_tests::timers::timer_wf}; -use std::collections::HashSet; use std::{ sync::atomic::{AtomicBool, AtomicUsize, Ordering}, time::Duration, }; use temporalio_client::WorkflowOptions; -use temporalio_common::worker::{PollerBehavior, WorkerTaskType}; +use temporalio_common::worker::{PollerBehavior, WorkerTaskTypes}; use temporalio_sdk::{WfContext, WorkflowResult}; use tokio::sync::Barrier; @@ -15,7 +14,7 @@ async fn timer_workflow_not_sticky() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])) + .task_types(WorkerTaskTypes::workflow_only()) .max_cached_workflows(0_usize); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), timer_wf); @@ -45,7 +44,7 @@ async fn timer_workflow_timeout_on_sticky() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); starter.workflow_options.task_timeout = Some(Duration::from_secs(2)); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), timer_timeout_wf); @@ -62,7 +61,7 @@ async fn cache_miss_ok() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])) + .task_types(WorkerTaskTypes::workflow_only()) .max_outstanding_workflow_tasks(2_usize) .max_cached_workflows(0_usize) .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(1_usize)); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/timers.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/timers.rs index 17aef0835..e5eacfc7f 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/timers.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/timers.rs @@ -1,8 +1,6 @@ -use std::collections::HashSet; -use std::time::Duration; - use crate::common::{CoreWfStarter, build_fake_sdk, init_core_and_create_wf}; -use temporalio_common::worker::WorkerTaskType; +use std::time::Duration; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ prost_dur, protos::{ @@ -32,7 +30,7 @@ async fn timer_workflow_workflow_driver() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), timer_wf); @@ -46,7 +44,7 @@ async fn timer_workflow_manual() { let core = starter.get_worker().await; starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let task = core.poll_workflow_activation().await.unwrap(); core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds( task.run_id, @@ -72,7 +70,7 @@ async fn timer_cancel_workflow() { let core = starter.get_worker().await; starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let task = core.poll_workflow_activation().await.unwrap(); core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds( task.run_id, @@ -133,7 +131,7 @@ async fn parallel_timers() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), parallel_timer_wf); diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs index 8fab9154f..af8551525 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs @@ -1,6 +1,5 @@ use crate::common::{CoreWfStarter, SEARCH_ATTR_INT, SEARCH_ATTR_TXT, build_fake_sdk}; use assert_matches::assert_matches; -use std::collections::HashSet; use std::{collections::HashMap, time::Duration}; use temporalio_client::{ GetWorkflowResultOpts, WfClientExt, WorkflowClientTrait, WorkflowExecutionResult, @@ -15,7 +14,7 @@ use temporalio_common::protos::{ enums::v1::EventType, }, }; -use temporalio_common::worker::WorkerTaskType; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::{WfContext, WfExitValue, WorkflowResult}; use temporalio_sdk_core::test_help::MockPollCfg; use uuid::Uuid; @@ -48,7 +47,7 @@ async fn sends_upsert() { let mut starter = CoreWfStarter::new(wf_name); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.register_wf(wf_name, search_attr_updater); diff --git a/crates/sdk-core/tests/manual_tests.rs b/crates/sdk-core/tests/manual_tests.rs index 3602544be..3dfc6d510 100644 --- a/crates/sdk-core/tests/manual_tests.rs +++ b/crates/sdk-core/tests/manual_tests.rs @@ -14,14 +14,13 @@ use futures_util::{ stream::FuturesUnordered, }; use rand::{Rng, SeedableRng}; -use std::collections::HashSet; use std::{ mem, net::SocketAddr, time::{Duration, Instant}, }; use temporalio_client::{GetWorkflowResultOpts, WfClientExt, WorkflowClientTrait, WorkflowOptions}; -use temporalio_common::worker::WorkerTaskType; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ protos::coresdk::AsJsonPayloadExt, telemetry::PrometheusExporterOptionsBuilder, worker::PollerBehavior, @@ -216,7 +215,7 @@ async fn poller_load_sustained() { maximum: 200, initial: 5, }) - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut worker = starter.worker().await; worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move { let sigchan = ctx.make_signal_channel(SIGNAME).map(Ok); diff --git a/crates/sdk-core/tests/shared_tests/mod.rs b/crates/sdk-core/tests/shared_tests/mod.rs index e1a7a9b57..ccb3d2ee1 100644 --- a/crates/sdk-core/tests/shared_tests/mod.rs +++ b/crates/sdk-core/tests/shared_tests/mod.rs @@ -1,13 +1,12 @@ //! Shared tests that are meant to be run against both local dev server and cloud use crate::common::CoreWfStarter; -use std::collections::HashSet; use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; use temporalio_common::protos::temporal::api::{ enums::v1::{EventType, WorkflowTaskFailedCause::GrpcMessageTooLarge}, history::v1::history_event::Attributes::WorkflowTaskFailedEventAttributes, }; -use temporalio_common::worker::WorkerTaskType; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_sdk::WfContext; pub(crate) mod priority; @@ -19,7 +18,7 @@ pub(crate) async fn grpc_message_too_large() { .unwrap(); starter .worker_config - .task_types(HashSet::from([WorkerTaskType::Workflows])); + .task_types(WorkerTaskTypes::workflow_only()); let mut core = starter.worker().await; static OVERSIZE_GRPC_MESSAGE_RUN: AtomicBool = AtomicBool::new(false); From 4044f5710ade9032c37000dbba43b217bb68866a Mon Sep 17 00:00:00 2001 From: Andrew Yuan Date: Sun, 16 Nov 2025 23:14:42 -0800 Subject: [PATCH 7/8] Clean up tests --- crates/sdk-core/src/core_tests/workers.rs | 602 +----------------- .../sdk-core/src/test_help/integ_helpers.rs | 10 +- crates/sdk-core/src/worker/mod.rs | 22 +- .../tests/integ_tests/worker_tests.rs | 174 +---- 4 files changed, 43 insertions(+), 765 deletions(-) diff --git a/crates/sdk-core/src/core_tests/workers.rs b/crates/sdk-core/src/core_tests/workers.rs index 0477d362e..b84b6b14f 100644 --- a/crates/sdk-core/src/core_tests/workers.rs +++ b/crates/sdk-core/src/core_tests/workers.rs @@ -1,4 +1,4 @@ -use temporalio_common::protos::temporal::api::workflowservice::v1::RespondNexusTaskCompletedResponse; +use crate::test_help::QueueResponse; use crate::{ PollError, prost_dur, test_help::{ @@ -15,6 +15,17 @@ use crate::{ }; use futures_util::{stream, stream::StreamExt}; use std::{cell::RefCell, time::Duration}; +use temporalio_common::protos::coresdk::ActivityTaskCompletion; +use temporalio_common::protos::coresdk::activity_result::ActivityExecutionResult; +use temporalio_common::protos::coresdk::nexus::NexusTaskCompletion; +use temporalio_common::protos::temporal::api::nexus::v1::{ + Request as NexusRequest, Response as NexusResponse, StartOperationRequest, + StartOperationResponse, start_operation_response, +}; +use temporalio_common::protos::temporal::api::workflowservice::v1::RespondNexusTaskCompletedResponse; +use temporalio_common::protos::temporal::api::workflowservice::v1::{ + PollActivityTaskQueueResponse, RespondActivityTaskCompletedResponse, +}; use temporalio_common::{ Worker, protos::{ @@ -24,23 +35,16 @@ use temporalio_common::{ workflow_commands::{CompleteWorkflowExecution, StartTimer, workflow_command}, workflow_completion::WorkflowActivationCompletion, }, + temporal::api::common::v1::{ActivityType, WorkflowExecution}, temporal::api::workflowservice::v1::{ - PollWorkflowTaskQueueResponse, PollNexusTaskQueueResponse, RespondWorkflowTaskCompletedResponse, - ShutdownWorkerResponse, + PollNexusTaskQueueResponse, PollWorkflowTaskQueueResponse, + RespondWorkflowTaskCompletedResponse, ShutdownWorkerResponse, }, - temporal::api::common::v1::{ActivityType, WorkflowExecution}, test_utils::start_timer_cmd, }, worker::{PollerBehavior, WorkerTaskTypes}, }; use tokio::sync::{Barrier, watch}; -use tokio::time::timeout; -use temporalio_common::protos::coresdk::activity_result::ActivityExecutionResult; -use temporalio_common::protos::coresdk::ActivityTaskCompletion; -use temporalio_common::protos::coresdk::nexus::NexusTaskCompletion; -use temporalio_common::protos::temporal::api::nexus::v1::{Request as NexusRequest, Response as NexusResponse, StartOperationRequest, StartOperationResponse, start_operation_response}; -use temporalio_common::protos::temporal::api::workflowservice::v1::{PollActivityTaskQueueResponse, RespondActivityTaskCompletedResponse}; -use crate::test_help::QueueResponse; #[tokio::test] async fn after_shutdown_of_worker_get_shutdown_err() { @@ -378,553 +382,6 @@ async fn worker_shutdown_api(#[case] use_cache: bool, #[case] api_success: bool) }); } -#[tokio::test] -async fn test_worker_type_shutdown_all_combinations() { - let combinations = [ - (WorkerTaskTypes::workflow_only(), "workflows only"), - (WorkerTaskTypes::activity_only(), "activities only"), - (WorkerTaskTypes::nexus_only(), "nexus only"), - ( - WorkerTaskTypes { - enable_workflows: true, - enable_activities: true, - enable_nexus: false, - }, - "workflows + activities", - ), - ( - WorkerTaskTypes { - enable_workflows: true, - enable_activities: false, - enable_nexus: true, - }, - "workflows + nexus", - ), - ( - WorkerTaskTypes { - enable_workflows: false, - enable_activities: true, - enable_nexus: true, - }, - "activities + nexus", - ), - (WorkerTaskTypes::all(), "all types"), - ]; - - for (task_types, description) in combinations { - let mut cfg = test_worker_cfg(); - cfg.task_types(task_types); - - let mock_inputs = MockWorkerInputs { - config: cfg.build().unwrap(), - ..Default::default() - }; - let worker = mock_worker(MocksHolder::from_mock_worker( - mock_worker_client(), - mock_inputs, - )); - - let shutdown_result = timeout(Duration::from_secs(1), worker.shutdown()).await; - assert!( - shutdown_result.is_ok(), - "worker shutdown should not hang for {description}" - ); - } -} - -// #[tokio::test] -// async fn test_type_shutdown_with_tasks2() { -// telemetry_init_fallback(); -// let combinations = [ -// // (WorkerTaskTypes::workflow_only(), "workflows only"), -// (WorkerTaskTypes::activity_only(), "activities only"), -// // (WorkerTaskTypes::nexus_only(), "nexus only"), -// // ( -// // WorkerTaskTypes { -// // enable_workflows: true, -// // enable_activities: true, -// // enable_nexus: false, -// // }, -// // "workflows + activities", -// // ), -// // ( -// // WorkerTaskTypes { -// // enable_workflows: true, -// // enable_activities: false, -// // enable_nexus: true, -// // }, -// // "workflows + nexus", -// // ), -// // ( -// // WorkerTaskTypes { -// // enable_workflows: false, -// // enable_activities: true, -// // enable_nexus: true, -// // }, -// // "activities + nexus", -// // ), -// // (WorkerTaskTypes::all(), "all types"), -// ]; -// -// for (task_types, description) in combinations { -// eprintln!("\nTesting: {description}"); -// -// let mut mock_client = mock_worker_client(); -// if task_types.enable_workflows { -// mock_client -// .expect_complete_workflow_task() -// .times(1) -// .returning(|_| Ok(RespondWorkflowTaskCompletedResponse::default())); -// // mock_client.expect_poll_workflow_task().times(1).returning(|_, _| Ok(PollWorkflowTaskQueueResponse::default())); -// } -// if task_types.enable_activities { -// mock_client.expect_poll_activity_task().times(1).returning(|_, _| Ok(PollActivityTaskQueueResponse { -// task_token: vec![1], -// ..Default::default() -// })); -// mock_client.expect_complete_activity_task().times(1).returning(|_, _| Ok(RespondActivityTaskCompletedResponse::default())); -// } -// if task_types.enable_nexus { -// mock_client.expect_poll_nexus_task().times(1).returning(|_, _| Ok(Default::default())); -// mock_client.expect_complete_nexus_task().times(1).returning(|_, _| Ok(RespondNexusTaskCompletedResponse::default())); -// } -// -// let t = canned_histories::single_timer("1"); -// let mut mh = MockPollCfg::from_resp_batches("fakeid", t, [1], mock_client); -// mh.enforce_correct_number_of_polls = false; -// let act_tasks: Vec> = vec![QueueResponse::from(PollActivityTaskQueueResponse::default())]; -// mh.activity_responses = Some(act_tasks); -// let mut mock = build_mock_pollers(mh); -// mock.worker_cfg(|w| { -// w.task_types = task_types; -// w.max_cached_workflows = 1; -// }); -// let worker = mock_worker(mock); -// -// println!("a"); -// if task_types.enable_workflows { -// let activation = worker.poll_workflow_activation().await.unwrap(); -// let _ = worker -// .complete_workflow_activation(WorkflowActivationCompletion::from_cmds( -// activation.run_id, -// vec![], -// )) -// .await; -// } -// -// println!("a"); -// if task_types.enable_activities { -// let task = worker.poll_activity_task().await.unwrap(); -// worker.complete_activity_task(ActivityTaskCompletion { -// task_token: task.task_token, -// result: Some(ActivityExecutionResult::ok(Default::default())), -// }) -// .await.unwrap(); -// } -// -// println!("a"); -// if task_types.enable_nexus { -// let task = worker.poll_nexus_task().await.unwrap(); -// worker -// .complete_nexus_task(NexusTaskCompletion { -// task_token: task.task_token().to_vec(), -// status: None, -// }) -// .await.unwrap(); -// -// } -// } -// } - -#[tokio::test] -async fn test_task_type_activity_only() { - let queue = "activity-only-queue"; - - let mut client = mock_worker_client(); - client - .expect_complete_activity_task() - .returning(|_, _| Ok(RespondActivityTaskCompletedResponse::default())); - - let mut act_task = PollActivityTaskQueueResponse::default(); - act_task.task_token = b"act-task".to_vec(); - act_task.workflow_execution = Some(WorkflowExecution { - workflow_id: "activity-only".to_string(), - run_id: "run-id".to_string(), - }); - act_task.activity_id = "activity".to_string(); - act_task.activity_type = Some(ActivityType { - name: "activity".to_string(), - }); - - let mut mocks = MocksHolder::from_client_with_activities( - client, - vec![QueueResponse::from(act_task)] - ); - mocks.worker_cfg(|w| { - w.task_queue = queue.to_string(); - w.task_types = WorkerTaskTypes::activity_only(); - w.skip_client_worker_set_check = true; - }); - let worker = mock_worker(mocks); - - let activity_task = worker.poll_activity_task().await.unwrap(); - worker - .complete_activity_task(ActivityTaskCompletion { - task_token: activity_task.task_token, - result: Some(ActivityExecutionResult::ok(vec![1].into())), - }) - .await - .unwrap(); - - worker.initiate_shutdown(); - assert_matches!( - worker.poll_activity_task().await.unwrap_err(), - PollError::ShutDown - ); - worker.shutdown().await; - worker.finalize_shutdown().await; -} - -#[tokio::test] -async fn test_task_type_nexus_only() { - let shared_queue = "shared-nexus-queue"; - - let t = canned_histories::single_timer("wf-only"); - let wf_cfg = MockPollCfg::from_resp_batches("wf-only", t, [1], mock_worker_client()); - let mut wf_mocks = build_mock_pollers(wf_cfg); - wf_mocks.worker_cfg(|w| { - w.task_queue = shared_queue.to_string(); - w.task_types = WorkerTaskTypes::workflow_only(); - w.skip_client_worker_set_check = true; - }); - let workflow_worker = mock_worker(wf_mocks); - - let mut nex_client = mock_worker_client(); - nex_client - .expect_complete_nexus_task() - .returning(|_, _| Ok(RespondNexusTaskCompletedResponse::default())); - let nex_task = PollNexusTaskQueueResponse { - task_token: b"nex-task".to_vec(), - request: Some(NexusRequest { - header: Default::default(), - scheduled_time: None, - variant: Some(temporalio_common::protos::temporal::api::nexus::v1::request::Variant::StartOperation( - StartOperationRequest { - service: "test-service".to_string(), - operation: "test-operation".to_string(), - request_id: "test-request-id".to_string(), - callback: "".to_string(), - payload: None, - callback_header: Default::default(), - links: vec![], - } - )), - }), - poller_scaling_decision: None, - }; - let mut nex_mocks = - MocksHolder::from_client_with_nexus(nex_client, vec![QueueResponse::from(nex_task)]); - nex_mocks.worker_cfg(|w| { - w.task_queue = shared_queue.to_string(); - w.task_types = WorkerTaskTypes::nexus_only(); - w.skip_client_worker_set_check = true; - }); - let nexus_worker = mock_worker(nex_mocks); - - let activation = workflow_worker.poll_workflow_activation().await.unwrap(); - workflow_worker - .complete_workflow_activation(WorkflowActivationCompletion::from_cmds( - activation.run_id.clone(), - vec![CompleteWorkflowExecution::default().into()], - )) - .await - .unwrap(); - - let nexus_task = nexus_worker.poll_nexus_task().await.unwrap(); - nexus_worker - .complete_nexus_task(NexusTaskCompletion { - task_token: nexus_task.task_token().to_vec(), - status: Some(temporalio_common::protos::coresdk::nexus::nexus_task_completion::Status::Completed( - NexusResponse { - variant: Some(temporalio_common::protos::temporal::api::nexus::v1::response::Variant::StartOperation( - StartOperationResponse { - variant: Some(start_operation_response::Variant::SyncSuccess( - start_operation_response::Sync { - payload: None, - links: vec![], - } - )), - } - )), - } - )), - }) - .await - .unwrap(); - - let workflow_shutdown = workflow_worker.drain_pollers_and_shutdown(); - let nexus_shutdown = async move { - nexus_worker.initiate_shutdown(); - assert_matches!( - nexus_worker.poll_nexus_task().await.unwrap_err(), - PollError::ShutDown - ); - nexus_worker.shutdown().await; - nexus_worker.finalize_shutdown().await; - }; - - timeout(Duration::from_secs(5), async { - tokio::join!(workflow_shutdown, nexus_shutdown); - }) - .await - .expect("workers failed to shutdown"); -} - -#[tokio::test] -async fn test_task_type_workflow_and_activity() { - let shared_queue = "shared-wf-act-queue"; - - let mut client = mock_worker_client(); - client - .expect_complete_activity_task() - .returning(|_, _| Ok(RespondActivityTaskCompletedResponse::default())); - - let t = canned_histories::single_timer("wf-act"); - let wf_cfg = MockPollCfg::from_resp_batches("wf-act", t, [1], client); - let mut act_task = PollActivityTaskQueueResponse::default(); - act_task.task_token = b"act-task".to_vec(); - act_task.workflow_execution = Some(WorkflowExecution { - workflow_id: "wf-act".to_string(), - run_id: "run-id".to_string(), - }); - act_task.activity_id = "activity".to_string(); - act_task.activity_type = Some(ActivityType { - name: "activity".to_string(), - }); - let mut mocks = build_mock_pollers(wf_cfg); - mocks.set_act_poller_from_resps(vec![QueueResponse::from(act_task)]); - mocks.worker_cfg(|w| { - w.task_queue = shared_queue.to_string(); - w.task_types = WorkerTaskTypes { - enable_workflows: true, - enable_activities: true, - enable_nexus: false, - }; - w.skip_client_worker_set_check = true; - }); - let worker = mock_worker(mocks); - - let activation = worker.poll_workflow_activation().await.unwrap(); - worker - .complete_workflow_activation(WorkflowActivationCompletion::from_cmds( - activation.run_id.clone(), - vec![CompleteWorkflowExecution::default().into()], - )) - .await - .unwrap(); - - let activity_task = worker.poll_activity_task().await.unwrap(); - worker - .complete_activity_task(ActivityTaskCompletion { - task_token: activity_task.task_token, - result: Some(ActivityExecutionResult::ok(vec![1].into())), - }) - .await - .unwrap(); - - let shutdown = worker.drain_pollers_and_shutdown(); - timeout(Duration::from_secs(5), shutdown) - .await - .expect("worker failed to shutdown"); -} - -#[tokio::test] -async fn test_task_type_workflow_and_nexus() { - let shared_queue = "shared-wf-nex-queue"; - - let mut client = mock_worker_client(); - client - .expect_complete_nexus_task() - .returning(|_, _| Ok(RespondNexusTaskCompletedResponse::default())); - - let t = canned_histories::single_timer("wf-nex"); - let wf_cfg = MockPollCfg::from_resp_batches("wf-nex", t, [1], client); - let nex_task = PollNexusTaskQueueResponse { - task_token: b"nex-task".to_vec(), - request: Some(NexusRequest { - header: Default::default(), - scheduled_time: None, - variant: Some(temporalio_common::protos::temporal::api::nexus::v1::request::Variant::StartOperation( - StartOperationRequest { - service: "test-service".to_string(), - operation: "test-operation".to_string(), - request_id: "test-request-id".to_string(), - callback: "".to_string(), - payload: None, - callback_header: Default::default(), - links: vec![], - } - )), - }), - poller_scaling_decision: None, - }; - let mut mocks = build_mock_pollers(wf_cfg); - mocks.set_nexus_poller_from_resps(vec![QueueResponse::from(nex_task)]); - mocks.worker_cfg(|w| { - w.task_queue = shared_queue.to_string(); - w.task_types = WorkerTaskTypes { - enable_workflows: true, - enable_activities: false, - enable_nexus: true, - }; - w.skip_client_worker_set_check = true; - }); - let worker = mock_worker(mocks); - - let activation = worker.poll_workflow_activation().await.unwrap(); - worker - .complete_workflow_activation(WorkflowActivationCompletion::from_cmds( - activation.run_id.clone(), - vec![CompleteWorkflowExecution::default().into()], - )) - .await - .unwrap(); - - let nexus_task = worker.poll_nexus_task().await.unwrap(); - worker - .complete_nexus_task(NexusTaskCompletion { - task_token: nexus_task.task_token().to_vec(), - status: Some(temporalio_common::protos::coresdk::nexus::nexus_task_completion::Status::Completed( - NexusResponse { - variant: Some(temporalio_common::protos::temporal::api::nexus::v1::response::Variant::StartOperation( - StartOperationResponse { - variant: Some(start_operation_response::Variant::SyncSuccess( - start_operation_response::Sync { - payload: None, - links: vec![], - } - )), - } - )), - } - )), - }) - .await - .unwrap(); - - worker.initiate_shutdown(); - // Drain nexus poller - assert_matches!( - worker.poll_nexus_task().await.unwrap_err(), - PollError::ShutDown - ); - worker.shutdown().await; - worker.finalize_shutdown().await; -} - -#[tokio::test] -async fn test_task_type_activity_and_nexus() { - let shared_queue = "shared-act-nex-queue"; - - let mut client = mock_worker_client(); - client - .expect_complete_activity_task() - .returning(|_, _| Ok(RespondActivityTaskCompletedResponse::default())); - client - .expect_complete_nexus_task() - .returning(|_, _| Ok(RespondNexusTaskCompletedResponse::default())); - - let mut act_task = PollActivityTaskQueueResponse::default(); - act_task.task_token = b"act-task".to_vec(); - act_task.workflow_execution = Some(WorkflowExecution { - workflow_id: "act-nex".to_string(), - run_id: "run-id".to_string(), - }); - act_task.activity_id = "activity".to_string(); - act_task.activity_type = Some(ActivityType { - name: "activity".to_string(), - }); - - let nex_task = PollNexusTaskQueueResponse { - task_token: b"nex-task".to_vec(), - request: Some(NexusRequest { - header: Default::default(), - scheduled_time: None, - variant: Some(temporalio_common::protos::temporal::api::nexus::v1::request::Variant::StartOperation( - StartOperationRequest { - service: "test-service".to_string(), - operation: "test-operation".to_string(), - request_id: "test-request-id".to_string(), - callback: "".to_string(), - payload: None, - callback_header: Default::default(), - links: vec![], - } - )), - }), - poller_scaling_decision: None, - }; - - let mut mocks = MocksHolder::from_client_with_activities(client, vec![QueueResponse::from(act_task)]); - mocks.set_nexus_poller_from_resps(vec![QueueResponse::from(nex_task)]); - mocks.worker_cfg(|w| { - w.task_queue = shared_queue.to_string(); - w.task_types = WorkerTaskTypes { - enable_workflows: false, - enable_activities: true, - enable_nexus: true, - }; - w.skip_client_worker_set_check = true; - }); - let worker = mock_worker(mocks); - - let activity_task = worker.poll_activity_task().await.unwrap(); - worker - .complete_activity_task(ActivityTaskCompletion { - task_token: activity_task.task_token, - result: Some(ActivityExecutionResult::ok(vec![1].into())), - }) - .await - .unwrap(); - - let nexus_task = worker.poll_nexus_task().await.unwrap(); - worker - .complete_nexus_task(NexusTaskCompletion { - task_token: nexus_task.task_token().to_vec(), - status: Some(temporalio_common::protos::coresdk::nexus::nexus_task_completion::Status::Completed( - NexusResponse { - variant: Some(temporalio_common::protos::temporal::api::nexus::v1::response::Variant::StartOperation( - StartOperationResponse { - variant: Some(start_operation_response::Variant::SyncSuccess( - start_operation_response::Sync { - payload: None, - links: vec![], - } - )), - } - )), - } - )), - }) - .await - .unwrap(); - - worker.initiate_shutdown(); - // Drain activity poller - assert_matches!( - worker.poll_activity_task().await.unwrap_err(), - PollError::ShutDown - ); - // Drain nexus poller - assert_matches!( - worker.poll_nexus_task().await.unwrap_err(), - PollError::ShutDown - ); - worker.shutdown().await; - worker.finalize_shutdown().await; -} - -// Helper functions for creating consistent test data fn create_test_activity_task() -> PollActivityTaskQueueResponse { PollActivityTaskQueueResponse { task_token: b"act-task".to_vec(), @@ -1007,21 +464,20 @@ async fn test_task_type_combinations_unified( ) { let mut client = mock_worker_client(); - // Setup expectations based on enabled types (only if with_task is true) - if enable_activities && with_task { - client - .expect_complete_activity_task() - .returning(|_, _| Ok(RespondActivityTaskCompletedResponse::default())); - } - if enable_nexus && with_task { - client - .expect_complete_nexus_task() - .returning(|_, _| Ok(RespondNexusTaskCompletedResponse::default())); + if with_task { + if enable_activities { + client + .expect_complete_activity_task() + .returning(|_, _| Ok(RespondActivityTaskCompletedResponse::default())); + } + if enable_nexus { + client + .expect_complete_nexus_task() + .returning(|_, _| Ok(RespondNexusTaskCompletedResponse::default())); + } } - // Build worker based on enabled task types let mut mocks = if enable_workflows && with_task { - // When workflows are enabled AND we have tasks, use build_mock_pollers as the base let t = canned_histories::single_timer(queue_name); let wf_cfg = MockPollCfg::from_resp_batches(queue_name, t, [1], client); let mut mocks = build_mock_pollers(wf_cfg); @@ -1033,8 +489,6 @@ async fn test_task_type_combinations_unified( } mocks } else { - // When workflows are disabled OR idle (no tasks), use from_client_with_custom - // Provide workflow stream if workflows enabled (but no tasks in it) let wft_stream = if enable_workflows { Some(stream::empty()) } else { @@ -1064,7 +518,6 @@ async fn test_task_type_combinations_unified( }); let worker = mock_worker(mocks); - // Poll and complete tasks ONLY if with_task is true if with_task { if enable_workflows { let activation = worker.poll_workflow_activation().await.unwrap(); @@ -1097,7 +550,6 @@ async fn test_task_type_combinations_unified( } } - // Shutdown (works whether tasks were processed or not) worker.initiate_shutdown(); if enable_workflows { assert_matches!( @@ -1120,5 +572,3 @@ async fn test_task_type_combinations_unified( worker.shutdown().await; worker.finalize_shutdown().await; } - - diff --git a/crates/sdk-core/src/test_help/integ_helpers.rs b/crates/sdk-core/src/test_help/integ_helpers.rs index bfede55a1..bc7faf456 100644 --- a/crates/sdk-core/src/test_help/integ_helpers.rs +++ b/crates/sdk-core/src/test_help/integ_helpers.rs @@ -233,6 +233,7 @@ impl MocksHolder { } /// Helper to create and set an activity poller from responses + #[cfg(test)] pub(crate) fn set_act_poller_from_resps(&mut self, act_tasks: ACT) where ACT: IntoIterator>, @@ -243,6 +244,7 @@ impl MocksHolder { } /// Helper to create and set a nexus poller from responses + #[cfg(test)] pub(crate) fn set_nexus_poller_from_resps(&mut self, nexus_tasks: NEX) where NEX: IntoIterator>, @@ -388,9 +390,11 @@ impl MocksHolder { client: impl WorkerClient + 'static, stream: impl Stream + Send + 'static, ) -> Self { - let wft_stream = Some(stream - .map(|r| Ok(r.try_into().expect("Mock responses must be valid work"))) - .boxed()); + let wft_stream = Some( + stream + .map(|r| Ok(r.try_into().expect("Mock responses must be valid work"))) + .boxed(), + ); let mock_worker = MockWorkerInputs { wft_stream, act_poller: None, diff --git a/crates/sdk-core/src/worker/mod.rs b/crates/sdk-core/src/worker/mod.rs index ce215997c..d6c0ceb79 100644 --- a/crates/sdk-core/src/worker/mod.rs +++ b/crates/sdk-core/src/worker/mod.rs @@ -115,13 +115,13 @@ pub struct Worker { client: Arc, /// Worker instance key, unique identifier for this worker worker_instance_key: Uuid, - /// Manages all workflows and WFT processing. None if workflow polling is disabled. + /// Manages all workflows and WFT processing. None if workflow polling is disabled workflows: Option, /// Manages activity tasks for this worker/task queue at_task_mgr: Option, - /// Manages local activities. None if workflow polling is disabled (local activities require workflows). + /// Manages local activities. None if workflow polling is disabled (local activities require workflows) local_act_mgr: Option>, - /// Manages Nexus tasks. None if nexus polling is disabled. + /// Manages Nexus tasks nexus_mgr: Option, /// Has shutdown been called? shutdown_token: CancellationToken, @@ -524,27 +524,21 @@ impl Worker { act_poller, nexus_poller, } => { - println!("wft_stream {:?}", wft_stream.is_some()); let wft_stream = config .task_types .enable_workflows .then_some(wft_stream) .flatten(); - println!("wft_stream after {:?}", wft_stream.is_some()); - println!("act_poller {:?}", act_poller.is_some()); let act_poller = config .task_types .enable_activities .then_some(act_poller) .flatten(); - println!("act_poller after {:?}", act_poller.is_some()); - println!("nexus_poller {:?}", nexus_poller.is_some()); let nexus_poller = config .task_types .enable_nexus .then_some(nexus_poller) .flatten(); - println!("nexus_poller after {:?}", nexus_poller.is_some()); let ap = act_poller .map(|ap| MockPermittedPollBuffer::new(Arc::new(act_slots.clone()), ap)); @@ -965,10 +959,12 @@ impl Worker { if let Err(ref e) = r { // This is covering the situation where WFT pollers dying is the reason for shutdown self.initiate_shutdown(); - if matches!(e, PollError::ShutDown) - && let Some(la_mgr) = &self.local_act_mgr - { - la_mgr.workflows_have_shutdown(); + if matches!(e, PollError::ShutDown) { + if let Some(la_mgr) = &self.local_act_mgr { + la_mgr.workflows_have_shutdown(); + } else { + dbg_panic!("la_mgr should be set if worker supports workflows"); + } } } r diff --git a/crates/sdk-core/tests/integ_tests/worker_tests.rs b/crates/sdk-core/tests/integ_tests/worker_tests.rs index 9cf825adf..a2ddb9e0e 100644 --- a/crates/sdk-core/tests/integ_tests/worker_tests.rs +++ b/crates/sdk-core/tests/integ_tests/worker_tests.rs @@ -3,7 +3,6 @@ use crate::{ CoreWfStarter, fake_grpc_server::fake_server, get_integ_runtime_options, get_integ_server_options, get_integ_telem_options, mock_sdk_cfg, }, - integ_tests::mk_nexus_endpoint, shared_tests, }; use assert_matches::assert_matches; @@ -20,7 +19,6 @@ use std::{ time::Duration, }; use temporalio_client::WorkflowOptions; -use temporalio_common::protos::coresdk::nexus::NexusOperationCancellationType; use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ Worker, @@ -30,7 +28,6 @@ use temporalio_common::{ coresdk::{ ActivityTaskCompletion, activity_result::ActivityExecutionResult, - nexus::{NexusTaskCompletion, nexus_task_completion}, workflow_completion::{ Failure, WorkflowActivationCompletion, workflow_activation_completion::Status, }, @@ -48,10 +45,6 @@ use temporalio_common::{ self as EventAttributes, WorkflowTaskFailedEventAttributes, }, }, - nexus::v1::{ - Response as NexusResponse, StartOperationResponse, response, - start_operation_response, - }, workflowservice::v1::{ GetWorkflowExecutionHistoryResponse, PollActivityTaskQueueResponse, RespondActivityTaskCompletedResponse, @@ -65,8 +58,7 @@ use temporalio_common::{ }, }; use temporalio_sdk::{ - ActivityOptions, LocalActivityOptions, NexusOperationOptions, WfContext, - interceptors::WorkerInterceptor, + ActivityOptions, LocalActivityOptions, WfContext, interceptors::WorkerInterceptor, }; use temporalio_sdk_core::{ CoreRuntime, ResourceBasedTuner, ResourceSlotOptions, TunerBuilder, init_worker, @@ -75,9 +67,7 @@ use temporalio_sdk_core::{ hist_to_poll_resp, mock_worker, mock_worker_client, }, }; -use tokio::join; use tokio::sync::{Barrier, Notify, Semaphore}; -use tokio::time::timeout; use tokio_util::sync::CancellationToken; use uuid::Uuid; @@ -905,165 +895,3 @@ async fn shutdown_worker_not_retried() { drain_pollers_and_shutdown(&worker).await; assert_eq!(shutdown_call_count.load(Ordering::Relaxed), 1); } - -#[tokio::test] -async fn worker_type_shutdown_all_combinations() { - let combinations = [ - (WorkerTaskTypes::workflow_only(), "workflows only"), - (WorkerTaskTypes::activity_only(), "activities only"), - (WorkerTaskTypes::nexus_only(), "nexus only"), - ( - WorkerTaskTypes { - enable_workflows: true, - enable_activities: true, - enable_nexus: false, - }, - "workflows + activities", - ), - ( - WorkerTaskTypes { - enable_workflows: true, - enable_activities: false, - enable_nexus: true, - }, - "workflows + nexus", - ), - ( - WorkerTaskTypes { - enable_workflows: false, - enable_activities: true, - enable_nexus: true, - }, - "activities + nexus", - ), - (WorkerTaskTypes::all(), "all types"), - ]; - - for (task_types, description) in combinations { - let wf_type = format!("worker_type_shutdown_{}", description.replace(" ", "_")); - let mut starter = CoreWfStarter::new(&wf_type); - if !task_types.enable_workflows { - starter.worker_config.max_cached_workflows(0usize); - } - starter.worker_config.task_types(task_types); - - let worker = starter.get_worker().await; - - let shutdown_result = timeout(Duration::from_secs(1), async { - drain_pollers_and_shutdown(&worker).await; - }) - .await; - - assert!( - shutdown_result.is_ok(), - "Worker shutdown should not hang for {description}" - ); - } -} - -#[tokio::test] -async fn test_type_shutdown_with_tasks1() { - let combinations = [ - (WorkerTaskTypes::workflow_only(), "workflows only"), - (WorkerTaskTypes::activity_only(), "activities only"), - (WorkerTaskTypes::nexus_only(), "nexus only"), - ( - WorkerTaskTypes { - enable_workflows: true, - enable_activities: true, - enable_nexus: false, - }, - "workflows + activities", - ), - ( - WorkerTaskTypes { - enable_workflows: true, - enable_activities: false, - enable_nexus: true, - }, - "workflows + nexus", - ), - ( - WorkerTaskTypes { - enable_workflows: false, - enable_activities: true, - enable_nexus: true, - }, - "activities + nexus", - ), - (WorkerTaskTypes::all(), "all types"), - ]; - - for (task_types, description) in combinations { - eprintln!("\nTesting: {description}"); - let wf_type = format!( - "test_type_shutdown_with_tasks_{}", - description.replace(" ", "_") - ); - let mut starter = CoreWfStarter::new(&wf_type); - if !task_types.enable_workflows { - starter.worker_config.max_cached_workflows(0usize); - } - starter.worker_config.task_types(task_types); - let core_worker = starter.get_worker().await; - - // Poll and complete one task of each enabled type directly - if task_types.enable_workflows { - starter.start_wf().await; - if let Ok(Ok(activation)) = timeout( - Duration::from_secs(1), - core_worker.poll_workflow_activation(), - ) - .await - { - println!("AAAAA workflow activation: {:?}", activation); - let _ = core_worker - .complete_workflow_activation(WorkflowActivationCompletion::from_cmds( - activation.run_id, - vec![], - )) - .await; - } - } - - if task_types.enable_activities { - let task = core_worker.poll_activity_task().await.unwrap(); - println!("AAAAA activity task: {:?}", task); - let _ = core_worker - .complete_activity_task(ActivityTaskCompletion { - task_token: task.task_token, - result: Some(ActivityExecutionResult::ok(Default::default())), - }) - .await; - - } - - if task_types.enable_nexus { - let task = core_worker.poll_nexus_task().await.unwrap(); - println!("AAAAA nexus task: {:?}", task); - let _ = core_worker - .complete_nexus_task(NexusTaskCompletion { - task_token: task.task_token().to_vec(), - status: Some(nexus_task_completion::Status::Completed(NexusResponse { - variant: Some(response::Variant::StartOperation( - StartOperationResponse { - variant: Some(start_operation_response::Variant::SyncSuccess( - start_operation_response::Sync { - payload: None, - links: vec![], - }, - )), - }, - )), - })), - }) - .await; - } - - // Test shutdown - timeout(Duration::from_secs(10), async { - drain_pollers_and_shutdown(&core_worker).await; - }) - .await.expect(&format!("shutdown must not hang for {description}")); - } -} From 5deef193524daffd73531f0c1c5c76098ae57fd0 Mon Sep 17 00:00:00 2001 From: Andrew Yuan Date: Mon, 17 Nov 2025 08:57:33 -0800 Subject: [PATCH 8/8] Remove default from task_types --- crates/common/src/worker.rs | 4 ---- crates/common/tests/worker_task_types_test.rs | 1 + crates/sdk-core/src/test_help/integ_helpers.rs | 2 ++ crates/sdk-core/tests/common/mod.rs | 7 ++++--- crates/sdk-core/tests/integ_tests/metrics_tests.rs | 1 + crates/sdk-core/tests/integ_tests/worker_tests.rs | 1 + 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/common/src/worker.rs b/crates/common/src/worker.rs index 7c27a3b05..df81f6a2a 100644 --- a/crates/common/src/worker.rs +++ b/crates/common/src/worker.rs @@ -119,11 +119,7 @@ pub struct WorkerConfig { pub nexus_task_poller_behavior: PollerBehavior, /// Specifies which task types this worker will poll for. /// - /// By default, workers poll for all task types (workflows, activities, and nexus). - /// You can restrict this to any combination. - /// /// Note: At least one task type must be specified or the worker will fail validation. - #[builder(default = "WorkerTaskTypes::all()")] pub task_types: WorkerTaskTypes, /// How long a workflow task is allowed to sit on the sticky queue before it is timed out /// and moved to the non-sticky queue where it may be picked up by any worker. diff --git a/crates/common/tests/worker_task_types_test.rs b/crates/common/tests/worker_task_types_test.rs index 44b50b5cb..8495a52f0 100644 --- a/crates/common/tests/worker_task_types_test.rs +++ b/crates/common/tests/worker_task_types_test.rs @@ -12,6 +12,7 @@ fn test_default_configuration_polls_all_types() { .namespace("default") .task_queue("test-queue") .versioning_strategy(default_versioning_strategy()) + .task_types(WorkerTaskTypes::all()) .build() .expect("Failed to build default config"); diff --git a/crates/sdk-core/src/test_help/integ_helpers.rs b/crates/sdk-core/src/test_help/integ_helpers.rs index bc7faf456..c0c47d75d 100644 --- a/crates/sdk-core/src/test_help/integ_helpers.rs +++ b/crates/sdk-core/src/test_help/integ_helpers.rs @@ -36,6 +36,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ Worker as WorkerTrait, errors::PollError, @@ -105,6 +106,7 @@ pub fn test_worker_cfg() -> WorkerConfigBuilder { build_id: "test_bin_id".to_string(), }) .ignore_evicts_on_shutdown(true) + .task_types(WorkerTaskTypes::all()) // Serial polling since it makes mocking much easier. .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(1_usize)); wcb diff --git a/crates/sdk-core/tests/common/mod.rs b/crates/sdk-core/tests/common/mod.rs index 5a1c400da..dbba4baf8 100644 --- a/crates/sdk-core/tests/common/mod.rs +++ b/crates/sdk-core/tests/common/mod.rs @@ -32,6 +32,7 @@ use temporalio_client::{ WfClientExt, WorkflowClientTrait, WorkflowExecutionInfo, WorkflowExecutionResult, WorkflowHandle, WorkflowOptions, }; +use temporalio_common::worker::WorkerTaskTypes; use temporalio_common::{ Worker as CoreWorker, protos::{ @@ -59,6 +60,8 @@ use temporalio_sdk::{ WorkerInterceptor, }, }; +#[cfg(any(feature = "test-utilities", test))] +pub(crate) use temporalio_sdk_core::test_help::NAMESPACE; use temporalio_sdk_core::{ ClientOptions, ClientOptionsBuilder, CoreRuntime, RuntimeOptions, RuntimeOptionsBuilder, WorkerConfig, WorkerConfigBuilder, init_replay_worker, init_worker, @@ -71,9 +74,6 @@ use tonic::IntoRequest; use tracing::{debug, warn}; use url::Url; use uuid::Uuid; - -#[cfg(any(feature = "test-utilities", test))] -pub(crate) use temporalio_sdk_core::test_help::NAMESPACE; /// The env var used to specify where the integ tests should point pub(crate) const INTEG_SERVER_TARGET_ENV_VAR: &str = "TEMPORAL_SERVICE_ADDRESS"; pub(crate) const INTEG_NAMESPACE_ENV_VAR: &str = "TEMPORAL_NAMESPACE"; @@ -112,6 +112,7 @@ pub(crate) fn integ_worker_config(tq: &str) -> WorkerConfigBuilder { .versioning_strategy(WorkerVersioningStrategy::None { build_id: "test_build_id".to_owned(), }) + .task_types(WorkerTaskTypes::all()) .skip_client_worker_set_check(true); b } diff --git a/crates/sdk-core/tests/integ_tests/metrics_tests.rs b/crates/sdk-core/tests/integ_tests/metrics_tests.rs index a63c07747..e938954fd 100644 --- a/crates/sdk-core/tests/integ_tests/metrics_tests.rs +++ b/crates/sdk-core/tests/integ_tests/metrics_tests.rs @@ -165,6 +165,7 @@ async fn one_slot_worker_reports_available_slot() { .max_outstanding_workflow_tasks(2_usize) .max_outstanding_nexus_tasks(1_usize) .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(2_usize)) + .task_types(WorkerTaskTypes::all()) .build() .unwrap(); diff --git a/crates/sdk-core/tests/integ_tests/worker_tests.rs b/crates/sdk-core/tests/integ_tests/worker_tests.rs index a2ddb9e0e..9b77be17f 100644 --- a/crates/sdk-core/tests/integ_tests/worker_tests.rs +++ b/crates/sdk-core/tests/integ_tests/worker_tests.rs @@ -90,6 +90,7 @@ async fn worker_validation_fails_on_nonexistent_namespace() { .versioning_strategy(WorkerVersioningStrategy::None { build_id: "blah".to_owned(), }) + .task_types(WorkerTaskTypes::all()) .build() .unwrap(), retrying_client,