From 517516380a5a91cc6549dc5f7a007d6e47860199 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 16 Mar 2026 11:20:35 -0700 Subject: [PATCH 1/8] Update API subtree to include pending changes --- .../api_upstream/openapi/openapiv2.json | 13 -------- .../api_upstream/openapi/openapiv3.yaml | 23 ------------- .../temporal/api/history/v1/message.proto | 17 ---------- .../workerservice/v1/request_response.proto | 1 + .../workerservice/v1/service.yaml | 32 +++++++++++++++++++ .../workflowservice/v1/request_response.proto | 2 +- 6 files changed, 34 insertions(+), 54 deletions(-) create mode 100644 crates/common/protos/api_upstream/temporal/api/nexusservices/workerservice/v1/service.yaml diff --git a/crates/common/protos/api_upstream/openapi/openapiv2.json b/crates/common/protos/api_upstream/openapi/openapiv2.json index 6544ba8a4..1d7fa49b9 100644 --- a/crates/common/protos/api_upstream/openapi/openapiv2.json +++ b/crates/common/protos/api_upstream/openapi/openapiv2.json @@ -13743,11 +13743,6 @@ "properties": { "deploymentVersion": { "$ref": "#/definitions/v1WorkerDeploymentVersion" - }, - "revisionNumber": { - "type": "string", - "format": "int64", - "description": "Revision number of the task queue routing config at the time the target\nwas declined. If an incoming target's revision is <= this value, it is\nnot newer and is not used for deciding whether or not to suppress the\nupgrade signal." } }, "description": "Wrapper for a target deployment version that the SDK declined to upgrade to.\nSee declined_target_version_upgrade on WorkflowExecutionStartedEventAttributes." @@ -19922,14 +19917,6 @@ "declinedTargetVersionUpgrade": { "$ref": "#/definitions/v1DeclinedTargetVersionUpgrade", "description": "During a previous run of this workflow, the server may have notified the SDK\nthat the Target Worker Deployment Version changed, but the SDK declined to\nupgrade (e.g., by continuing-as-new with PINNED behavior). This field records\nthe target version that was declined.\n\nThis is a wrapper message to distinguish \"never declined\" (nil wrapper) from\n\"declined an unversioned target\" (non-nil wrapper with nil deployment_version).\n\nUsed internally by the server during continue-as-new and retry.\nShould not be read or interpreted by SDKs." - }, - "timeSkippingConfig": { - "$ref": "#/definitions/v1TimeSkippingConfig", - "description": "Initial time-skipping configuration for this workflow execution, recorded at start time.\nThis may have been set explicitly via the start workflow request, or propagated from a\nparent/previous execution.\n\nThe configuration may be updated after start via UpdateWorkflowExecutionOptions, which\nwill be reflected in the WorkflowExecutionOptionsUpdatedEvent." - }, - "initialSkippedDuration": { - "type": "string", - "description": "The time skipped by the previous execution that started this workflow.\nIt can happen in cases of child workflows and continue-as-new workflows." } }, "title": "Always the first event in workflow history" diff --git a/crates/common/protos/api_upstream/openapi/openapiv3.yaml b/crates/common/protos/api_upstream/openapi/openapiv3.yaml index 50030712a..0a89ce094 100644 --- a/crates/common/protos/api_upstream/openapi/openapiv3.yaml +++ b/crates/common/protos/api_upstream/openapi/openapiv3.yaml @@ -10429,13 +10429,6 @@ components: properties: deploymentVersion: $ref: '#/components/schemas/WorkerDeploymentVersion' - revisionNumber: - type: string - description: |- - Revision number of the task queue routing config at the time the target - was declined. If an incoming target's revision is <= this value, it is - not newer and is not used for deciding whether or not to suppress the - upgrade signal. description: |- Wrapper for a target deployment version that the SDK declined to upgrade to. See declined_target_version_upgrade on WorkflowExecutionStartedEventAttributes. @@ -18549,22 +18542,6 @@ components: Used internally by the server during continue-as-new and retry. Should not be read or interpreted by SDKs. - timeSkippingConfig: - allOf: - - $ref: '#/components/schemas/TimeSkippingConfig' - description: |- - Initial time-skipping configuration for this workflow execution, recorded at start time. - This may have been set explicitly via the start workflow request, or propagated from a - parent/previous execution. - - The configuration may be updated after start via UpdateWorkflowExecutionOptions, which - will be reflected in the WorkflowExecutionOptionsUpdatedEvent. - initialSkippedDuration: - pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ - type: string - description: |- - The time skipped by the previous execution that started this workflow. - It can happen in cases of child workflows and continue-as-new workflows. description: Always the first event in workflow history WorkflowExecutionTerminatedEventAttributes: type: object diff --git a/crates/common/protos/api_upstream/temporal/api/history/v1/message.proto b/crates/common/protos/api_upstream/temporal/api/history/v1/message.proto index e57ec8d60..facbc4895 100644 --- a/crates/common/protos/api_upstream/temporal/api/history/v1/message.proto +++ b/crates/common/protos/api_upstream/temporal/api/history/v1/message.proto @@ -196,29 +196,12 @@ message WorkflowExecutionStartedEventAttributes { // Used internally by the server during continue-as-new and retry. // Should not be read or interpreted by SDKs. DeclinedTargetVersionUpgrade declined_target_version_upgrade = 40; - - // Initial time-skipping configuration for this workflow execution, recorded at start time. - // This may have been set explicitly via the start workflow request, or propagated from a - // parent/previous execution. - // - // The configuration may be updated after start via UpdateWorkflowExecutionOptions, which - // will be reflected in the WorkflowExecutionOptionsUpdatedEvent. - temporal.api.workflow.v1.TimeSkippingConfig time_skipping_config = 41; - - // The time skipped by the previous execution that started this workflow. - // It can happen in cases of child workflows and continue-as-new workflows. - google.protobuf.Duration initial_skipped_duration = 42; } // Wrapper for a target deployment version that the SDK declined to upgrade to. // See declined_target_version_upgrade on WorkflowExecutionStartedEventAttributes. message DeclinedTargetVersionUpgrade { temporal.api.deployment.v1.WorkerDeploymentVersion deployment_version = 1; - // Revision number of the task queue routing config at the time the target - // was declined. If an incoming target's revision is <= this value, it is - // not newer and is not used for deciding whether or not to suppress the - // upgrade signal. - int64 revision_number = 2; } message WorkflowExecutionCompletedEventAttributes { diff --git a/crates/common/protos/api_upstream/temporal/api/nexusservices/workerservice/v1/request_response.proto b/crates/common/protos/api_upstream/temporal/api/nexusservices/workerservice/v1/request_response.proto index e5b46e7ce..0daa67b64 100644 --- a/crates/common/protos/api_upstream/temporal/api/nexusservices/workerservice/v1/request_response.proto +++ b/crates/common/protos/api_upstream/temporal/api/nexusservices/workerservice/v1/request_response.proto @@ -13,6 +13,7 @@ import "temporal/api/worker/v1/message.proto"; // (-- // Internal Nexus service for server-to-worker communication. +// See service.yaml for the service definition. // --) // Request payload for the "ExecuteCommands" Nexus operation. diff --git a/crates/common/protos/api_upstream/temporal/api/nexusservices/workerservice/v1/service.yaml b/crates/common/protos/api_upstream/temporal/api/nexusservices/workerservice/v1/service.yaml new file mode 100644 index 000000000..cb49cb118 --- /dev/null +++ b/crates/common/protos/api_upstream/temporal/api/nexusservices/workerservice/v1/service.yaml @@ -0,0 +1,32 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nexus-rpc/nexus-rpc-gen/main/schemas/nexus-rpc-gen.json +# +# Nexus service definition for server-to-worker communication. +# See request_response.proto for message definitions. +# +# Task queue format: /temporal-sys/worker-commands/{namespace}/{worker_grouping_key} + +nexusrpc: 1.0.0 + +services: + temporal.api.nexusservices.workerservice.v1.WorkerService: + description: > + Internal Nexus service for server-to-worker communication. + Used by the Temporal server to send commands to workers. + operations: + ExecuteCommands: + description: Executes worker commands sent by the server. + input: + $goRef: "go.temporal.io/api/nexusservices/workerservice/v1.ExecuteCommandsRequest" + $javaRef: "io.temporal.api.nexusservices.workerservice.v1.ExecuteCommandsRequest" + $pythonRef: "temporalio.api.nexusservices.workerservice.v1.ExecuteCommandsRequest" + $typescriptRef: "@temporalio/api/nexusservices/workerservice/v1.ExecuteCommandsRequest" + $dotnetRef: "Temporalio.Api.Nexusservices.Workerservice.V1.ExecuteCommandsRequest" + $rubyRef: "Temporalio::Api::Nexusservices::Workerservice::V1::ExecuteCommandsRequest" + output: + $goRef: "go.temporal.io/api/nexusservices/workerservice/v1.ExecuteCommandsResponse" + $javaRef: "io.temporal.api.nexusservices.workerservice.v1.ExecuteCommandsResponse" + $pythonRef: "temporalio.api.nexusservices.workerservice.v1.ExecuteCommandsResponse" + $typescriptRef: "@temporalio/api/nexusservices/workerservice/v1.ExecuteCommandsResponse" + $dotnetRef: "Temporalio.Api.Nexusservices.Workerservice.V1.ExecuteCommandsResponse" + $rubyRef: "Temporalio::Api::Nexusservices::Workerservice::V1::ExecuteCommandsResponse" + diff --git a/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto b/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto index 5653ddf23..ca9f28e3e 100644 --- a/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +++ b/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto @@ -2491,7 +2491,7 @@ message CreateWorkerDeploymentVersionRequest { // Optional. The identity of the client who initiated this request. string identity = 3; - + // A unique identifier for this create request for idempotence. Typically UUIDv4. // If a second request with the same ID is recieved, it is considered a successful no-op. // Retrying with a different request ID for the same deployment name + build ID is an error. From ece173c8788c8017959ea8e58bda9e973ee07432 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 16 Mar 2026 16:55:22 -0700 Subject: [PATCH 2/8] Add handling of activity cancel nexus command --- crates/client/src/worker.rs | 52 ++- crates/common/build.rs | 2 + crates/common/src/protos/mod.rs | 7 + crates/sdk-core/src/worker/activities.rs | 26 ++ crates/sdk-core/src/worker/client.rs | 15 +- crates/sdk-core/src/worker/heartbeat.rs | 345 ++++++++++++++++-- crates/sdk-core/src/worker/mod.rs | 12 +- .../integ_tests/worker_heartbeat_tests.rs | 5 +- 8 files changed, 422 insertions(+), 42 deletions(-) diff --git a/crates/client/src/worker.rs b/crates/client/src/worker.rs index 8f76f880d..33263d3eb 100644 --- a/crates/client/src/worker.rs +++ b/crates/client/src/worker.rs @@ -11,8 +11,11 @@ use std::{ sync::Arc, }; use temporalio_common::{ - protos::temporal::api::{ - worker::v1::WorkerHeartbeat, workflowservice::v1::PollWorkflowTaskQueueResponse, + protos::{ + TaskToken, + temporal::api::{ + worker::v1::WorkerHeartbeat, workflowservice::v1::PollWorkflowTaskQueueResponse, + }, }, worker::{WorkerDeploymentOptions, WorkerTaskTypes}, }; @@ -194,7 +197,13 @@ impl ClientWorkerSetImpl { v.insert(shared_worker) } }; - shared_worker.register_callback(worker_instance_key, heartbeat_callback); + shared_worker.register_callback( + worker_instance_key, + WorkerCallbacks { + heartbeat: heartbeat_callback, + cancel_activity: worker.cancel_activity_callback(), + }, + ); } let worker_info = @@ -283,13 +292,13 @@ pub trait SharedNamespaceWorkerTrait { /// Namespace that the shared namespace worker is connected to. fn namespace(&self) -> String; - /// Registers a heartbeat callback. - fn register_callback(&self, worker_instance_key: Uuid, heartbeat_callback: HeartbeatCallback); + /// Registers worker callbacks. + fn register_callback(&self, worker_instance_key: Uuid, callbacks: WorkerCallbacks); - /// Unregisters a heartbeat callback. Returns the callback removed, as well as a bool that + /// Unregisters worker callbacks. Returns the callbacks removed, as well as a bool that /// indicates if there are no remaining callbacks in the SharedNamespaceWorker, indicating /// the shared worker itself can be shut down. - fn unregister_callback(&self, worker_instance_key: Uuid) -> (Option, bool); + fn unregister_callback(&self, worker_instance_key: Uuid) -> (Option, bool); /// Returns the number of workers registered to this shared worker. fn num_workers(&self) -> usize; @@ -393,6 +402,17 @@ impl std::fmt::Debug for ClientWorkerSet { /// Contains a worker heartbeat callback, wrapped for mocking pub type HeartbeatCallback = Arc WorkerHeartbeat + Send + Sync>; +/// Callback to cancel an activity by task token. Returns true if the activity was found. +pub type CancelActivityCallback = Arc bool + Send + Sync>; + +/// Bundles all per-worker callbacks registered with the SharedNamespaceWorker. +pub struct WorkerCallbacks { + /// Callback to collect heartbeat data from the worker. + pub heartbeat: HeartbeatCallback, + /// Callback to cancel an activity by task token. + pub cancel_activity: Option, +} + /// Represents a complete worker that can handle both slot management /// and worker heartbeat functionality. #[cfg_attr(test, mockall::automock)] @@ -423,6 +443,9 @@ pub trait ClientWorker: Send + Sync { /// Returns the heartbeat callback that can be used to get WorkerHeartbeat data. fn heartbeat_callback(&self) -> Option; + /// Returns a callback that can cancel an activity by task token. + fn cancel_activity_callback(&self) -> Option; + /// Creates a new worker that implements the [SharedNamespaceWorkerTrait] fn new_shared_namespace_worker( &self, @@ -713,7 +736,7 @@ mod tests { struct MockSharedNamespaceWorker { namespace: String, - callbacks: Arc>>, + callbacks: Arc>>, } impl std::fmt::Debug for MockSharedNamespaceWorker { @@ -739,20 +762,16 @@ mod tests { self.namespace.clone() } - fn register_callback( - &self, - worker_instance_key: Uuid, - heartbeat_callback: HeartbeatCallback, - ) { + fn register_callback(&self, worker_instance_key: Uuid, callbacks: WorkerCallbacks) { self.callbacks .write() - .insert(worker_instance_key, heartbeat_callback); + .insert(worker_instance_key, callbacks); } fn unregister_callback( &self, worker_instance_key: Uuid, - ) -> (Option, bool) { + ) -> (Option, bool) { let mut callbacks = self.callbacks.write(); let callback = callbacks.remove(&worker_instance_key); let is_empty = callbacks.is_empty(); @@ -805,6 +824,9 @@ mod tests { mock_provider .expect_heartbeat_callback() .returning(|| Some(Arc::new(WorkerHeartbeat::default))); + mock_provider + .expect_cancel_activity_callback() + .returning(|| None); let namespace_clone = namespace.clone(); mock_provider diff --git a/crates/common/build.rs b/crates/common/build.rs index 5c2ec3f04..7b3697fb0 100644 --- a/crates/common/build.rs +++ b/crates/common/build.rs @@ -39,6 +39,7 @@ const SERDE_DERIVE_PREFIXES: &[&str] = &[ ".temporal.api.history", ".temporal.api.namespace", ".temporal.api.nexus", + ".temporal.api.nexusservices", ".temporal.api.operatorservice", ".temporal.api.protocol", ".temporal.api.query", @@ -162,6 +163,7 @@ fn main() -> Result<(), Box> { "./protos/local/temporal/sdk/core/core_interface.proto", "./protos/api_upstream/temporal/api/sdk/v1/workflow_metadata.proto", "./protos/api_upstream/temporal/api/workflowservice/v1/service.proto", + "./protos/api_upstream/temporal/api/nexusservices/workerservice/v1/request_response.proto", "./protos/api_upstream/temporal/api/operatorservice/v1/service.proto", "./protos/api_upstream/temporal/api/errordetails/v1/message.proto", "./protos/api_cloud_upstream/temporal/api/cloud/cloudservice/v1/service.proto", diff --git a/crates/common/src/protos/mod.rs b/crates/common/src/protos/mod.rs index 6e39904f8..2eeb10fba 100644 --- a/crates/common/src/protos/mod.rs +++ b/crates/common/src/protos/mod.rs @@ -2772,6 +2772,13 @@ pub mod temporal { } } } + pub mod nexusservices { + pub mod workerservice { + pub mod v1 { + tonic::include_proto!("temporal.api.nexusservices.workerservice.v1"); + } + } + } pub mod workflowservice { pub mod v1 { use std::{ diff --git a/crates/sdk-core/src/worker/activities.rs b/crates/sdk-core/src/worker/activities.rs index e7bdc3d0d..ab496c55a 100644 --- a/crates/sdk-core/src/worker/activities.rs +++ b/crates/sdk-core/src/worker/activities.rs @@ -36,6 +36,7 @@ use std::{ }, time::{Duration, Instant, SystemTime}, }; +use temporalio_client::worker::CancelActivityCallback; use temporalio_common::protos::{ coresdk::{ ActivityHeartbeat, ActivitySlotInfo, @@ -170,6 +171,8 @@ pub(crate) struct WorkerActivityTasks { complete_notify: Arc, /// Token to notify when poll returned a shutdown error poll_returned_shutdown_token: CancellationToken, + /// Used to inject external cancellations (e.g. from nexus worker commands) + cancels_tx: UnboundedSender, } #[derive(derive_more::From)] @@ -205,6 +208,7 @@ impl WorkerActivityTasks { start_tasks_stream_complete.clone(), ); let (cancels_tx, cancels_rx) = unbounded_channel(); + let external_cancels_tx = cancels_tx.clone(); let heartbeat_manager = ActivityHeartbeatManager::new(client, cancels_tx.clone()); let complete_notify = Arc::new(Notify::new()); let source_stream = stream::select_with_strategy( @@ -239,6 +243,7 @@ impl WorkerActivityTasks { poll_returned_shutdown_token: CancellationToken::new(), outstanding_activity_tasks, completers_lock: Default::default(), + cancels_tx: external_cancels_tx, } } @@ -475,6 +480,27 @@ impl WorkerActivityTasks { } } + /// Returns a callback that can be used to cancel activities from outside this manager. + pub(crate) fn cancel_activity_callback(&self) -> CancelActivityCallback { + let outstanding = self.outstanding_activity_tasks.clone(); + let cancels_tx = self.cancels_tx.clone(); + Arc::new(move |task_token: TaskToken| { + if outstanding.contains_key(&task_token) { + let _ = cancels_tx.send(PendingActivityCancel::new( + task_token, + ActivityCancelReason::Cancelled, + ActivityCancellationDetails { + is_cancelled: true, + ..Default::default() + }, + )); + true + } else { + false + } + }) + } + #[cfg(test)] pub(crate) fn unused_permits(&self) -> Option { self.eager_activities_semaphore.unused_permits() diff --git a/crates/sdk-core/src/worker/client.rs b/crates/sdk-core/src/worker/client.rs index 10a84a44a..1e66d85d6 100644 --- a/crates/sdk-core/src/worker/client.rs +++ b/crates/sdk-core/src/worker/client.rs @@ -134,6 +134,14 @@ impl WorkerClientBag { None } } + + fn worker_control_task_queue(&self) -> String { + format!( + "temporal-sys/worker-commands/{}/{}", + self.namespace, + self.worker_grouping_key() + ) + } } /// This trait contains everything workers need to interact with Temporal, and hence provides a @@ -310,8 +318,8 @@ impl WorkerClient for WorkerClientBag { worker_version_capabilities: self.worker_version_capabilities(), deployment_options: self.deployment_options(), worker_instance_key: self.worker_instance_key.to_string(), + worker_control_task_queue: self.worker_control_task_queue(), poller_group_id: Default::default(), - worker_control_task_queue: Default::default(), } .into_request(); request.extensions_mut().insert(IsWorkerTaskLongPoll); @@ -350,8 +358,8 @@ impl WorkerClient for WorkerClientBag { worker_version_capabilities: self.worker_version_capabilities(), deployment_options: self.deployment_options(), worker_instance_key: self.worker_instance_key.to_string(), + worker_control_task_queue: self.worker_control_task_queue(), poller_group_id: Default::default(), - worker_control_task_queue: Default::default(), } .into_request(); request.extensions_mut().insert(IsWorkerTaskLongPoll); @@ -449,9 +457,10 @@ impl WorkerClient for WorkerClientBag { deployment: None, versioning_behavior: request.versioning_behavior.into(), deployment_options: self.deployment_options(), + worker_instance_key: self.worker_instance_key.to_string(), + worker_control_task_queue: self.worker_control_task_queue(), resource_id: Default::default(), worker_instance_key: self.worker_instance_key.to_string(), - worker_control_task_queue: Default::default(), }; Ok(self .connection diff --git a/crates/sdk-core/src/worker/heartbeat.rs b/crates/sdk-core/src/worker/heartbeat.rs index 45f146544..e93d8b98e 100644 --- a/crates/sdk-core/src/worker/heartbeat.rs +++ b/crates/sdk-core/src/worker/heartbeat.rs @@ -1,12 +1,26 @@ use crate::{ WorkerClient, WorkerConfig, - worker::{PollerBehavior, TaskPollers, WorkerTelemetry, WorkerVersioningStrategy}, + worker::{PollError, PollerBehavior, TaskPollers, WorkerTelemetry, WorkerVersioningStrategy}, }; use parking_lot::RwLock; +use prost::Message; use std::{collections::HashMap, sync::Arc, time::Duration}; -use temporalio_client::worker::SharedNamespaceWorkerTrait; +use temporalio_client::worker::{SharedNamespaceWorkerTrait, WorkerCallbacks}; use temporalio_common::{ - protos::temporal::api::worker::v1::WorkerHeartbeat, worker::WorkerTaskTypes, + protos::{ + TaskToken, + coresdk::nexus::{NexusTask, NexusTaskCompletion, nexus_task, nexus_task_completion}, + temporal::api::{ + common::v1::Payload, + nexus, + nexusservices::workerservice::v1::{ExecuteCommandsRequest, ExecuteCommandsResponse}, + worker::v1::{ + WorkerCommandResult, WorkerHeartbeat, worker_command::Type as WorkerCommandType, + worker_command_result::Type as WorkerCommandResultType, + }, + }, + }, + worker::WorkerTaskTypes, }; use tokio::sync::Notify; use tokio_util::sync::CancellationToken; @@ -19,7 +33,7 @@ pub(crate) type HeartbeatFn = Arc WorkerHeartbeat + Send + Sync>; /// worker heartbeats to the server. This invokes callbacks on all workers in the same process that /// share the same namespace. pub(crate) struct SharedNamespaceWorker { - heartbeat_map: Arc>>, + callbacks_map: Arc>>, namespace: String, cancel: CancellationToken, } @@ -62,8 +76,8 @@ impl SharedNamespaceWorker { let client_clone = client; let namespace_clone = namespace.clone(); - let heartbeat_map = Arc::new(RwLock::new(HashMap::::new())); - let heartbeat_map_clone = heartbeat_map.clone(); + let callbacks_map = Arc::new(RwLock::new(HashMap::::new())); + let callbacks_map_clone = callbacks_map.clone(); tokio::spawn(async move { match client_clone.describe_namespace().await { @@ -92,8 +106,10 @@ impl SharedNamespaceWorker { tokio::select! { _ = ticker.tick() => { let mut hb_to_send = Vec::new(); - let hb_callbacks = { - heartbeat_map_clone.read().values().cloned().collect::>() + let hb_callbacks: Vec<_> = { + callbacks_map_clone.read().values() + .map(|cb| cb.heartbeat.clone()) + .collect() }; for heartbeat_callback in hb_callbacks { let mut heartbeat = heartbeat_callback(); @@ -111,6 +127,23 @@ impl SharedNamespaceWorker { warn!(error=?e, "Network error while sending worker heartbeat"); } } + nexus_result = worker.poll_nexus_task() => { + match nexus_result { + Ok(task) => { + handle_worker_command_task( + &worker, + &callbacks_map_clone, + task, + ).await; + } + Err(PollError::ShutDown) => { + break; + } + Err(e) => { + warn!(error=?e, "Error polling nexus task for worker commands"); + } + } + } _ = reset_notify.notified() => { ticker.reset(); } @@ -123,7 +156,7 @@ impl SharedNamespaceWorker { }); Ok(Self { - heartbeat_map, + callbacks_map, namespace, cancel, }) @@ -135,23 +168,116 @@ impl SharedNamespaceWorkerTrait for SharedNamespaceWorker { self.namespace.clone() } - fn register_callback(&self, worker_instance_key: Uuid, heartbeat_callback: HeartbeatFn) { - self.heartbeat_map + fn register_callback(&self, worker_instance_key: Uuid, callbacks: WorkerCallbacks) { + self.callbacks_map .write() - .insert(worker_instance_key, heartbeat_callback); + .insert(worker_instance_key, callbacks); } - fn unregister_callback(&self, worker_instance_key: Uuid) -> (Option, bool) { - let mut heartbeat_map = self.heartbeat_map.write(); - let heartbeat_callback = heartbeat_map.remove(&worker_instance_key); - if heartbeat_map.is_empty() { + fn unregister_callback(&self, worker_instance_key: Uuid) -> (Option, bool) { + let mut callbacks_map = self.callbacks_map.write(); + let callbacks = callbacks_map.remove(&worker_instance_key); + if callbacks_map.is_empty() { self.cancel.cancel(); } - (heartbeat_callback, heartbeat_map.is_empty()) + (callbacks, callbacks_map.is_empty()) } fn num_workers(&self) -> usize { - self.heartbeat_map.read().len() + self.callbacks_map.read().len() + } +} + +async fn handle_worker_command_task( + worker: &crate::worker::Worker, + callbacks_map: &Arc>>, + task: NexusTask, +) { + let Some(nexus_task::Variant::Task(poll_resp)) = task.variant else { + return; + }; + + let task_token = poll_resp.task_token.clone(); + + let Some(request) = poll_resp.request.as_ref() else { + warn!("Worker command nexus task missing request"); + return; + }; + + let Some(nexus::v1::request::Variant::StartOperation(start_op)) = &request.variant else { + warn!("Worker command nexus task has unexpected request variant"); + return; + }; + + let payload_data = start_op + .payload + .as_ref() + .map(|p| p.data.as_slice()) + .unwrap_or_default(); + + let exec_req = match ExecuteCommandsRequest::decode(payload_data) { + Ok(req) => req, + Err(e) => { + warn!(error=?e, "Failed to decode ExecuteCommandsRequest"); + return; + } + }; + + let mut results = Vec::with_capacity(exec_req.commands.len()); + for command in &exec_req.commands { + let result_type = match &command.r#type { + Some(WorkerCommandType::CancelActivity(cancel_cmd)) => { + let tt = TaskToken(cancel_cmd.task_token.clone()); + let cancel_callbacks: Vec<_> = callbacks_map + .read() + .values() + .filter_map(|cb| cb.cancel_activity.clone()) + .collect(); + for cb in cancel_callbacks { + if cb(tt.clone()) { + break; + } + } + Some(WorkerCommandResultType::CancelActivity( + temporalio_common::protos::temporal::api::worker::v1::CancelActivityResult {}, + )) + } + None => { + warn!("Worker command has no type set"); + None + } + }; + results.push(WorkerCommandResult { + r#type: result_type, + }); + } + + let response = ExecuteCommandsResponse { results }; + let response_bytes = response.encode_to_vec(); + + let completion = NexusTaskCompletion { + task_token, + status: Some(nexus_task_completion::Status::Completed( + nexus::v1::Response { + variant: Some(nexus::v1::response::Variant::StartOperation( + nexus::v1::StartOperationResponse { + variant: Some(nexus::v1::start_operation_response::Variant::SyncSuccess( + nexus::v1::start_operation_response::Sync { + payload: Some(Payload { + data: response_bytes, + ..Default::default() + }), + links: vec![], + }, + )), + }, + )), + }, + )), + }; + + if let Err(e) = worker.complete_nexus_task(completion).await { + warn!(error=?e, "Failed to complete worker command nexus task"); } } @@ -160,19 +286,35 @@ mod tests { use crate::{ test_help::{WorkerExt, test_worker_cfg}, worker, - worker::{PollerBehavior, client::mocks::mock_worker_client}, + worker::{ + PollerBehavior, + client::{ + MockWorkerClient, + mocks::{DEFAULT_TEST_CAPABILITIES, mock_worker_client}, + }, + }, }; + use prost::Message; use std::{ sync::{ - Arc, - atomic::{AtomicUsize, Ordering}, + Arc, Mutex, + atomic::{AtomicBool, AtomicUsize, Ordering}, }, time::Duration, }; + use temporalio_client::worker::ClientWorkerSet; use temporalio_common::protos::temporal::api::{ + common::v1::Payload, namespace::v1::{NamespaceInfo, namespace_info::Capabilities}, - workflowservice::v1::{DescribeNamespaceResponse, RecordWorkerHeartbeatResponse}, + nexus::v1::{Request, StartOperationRequest, request}, + nexusservices::workerservice::v1::ExecuteCommandsRequest, + worker::v1::{CancelActivityCommand, WorkerCommand, worker_command}, + workflowservice::v1::{ + DescribeNamespaceResponse, PollNexusTaskQueueResponse, RecordWorkerHeartbeatResponse, + RespondNexusTaskCompletedResponse, + }, }; + use uuid::Uuid; #[tokio::test] async fn worker_heartbeat_basic() { @@ -239,4 +381,163 @@ mod tests { assert_eq!(3, heartbeat_count.load(Ordering::Relaxed)); } + + fn make_execute_commands_nexus_response( + task_token: Vec, + commands: Vec, + ) -> PollNexusTaskQueueResponse { + let exec_req = ExecuteCommandsRequest { commands }; + PollNexusTaskQueueResponse { + task_token, + request: Some(Request { + header: Default::default(), + scheduled_time: None, + endpoint: String::new(), + variant: Some(request::Variant::StartOperation(StartOperationRequest { + service: "temporal.api.nexusservices.workerservice.v1.WorkerService" + .to_string(), + operation: "ExecuteCommands".to_string(), + request_id: "test-req-id".to_string(), + callback: String::new(), + payload: Some(Payload { + data: exec_req.encode_to_vec(), + ..Default::default() + }), + callback_header: Default::default(), + links: vec![], + })), + capabilities: None, + }), + ..Default::default() + } + } + + #[tokio::test] + async fn worker_command_cancel_activity() { + let mut mock = MockWorkerClient::new(); + let isolated_registry = Arc::new(ClientWorkerSet::new()); + mock.expect_workers().return_const(isolated_registry); + mock.expect_capabilities() + .returning(|| Some(*DEFAULT_TEST_CAPABILITIES)); + mock.expect_is_mock().returning(|| true); + mock.expect_shutdown_worker() + .returning(|_, _, _, _| { + use temporalio_common::protos::temporal::api::workflowservice::v1::ShutdownWorkerResponse; + Ok(ShutdownWorkerResponse {}) + }); + mock.expect_sdk_name_and_version() + .returning(|| ("test-core".to_string(), "0.0.0".to_string())); + mock.expect_identity() + .returning(|| "test-identity".to_string()); + mock.expect_worker_grouping_key().returning(Uuid::new_v4); + mock.expect_worker_instance_key().returning(Uuid::new_v4); + mock.expect_set_heartbeat_client_fields() + .returning(|_hb| {}); + + let activity_task_token = vec![1, 2, 3, 4]; + + let completed_response = Arc::new(Mutex::new( + None::, + )); + let completed_response_clone = completed_response.clone(); + + let poll_returned_command = Arc::new(AtomicBool::new(false)); + let poll_returned_command_clone = poll_returned_command.clone(); + let at_clone = activity_task_token.clone(); + mock.expect_poll_nexus_task() + .returning(move |poll_options, _send_heartbeat| { + if poll_options + .task_queue + .starts_with("temporal-sys/worker-commands/") + && !poll_returned_command_clone.swap(true, Ordering::SeqCst) + { + Ok(make_execute_commands_nexus_response( + vec![99], + vec![WorkerCommand { + r#type: Some(worker_command::Type::CancelActivity( + CancelActivityCommand { + task_token: at_clone.clone(), + }, + )), + }], + )) + } else { + Ok(Default::default()) + } + }); + mock.expect_complete_nexus_task() + .returning(move |_task_token, response| { + *completed_response_clone.lock().unwrap() = Some(response); + Ok(RespondNexusTaskCompletedResponse {}) + }); + + mock.expect_poll_workflow_task() + .returning(move |_namespace, _task_queue| Ok(Default::default())); + mock.expect_record_worker_heartbeat() + .returning(move |_namespace, _worker_heartbeat| Ok(RecordWorkerHeartbeatResponse {})); + mock.expect_describe_namespace().returning(move || { + Ok(DescribeNamespaceResponse { + namespace_info: Some(NamespaceInfo { + capabilities: Some(Capabilities { + worker_heartbeats: true, + ..Capabilities::default() + }), + ..NamespaceInfo::default() + }), + ..DescribeNamespaceResponse::default() + }) + }); + + let config = test_worker_cfg() + .activity_task_poller_behavior(PollerBehavior::SimpleMaximum(1_usize)) + .max_outstanding_activities(1_usize) + .build() + .unwrap(); + + let client = Arc::new(mock); + let worker = worker::Worker::new( + config, + None, + client.clone(), + None, + Some(Duration::from_millis(100)), + ) + .unwrap(); + + // Give time for the SharedNamespaceWorker to poll and process the nexus task + tokio::time::sleep(Duration::from_millis(500)).await; + worker.drain_activity_poller_and_shutdown().await; + + // Verify the nexus task was completed with a valid ExecuteCommandsResponse + let response = completed_response + .lock() + .unwrap() + .take() + .expect("Nexus task should have been completed"); + + use temporalio_common::protos::temporal::api::{ + nexus::v1::{response, start_operation_response}, + nexusservices::workerservice::v1::ExecuteCommandsResponse, + worker::v1::worker_command_result, + }; + let start_op = match response.variant { + Some(response::Variant::StartOperation(s)) => s, + other => panic!("Expected StartOperation response, got {:?}", other), + }; + let sync_resp = match start_op.variant { + Some(start_operation_response::Variant::SyncSuccess(s)) => s, + other => panic!("Expected SyncSuccess, got {:?}", other), + }; + let payload_data = sync_resp.payload.expect("Should have payload").data; + let exec_resp = ExecuteCommandsResponse::decode(payload_data.as_slice()) + .expect("Should decode ExecuteCommandsResponse"); + assert_eq!(exec_resp.results.len(), 1, "Should have one result"); + assert!( + matches!( + exec_resp.results[0].r#type, + Some(worker_command_result::Type::CancelActivity(_)) + ), + "Result should be CancelActivity" + ); + } } diff --git a/crates/sdk-core/src/worker/mod.rs b/crates/sdk-core/src/worker/mod.rs index 0a5ecd5e1..3f789544c 100644 --- a/crates/sdk-core/src/worker/mod.rs +++ b/crates/sdk-core/src/worker/mod.rs @@ -78,7 +78,8 @@ use std::{ time::{Duration, SystemTime}, }; use temporalio_client::worker::{ - ClientWorker, HeartbeatCallback, SharedNamespaceWorkerTrait, Slot as SlotTrait, + CancelActivityCallback, ClientWorker, HeartbeatCallback, SharedNamespaceWorkerTrait, + Slot as SlotTrait, }; use temporalio_common::{ protos::{ @@ -869,10 +870,14 @@ impl Worker { ) }); + let cancel_activity_callback = at_task_mgr + .as_ref() + .map(|mgr| mgr.cancel_activity_callback()); let client_worker_registrator = Arc::new(ClientWorkerRegistrator { worker_instance_key, slot_provider: provider, heartbeat_manager: worker_heartbeat, + cancel_activity_callback, client: RwLock::new(client.clone()), shared_namespace_worker, task_types: config.task_types, @@ -1953,6 +1958,7 @@ struct ClientWorkerRegistrator { worker_instance_key: Uuid, slot_provider: SlotProvider, heartbeat_manager: Option, + cancel_activity_callback: Option, client: RwLock>, shared_namespace_worker: bool, task_types: WorkerTaskTypes, @@ -1990,6 +1996,10 @@ impl ClientWorker for ClientWorkerRegistrator { } } + fn cancel_activity_callback(&self) -> Option { + self.cancel_activity_callback.clone() + } + fn new_shared_namespace_worker( &self, ) -> Result, anyhow::Error> { diff --git a/crates/sdk-core/tests/integ_tests/worker_heartbeat_tests.rs b/crates/sdk-core/tests/integ_tests/worker_heartbeat_tests.rs index 5f6449931..9b40fdf7e 100644 --- a/crates/sdk-core/tests/integ_tests/worker_heartbeat_tests.rs +++ b/crates/sdk-core/tests/integ_tests/worker_heartbeat_tests.rs @@ -71,6 +71,7 @@ fn to_system_time(ts: Timestamp) -> SystemTime { UNIX_EPOCH + Duration::new(ts.seconds as u64, ts.nanos as u32) } +#[allow(deprecated)] async fn list_worker_heartbeats(client: &Client, query: impl Into) -> Vec { let mut raw_client = client.clone(); let response = WorkflowService::list_workers( @@ -278,7 +279,8 @@ async fn docker_worker_heartbeat_basic(#[values("otel", "prom", "no_metrics")] b .unwrap() .into_inner(); #[allow(deprecated)] - let hb = workers_list + #[allow(deprecated)] + let hb = workers_list .workers_info .iter() .find_map(|wi| { @@ -974,6 +976,7 @@ async fn worker_heartbeat_failure_metrics() { eventually( || async { let heartbeats = list_worker_heartbeats(&client, query.clone()).await; + #[allow(deprecated)] let heartbeat = heartbeats .into_iter() .find(|hb| hb.worker_instance_key == worker_key) From 5398664b85a12c384022f4e879a05da22e4637ea Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 16 Mar 2026 17:08:36 -0700 Subject: [PATCH 3/8] Add integration test --- .../integ_tests/workflow_tests/activities.rs | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/activities.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/activities.rs index 463973f67..757641206 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/activities.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/activities.rs @@ -1615,3 +1615,77 @@ async fn immediate_activity_cancelation() { worker.set_worker_interceptor(aai); worker.run().await.unwrap(); } + +/// Verifies that activity cancellation is delivered via the nexus worker command channel +/// even when the activity does not heartbeat. +#[tokio::test] +async fn activity_cancel_delivered_without_heartbeat() { + let wf_name = "activity_cancel_delivered_without_heartbeat"; + let mut starter = CoreWfStarter::new(wf_name); + + struct WaitForCancelActivities {} + #[activities] + impl WaitForCancelActivities { + #[activity] + async fn wait_for_cancel( + self: Arc, + ctx: ActivityContext, + _: String, + ) -> Result { + ctx.cancelled().await; + Ok("done".to_string()) + } + } + + starter + .sdk_config + .register_activities(WaitForCancelActivities {}); + let mut worker = starter.worker().await; + + #[workflow] + #[derive(Default)] + struct CancelWithoutHeartbeatWorkflow; + + #[workflow_methods] + impl CancelWithoutHeartbeatWorkflow { + #[run] + async fn run(ctx: &mut WorkflowContext) -> WorkflowResult<()> { + let act_fut = ctx.start_activity( + WaitForCancelActivities::wait_for_cancel, + "hi".to_string(), + ActivityOptions { + // Longer than workflow timeout + start_to_close_timeout: Some(Duration::from_secs(30)), + retry_policy: Some(RetryPolicy { + maximum_attempts: 1, + ..Default::default() + }), + cancellation_type: ActivityCancellationType::WaitCancellationCompleted, + ..Default::default() + }, + ); + // Timer needed to avoid cancel-before-sent + ctx.timer(Duration::from_millis(10)).await; + act_fut.cancel(); + let _ = act_fut.await; + Ok(()) + } + } + + worker.register_workflow::(); + + let task_queue = starter.get_task_queue().to_owned(); + let handle = worker + .submit_workflow( + CancelWithoutHeartbeatWorkflow::run, + (), + WorkflowStartOptions::new(task_queue, wf_name.to_owned()) + .run_timeout(Duration::from_secs(10)) + .build(), + ) + .await + .unwrap(); + // Fails with workflow timeout if cancel doesn't work + worker.run_until_done().await.unwrap(); + handle.get_result(Default::default()).await.unwrap(); +} From 6ef3a4625a3c3e82728b963469e4669ed19da635 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 11 May 2026 09:58:22 -0700 Subject: [PATCH 4/8] Merge fixes --- crates/sdk-core/src/worker/activities.rs | 2 +- crates/sdk-core/src/worker/client.rs | 1 - .../tests/integ_tests/workflow_tests/activities.rs | 13 +++++-------- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/crates/sdk-core/src/worker/activities.rs b/crates/sdk-core/src/worker/activities.rs index ab496c55a..8a160062c 100644 --- a/crates/sdk-core/src/worker/activities.rs +++ b/crates/sdk-core/src/worker/activities.rs @@ -485,7 +485,7 @@ impl WorkerActivityTasks { let outstanding = self.outstanding_activity_tasks.clone(); let cancels_tx = self.cancels_tx.clone(); Arc::new(move |task_token: TaskToken| { - if outstanding.contains_key(&task_token) { + if outstanding.lock().contains_key(&task_token) { let _ = cancels_tx.send(PendingActivityCancel::new( task_token, ActivityCancelReason::Cancelled, diff --git a/crates/sdk-core/src/worker/client.rs b/crates/sdk-core/src/worker/client.rs index 1e66d85d6..d70e6eba6 100644 --- a/crates/sdk-core/src/worker/client.rs +++ b/crates/sdk-core/src/worker/client.rs @@ -460,7 +460,6 @@ impl WorkerClient for WorkerClientBag { worker_instance_key: self.worker_instance_key.to_string(), worker_control_task_queue: self.worker_control_task_queue(), resource_id: Default::default(), - worker_instance_key: self.worker_instance_key.to_string(), }; Ok(self .connection diff --git a/crates/sdk-core/tests/integ_tests/workflow_tests/activities.rs b/crates/sdk-core/tests/integ_tests/workflow_tests/activities.rs index 757641206..5a5ef4720 100644 --- a/crates/sdk-core/tests/integ_tests/workflow_tests/activities.rs +++ b/crates/sdk-core/tests/integ_tests/workflow_tests/activities.rs @@ -1653,16 +1653,13 @@ async fn activity_cancel_delivered_without_heartbeat() { let act_fut = ctx.start_activity( WaitForCancelActivities::wait_for_cancel, "hi".to_string(), - ActivityOptions { - // Longer than workflow timeout - start_to_close_timeout: Some(Duration::from_secs(30)), - retry_policy: Some(RetryPolicy { + ActivityOptions::with_start_to_close_timeout(Duration::from_secs(30)) + .retry_policy(RetryPolicy { maximum_attempts: 1, ..Default::default() - }), - cancellation_type: ActivityCancellationType::WaitCancellationCompleted, - ..Default::default() - }, + }) + .cancellation_type(ActivityCancellationType::WaitCancellationCompleted) + .build(), ); // Timer needed to avoid cancel-before-sent ctx.timer(Duration::from_millis(10)).await; From aea5fc3b84c5dbf1c1f27f606398a3a33c1e8910 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 11 May 2026 10:02:19 -0700 Subject: [PATCH 5/8] Update/mergefix protos to v1.62.11 --- .../api_upstream/openapi/openapiv2.json | 32 +++++++----- .../api_upstream/openapi/openapiv3.yaml | 49 +++++++++++++------ .../temporal/api/history/v1/message.proto | 19 ++++--- .../temporal/api/nexus/v1/message.proto | 1 + .../workerservice/v1/request_response.proto | 1 - .../temporal/api/workflow/v1/message.proto | 45 +++++++++-------- .../workflowservice/v1/request_response.proto | 3 +- 7 files changed, 96 insertions(+), 54 deletions(-) diff --git a/crates/common/protos/api_upstream/openapi/openapiv2.json b/crates/common/protos/api_upstream/openapi/openapiv2.json index 1d7fa49b9..10830b299 100644 --- a/crates/common/protos/api_upstream/openapi/openapiv2.json +++ b/crates/common/protos/api_upstream/openapi/openapiv2.json @@ -13743,6 +13743,11 @@ "properties": { "deploymentVersion": { "$ref": "#/definitions/v1WorkerDeploymentVersion" + }, + "revisionNumber": { + "type": "string", + "format": "int64", + "description": "Revision number of the task queue routing config at the time the target\nwas declined. If an incoming target's revision is <= this value, it is\nnot newer and is not used for deciding whether or not to suppress the\nupgrade signal." } }, "description": "Wrapper for a target deployment version that the SDK declined to upgrade to.\nSee declined_target_version_upgrade on WorkflowExecutionStartedEventAttributes." @@ -17960,14 +17965,6 @@ "priority": { "$ref": "#/definitions/v1Priority", "title": "Priority metadata" - }, - "timeSkippingConfig": { - "$ref": "#/definitions/v1TimeSkippingConfig", - "description": "The propagated time-skipping configuration for the child workflow." - }, - "initialSkippedDuration": { - "type": "string", - "description": "Propagate the duration skipped to the child workflow." } } }, @@ -18356,7 +18353,11 @@ "properties": { "enabled": { "type": "boolean", - "description": "Enables or disables time skipping for this workflow execution." + "description": "Enables or disables time skipping for this workflow execution.\nBy default, this field is propagated to transitively related workflows (child workflows/start-as-new/reset) \nat the time they are started.\nChanges made after a transitively related workflow has started are not propagated." + }, + "disablePropagation": { + "type": "boolean", + "description": "If set, the enabled field is not propagated to transitively related workflows." }, "maxSkippedDuration": { "type": "string", @@ -18365,9 +18366,14 @@ "maxElapsedDuration": { "type": "string", "description": "Maximum elapsed time since time skipping was enabled.\nThis includes both skipped time and real time elapsing." + }, + "maxTargetTime": { + "type": "string", + "format": "date-time", + "description": "Absolute virtual timestamp at which time skipping is disabled.\nTime skipping will not advance beyond this point." } }, - "description": "Configuration for time skipping during a workflow execution.\nWhen enabled, virtual time advances automatically whenever there is no in-flight work.\nIn-flight work includes activities, child workflows, Nexus operations, signal/cancel external workflow operations,\nand possibly other features added in the future.\nUser timers are not classified as in-flight work and will be skipped over.\nWhen time advances, it skips to the earlier of the next user timer or the configured bound, if either exists.\n\nPropagation behavior of time skipping:\nThe enabled flag, bound fields, and accumulated skipped duration are propagated to related executions as follows:\n(1) Child workflows and continue-as-new: both the configuration and the accumulated skipped duration are\n inherited from the current execution. The configured bound is shared between the inherited skipped\n duration and any additional duration skipped by the new run.\n(2) Retry and cron: the configuration and accumulated skipped duration are inherited as recorded when the\n current workflow started; the accumulated skipped duration of the current run is not propagated.\n(3) Reset: the new run retains the time-skipping configuration of the current execution. Because reset replays\n all events up to the reset point and re-applies any UpdateWorkflowExecutionOptions changes made after that\n point, the resulting run ends up with the same final time-skipping configuration as the previous run." + "description": "Configuration for time skipping during a workflow execution.\nWhen enabled, virtual time advances automatically whenever there is no in-flight work.\nIn-flight work includes activities, child workflows, Nexus operations, signal/cancel external workflow operations,\nand possibly other features added in the future.\nUser timers are not classified as in-flight work and will be skipped over.\nWhen time advances, it skips to the earlier of the next user timer or the configured bound, if either exists." }, "v1TimeoutFailureInfo": { "type": "object", @@ -19651,7 +19657,7 @@ }, "timeSkippingConfig": { "$ref": "#/definitions/v1TimeSkippingConfig", - "description": "Time-skipping configuration for this workflow execution.\nIf not set, the time-skipping configuration is not updated by this request;\nthe existing configuration is preserved." + "description": "Time-skipping configuration for this workflow execution.\nIf not set, the time-skipping conf will not get updated upon request, \ni.e. the existing time-skipping conf will be preserved." } } }, @@ -19917,6 +19923,10 @@ "declinedTargetVersionUpgrade": { "$ref": "#/definitions/v1DeclinedTargetVersionUpgrade", "description": "During a previous run of this workflow, the server may have notified the SDK\nthat the Target Worker Deployment Version changed, but the SDK declined to\nupgrade (e.g., by continuing-as-new with PINNED behavior). This field records\nthe target version that was declined.\n\nThis is a wrapper message to distinguish \"never declined\" (nil wrapper) from\n\"declined an unversioned target\" (non-nil wrapper with nil deployment_version).\n\nUsed internally by the server during continue-as-new and retry.\nShould not be read or interpreted by SDKs." + }, + "timeSkippingConfig": { + "$ref": "#/definitions/v1TimeSkippingConfig", + "description": "Initial time-skipping configuration for this workflow execution, recorded at start time.\nThis may have been set explicitly via the start workflow request, or propagated from a\nparent/previous execution.\n\nThe configuration may be updated after start via UpdateWorkflowExecutionOptions, which\nwill be reflected in the WorkflowExecutionOptionsUpdatedEvent." } }, "title": "Always the first event in workflow history" diff --git a/crates/common/protos/api_upstream/openapi/openapiv3.yaml b/crates/common/protos/api_upstream/openapi/openapiv3.yaml index 0a89ce094..4d816f659 100644 --- a/crates/common/protos/api_upstream/openapi/openapiv3.yaml +++ b/crates/common/protos/api_upstream/openapi/openapiv3.yaml @@ -10429,6 +10429,13 @@ components: properties: deploymentVersion: $ref: '#/components/schemas/WorkerDeploymentVersion' + revisionNumber: + type: string + description: |- + Revision number of the task queue routing config at the time the target + was declined. If an incoming target's revision is <= this value, it is + not newer and is not used for deciding whether or not to suppress the + upgrade signal. description: |- Wrapper for a target deployment version that the SDK declined to upgrade to. See declined_target_version_upgrade on WorkflowExecutionStartedEventAttributes. @@ -15550,14 +15557,6 @@ components: allOf: - $ref: '#/components/schemas/Priority' description: Priority metadata - timeSkippingConfig: - allOf: - - $ref: '#/components/schemas/TimeSkippingConfig' - description: The propagated time-skipping configuration for the child workflow. - initialSkippedDuration: - pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ - type: string - description: Propagate the duration skipped to the child workflow. StartNexusOperationExecutionRequest: type: object properties: @@ -16244,7 +16243,10 @@ components: properties: enabled: type: boolean - description: Enables or disables time skipping for this workflow execution. + description: "Enables or disables time skipping for this workflow execution.\n By default, this field is propagated to transitively related workflows (child workflows/start-as-new/reset) \n at the time they are started.\n Changes made after a transitively related workflow has started are not propagated." + disablePropagation: + type: boolean + description: If set, the enabled field is not propagated to transitively related workflows. maxSkippedDuration: pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ type: string @@ -16255,7 +16257,19 @@ components: description: |- Maximum elapsed time since time skipping was enabled. This includes both skipped time and real time elapsing. - description: "Configuration for time skipping during a workflow execution.\n When enabled, virtual time advances automatically whenever there is no in-flight work.\n In-flight work includes activities, child workflows, Nexus operations, signal/cancel external workflow operations,\n and possibly other features added in the future.\n User timers are not classified as in-flight work and will be skipped over.\n When time advances, it skips to the earlier of the next user timer or the configured bound, if either exists.\n \n Propagation behavior of time skipping:\n The enabled flag, bound fields, and accumulated skipped duration are propagated to related executions as follows:\n (1) Child workflows and continue-as-new: both the configuration and the accumulated skipped duration are\n inherited from the current execution. The configured bound is shared between the inherited skipped\n duration and any additional duration skipped by the new run.\n (2) Retry and cron: the configuration and accumulated skipped duration are inherited as recorded when the\n current workflow started; the accumulated skipped duration of the current run is not propagated.\n (3) Reset: the new run retains the time-skipping configuration of the current execution. Because reset replays\n all events up to the reset point and re-applies any UpdateWorkflowExecutionOptions changes made after that\n point, the resulting run ends up with the same final time-skipping configuration as the previous run." + maxTargetTime: + type: string + description: |- + Absolute virtual timestamp at which time skipping is disabled. + Time skipping will not advance beyond this point. + format: date-time + description: |- + Configuration for time skipping during a workflow execution. + When enabled, virtual time advances automatically whenever there is no in-flight work. + In-flight work includes activities, child workflows, Nexus operations, signal/cancel external workflow operations, + and possibly other features added in the future. + User timers are not classified as in-flight work and will be skipped over. + When time advances, it skips to the earlier of the next user timer or the configured bound, if either exists. TimeoutFailureInfo: type: object properties: @@ -18205,10 +18219,7 @@ components: timeSkippingConfig: allOf: - $ref: '#/components/schemas/TimeSkippingConfig' - description: |- - Time-skipping configuration for this workflow execution. - If not set, the time-skipping configuration is not updated by this request; - the existing configuration is preserved. + description: "Time-skipping configuration for this workflow execution.\n If not set, the time-skipping conf will not get updated upon request, \n i.e. the existing time-skipping conf will be preserved." WorkflowExecutionOptionsUpdatedEventAttributes: type: object properties: @@ -18542,6 +18553,16 @@ components: Used internally by the server during continue-as-new and retry. Should not be read or interpreted by SDKs. + timeSkippingConfig: + allOf: + - $ref: '#/components/schemas/TimeSkippingConfig' + description: |- + Initial time-skipping configuration for this workflow execution, recorded at start time. + This may have been set explicitly via the start workflow request, or propagated from a + parent/previous execution. + + The configuration may be updated after start via UpdateWorkflowExecutionOptions, which + will be reflected in the WorkflowExecutionOptionsUpdatedEvent. description: Always the first event in workflow history WorkflowExecutionTerminatedEventAttributes: type: object diff --git a/crates/common/protos/api_upstream/temporal/api/history/v1/message.proto b/crates/common/protos/api_upstream/temporal/api/history/v1/message.proto index facbc4895..e873412bf 100644 --- a/crates/common/protos/api_upstream/temporal/api/history/v1/message.proto +++ b/crates/common/protos/api_upstream/temporal/api/history/v1/message.proto @@ -196,12 +196,25 @@ message WorkflowExecutionStartedEventAttributes { // Used internally by the server during continue-as-new and retry. // Should not be read or interpreted by SDKs. DeclinedTargetVersionUpgrade declined_target_version_upgrade = 40; + + // Initial time-skipping configuration for this workflow execution, recorded at start time. + // This may have been set explicitly via the start workflow request, or propagated from a + // parent/previous execution. + // + // The configuration may be updated after start via UpdateWorkflowExecutionOptions, which + // will be reflected in the WorkflowExecutionOptionsUpdatedEvent. + temporal.api.workflow.v1.TimeSkippingConfig time_skipping_config = 41; } // Wrapper for a target deployment version that the SDK declined to upgrade to. // See declined_target_version_upgrade on WorkflowExecutionStartedEventAttributes. message DeclinedTargetVersionUpgrade { temporal.api.deployment.v1.WorkerDeploymentVersion deployment_version = 1; + // Revision number of the task queue routing config at the time the target + // was declined. If an incoming target's revision is <= this value, it is + // not newer and is not used for deciding whether or not to suppress the + // upgrade signal. + int64 revision_number = 2; } message WorkflowExecutionCompletedEventAttributes { @@ -757,12 +770,6 @@ message StartChildWorkflowExecutionInitiatedEventAttributes { bool inherit_build_id = 19 [deprecated = true]; // Priority metadata temporal.api.common.v1.Priority priority = 20; - - // The propagated time-skipping configuration for the child workflow. - temporal.api.workflow.v1.TimeSkippingConfig time_skipping_config = 21; - - // Propagate the duration skipped to the child workflow. - google.protobuf.Duration initial_skipped_duration = 30; } message StartChildWorkflowExecutionFailedEventAttributes { diff --git a/crates/common/protos/api_upstream/temporal/api/nexus/v1/message.proto b/crates/common/protos/api_upstream/temporal/api/nexus/v1/message.proto index 3623daa70..ec03a2ac7 100644 --- a/crates/common/protos/api_upstream/temporal/api/nexus/v1/message.proto +++ b/crates/common/protos/api_upstream/temporal/api/nexus/v1/message.proto @@ -202,6 +202,7 @@ message EndpointTarget { // Nexus task queue to route requests to. string task_queue = 2; } + // Target an external server by URL. // At a later point, this will support providing credentials, in the meantime, an http.RoundTripper can be injected // into the server to modify the request. diff --git a/crates/common/protos/api_upstream/temporal/api/nexusservices/workerservice/v1/request_response.proto b/crates/common/protos/api_upstream/temporal/api/nexusservices/workerservice/v1/request_response.proto index 0daa67b64..e5b46e7ce 100644 --- a/crates/common/protos/api_upstream/temporal/api/nexusservices/workerservice/v1/request_response.proto +++ b/crates/common/protos/api_upstream/temporal/api/nexusservices/workerservice/v1/request_response.proto @@ -13,7 +13,6 @@ import "temporal/api/worker/v1/message.proto"; // (-- // Internal Nexus service for server-to-worker communication. -// See service.yaml for the service definition. // --) // Request payload for the "ExecuteCommands" Nexus operation. diff --git a/crates/common/protos/api_upstream/temporal/api/workflow/v1/message.proto b/crates/common/protos/api_upstream/temporal/api/workflow/v1/message.proto index 51b39b76a..c21d84953 100644 --- a/crates/common/protos/api_upstream/temporal/api/workflow/v1/message.proto +++ b/crates/common/protos/api_upstream/temporal/api/workflow/v1/message.proto @@ -580,8 +580,8 @@ message WorkflowExecutionOptions { temporal.api.common.v1.Priority priority = 2; // Time-skipping configuration for this workflow execution. - // If not set, the time-skipping configuration is not updated by this request; - // the existing configuration is preserved. + // If not set, the time-skipping conf will not get updated upon request, + // i.e. the existing time-skipping conf will be preserved. TimeSkippingConfig time_skipping_config = 3; } @@ -591,32 +591,31 @@ message WorkflowExecutionOptions { // and possibly other features added in the future. // User timers are not classified as in-flight work and will be skipped over. // When time advances, it skips to the earlier of the next user timer or the configured bound, if either exists. -// -// Propagation behavior of time skipping: -// The enabled flag, bound fields, and accumulated skipped duration are propagated to related executions as follows: -// (1) Child workflows and continue-as-new: both the configuration and the accumulated skipped duration are -// inherited from the current execution. The configured bound is shared between the inherited skipped -// duration and any additional duration skipped by the new run. -// (2) Retry and cron: the configuration and accumulated skipped duration are inherited as recorded when the -// current workflow started; the accumulated skipped duration of the current run is not propagated. -// (3) Reset: the new run retains the time-skipping configuration of the current execution. Because reset replays -// all events up to the reset point and re-applies any UpdateWorkflowExecutionOptions changes made after that -// point, the resulting run ends up with the same final time-skipping configuration as the previous run. message TimeSkippingConfig { - reserved 2, 6; - reserved "disable_propagation", "max_target_time"; // Enables or disables time skipping for this workflow execution. + // By default, this field is propagated to transitively related workflows (child workflows/start-as-new/reset) + // at the time they are started. + // Changes made after a transitively related workflow has started are not propagated. bool enabled = 1; - // Optional bound that limits the gap between the virtual time of this execution and wall-clock time. - // Once the bound is reached, time skipping is automatically disabled, - // but can be re-enabled by setting `enabled` to true via UpdateWorkflowExecutionOptions. - // This bound cannot be set to a value smaller than the execution's currently skipped duration. + // If set, the enabled field is not propagated to transitively related workflows. + bool disable_propagation = 2; + + // Optional bound that limits how long time skipping remains active. + // Once the bound is reached, time skipping is automatically disabled. + // It can later be re-enabled via UpdateWorkflowExecutionOptions. + // + // This is particularly useful in testing scenarios where workflows + // are expected to receive signals, updates, or other events while + // timers are in progress. // - // This is useful in testing scenarios where a workflow is expected to receive - // signals, updates, or other external events while timers are in progress. + // This bound is not propagated to transitively related workflows. + // When bound is also needed for transitively related workflows, + // it is recommended to set disable_propagation to true + // and configure TimeSkippingConfig explicitly for transitively related workflows. oneof bound { + // Maximum total virtual time that can be skipped. google.protobuf.Duration max_skipped_duration = 4; @@ -624,6 +623,10 @@ message TimeSkippingConfig { // This includes both skipped time and real time elapsing. // (-- api-linter: core::0142::time-field-names=disabled --) google.protobuf.Duration max_elapsed_duration = 5; + + // Absolute virtual timestamp at which time skipping is disabled. + // Time skipping will not advance beyond this point. + google.protobuf.Timestamp max_target_time = 6; } } diff --git a/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto b/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto index ca9f28e3e..c7374abc7 100644 --- a/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +++ b/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto @@ -2491,7 +2491,7 @@ message CreateWorkerDeploymentVersionRequest { // Optional. The identity of the client who initiated this request. string identity = 3; - + // A unique identifier for this create request for idempotence. Typically UUIDv4. // If a second request with the same ID is recieved, it is considered a successful no-op. // Retrying with a different request ID for the same deployment name + build ID is an error. @@ -2754,6 +2754,7 @@ message RecordWorkerHeartbeatRequest { string identity = 2; repeated temporal.api.worker.v1.WorkerHeartbeat worker_heartbeat = 3; + // Resource ID for routing. Contains the worker grouping key. string resource_id = 4; } From 94adca1367045847969ed9c45686875037d52765 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 11 May 2026 10:41:31 -0700 Subject: [PATCH 6/8] Include WorkerCommands task queue kind --- crates/sdk-core/src/pollers/poll_buffer.rs | 18 +++++++++--------- crates/sdk-core/src/worker/client.rs | 12 +++++++++--- crates/sdk-core/src/worker/client/mocks.rs | 2 +- crates/sdk-core/src/worker/heartbeat.rs | 4 ++-- crates/sdk-core/src/worker/mod.rs | 2 +- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/crates/sdk-core/src/pollers/poll_buffer.rs b/crates/sdk-core/src/pollers/poll_buffer.rs index a4d1353de..8a32e06e5 100644 --- a/crates/sdk-core/src/pollers/poll_buffer.rs +++ b/crates/sdk-core/src/pollers/poll_buffer.rs @@ -153,6 +153,7 @@ impl LongPollBuffer { task_queue, no_retry, timeout_override, + worker_commands_queue: false, }, PollWorkflowOptions { sticky_queue_name }, ) @@ -210,6 +211,7 @@ impl LongPollBuffer { task_queue, no_retry, timeout_override, + worker_commands_queue: false, }, PollActivityOptions { max_tasks_per_sec: options.max_tps, @@ -248,8 +250,8 @@ impl LongPollBuffer { shutdown: CancellationToken, num_pollers_handler: Option, last_successful_poll_time: Arc>>, - send_heartbeat: bool, capabilities: Arc, + worker_commands_queue: bool, ) -> Self { let no_retry = if matches!(poller_behavior, PollerBehavior::Autoscaling { .. }) { Some(NoRetryOnMatching { @@ -263,14 +265,12 @@ impl LongPollBuffer { let task_queue = task_queue.clone(); async move { client - .poll_nexus_task( - PollOptions { - task_queue, - no_retry, - timeout_override, - }, - send_heartbeat, - ) + .poll_nexus_task(PollOptions { + task_queue, + no_retry, + timeout_override, + worker_commands_queue, + }) .await } }; diff --git a/crates/sdk-core/src/worker/client.rs b/crates/sdk-core/src/worker/client.rs index d70e6eba6..f4f71f589 100644 --- a/crates/sdk-core/src/worker/client.rs +++ b/crates/sdk-core/src/worker/client.rs @@ -165,7 +165,6 @@ pub trait WorkerClient: Sync + Send { async fn poll_nexus_task( &self, poll_options: PollOptions, - send_heartbeat: bool, ) -> Result; /// Complete a workflow task async fn complete_workflow_task( @@ -275,6 +274,9 @@ pub struct PollOptions { pub no_retry: Option, /// Overrides the default RPC timeout for the poll request pub timeout_override: Option, + /// If true, poll using `TaskQueueKind::WorkerCommands`. Currently only meaningful for Nexus + /// polls issued by the shared-namespace worker. + pub worker_commands_queue: bool, } /// Additional options specific to workflow task polling #[derive(Debug, Clone)] @@ -381,14 +383,18 @@ impl WorkerClient for WorkerClientBag { async fn poll_nexus_task( &self, poll_options: PollOptions, - _send_heartbeat: bool, ) -> Result { + let kind = if poll_options.worker_commands_queue { + TaskQueueKind::WorkerCommands + } else { + TaskQueueKind::Normal + }; #[allow(deprecated)] // want to list all fields explicitly let mut request = PollNexusTaskQueueRequest { namespace: self.namespace.clone(), task_queue: Some(TaskQueue { name: poll_options.task_queue, - kind: TaskQueueKind::Normal as i32, + kind: kind as i32, normal_name: "".to_string(), }), identity: self.identity(), diff --git a/crates/sdk-core/src/worker/client/mocks.rs b/crates/sdk-core/src/worker/client/mocks.rs index 00218e45f..8df2f4a43 100644 --- a/crates/sdk-core/src/worker/client/mocks.rs +++ b/crates/sdk-core/src/worker/client/mocks.rs @@ -81,7 +81,7 @@ mockall::mock! { -> impl Future> + Send + 'b where 'a: 'b, Self: 'b; - fn poll_nexus_task<'a, 'b>(&self, poll_options: PollOptions, send_heartbeat: bool) + fn poll_nexus_task<'a, 'b>(&self, poll_options: PollOptions) -> impl Future> + Send + 'b where 'a: 'b, Self: 'b; diff --git a/crates/sdk-core/src/worker/heartbeat.rs b/crates/sdk-core/src/worker/heartbeat.rs index e93d8b98e..df4315760 100644 --- a/crates/sdk-core/src/worker/heartbeat.rs +++ b/crates/sdk-core/src/worker/heartbeat.rs @@ -324,7 +324,7 @@ mod tests { mock.expect_poll_workflow_task() .returning(move |_namespace, _task_queue| Ok(Default::default())); mock.expect_poll_nexus_task() - .returning(move |_poll_options, _send_heartbeat| Ok(Default::default())); + .returning(move |_poll_options| Ok(Default::default())); mock.expect_record_worker_heartbeat().times(3).returning( move |_namespace, worker_heartbeat| { assert_eq!(1, worker_heartbeat.len()); @@ -445,7 +445,7 @@ mod tests { let poll_returned_command_clone = poll_returned_command.clone(); let at_clone = activity_task_token.clone(); mock.expect_poll_nexus_task() - .returning(move |poll_options, _send_heartbeat| { + .returning(move |poll_options| { if poll_options .task_queue .starts_with("temporal-sys/worker-commands/") diff --git a/crates/sdk-core/src/worker/mod.rs b/crates/sdk-core/src/worker/mod.rs index 3f789544c..f443d8b99 100644 --- a/crates/sdk-core/src/worker/mod.rs +++ b/crates/sdk-core/src/worker/mod.rs @@ -727,8 +727,8 @@ impl Worker { shutdown_token.child_token(), Some(move |np| np_metrics.record_num_pollers(np)), nexus_last_suc_poll_time.clone(), - shared_namespace_worker, capabilities.clone(), + shared_namespace_worker, )) as BoxedNexusPoller) } else { None From 11546913f56bee55afae14841c18de6e26498af1 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 11 May 2026 10:54:46 -0700 Subject: [PATCH 7/8] Update protos to HEAD for worker_commands capability --- crates/client/src/grpc.rs | 36 + crates/client/src/lib.rs | 6 +- .../api_upstream/openapi/openapiv2.json | 1333 ++++++++++++++-- .../api_upstream/openapi/openapiv3.yaml | 1357 +++++++++++++++-- .../temporal/api/history/v1/message.proto | 10 + .../temporal/api/namespace/v1/message.proto | 2 + .../workflowservice/v1/request_response.proto | 162 +- .../api/workflowservice/v1/service.proto | 139 ++ crates/sdk-core-c-bridge/src/client.rs | 17 + 9 files changed, 2728 insertions(+), 334 deletions(-) diff --git a/crates/client/src/grpc.rs b/crates/client/src/grpc.rs index 00e64447a..d0fc5526d 100644 --- a/crates/client/src/grpc.rs +++ b/crates/client/src/grpc.rs @@ -1587,6 +1587,42 @@ proxier! { r.extensions_mut().insert(labels); } ); + ( + pause_activity_execution, + PauseActivityExecutionRequest, + PauseActivityExecutionResponse, + |r| { + let labels = namespaced_request!(r); + r.extensions_mut().insert(labels); + } + ); + ( + unpause_activity_execution, + UnpauseActivityExecutionRequest, + UnpauseActivityExecutionResponse, + |r| { + let labels = namespaced_request!(r); + r.extensions_mut().insert(labels); + } + ); + ( + reset_activity_execution, + ResetActivityExecutionRequest, + ResetActivityExecutionResponse, + |r| { + let labels = namespaced_request!(r); + r.extensions_mut().insert(labels); + } + ); + ( + update_activity_execution_options, + UpdateActivityExecutionOptionsRequest, + UpdateActivityExecutionOptionsResponse, + |r| { + let labels = namespaced_request!(r); + r.extensions_mut().insert(labels); + } + ); ( count_nexus_operation_executions, CountNexusOperationExecutionsRequest, diff --git a/crates/client/src/lib.rs b/crates/client/src/lib.rs index 1f1f6ef05..db3b9fdc0 100644 --- a/crates/client/src/lib.rs +++ b/crates/client/src/lib.rs @@ -765,7 +765,11 @@ impl Namespace { Namespace::Name(n) => (n, "".to_owned()), Namespace::Id(n) => ("".to_owned(), n), }; - DescribeNamespaceRequest { namespace, id } + DescribeNamespaceRequest { + namespace, + id, + weak_consistency: false, + } } } diff --git a/crates/common/protos/api_upstream/openapi/openapiv2.json b/crates/common/protos/api_upstream/openapi/openapiv2.json index 10830b299..917fce76c 100644 --- a/crates/common/protos/api_upstream/openapi/openapiv2.json +++ b/crates/common/protos/api_upstream/openapi/openapiv2.json @@ -150,6 +150,13 @@ "in": "query", "required": false, "type": "string" + }, + { + "name": "weakConsistency", + "description": "If true, the server may serve the response from an eventually-consistent\nsource instead of reading through to persistence. Defaults to false,\nwhich preserves read-after-write consistency. SDKs should set this when\nfetching namespace capabilities on worker/client startup.", + "in": "query", + "required": false, + "type": "boolean" } ], "tags": [ @@ -716,6 +723,102 @@ ] } }, + "/api/v1/namespaces/{namespace}/activities/{activityId}/pause": { + "post": { + "summary": "PauseActivityExecution pauses the execution of an activity specified by its ID.\nThis API can be used to target a workflow activity or a standalone activity", + "description": "Pausing an activity means:\n- If the activity is currently waiting for a retry or is running and subsequently fails,\n it will not be rescheduled until it is unpaused.\n- If the activity is already paused, calling this method will have no effect.\n- If the activity is running and finishes successfully, the activity will be completed.\n- If the activity is running and finishes with failure:\n * if there is no retry left - the activity will be completed.\n * if there are more retries left - the activity will be paused.\nFor long-running activities:\n- activities in paused state will send a cancellation with \"activity_paused\" set to 'true' in response to 'RecordActivityTaskHeartbeat'.\n\nReturns a `NotFound` error if there is no pending activity with the provided ID", + "operationId": "PauseActivityExecution2", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1PauseActivityExecutionResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "namespace", + "description": "Namespace of the workflow which scheduled this activity.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "activityId", + "description": "The ID of the activity to target.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/WorkflowServicePauseActivityExecutionBody" + } + } + ], + "tags": [ + "WorkflowService" + ] + } + }, + "/api/v1/namespaces/{namespace}/activities/{activityId}/reset": { + "post": { + "summary": "ResetActivityExecution resets the execution of an activity specified by its ID.\nThis API can be used to target a workflow activity or a standalone activity.", + "description": "Resetting an activity means:\n* number of attempts will be reset to 0.\n* activity timeouts will be reset.\n* if the activity is waiting for retry, and it is not paused or 'keep_paused' is not provided:\n it will be scheduled immediately (* see 'jitter' flag)\n\nReturns a `NotFound` error if there is no pending activity with the provided ID or type.", + "operationId": "ResetActivityExecution2", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ResetActivityExecutionResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "namespace", + "description": "Namespace of the workflow which scheduled this activity.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "activityId", + "description": "The ID of the activity to target.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/WorkflowServiceResetActivityExecutionBody" + } + } + ], + "tags": [ + "WorkflowService" + ] + } + }, "/api/v1/namespaces/{namespace}/activities/{activityId}/resolve-as-canceled": { "post": { "summary": "See `RespondActivityTaskCanceled`. This version allows clients to record failures by\nnamespace/workflow id/activity id instead of task token.", @@ -809,6 +912,101 @@ ] } }, + "/api/v1/namespaces/{namespace}/activities/{activityId}/unpause": { + "post": { + "summary": "UnpauseActivityExecution unpauses the execution of an activity specified by its ID.\nThis API can be used to target a workflow activity or a standalone activity.", + "description": "If activity is not paused, this call will have no effect.\nIf the activity was paused while waiting for retry, it will be scheduled immediately (* see 'jitter' flag).\nOnce the activity is unpaused, all timeout timers will be regenerated.\n\nReturns a `NotFound` error if there is no pending activity with the provided ID", + "operationId": "UnpauseActivityExecution2", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1UnpauseActivityExecutionResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "namespace", + "description": "Namespace of the workflow which scheduled this activity.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "activityId", + "description": "The ID of the activity to target.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/WorkflowServiceUnpauseActivityExecutionBody" + } + } + ], + "tags": [ + "WorkflowService" + ] + } + }, + "/api/v1/namespaces/{namespace}/activities/{activityId}/update-options": { + "post": { + "summary": "UpdateActivityExecutionOptions is called by the client to update the options of an activity by its ID.\nThis API can be used to target a workflow activity or a standalone activity.", + "operationId": "UpdateActivityExecutionOptions2", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1UpdateActivityExecutionOptionsResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "namespace", + "description": "Namespace of the workflow which scheduled this activity", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "activityId", + "description": "The ID of the activity to target.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/WorkflowServiceUpdateActivityExecutionOptionsBody" + } + } + ], + "tags": [ + "WorkflowService" + ] + } + }, "/api/v1/namespaces/{namespace}/activity-complete": { "post": { "summary": "RespondActivityTaskCompleted is called by workers when they successfully complete an activity\ntask.", @@ -3290,6 +3488,13 @@ "in": "query", "required": false, "type": "string" + }, + { + "name": "includeSystemWorkers", + "description": "When true, the response will include system workers that are created implicitly\nby the server and not by the user. By default, system workers are excluded.", + "in": "query", + "required": false, + "type": "boolean" } ], "tags": [ @@ -4470,15 +4675,16 @@ ] } }, - "/api/v1/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/resolve-as-canceled": { + "/api/v1/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/pause": { "post": { - "summary": "See `RespondActivityTaskCanceled`. This version allows clients to record failures by\nnamespace/workflow id/activity id instead of task token.", - "operationId": "RespondActivityTaskCanceledById4", + "summary": "PauseActivityExecution pauses the execution of an activity specified by its ID.\nThis API can be used to target a workflow activity or a standalone activity", + "description": "Pausing an activity means:\n- If the activity is currently waiting for a retry or is running and subsequently fails,\n it will not be rescheduled until it is unpaused.\n- If the activity is already paused, calling this method will have no effect.\n- If the activity is running and finishes successfully, the activity will be completed.\n- If the activity is running and finishes with failure:\n * if there is no retry left - the activity will be completed.\n * if there are more retries left - the activity will be paused.\nFor long-running activities:\n- activities in paused state will send a cancellation with \"activity_paused\" set to 'true' in response to 'RecordActivityTaskHeartbeat'.\n\nReturns a `NotFound` error if there is no pending activity with the provided ID", + "operationId": "PauseActivityExecution4", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1RespondActivityTaskCanceledByIdResponse" + "$ref": "#/definitions/v1PauseActivityExecutionResponse" } }, "default": { @@ -4491,21 +4697,21 @@ "parameters": [ { "name": "namespace", - "description": "Namespace of the workflow which scheduled this activity", + "description": "Namespace of the workflow which scheduled this activity.", "in": "path", "required": true, "type": "string" }, { "name": "workflowId", - "description": "Id of the workflow which scheduled this activity, leave empty to target a standalone activity", + "description": "If provided, pause a workflow activity (or activities) for the given workflow ID.\nIf empty, targets a standalone activity.", "in": "path", "required": true, "type": "string" }, { "name": "activityId", - "description": "Id of the activity to confirm is cancelled", + "description": "The ID of the activity to target.", "in": "path", "required": true, "type": "string" @@ -4515,7 +4721,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/WorkflowServiceRespondActivityTaskCanceledByIdBody" + "$ref": "#/definitions/WorkflowServicePauseActivityExecutionBody" } } ], @@ -4524,15 +4730,16 @@ ] } }, - "/api/v1/namespaces/{namespace}/workflows/{workflowId}/pause": { + "/api/v1/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/reset": { "post": { - "summary": "Note: This is an experimental API and the behavior may change in a future release.\nPauseWorkflowExecution pauses the workflow execution specified in the request. Pausing a workflow execution results in\n- The workflow execution status changes to `PAUSED` and a new WORKFLOW_EXECUTION_PAUSED event is added to the history\n- No new workflow tasks or activity tasks are dispatched.\n - Any workflow task currently executing on the worker will be allowed to complete.\n - Any activity task currently executing will be paused.\n- All server-side events will continue to be processed by the server.\n- Queries & Updates on a paused workflow will be rejected.", - "operationId": "PauseWorkflowExecution2", + "summary": "ResetActivityExecution resets the execution of an activity specified by its ID.\nThis API can be used to target a workflow activity or a standalone activity.", + "description": "Resetting an activity means:\n* number of attempts will be reset to 0.\n* activity timeouts will be reset.\n* if the activity is waiting for retry, and it is not paused or 'keep_paused' is not provided:\n it will be scheduled immediately (* see 'jitter' flag)\n\nReturns a `NotFound` error if there is no pending activity with the provided ID or type.", + "operationId": "ResetActivityExecution4", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1PauseWorkflowExecutionResponse" + "$ref": "#/definitions/v1ResetActivityExecutionResponse" } }, "default": { @@ -4545,14 +4752,21 @@ "parameters": [ { "name": "namespace", - "description": "Namespace of the workflow to pause.", + "description": "Namespace of the workflow which scheduled this activity.", "in": "path", "required": true, "type": "string" }, { "name": "workflowId", - "description": "ID of the workflow execution to be paused. Required.", + "description": "If provided, targets a workflow activity for the given workflow ID.\nIf empty, targets a standalone activity.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "activityId", + "description": "The ID of the activity to target.", "in": "path", "required": true, "type": "string" @@ -4562,7 +4776,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/WorkflowServicePauseWorkflowExecutionBody" + "$ref": "#/definitions/WorkflowServiceResetActivityExecutionBody" } } ], @@ -4571,16 +4785,15 @@ ] } }, - "/api/v1/namespaces/{namespace}/workflows/{workflowId}/signal-with-start/{signalName}": { + "/api/v1/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/resolve-as-canceled": { "post": { - "summary": "SignalWithStartWorkflowExecution is used to ensure a signal is sent to a workflow, even if\nit isn't yet started.", - "description": "If the workflow is running, a `WORKFLOW_EXECUTION_SIGNALED` event is recorded in the history\nand a workflow task is generated.\n\nIf the workflow is not running or not found, then the workflow is created with\n`WORKFLOW_EXECUTION_STARTED` and `WORKFLOW_EXECUTION_SIGNALED` events in its history, and a\nworkflow task is generated.", - "operationId": "SignalWithStartWorkflowExecution2", + "summary": "See `RespondActivityTaskCanceled`. This version allows clients to record failures by\nnamespace/workflow id/activity id instead of task token.", + "operationId": "RespondActivityTaskCanceledById4", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1SignalWithStartWorkflowExecutionResponse" + "$ref": "#/definitions/v1RespondActivityTaskCanceledByIdResponse" } }, "default": { @@ -4593,19 +4806,21 @@ "parameters": [ { "name": "namespace", + "description": "Namespace of the workflow which scheduled this activity", "in": "path", "required": true, "type": "string" }, { "name": "workflowId", + "description": "Id of the workflow which scheduled this activity, leave empty to target a standalone activity", "in": "path", "required": true, "type": "string" }, { - "name": "signalName", - "description": "The workflow author-defined name of the signal to send to the workflow", + "name": "activityId", + "description": "Id of the activity to confirm is cancelled", "in": "path", "required": true, "type": "string" @@ -4615,7 +4830,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/WorkflowServiceSignalWithStartWorkflowExecutionBody" + "$ref": "#/definitions/WorkflowServiceRespondActivityTaskCanceledByIdBody" } } ], @@ -4624,15 +4839,16 @@ ] } }, - "/api/v1/namespaces/{namespace}/workflows/{workflowId}/unpause": { + "/api/v1/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/unpause": { "post": { - "summary": "Note: This is an experimental API and the behavior may change in a future release.\nUnpauseWorkflowExecution unpauses a previously paused workflow execution specified in the request.\nUnpausing a workflow execution results in\n- The workflow execution status changes to `RUNNING` and a new WORKFLOW_EXECUTION_UNPAUSED event is added to the history\n- Workflow tasks and activity tasks are resumed.", - "operationId": "UnpauseWorkflowExecution2", + "summary": "UnpauseActivityExecution unpauses the execution of an activity specified by its ID.\nThis API can be used to target a workflow activity or a standalone activity.", + "description": "If activity is not paused, this call will have no effect.\nIf the activity was paused while waiting for retry, it will be scheduled immediately (* see 'jitter' flag).\nOnce the activity is unpaused, all timeout timers will be regenerated.\n\nReturns a `NotFound` error if there is no pending activity with the provided ID", + "operationId": "UnpauseActivityExecution4", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1UnpauseWorkflowExecutionResponse" + "$ref": "#/definitions/v1UnpauseActivityExecutionResponse" } }, "default": { @@ -4645,14 +4861,21 @@ "parameters": [ { "name": "namespace", - "description": "Namespace of the workflow to unpause.", + "description": "Namespace of the workflow which scheduled this activity.", "in": "path", "required": true, "type": "string" }, { "name": "workflowId", - "description": "ID of the workflow execution to be paused. Required.", + "description": "If provided, targets a workflow activity for the given workflow ID.\nIf empty, targets a standalone activity.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "activityId", + "description": "The ID of the activity to target.", "in": "path", "required": true, "type": "string" @@ -4662,7 +4885,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/WorkflowServiceUnpauseWorkflowExecutionBody" + "$ref": "#/definitions/WorkflowServiceUnpauseActivityExecutionBody" } } ], @@ -4671,15 +4894,15 @@ ] } }, - "/api/v1/nexus/endpoints": { - "get": { - "summary": "List all Nexus endpoints for the cluster, sorted by ID in ascending order. Set page_token in the request to the\nnext_page_token field of the previous response to get the next page of results. An empty next_page_token\nindicates that there are no more results. During pagination, a newly added service with an ID lexicographically\nearlier than the previous page's last endpoint's ID may be missed.", - "operationId": "ListNexusEndpoints2", + "/api/v1/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/update-options": { + "post": { + "summary": "UpdateActivityExecutionOptions is called by the client to update the options of an activity by its ID.\nThis API can be used to target a workflow activity or a standalone activity.", + "operationId": "UpdateActivityExecutionOptions4", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1ListNexusEndpointsResponse" + "$ref": "#/definitions/v1UpdateActivityExecutionOptionsResponse" } }, "default": { @@ -4691,23 +4914,224 @@ }, "parameters": [ { - "name": "pageSize", - "in": "query", - "required": false, - "type": "integer", - "format": "int32" + "name": "namespace", + "description": "Namespace of the workflow which scheduled this activity", + "in": "path", + "required": true, + "type": "string" }, { - "name": "nextPageToken", - "description": "To get the next page, pass in `ListNexusEndpointsResponse.next_page_token` from the previous page's\nresponse, the token will be empty if there's no other page.\nNote: the last page may be empty if the total number of endpoints registered is a multiple of the page size.", - "in": "query", - "required": false, - "type": "string", - "format": "byte" + "name": "workflowId", + "description": "If provided, targets a workflow activity for the given workflow ID.\nIf empty, targets a standalone activity.", + "in": "path", + "required": true, + "type": "string" }, { - "name": "name", - "description": "Name of the incoming endpoint to filter on - optional. Specifying this will result in zero or one results.", + "name": "activityId", + "description": "The ID of the activity to target.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/WorkflowServiceUpdateActivityExecutionOptionsBody" + } + } + ], + "tags": [ + "WorkflowService" + ] + } + }, + "/api/v1/namespaces/{namespace}/workflows/{workflowId}/pause": { + "post": { + "summary": "Note: This is an experimental API and the behavior may change in a future release.\nPauseWorkflowExecution pauses the workflow execution specified in the request. Pausing a workflow execution results in\n- The workflow execution status changes to `PAUSED` and a new WORKFLOW_EXECUTION_PAUSED event is added to the history\n- No new workflow tasks or activity tasks are dispatched.\n - Any workflow task currently executing on the worker will be allowed to complete.\n - Any activity task currently executing will be paused.\n- All server-side events will continue to be processed by the server.\n- Queries & Updates on a paused workflow will be rejected.", + "operationId": "PauseWorkflowExecution2", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1PauseWorkflowExecutionResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "namespace", + "description": "Namespace of the workflow to pause.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "workflowId", + "description": "ID of the workflow execution to be paused. Required.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/WorkflowServicePauseWorkflowExecutionBody" + } + } + ], + "tags": [ + "WorkflowService" + ] + } + }, + "/api/v1/namespaces/{namespace}/workflows/{workflowId}/signal-with-start/{signalName}": { + "post": { + "summary": "SignalWithStartWorkflowExecution is used to ensure a signal is sent to a workflow, even if\nit isn't yet started.", + "description": "If the workflow is running, a `WORKFLOW_EXECUTION_SIGNALED` event is recorded in the history\nand a workflow task is generated.\n\nIf the workflow is not running or not found, then the workflow is created with\n`WORKFLOW_EXECUTION_STARTED` and `WORKFLOW_EXECUTION_SIGNALED` events in its history, and a\nworkflow task is generated.", + "operationId": "SignalWithStartWorkflowExecution2", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1SignalWithStartWorkflowExecutionResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "namespace", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "workflowId", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "signalName", + "description": "The workflow author-defined name of the signal to send to the workflow", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/WorkflowServiceSignalWithStartWorkflowExecutionBody" + } + } + ], + "tags": [ + "WorkflowService" + ] + } + }, + "/api/v1/namespaces/{namespace}/workflows/{workflowId}/unpause": { + "post": { + "summary": "Note: This is an experimental API and the behavior may change in a future release.\nUnpauseWorkflowExecution unpauses a previously paused workflow execution specified in the request.\nUnpausing a workflow execution results in\n- The workflow execution status changes to `RUNNING` and a new WORKFLOW_EXECUTION_UNPAUSED event is added to the history\n- Workflow tasks and activity tasks are resumed.", + "operationId": "UnpauseWorkflowExecution2", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1UnpauseWorkflowExecutionResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "namespace", + "description": "Namespace of the workflow to unpause.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "workflowId", + "description": "ID of the workflow execution to be paused. Required.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/WorkflowServiceUnpauseWorkflowExecutionBody" + } + } + ], + "tags": [ + "WorkflowService" + ] + } + }, + "/api/v1/nexus/endpoints": { + "get": { + "summary": "List all Nexus endpoints for the cluster, sorted by ID in ascending order. Set page_token in the request to the\nnext_page_token field of the previous response to get the next page of results. An empty next_page_token\nindicates that there are no more results. During pagination, a newly added service with an ID lexicographically\nearlier than the previous page's last endpoint's ID may be missed.", + "operationId": "ListNexusEndpoints2", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ListNexusEndpointsResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "pageSize", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "nextPageToken", + "description": "To get the next page, pass in `ListNexusEndpointsResponse.next_page_token` from the previous page's\nresponse, the token will be empty if there's no other page.\nNote: the last page may be empty if the total number of endpoints registered is a multiple of the page size.", + "in": "query", + "required": false, + "type": "string", + "format": "byte" + }, + { + "name": "name", + "description": "Name of the incoming endpoint to filter on - optional. Specifying this will result in zero or one results.", "in": "query", "required": false, "type": "string" @@ -5013,6 +5437,13 @@ "in": "query", "required": false, "type": "string" + }, + { + "name": "weakConsistency", + "description": "If true, the server may serve the response from an eventually-consistent\nsource instead of reading through to persistence. Defaults to false,\nwhich preserves read-after-write consistency. SDKs should set this when\nfetching namespace capabilities on worker/client startup.", + "in": "query", + "required": false, + "type": "boolean" } ], "tags": [ @@ -5660,7 +6091,193 @@ "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1RespondActivityTaskCompletedByIdResponse" + "$ref": "#/definitions/v1RespondActivityTaskCompletedByIdResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "namespace", + "description": "Namespace of the workflow which scheduled this activity", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "activityId", + "description": "Id of the activity to complete", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/WorkflowServiceRespondActivityTaskCompletedByIdBody" + } + } + ], + "tags": [ + "WorkflowService" + ] + } + }, + "/namespaces/{namespace}/activities/{activityId}/fail": { + "post": { + "summary": "See `RecordActivityTaskFailed`. This version allows clients to record failures by\nnamespace/workflow id/activity id instead of task token.", + "operationId": "RespondActivityTaskFailedById", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1RespondActivityTaskFailedByIdResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "namespace", + "description": "Namespace of the workflow which scheduled this activity", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "activityId", + "description": "Id of the activity to fail", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/WorkflowServiceRespondActivityTaskFailedByIdBody" + } + } + ], + "tags": [ + "WorkflowService" + ] + } + }, + "/namespaces/{namespace}/activities/{activityId}/heartbeat": { + "post": { + "summary": "See `RecordActivityTaskHeartbeat`. This version allows clients to record heartbeats by\nnamespace/workflow id/activity id instead of task token.", + "operationId": "RecordActivityTaskHeartbeatById", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1RecordActivityTaskHeartbeatByIdResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "namespace", + "description": "Namespace of the workflow which scheduled this activity", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "activityId", + "description": "Id of the activity we're heartbeating", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/WorkflowServiceRecordActivityTaskHeartbeatByIdBody" + } + } + ], + "tags": [ + "WorkflowService" + ] + } + }, + "/namespaces/{namespace}/activities/{activityId}/outcome": { + "get": { + "summary": "PollActivityExecution long-polls for an activity execution to complete and returns the\noutcome (result or failure).", + "operationId": "PollActivityExecution", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1PollActivityExecutionResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "namespace", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "activityId", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "runId", + "description": "Activity run ID. If empty the request targets the latest run.", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "WorkflowService" + ] + } + }, + "/namespaces/{namespace}/activities/{activityId}/pause": { + "post": { + "summary": "PauseActivityExecution pauses the execution of an activity specified by its ID.\nThis API can be used to target a workflow activity or a standalone activity", + "description": "Pausing an activity means:\n- If the activity is currently waiting for a retry or is running and subsequently fails,\n it will not be rescheduled until it is unpaused.\n- If the activity is already paused, calling this method will have no effect.\n- If the activity is running and finishes successfully, the activity will be completed.\n- If the activity is running and finishes with failure:\n * if there is no retry left - the activity will be completed.\n * if there are more retries left - the activity will be paused.\nFor long-running activities:\n- activities in paused state will send a cancellation with \"activity_paused\" set to 'true' in response to 'RecordActivityTaskHeartbeat'.\n\nReturns a `NotFound` error if there is no pending activity with the provided ID", + "operationId": "PauseActivityExecution", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1PauseActivityExecutionResponse" } }, "default": { @@ -5673,14 +6290,14 @@ "parameters": [ { "name": "namespace", - "description": "Namespace of the workflow which scheduled this activity", + "description": "Namespace of the workflow which scheduled this activity.", "in": "path", "required": true, "type": "string" }, { "name": "activityId", - "description": "Id of the activity to complete", + "description": "The ID of the activity to target.", "in": "path", "required": true, "type": "string" @@ -5690,7 +6307,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/WorkflowServiceRespondActivityTaskCompletedByIdBody" + "$ref": "#/definitions/WorkflowServicePauseActivityExecutionBody" } } ], @@ -5699,15 +6316,16 @@ ] } }, - "/namespaces/{namespace}/activities/{activityId}/fail": { + "/namespaces/{namespace}/activities/{activityId}/reset": { "post": { - "summary": "See `RecordActivityTaskFailed`. This version allows clients to record failures by\nnamespace/workflow id/activity id instead of task token.", - "operationId": "RespondActivityTaskFailedById", + "summary": "ResetActivityExecution resets the execution of an activity specified by its ID.\nThis API can be used to target a workflow activity or a standalone activity.", + "description": "Resetting an activity means:\n* number of attempts will be reset to 0.\n* activity timeouts will be reset.\n* if the activity is waiting for retry, and it is not paused or 'keep_paused' is not provided:\n it will be scheduled immediately (* see 'jitter' flag)\n\nReturns a `NotFound` error if there is no pending activity with the provided ID or type.", + "operationId": "ResetActivityExecution", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1RespondActivityTaskFailedByIdResponse" + "$ref": "#/definitions/v1ResetActivityExecutionResponse" } }, "default": { @@ -5720,14 +6338,14 @@ "parameters": [ { "name": "namespace", - "description": "Namespace of the workflow which scheduled this activity", + "description": "Namespace of the workflow which scheduled this activity.", "in": "path", "required": true, "type": "string" }, { "name": "activityId", - "description": "Id of the activity to fail", + "description": "The ID of the activity to target.", "in": "path", "required": true, "type": "string" @@ -5737,7 +6355,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/WorkflowServiceRespondActivityTaskFailedByIdBody" + "$ref": "#/definitions/WorkflowServiceResetActivityExecutionBody" } } ], @@ -5746,15 +6364,15 @@ ] } }, - "/namespaces/{namespace}/activities/{activityId}/heartbeat": { + "/namespaces/{namespace}/activities/{activityId}/resolve-as-canceled": { "post": { - "summary": "See `RecordActivityTaskHeartbeat`. This version allows clients to record heartbeats by\nnamespace/workflow id/activity id instead of task token.", - "operationId": "RecordActivityTaskHeartbeatById", + "summary": "See `RespondActivityTaskCanceled`. This version allows clients to record failures by\nnamespace/workflow id/activity id instead of task token.", + "operationId": "RespondActivityTaskCanceledById", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1RecordActivityTaskHeartbeatByIdResponse" + "$ref": "#/definitions/v1RespondActivityTaskCanceledByIdResponse" } }, "default": { @@ -5774,7 +6392,7 @@ }, { "name": "activityId", - "description": "Id of the activity we're heartbeating", + "description": "Id of the activity to confirm is cancelled", "in": "path", "required": true, "type": "string" @@ -5784,7 +6402,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/WorkflowServiceRecordActivityTaskHeartbeatByIdBody" + "$ref": "#/definitions/WorkflowServiceRespondActivityTaskCanceledByIdBody" } } ], @@ -5793,15 +6411,16 @@ ] } }, - "/namespaces/{namespace}/activities/{activityId}/outcome": { - "get": { - "summary": "PollActivityExecution long-polls for an activity execution to complete and returns the\noutcome (result or failure).", - "operationId": "PollActivityExecution", + "/namespaces/{namespace}/activities/{activityId}/terminate": { + "post": { + "summary": "TerminateActivityExecution terminates an existing activity execution immediately.", + "description": "Termination does not reach the worker and the activity code cannot react to it. A terminated activity may have a\nrunning attempt.", + "operationId": "TerminateActivityExecution", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1PollActivityExecutionResponse" + "$ref": "#/definitions/v1TerminateActivityExecutionResponse" } }, "default": { @@ -5825,11 +6444,12 @@ "type": "string" }, { - "name": "runId", - "description": "Activity run ID. If empty the request targets the latest run.", - "in": "query", - "required": false, - "type": "string" + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/WorkflowServiceTerminateActivityExecutionBody" + } } ], "tags": [ @@ -5837,15 +6457,16 @@ ] } }, - "/namespaces/{namespace}/activities/{activityId}/resolve-as-canceled": { + "/namespaces/{namespace}/activities/{activityId}/unpause": { "post": { - "summary": "See `RespondActivityTaskCanceled`. This version allows clients to record failures by\nnamespace/workflow id/activity id instead of task token.", - "operationId": "RespondActivityTaskCanceledById", + "summary": "UnpauseActivityExecution unpauses the execution of an activity specified by its ID.\nThis API can be used to target a workflow activity or a standalone activity.", + "description": "If activity is not paused, this call will have no effect.\nIf the activity was paused while waiting for retry, it will be scheduled immediately (* see 'jitter' flag).\nOnce the activity is unpaused, all timeout timers will be regenerated.\n\nReturns a `NotFound` error if there is no pending activity with the provided ID", + "operationId": "UnpauseActivityExecution", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1RespondActivityTaskCanceledByIdResponse" + "$ref": "#/definitions/v1UnpauseActivityExecutionResponse" } }, "default": { @@ -5858,14 +6479,14 @@ "parameters": [ { "name": "namespace", - "description": "Namespace of the workflow which scheduled this activity", + "description": "Namespace of the workflow which scheduled this activity.", "in": "path", "required": true, "type": "string" }, { "name": "activityId", - "description": "Id of the activity to confirm is cancelled", + "description": "The ID of the activity to target.", "in": "path", "required": true, "type": "string" @@ -5875,7 +6496,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/WorkflowServiceRespondActivityTaskCanceledByIdBody" + "$ref": "#/definitions/WorkflowServiceUnpauseActivityExecutionBody" } } ], @@ -5884,16 +6505,15 @@ ] } }, - "/namespaces/{namespace}/activities/{activityId}/terminate": { + "/namespaces/{namespace}/activities/{activityId}/update-options": { "post": { - "summary": "TerminateActivityExecution terminates an existing activity execution immediately.", - "description": "Termination does not reach the worker and the activity code cannot react to it. A terminated activity may have a\nrunning attempt.", - "operationId": "TerminateActivityExecution", + "summary": "UpdateActivityExecutionOptions is called by the client to update the options of an activity by its ID.\nThis API can be used to target a workflow activity or a standalone activity.", + "operationId": "UpdateActivityExecutionOptions", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1TerminateActivityExecutionResponse" + "$ref": "#/definitions/v1UpdateActivityExecutionOptionsResponse" } }, "default": { @@ -5906,12 +6526,14 @@ "parameters": [ { "name": "namespace", + "description": "Namespace of the workflow which scheduled this activity", "in": "path", "required": true, "type": "string" }, { "name": "activityId", + "description": "The ID of the activity to target.", "in": "path", "required": true, "type": "string" @@ -5921,7 +6543,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/WorkflowServiceTerminateActivityExecutionBody" + "$ref": "#/definitions/WorkflowServiceUpdateActivityExecutionOptionsBody" } } ], @@ -8341,6 +8963,13 @@ "in": "query", "required": false, "type": "string" + }, + { + "name": "includeSystemWorkers", + "description": "When true, the response will include system workers that are created implicitly\nby the server and not by the user. By default, system workers are excluded.", + "in": "query", + "required": false, + "type": "boolean" } ], "tags": [ @@ -9169,15 +9798,205 @@ ] } }, - "/namespaces/{namespace}/workflows/{workflowExecution.workflowId}/terminate": { + "/namespaces/{namespace}/workflows/{workflowExecution.workflowId}/terminate": { + "post": { + "summary": "TerminateWorkflowExecution terminates an existing workflow execution by recording a\n`WORKFLOW_EXECUTION_TERMINATED` event in the history and immediately terminating the\nexecution instance.", + "operationId": "TerminateWorkflowExecution", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1TerminateWorkflowExecutionResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "namespace", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "workflowExecution.workflowId", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/WorkflowServiceTerminateWorkflowExecutionBody" + } + } + ], + "tags": [ + "WorkflowService" + ] + } + }, + "/namespaces/{namespace}/workflows/{workflowExecution.workflowId}/update-options": { + "post": { + "summary": "UpdateWorkflowExecutionOptions partially updates the WorkflowExecutionOptions of an existing workflow execution.", + "operationId": "UpdateWorkflowExecutionOptions", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1UpdateWorkflowExecutionOptionsResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "namespace", + "description": "The namespace name of the target Workflow.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "workflowExecution.workflowId", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/WorkflowServiceUpdateWorkflowExecutionOptionsBody" + } + } + ], + "tags": [ + "WorkflowService" + ] + } + }, + "/namespaces/{namespace}/workflows/{workflowExecution.workflowId}/update/{request.input.name}": { + "post": { + "summary": "Invokes the specified Update function on user Workflow code.", + "operationId": "UpdateWorkflowExecution", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1UpdateWorkflowExecutionResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "namespace", + "description": "The namespace name of the target Workflow.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "workflowExecution.workflowId", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "request.input.name", + "description": "The name of the Update handler to invoke on the target Workflow.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/WorkflowServiceUpdateWorkflowExecutionBody" + } + } + ], + "tags": [ + "WorkflowService" + ] + } + }, + "/namespaces/{namespace}/workflows/{workflowId}": { + "post": { + "summary": "StartWorkflowExecution starts a new workflow execution.", + "description": "It will create the execution with a `WORKFLOW_EXECUTION_STARTED` event in its history and\nalso schedule the first workflow task. Returns `WorkflowExecutionAlreadyStarted`, if an\ninstance already exists with same workflow id.", + "operationId": "StartWorkflowExecution", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1StartWorkflowExecutionResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "namespace", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "workflowId", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/WorkflowServiceStartWorkflowExecutionBody" + } + } + ], + "tags": [ + "WorkflowService" + ] + } + }, + "/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/complete": { "post": { - "summary": "TerminateWorkflowExecution terminates an existing workflow execution by recording a\n`WORKFLOW_EXECUTION_TERMINATED` event in the history and immediately terminating the\nexecution instance.", - "operationId": "TerminateWorkflowExecution", + "summary": "See `RespondActivityTaskCompleted`. This version allows clients to record completions by\nnamespace/workflow id/activity id instead of task token.", + "operationId": "RespondActivityTaskCompletedById3", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1TerminateWorkflowExecutionResponse" + "$ref": "#/definitions/v1RespondActivityTaskCompletedByIdResponse" } }, "default": { @@ -9190,12 +10009,21 @@ "parameters": [ { "name": "namespace", + "description": "Namespace of the workflow which scheduled this activity", "in": "path", "required": true, "type": "string" }, { - "name": "workflowExecution.workflowId", + "name": "workflowId", + "description": "Id of the workflow which scheduled this activity, leave empty to target a standalone activity", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "activityId", + "description": "Id of the activity to complete", "in": "path", "required": true, "type": "string" @@ -9205,7 +10033,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/WorkflowServiceTerminateWorkflowExecutionBody" + "$ref": "#/definitions/WorkflowServiceRespondActivityTaskCompletedByIdBody" } } ], @@ -9214,15 +10042,15 @@ ] } }, - "/namespaces/{namespace}/workflows/{workflowExecution.workflowId}/update-options": { + "/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/fail": { "post": { - "summary": "UpdateWorkflowExecutionOptions partially updates the WorkflowExecutionOptions of an existing workflow execution.", - "operationId": "UpdateWorkflowExecutionOptions", + "summary": "See `RecordActivityTaskFailed`. This version allows clients to record failures by\nnamespace/workflow id/activity id instead of task token.", + "operationId": "RespondActivityTaskFailedById3", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1UpdateWorkflowExecutionOptionsResponse" + "$ref": "#/definitions/v1RespondActivityTaskFailedByIdResponse" } }, "default": { @@ -9235,13 +10063,21 @@ "parameters": [ { "name": "namespace", - "description": "The namespace name of the target Workflow.", + "description": "Namespace of the workflow which scheduled this activity", "in": "path", "required": true, "type": "string" }, { - "name": "workflowExecution.workflowId", + "name": "workflowId", + "description": "Id of the workflow which scheduled this activity, leave empty to target a standalone activity", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "activityId", + "description": "Id of the activity to fail", "in": "path", "required": true, "type": "string" @@ -9251,7 +10087,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/WorkflowServiceUpdateWorkflowExecutionOptionsBody" + "$ref": "#/definitions/WorkflowServiceRespondActivityTaskFailedByIdBody" } } ], @@ -9260,15 +10096,15 @@ ] } }, - "/namespaces/{namespace}/workflows/{workflowExecution.workflowId}/update/{request.input.name}": { + "/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/heartbeat": { "post": { - "summary": "Invokes the specified Update function on user Workflow code.", - "operationId": "UpdateWorkflowExecution", + "summary": "See `RecordActivityTaskHeartbeat`. This version allows clients to record heartbeats by\nnamespace/workflow id/activity id instead of task token.", + "operationId": "RecordActivityTaskHeartbeatById3", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1UpdateWorkflowExecutionResponse" + "$ref": "#/definitions/v1RecordActivityTaskHeartbeatByIdResponse" } }, "default": { @@ -9281,20 +10117,21 @@ "parameters": [ { "name": "namespace", - "description": "The namespace name of the target Workflow.", + "description": "Namespace of the workflow which scheduled this activity", "in": "path", "required": true, "type": "string" }, { - "name": "workflowExecution.workflowId", + "name": "workflowId", + "description": "Id of the workflow which scheduled this activity, leave empty to target a standalone activity", "in": "path", "required": true, "type": "string" }, { - "name": "request.input.name", - "description": "The name of the Update handler to invoke on the target Workflow.", + "name": "activityId", + "description": "Id of the activity we're heartbeating", "in": "path", "required": true, "type": "string" @@ -9304,7 +10141,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/WorkflowServiceUpdateWorkflowExecutionBody" + "$ref": "#/definitions/WorkflowServiceRecordActivityTaskHeartbeatByIdBody" } } ], @@ -9313,16 +10150,16 @@ ] } }, - "/namespaces/{namespace}/workflows/{workflowId}": { + "/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/pause": { "post": { - "summary": "StartWorkflowExecution starts a new workflow execution.", - "description": "It will create the execution with a `WORKFLOW_EXECUTION_STARTED` event in its history and\nalso schedule the first workflow task. Returns `WorkflowExecutionAlreadyStarted`, if an\ninstance already exists with same workflow id.", - "operationId": "StartWorkflowExecution", + "summary": "PauseActivityExecution pauses the execution of an activity specified by its ID.\nThis API can be used to target a workflow activity or a standalone activity", + "description": "Pausing an activity means:\n- If the activity is currently waiting for a retry or is running and subsequently fails,\n it will not be rescheduled until it is unpaused.\n- If the activity is already paused, calling this method will have no effect.\n- If the activity is running and finishes successfully, the activity will be completed.\n- If the activity is running and finishes with failure:\n * if there is no retry left - the activity will be completed.\n * if there are more retries left - the activity will be paused.\nFor long-running activities:\n- activities in paused state will send a cancellation with \"activity_paused\" set to 'true' in response to 'RecordActivityTaskHeartbeat'.\n\nReturns a `NotFound` error if there is no pending activity with the provided ID", + "operationId": "PauseActivityExecution3", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1StartWorkflowExecutionResponse" + "$ref": "#/definitions/v1PauseActivityExecutionResponse" } }, "default": { @@ -9335,12 +10172,21 @@ "parameters": [ { "name": "namespace", + "description": "Namespace of the workflow which scheduled this activity.", "in": "path", "required": true, "type": "string" }, { "name": "workflowId", + "description": "If provided, pause a workflow activity (or activities) for the given workflow ID.\nIf empty, targets a standalone activity.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "activityId", + "description": "The ID of the activity to target.", "in": "path", "required": true, "type": "string" @@ -9350,7 +10196,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/WorkflowServiceStartWorkflowExecutionBody" + "$ref": "#/definitions/WorkflowServicePauseActivityExecutionBody" } } ], @@ -9359,15 +10205,16 @@ ] } }, - "/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/complete": { + "/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/reset": { "post": { - "summary": "See `RespondActivityTaskCompleted`. This version allows clients to record completions by\nnamespace/workflow id/activity id instead of task token.", - "operationId": "RespondActivityTaskCompletedById3", + "summary": "ResetActivityExecution resets the execution of an activity specified by its ID.\nThis API can be used to target a workflow activity or a standalone activity.", + "description": "Resetting an activity means:\n* number of attempts will be reset to 0.\n* activity timeouts will be reset.\n* if the activity is waiting for retry, and it is not paused or 'keep_paused' is not provided:\n it will be scheduled immediately (* see 'jitter' flag)\n\nReturns a `NotFound` error if there is no pending activity with the provided ID or type.", + "operationId": "ResetActivityExecution3", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1RespondActivityTaskCompletedByIdResponse" + "$ref": "#/definitions/v1ResetActivityExecutionResponse" } }, "default": { @@ -9380,21 +10227,21 @@ "parameters": [ { "name": "namespace", - "description": "Namespace of the workflow which scheduled this activity", + "description": "Namespace of the workflow which scheduled this activity.", "in": "path", "required": true, "type": "string" }, { "name": "workflowId", - "description": "Id of the workflow which scheduled this activity, leave empty to target a standalone activity", + "description": "If provided, targets a workflow activity for the given workflow ID.\nIf empty, targets a standalone activity.", "in": "path", "required": true, "type": "string" }, { "name": "activityId", - "description": "Id of the activity to complete", + "description": "The ID of the activity to target.", "in": "path", "required": true, "type": "string" @@ -9404,7 +10251,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/WorkflowServiceRespondActivityTaskCompletedByIdBody" + "$ref": "#/definitions/WorkflowServiceResetActivityExecutionBody" } } ], @@ -9413,15 +10260,15 @@ ] } }, - "/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/fail": { + "/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/resolve-as-canceled": { "post": { - "summary": "See `RecordActivityTaskFailed`. This version allows clients to record failures by\nnamespace/workflow id/activity id instead of task token.", - "operationId": "RespondActivityTaskFailedById3", + "summary": "See `RespondActivityTaskCanceled`. This version allows clients to record failures by\nnamespace/workflow id/activity id instead of task token.", + "operationId": "RespondActivityTaskCanceledById3", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1RespondActivityTaskFailedByIdResponse" + "$ref": "#/definitions/v1RespondActivityTaskCanceledByIdResponse" } }, "default": { @@ -9448,7 +10295,7 @@ }, { "name": "activityId", - "description": "Id of the activity to fail", + "description": "Id of the activity to confirm is cancelled", "in": "path", "required": true, "type": "string" @@ -9458,7 +10305,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/WorkflowServiceRespondActivityTaskFailedByIdBody" + "$ref": "#/definitions/WorkflowServiceRespondActivityTaskCanceledByIdBody" } } ], @@ -9467,15 +10314,16 @@ ] } }, - "/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/heartbeat": { + "/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/unpause": { "post": { - "summary": "See `RecordActivityTaskHeartbeat`. This version allows clients to record heartbeats by\nnamespace/workflow id/activity id instead of task token.", - "operationId": "RecordActivityTaskHeartbeatById3", + "summary": "UnpauseActivityExecution unpauses the execution of an activity specified by its ID.\nThis API can be used to target a workflow activity or a standalone activity.", + "description": "If activity is not paused, this call will have no effect.\nIf the activity was paused while waiting for retry, it will be scheduled immediately (* see 'jitter' flag).\nOnce the activity is unpaused, all timeout timers will be regenerated.\n\nReturns a `NotFound` error if there is no pending activity with the provided ID", + "operationId": "UnpauseActivityExecution3", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1RecordActivityTaskHeartbeatByIdResponse" + "$ref": "#/definitions/v1UnpauseActivityExecutionResponse" } }, "default": { @@ -9488,21 +10336,21 @@ "parameters": [ { "name": "namespace", - "description": "Namespace of the workflow which scheduled this activity", + "description": "Namespace of the workflow which scheduled this activity.", "in": "path", "required": true, "type": "string" }, { "name": "workflowId", - "description": "Id of the workflow which scheduled this activity, leave empty to target a standalone activity", + "description": "If provided, targets a workflow activity for the given workflow ID.\nIf empty, targets a standalone activity.", "in": "path", "required": true, "type": "string" }, { "name": "activityId", - "description": "Id of the activity we're heartbeating", + "description": "The ID of the activity to target.", "in": "path", "required": true, "type": "string" @@ -9512,7 +10360,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/WorkflowServiceRecordActivityTaskHeartbeatByIdBody" + "$ref": "#/definitions/WorkflowServiceUnpauseActivityExecutionBody" } } ], @@ -9521,15 +10369,15 @@ ] } }, - "/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/resolve-as-canceled": { + "/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/update-options": { "post": { - "summary": "See `RespondActivityTaskCanceled`. This version allows clients to record failures by\nnamespace/workflow id/activity id instead of task token.", - "operationId": "RespondActivityTaskCanceledById3", + "summary": "UpdateActivityExecutionOptions is called by the client to update the options of an activity by its ID.\nThis API can be used to target a workflow activity or a standalone activity.", + "operationId": "UpdateActivityExecutionOptions3", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1RespondActivityTaskCanceledByIdResponse" + "$ref": "#/definitions/v1UpdateActivityExecutionOptionsResponse" } }, "default": { @@ -9549,14 +10397,14 @@ }, { "name": "workflowId", - "description": "Id of the workflow which scheduled this activity, leave empty to target a standalone activity", + "description": "If provided, targets a workflow activity for the given workflow ID.\nIf empty, targets a standalone activity.", "in": "path", "required": true, "type": "string" }, { "name": "activityId", - "description": "Id of the activity to confirm is cancelled", + "description": "The ID of the activity to target.", "in": "path", "required": true, "type": "string" @@ -9566,7 +10414,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/WorkflowServiceRespondActivityTaskCanceledByIdBody" + "$ref": "#/definitions/WorkflowServiceUpdateActivityExecutionOptionsBody" } } ], @@ -10402,6 +11250,36 @@ "reason": { "type": "string", "description": "Reason to pause the activity." + }, + "requestId": { + "type": "string", + "description": "Used to de-dupe pause requests." + } + }, + "description": "Deprecated. Use `PauseActivityExecutionRequest`." + }, + "WorkflowServicePauseActivityExecutionBody": { + "type": "object", + "properties": { + "runId": { + "type": "string", + "description": "Run ID of the workflow or standalone activity." + }, + "identity": { + "type": "string", + "description": "The identity of the client who initiated this request." + }, + "reason": { + "type": "string", + "description": "Reason to pause the activity." + }, + "resourceId": { + "type": "string", + "description": "Resource ID for routing. Contains \"workflow:{workflow_id}\" for workflow activities or \"activity:{activity_id}\" for standalone activities." + }, + "requestId": { + "type": "string", + "description": "Used to de-dupe pause requests." } } }, @@ -10639,10 +11517,43 @@ }, "restoreOriginalOptions": { "type": "boolean", - "description": "If set, the activity options will be restored to the defaults.\nDefault options are then options activity was created with.\nThey are part of the first SCHEDULE event." + "description": "If set, the activity options will be restored to the defaults.\nDefault options are then options activity was created with.\nThey are part of the first schedule event." } }, - "title": "NOTE: keep in sync with temporal.api.batch.v1.BatchOperationResetActivities" + "description": "NOTE: keep in sync with temporal.api.batch.v1.BatchOperationResetActivities\nDeprecated. Use `ResetActivityExecutionRequest`." + }, + "WorkflowServiceResetActivityExecutionBody": { + "type": "object", + "properties": { + "runId": { + "type": "string", + "description": "Run ID of the workflow or standalone activity." + }, + "identity": { + "type": "string", + "description": "The identity of the client who initiated this request." + }, + "resetHeartbeat": { + "type": "boolean", + "description": "Indicates that activity should reset heartbeat details.\nThis flag will be applied only to the new instance of the activity." + }, + "keepPaused": { + "type": "boolean", + "title": "If activity is paused, it will remain paused after reset" + }, + "jitter": { + "type": "string", + "title": "If set, and activity is in backoff, the activity will start at a random time within the specified jitter duration.\n(unless it is paused and keep_paused is set)" + }, + "restoreOriginalOptions": { + "type": "boolean", + "description": "If set, the activity options will be restored to the defaults.\nDefault options are then options activity was created with.\nThey are part of the first schedule event." + }, + "resourceId": { + "type": "string", + "description": "Resource ID for routing. Contains \"workflow:{workflow_id}\" for workflow activities or \"activity:{activity_id}\" for standalone activities." + } + } }, "WorkflowServiceResetWorkflowExecutionBody": { "type": "object", @@ -11601,6 +12512,40 @@ "type": "string", "description": "If set, the activity will start at a random time within the specified jitter duration." } + }, + "description": "Deprecated. Use `UnpauseActivityExecutionRequest`." + }, + "WorkflowServiceUnpauseActivityExecutionBody": { + "type": "object", + "properties": { + "runId": { + "type": "string", + "description": "Run ID of the workflow or standalone activity." + }, + "identity": { + "type": "string", + "description": "The identity of the client who initiated this request." + }, + "resetAttempts": { + "type": "boolean", + "description": "Providing this flag will also reset the number of attempts." + }, + "resetHeartbeat": { + "type": "boolean", + "description": "Providing this flag will also reset the heartbeat details." + }, + "reason": { + "type": "string", + "description": "Reason to unpause the activity." + }, + "jitter": { + "type": "string", + "description": "If set, the activity will start at a random time within the specified jitter duration." + }, + "resourceId": { + "type": "string", + "description": "Resource ID for routing. Contains \"workflow:{workflow_id}\" for workflow activities or \"activity:{activity_id}\" for standalone activities." + } } }, "WorkflowServiceUnpauseWorkflowExecutionBody": { @@ -11624,6 +12569,35 @@ } } }, + "WorkflowServiceUpdateActivityExecutionOptionsBody": { + "type": "object", + "properties": { + "runId": { + "type": "string", + "description": "Run ID of the workflow or standalone activity." + }, + "identity": { + "type": "string", + "title": "The identity of the client who initiated this request" + }, + "activityOptions": { + "$ref": "#/definitions/v1ActivityOptions", + "title": "Activity options. Partial updates are accepted and controlled by update_mask" + }, + "updateMask": { + "type": "string", + "title": "Controls which fields from `activity_options` will be applied" + }, + "restoreOriginal": { + "type": "boolean", + "description": "If set, the activity options will be restored to the default.\nDefault options are then options activity was created with.\nThey are part of the first schedule event.\nThis flag cannot be combined with any other option; if you supply\nrestore_original together with other options, the request will be rejected." + }, + "resourceId": { + "type": "string", + "description": "Resource ID for routing. Contains \"workflow:{workflow_id}\" for workflow activities or \"activity:{activity_id}\" for standalone activities." + } + } + }, "WorkflowServiceUpdateActivityOptionsBody": { "type": "object", "properties": { @@ -11657,10 +12631,10 @@ }, "restoreOriginal": { "type": "boolean", - "description": "If set, the activity options will be restored to the default.\nDefault options are then options activity was created with.\nThey are part of the first SCHEDULE event.\nThis flag cannot be combined with any other option; if you supply\nrestore_original together with other options, the request will be rejected." + "description": "If set, the activity options will be restored to the default.\nDefault options are then options activity was created with.\nThey are part of the first schedule event.\nThis flag cannot be combined with any other option; if you supply\nrestore_original together with other options, the request will be rejected." } }, - "title": "NOTE: keep in sync with temporal.api.batch.v1.BatchOperationUpdateActivityOptions" + "description": "NOTE: keep in sync with temporal.api.batch.v1.BatchOperationUpdateActivityOptions\nDeprecated. Use `UpdateActivityExecutionOptionsRequest`." }, "WorkflowServiceUpdateNamespaceBody": { "type": "object", @@ -15476,6 +16450,10 @@ "pollerAutoscaling": { "type": "boolean", "title": "True if the namespace supports poller autoscaling" + }, + "workerCommands": { + "type": "boolean", + "description": "True if the namespace supports worker commands (server-to-worker communication via control queues)." } }, "description": "Namespace capability details. Should contain what features are enabled in a namespace." @@ -16184,9 +17162,13 @@ "v1PatchScheduleResponse": { "type": "object" }, - "v1PauseActivityResponse": { + "v1PauseActivityExecutionResponse": { "type": "object" }, + "v1PauseActivityResponse": { + "type": "object", + "description": "Deprecated. Use `PauseActivityExecutionResponse`." + }, "v1PauseWorkflowExecutionResponse": { "type": "object", "description": "Response to a successful PauseWorkflowExecution request." @@ -17019,9 +18001,13 @@ }, "description": "RequestIdInfo contains details of a request ID." }, - "v1ResetActivityResponse": { + "v1ResetActivityExecutionResponse": { "type": "object" }, + "v1ResetActivityResponse": { + "type": "object", + "description": "Deprecated. Use `ResetActivityExecutionRequest`." + }, "v1ResetOptions": { "type": "object", "properties": { @@ -17965,6 +18951,14 @@ "priority": { "$ref": "#/definitions/v1Priority", "title": "Priority metadata" + }, + "timeSkippingConfig": { + "$ref": "#/definitions/v1TimeSkippingConfig", + "description": "The propagated time-skipping configuration for the child workflow." + }, + "initialSkippedDuration": { + "type": "string", + "description": "Propagate the duration skipped to the child workflow." } } }, @@ -18353,11 +19347,7 @@ "properties": { "enabled": { "type": "boolean", - "description": "Enables or disables time skipping for this workflow execution.\nBy default, this field is propagated to transitively related workflows (child workflows/start-as-new/reset) \nat the time they are started.\nChanges made after a transitively related workflow has started are not propagated." - }, - "disablePropagation": { - "type": "boolean", - "description": "If set, the enabled field is not propagated to transitively related workflows." + "description": "Enables or disables time skipping for this workflow execution." }, "maxSkippedDuration": { "type": "string", @@ -18366,14 +19356,9 @@ "maxElapsedDuration": { "type": "string", "description": "Maximum elapsed time since time skipping was enabled.\nThis includes both skipped time and real time elapsing." - }, - "maxTargetTime": { - "type": "string", - "format": "date-time", - "description": "Absolute virtual timestamp at which time skipping is disabled.\nTime skipping will not advance beyond this point." } }, - "description": "Configuration for time skipping during a workflow execution.\nWhen enabled, virtual time advances automatically whenever there is no in-flight work.\nIn-flight work includes activities, child workflows, Nexus operations, signal/cancel external workflow operations,\nand possibly other features added in the future.\nUser timers are not classified as in-flight work and will be skipped over.\nWhen time advances, it skips to the earlier of the next user timer or the configured bound, if either exists." + "description": "Configuration for time skipping during a workflow execution.\nWhen enabled, virtual time advances automatically whenever there is no in-flight work.\nIn-flight work includes activities, child workflows, Nexus operations, signal/cancel external workflow operations,\nand possibly other features added in the future.\nUser timers are not classified as in-flight work and will be skipped over.\nWhen time advances, it skips to the earlier of the next user timer or the configured bound, if either exists.\n\nPropagation behavior of time skipping:\nThe enabled flag, bound fields, and accumulated skipped duration are propagated to related executions as follows:\n(1) Child workflows and continue-as-new: both the configuration and the accumulated skipped duration are\n inherited from the current execution. The configured bound is shared between the inherited skipped\n duration and any additional duration skipped by the new run.\n(2) Retry and cron: the configuration and accumulated skipped duration are inherited as recorded when the\n current workflow started; the accumulated skipped duration of the current run is not propagated.\n(3) Reset: the new run retains the time-skipping configuration of the current execution. Because reset replays\n all events up to the reset point and re-applies any UpdateWorkflowExecutionOptions changes made after that\n point, the resulting run ends up with the same final time-skipping configuration as the previous run." }, "v1TimeoutFailureInfo": { "type": "object", @@ -18499,14 +19484,18 @@ } } }, - "v1UnpauseActivityResponse": { + "v1UnpauseActivityExecutionResponse": { "type": "object" }, + "v1UnpauseActivityResponse": { + "type": "object", + "description": "Deprecated. Use `UnpauseActivityExecutionResponse`." + }, "v1UnpauseWorkflowExecutionResponse": { "type": "object", "description": "Response to a successful UnpauseWorkflowExecution request." }, - "v1UpdateActivityOptionsResponse": { + "v1UpdateActivityExecutionOptionsResponse": { "type": "object", "properties": { "activityOptions": { @@ -18515,6 +19504,16 @@ } } }, + "v1UpdateActivityOptionsResponse": { + "type": "object", + "properties": { + "activityOptions": { + "$ref": "#/definitions/v1ActivityOptions", + "title": "Activity options after an update" + } + }, + "description": "Deprecated. Use `UpdateActivityExecutionOptionsResponse`." + }, "v1UpdateAdmittedEventOrigin": { "type": "string", "enum": [ @@ -19657,7 +20656,7 @@ }, "timeSkippingConfig": { "$ref": "#/definitions/v1TimeSkippingConfig", - "description": "Time-skipping configuration for this workflow execution.\nIf not set, the time-skipping conf will not get updated upon request, \ni.e. the existing time-skipping conf will be preserved." + "description": "Time-skipping configuration for this workflow execution.\nIf not set, the time-skipping configuration is not updated by this request;\nthe existing configuration is preserved." } } }, @@ -19927,6 +20926,10 @@ "timeSkippingConfig": { "$ref": "#/definitions/v1TimeSkippingConfig", "description": "Initial time-skipping configuration for this workflow execution, recorded at start time.\nThis may have been set explicitly via the start workflow request, or propagated from a\nparent/previous execution.\n\nThe configuration may be updated after start via UpdateWorkflowExecutionOptions, which\nwill be reflected in the WorkflowExecutionOptionsUpdatedEvent." + }, + "initialSkippedDuration": { + "type": "string", + "description": "The time skipped by the previous execution that started this workflow.\nIt can happen in cases of child workflows and continue-as-new workflows." } }, "title": "Always the first event in workflow history" diff --git a/crates/common/protos/api_upstream/openapi/openapiv3.yaml b/crates/common/protos/api_upstream/openapi/openapiv3.yaml index 4d816f659..4ada710f0 100644 --- a/crates/common/protos/api_upstream/openapi/openapiv3.yaml +++ b/crates/common/protos/api_upstream/openapi/openapiv3.yaml @@ -109,6 +109,15 @@ paths: in: query schema: type: string + - name: weakConsistency + in: query + description: |- + If true, the server may serve the response from an eventually-consistent + source instead of reading through to persistence. Defaults to false, + which preserves read-after-write consistency. SDKs should set this when + fetching namespace capabilities on worker/client startup. + schema: + type: boolean responses: "200": description: OK @@ -661,6 +670,107 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' + /api/v1/namespaces/{namespace}/activities/{activityId}/pause: + post: + tags: + - WorkflowService + description: |- + PauseActivityExecution pauses the execution of an activity specified by its ID. + This API can be used to target a workflow activity or a standalone activity + + Pausing an activity means: + - If the activity is currently waiting for a retry or is running and subsequently fails, + it will not be rescheduled until it is unpaused. + - If the activity is already paused, calling this method will have no effect. + - If the activity is running and finishes successfully, the activity will be completed. + - If the activity is running and finishes with failure: + * if there is no retry left - the activity will be completed. + * if there are more retries left - the activity will be paused. + For long-running activities: + - activities in paused state will send a cancellation with "activity_paused" set to 'true' in response to 'RecordActivityTaskHeartbeat'. + + Returns a `NotFound` error if there is no pending activity with the provided ID + operationId: PauseActivityExecution + parameters: + - name: namespace + in: path + description: Namespace of the workflow which scheduled this activity. + required: true + schema: + type: string + - name: activityId + in: path + description: The ID of the activity to target. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PauseActivityExecutionRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PauseActivityExecutionResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /api/v1/namespaces/{namespace}/activities/{activityId}/reset: + post: + tags: + - WorkflowService + description: |- + ResetActivityExecution resets the execution of an activity specified by its ID. + This API can be used to target a workflow activity or a standalone activity. + + Resetting an activity means: + * number of attempts will be reset to 0. + * activity timeouts will be reset. + * if the activity is waiting for retry, and it is not paused or 'keep_paused' is not provided: + it will be scheduled immediately (* see 'jitter' flag) + + Returns a `NotFound` error if there is no pending activity with the provided ID or type. + operationId: ResetActivityExecution + parameters: + - name: namespace + in: path + description: Namespace of the workflow which scheduled this activity. + required: true + schema: + type: string + - name: activityId + in: path + description: The ID of the activity to target. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ResetActivityExecutionRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ResetActivityExecutionResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' /api/v1/namespaces/{namespace}/activities/{activityId}/resolve-as-canceled: post: tags: @@ -744,6 +854,92 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' + /api/v1/namespaces/{namespace}/activities/{activityId}/unpause: + post: + tags: + - WorkflowService + description: |- + UnpauseActivityExecution unpauses the execution of an activity specified by its ID. + This API can be used to target a workflow activity or a standalone activity. + + If activity is not paused, this call will have no effect. + If the activity was paused while waiting for retry, it will be scheduled immediately (* see 'jitter' flag). + Once the activity is unpaused, all timeout timers will be regenerated. + + Returns a `NotFound` error if there is no pending activity with the provided ID + operationId: UnpauseActivityExecution + parameters: + - name: namespace + in: path + description: Namespace of the workflow which scheduled this activity. + required: true + schema: + type: string + - name: activityId + in: path + description: The ID of the activity to target. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UnpauseActivityExecutionRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/UnpauseActivityExecutionResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /api/v1/namespaces/{namespace}/activities/{activityId}/update-options: + post: + tags: + - WorkflowService + description: |- + UpdateActivityExecutionOptions is called by the client to update the options of an activity by its ID. + This API can be used to target a workflow activity or a standalone activity. + operationId: UpdateActivityExecutionOptions + parameters: + - name: namespace + in: path + description: Namespace of the workflow which scheduled this activity + required: true + schema: + type: string + - name: activityId + in: path + description: The ID of the activity to target. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateActivityExecutionOptionsRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateActivityExecutionOptionsResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' /api/v1/namespaces/{namespace}/activity-complete: post: tags: @@ -2965,6 +3161,13 @@ paths: * Status schema: type: string + - name: includeSystemWorkers + in: query + description: |- + When true, the response will include system workers that are created implicitly + by the server and not by the user. By default, system workers are excluded. + schema: + type: boolean responses: "200": description: OK @@ -3739,33 +3942,45 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' - /api/v1/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/resolve-as-canceled: + /api/v1/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/pause: post: tags: - WorkflowService description: |- - See `RespondActivityTaskCanceled`. This version allows clients to record failures by - namespace/workflow id/activity id instead of task token. + PauseActivityExecution pauses the execution of an activity specified by its ID. + This API can be used to target a workflow activity or a standalone activity - (-- api-linter: core::0136::prepositions=disabled - aip.dev/not-precedent: "By" is used to indicate request type. --) - operationId: RespondActivityTaskCanceledById + Pausing an activity means: + - If the activity is currently waiting for a retry or is running and subsequently fails, + it will not be rescheduled until it is unpaused. + - If the activity is already paused, calling this method will have no effect. + - If the activity is running and finishes successfully, the activity will be completed. + - If the activity is running and finishes with failure: + * if there is no retry left - the activity will be completed. + * if there are more retries left - the activity will be paused. + For long-running activities: + - activities in paused state will send a cancellation with "activity_paused" set to 'true' in response to 'RecordActivityTaskHeartbeat'. + + Returns a `NotFound` error if there is no pending activity with the provided ID + operationId: PauseActivityExecution parameters: - name: namespace in: path - description: Namespace of the workflow which scheduled this activity + description: Namespace of the workflow which scheduled this activity. required: true schema: type: string - name: workflowId in: path - description: Id of the workflow which scheduled this activity, leave empty to target a standalone activity + description: |- + If provided, pause a workflow activity (or activities) for the given workflow ID. + If empty, targets a standalone activity. required: true schema: type: string - name: activityId in: path - description: Id of the activity to confirm is cancelled + description: The ID of the activity to target. required: true schema: type: string @@ -3773,7 +3988,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RespondActivityTaskCanceledByIdRequest' + $ref: '#/components/schemas/PauseActivityExecutionRequest' required: true responses: "200": @@ -3781,37 +3996,47 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RespondActivityTaskCanceledByIdResponse' + $ref: '#/components/schemas/PauseActivityExecutionResponse' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' - /api/v1/namespaces/{namespace}/workflows/{workflowId}/pause: + /api/v1/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/reset: post: tags: - WorkflowService description: |- - Note: This is an experimental API and the behavior may change in a future release. - PauseWorkflowExecution pauses the workflow execution specified in the request. Pausing a workflow execution results in - - The workflow execution status changes to `PAUSED` and a new WORKFLOW_EXECUTION_PAUSED event is added to the history - - No new workflow tasks or activity tasks are dispatched. - - Any workflow task currently executing on the worker will be allowed to complete. - - Any activity task currently executing will be paused. - - All server-side events will continue to be processed by the server. - - Queries & Updates on a paused workflow will be rejected. - operationId: PauseWorkflowExecution + ResetActivityExecution resets the execution of an activity specified by its ID. + This API can be used to target a workflow activity or a standalone activity. + + Resetting an activity means: + * number of attempts will be reset to 0. + * activity timeouts will be reset. + * if the activity is waiting for retry, and it is not paused or 'keep_paused' is not provided: + it will be scheduled immediately (* see 'jitter' flag) + + Returns a `NotFound` error if there is no pending activity with the provided ID or type. + operationId: ResetActivityExecution parameters: - name: namespace in: path - description: Namespace of the workflow to pause. + description: Namespace of the workflow which scheduled this activity. required: true schema: type: string - name: workflowId in: path - description: ID of the workflow execution to be paused. Required. + description: |- + If provided, targets a workflow activity for the given workflow ID. + If empty, targets a standalone activity. + required: true + schema: + type: string + - name: activityId + in: path + description: The ID of the activity to target. required: true schema: type: string @@ -3819,7 +4044,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PauseWorkflowExecutionRequest' + $ref: '#/components/schemas/ResetActivityExecutionRequest' required: true responses: "200": @@ -3827,45 +4052,40 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PauseWorkflowExecutionResponse' + $ref: '#/components/schemas/ResetActivityExecutionResponse' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' - /api/v1/namespaces/{namespace}/workflows/{workflowId}/signal-with-start/{signalName}: + /api/v1/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/resolve-as-canceled: post: tags: - WorkflowService description: |- - SignalWithStartWorkflowExecution is used to ensure a signal is sent to a workflow, even if - it isn't yet started. - - If the workflow is running, a `WORKFLOW_EXECUTION_SIGNALED` event is recorded in the history - and a workflow task is generated. - - If the workflow is not running or not found, then the workflow is created with - `WORKFLOW_EXECUTION_STARTED` and `WORKFLOW_EXECUTION_SIGNALED` events in its history, and a - workflow task is generated. + See `RespondActivityTaskCanceled`. This version allows clients to record failures by + namespace/workflow id/activity id instead of task token. (-- api-linter: core::0136::prepositions=disabled - aip.dev/not-precedent: "With" is used to indicate combined operation. --) - operationId: SignalWithStartWorkflowExecution + aip.dev/not-precedent: "By" is used to indicate request type. --) + operationId: RespondActivityTaskCanceledById parameters: - name: namespace in: path + description: Namespace of the workflow which scheduled this activity required: true schema: type: string - name: workflowId in: path + description: Id of the workflow which scheduled this activity, leave empty to target a standalone activity required: true schema: type: string - - name: signalName + - name: activityId in: path - description: The workflow author-defined name of the signal to send to the workflow + description: Id of the activity to confirm is cancelled required: true schema: type: string @@ -3873,7 +4093,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/SignalWithStartWorkflowExecutionRequest' + $ref: '#/components/schemas/RespondActivityTaskCanceledByIdRequest' required: true responses: "200": @@ -3881,28 +4101,230 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/SignalWithStartWorkflowExecutionResponse' + $ref: '#/components/schemas/RespondActivityTaskCanceledByIdResponse' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' - /api/v1/namespaces/{namespace}/workflows/{workflowId}/unpause: + /api/v1/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/unpause: post: tags: - WorkflowService description: |- - Note: This is an experimental API and the behavior may change in a future release. - UnpauseWorkflowExecution unpauses a previously paused workflow execution specified in the request. - Unpausing a workflow execution results in - - The workflow execution status changes to `RUNNING` and a new WORKFLOW_EXECUTION_UNPAUSED event is added to the history - - Workflow tasks and activity tasks are resumed. - operationId: UnpauseWorkflowExecution + UnpauseActivityExecution unpauses the execution of an activity specified by its ID. + This API can be used to target a workflow activity or a standalone activity. + + If activity is not paused, this call will have no effect. + If the activity was paused while waiting for retry, it will be scheduled immediately (* see 'jitter' flag). + Once the activity is unpaused, all timeout timers will be regenerated. + + Returns a `NotFound` error if there is no pending activity with the provided ID + operationId: UnpauseActivityExecution parameters: - name: namespace in: path - description: Namespace of the workflow to unpause. + description: Namespace of the workflow which scheduled this activity. + required: true + schema: + type: string + - name: workflowId + in: path + description: |- + If provided, targets a workflow activity for the given workflow ID. + If empty, targets a standalone activity. + required: true + schema: + type: string + - name: activityId + in: path + description: The ID of the activity to target. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UnpauseActivityExecutionRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/UnpauseActivityExecutionResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /api/v1/namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/update-options: + post: + tags: + - WorkflowService + description: |- + UpdateActivityExecutionOptions is called by the client to update the options of an activity by its ID. + This API can be used to target a workflow activity or a standalone activity. + operationId: UpdateActivityExecutionOptions + parameters: + - name: namespace + in: path + description: Namespace of the workflow which scheduled this activity + required: true + schema: + type: string + - name: workflowId + in: path + description: |- + If provided, targets a workflow activity for the given workflow ID. + If empty, targets a standalone activity. + required: true + schema: + type: string + - name: activityId + in: path + description: The ID of the activity to target. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateActivityExecutionOptionsRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateActivityExecutionOptionsResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /api/v1/namespaces/{namespace}/workflows/{workflowId}/pause: + post: + tags: + - WorkflowService + description: |- + Note: This is an experimental API and the behavior may change in a future release. + PauseWorkflowExecution pauses the workflow execution specified in the request. Pausing a workflow execution results in + - The workflow execution status changes to `PAUSED` and a new WORKFLOW_EXECUTION_PAUSED event is added to the history + - No new workflow tasks or activity tasks are dispatched. + - Any workflow task currently executing on the worker will be allowed to complete. + - Any activity task currently executing will be paused. + - All server-side events will continue to be processed by the server. + - Queries & Updates on a paused workflow will be rejected. + operationId: PauseWorkflowExecution + parameters: + - name: namespace + in: path + description: Namespace of the workflow to pause. + required: true + schema: + type: string + - name: workflowId + in: path + description: ID of the workflow execution to be paused. Required. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PauseWorkflowExecutionRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PauseWorkflowExecutionResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /api/v1/namespaces/{namespace}/workflows/{workflowId}/signal-with-start/{signalName}: + post: + tags: + - WorkflowService + description: |- + SignalWithStartWorkflowExecution is used to ensure a signal is sent to a workflow, even if + it isn't yet started. + + If the workflow is running, a `WORKFLOW_EXECUTION_SIGNALED` event is recorded in the history + and a workflow task is generated. + + If the workflow is not running or not found, then the workflow is created with + `WORKFLOW_EXECUTION_STARTED` and `WORKFLOW_EXECUTION_SIGNALED` events in its history, and a + workflow task is generated. + + (-- api-linter: core::0136::prepositions=disabled + aip.dev/not-precedent: "With" is used to indicate combined operation. --) + operationId: SignalWithStartWorkflowExecution + parameters: + - name: namespace + in: path + required: true + schema: + type: string + - name: workflowId + in: path + required: true + schema: + type: string + - name: signalName + in: path + description: The workflow author-defined name of the signal to send to the workflow + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SignalWithStartWorkflowExecutionRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SignalWithStartWorkflowExecutionResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /api/v1/namespaces/{namespace}/workflows/{workflowId}/unpause: + post: + tags: + - WorkflowService + description: |- + Note: This is an experimental API and the behavior may change in a future release. + UnpauseWorkflowExecution unpauses a previously paused workflow execution specified in the request. + Unpausing a workflow execution results in + - The workflow execution status changes to `RUNNING` and a new WORKFLOW_EXECUTION_UNPAUSED event is added to the history + - Workflow tasks and activity tasks are resumed. + operationId: UnpauseWorkflowExecution + parameters: + - name: namespace + in: path + description: Namespace of the workflow to unpause. required: true schema: type: string @@ -4466,6 +4888,15 @@ paths: in: query schema: type: string + - name: weakConsistency + in: query + description: |- + If true, the server may serve the response from an eventually-consistent + source instead of reading through to persistence. Defaults to false, + which preserves read-after-write consistency. SDKs should set this when + fetching namespace capabilities on worker/client startup. + schema: + type: boolean responses: "200": description: OK @@ -5119,27 +5550,208 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' - /namespaces/{namespace}/activities/{activityId}/fail: + /namespaces/{namespace}/activities/{activityId}/fail: + post: + tags: + - WorkflowService + description: |- + See `RecordActivityTaskFailed`. This version allows clients to record failures by + namespace/workflow id/activity id instead of task token. + + (-- api-linter: core::0136::prepositions=disabled + aip.dev/not-precedent: "By" is used to indicate request type. --) + operationId: RespondActivityTaskFailedById + parameters: + - name: namespace + in: path + description: Namespace of the workflow which scheduled this activity + required: true + schema: + type: string + - name: activityId + in: path + description: Id of the activity to fail + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RespondActivityTaskFailedByIdRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/RespondActivityTaskFailedByIdResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /namespaces/{namespace}/activities/{activityId}/heartbeat: + post: + tags: + - WorkflowService + description: |- + See `RecordActivityTaskHeartbeat`. This version allows clients to record heartbeats by + namespace/workflow id/activity id instead of task token. + + (-- api-linter: core::0136::prepositions=disabled + aip.dev/not-precedent: "By" is used to indicate request type. --) + operationId: RecordActivityTaskHeartbeatById + parameters: + - name: namespace + in: path + description: Namespace of the workflow which scheduled this activity + required: true + schema: + type: string + - name: activityId + in: path + description: Id of the activity we're heartbeating + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RecordActivityTaskHeartbeatByIdRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/RecordActivityTaskHeartbeatByIdResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /namespaces/{namespace}/activities/{activityId}/outcome: + get: + tags: + - WorkflowService + description: |- + PollActivityExecution long-polls for an activity execution to complete and returns the + outcome (result or failure). + operationId: PollActivityExecution + parameters: + - name: namespace + in: path + required: true + schema: + type: string + - name: activityId + in: path + required: true + schema: + type: string + - name: runId + in: query + description: Activity run ID. If empty the request targets the latest run. + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PollActivityExecutionResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /namespaces/{namespace}/activities/{activityId}/pause: + post: + tags: + - WorkflowService + description: |- + PauseActivityExecution pauses the execution of an activity specified by its ID. + This API can be used to target a workflow activity or a standalone activity + + Pausing an activity means: + - If the activity is currently waiting for a retry or is running and subsequently fails, + it will not be rescheduled until it is unpaused. + - If the activity is already paused, calling this method will have no effect. + - If the activity is running and finishes successfully, the activity will be completed. + - If the activity is running and finishes with failure: + * if there is no retry left - the activity will be completed. + * if there are more retries left - the activity will be paused. + For long-running activities: + - activities in paused state will send a cancellation with "activity_paused" set to 'true' in response to 'RecordActivityTaskHeartbeat'. + + Returns a `NotFound` error if there is no pending activity with the provided ID + operationId: PauseActivityExecution + parameters: + - name: namespace + in: path + description: Namespace of the workflow which scheduled this activity. + required: true + schema: + type: string + - name: activityId + in: path + description: The ID of the activity to target. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PauseActivityExecutionRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PauseActivityExecutionResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /namespaces/{namespace}/activities/{activityId}/reset: post: tags: - WorkflowService description: |- - See `RecordActivityTaskFailed`. This version allows clients to record failures by - namespace/workflow id/activity id instead of task token. + ResetActivityExecution resets the execution of an activity specified by its ID. + This API can be used to target a workflow activity or a standalone activity. - (-- api-linter: core::0136::prepositions=disabled - aip.dev/not-precedent: "By" is used to indicate request type. --) - operationId: RespondActivityTaskFailedById + Resetting an activity means: + * number of attempts will be reset to 0. + * activity timeouts will be reset. + * if the activity is waiting for retry, and it is not paused or 'keep_paused' is not provided: + it will be scheduled immediately (* see 'jitter' flag) + + Returns a `NotFound` error if there is no pending activity with the provided ID or type. + operationId: ResetActivityExecution parameters: - name: namespace in: path - description: Namespace of the workflow which scheduled this activity + description: Namespace of the workflow which scheduled this activity. required: true schema: type: string - name: activityId in: path - description: Id of the activity to fail + description: The ID of the activity to target. required: true schema: type: string @@ -5147,7 +5759,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RespondActivityTaskFailedByIdRequest' + $ref: '#/components/schemas/ResetActivityExecutionRequest' required: true responses: "200": @@ -5155,24 +5767,24 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RespondActivityTaskFailedByIdResponse' + $ref: '#/components/schemas/ResetActivityExecutionResponse' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' - /namespaces/{namespace}/activities/{activityId}/heartbeat: + /namespaces/{namespace}/activities/{activityId}/resolve-as-canceled: post: tags: - WorkflowService description: |- - See `RecordActivityTaskHeartbeat`. This version allows clients to record heartbeats by + See `RespondActivityTaskCanceled`. This version allows clients to record failures by namespace/workflow id/activity id instead of task token. (-- api-linter: core::0136::prepositions=disabled aip.dev/not-precedent: "By" is used to indicate request type. --) - operationId: RecordActivityTaskHeartbeatById + operationId: RespondActivityTaskCanceledById parameters: - name: namespace in: path @@ -5182,7 +5794,7 @@ paths: type: string - name: activityId in: path - description: Id of the activity we're heartbeating + description: Id of the activity to confirm is cancelled required: true schema: type: string @@ -5190,7 +5802,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RecordActivityTaskHeartbeatByIdRequest' + $ref: '#/components/schemas/RespondActivityTaskCanceledByIdRequest' required: true responses: "200": @@ -5198,21 +5810,23 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RecordActivityTaskHeartbeatByIdResponse' + $ref: '#/components/schemas/RespondActivityTaskCanceledByIdResponse' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' - /namespaces/{namespace}/activities/{activityId}/outcome: - get: + /namespaces/{namespace}/activities/{activityId}/terminate: + post: tags: - WorkflowService description: |- - PollActivityExecution long-polls for an activity execution to complete and returns the - outcome (result or failure). - operationId: PollActivityExecution + TerminateActivityExecution terminates an existing activity execution immediately. + + Termination does not reach the worker and the activity code cannot react to it. A terminated activity may have a + running attempt. + operationId: TerminateActivityExecution parameters: - name: namespace in: path @@ -5224,45 +5838,49 @@ paths: required: true schema: type: string - - name: runId - in: query - description: Activity run ID. If empty the request targets the latest run. - schema: - type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TerminateActivityExecutionRequest' + required: true responses: "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/PollActivityExecutionResponse' + $ref: '#/components/schemas/TerminateActivityExecutionResponse' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' - /namespaces/{namespace}/activities/{activityId}/resolve-as-canceled: + /namespaces/{namespace}/activities/{activityId}/unpause: post: tags: - WorkflowService description: |- - See `RespondActivityTaskCanceled`. This version allows clients to record failures by - namespace/workflow id/activity id instead of task token. + UnpauseActivityExecution unpauses the execution of an activity specified by its ID. + This API can be used to target a workflow activity or a standalone activity. - (-- api-linter: core::0136::prepositions=disabled - aip.dev/not-precedent: "By" is used to indicate request type. --) - operationId: RespondActivityTaskCanceledById + If activity is not paused, this call will have no effect. + If the activity was paused while waiting for retry, it will be scheduled immediately (* see 'jitter' flag). + Once the activity is unpaused, all timeout timers will be regenerated. + + Returns a `NotFound` error if there is no pending activity with the provided ID + operationId: UnpauseActivityExecution parameters: - name: namespace in: path - description: Namespace of the workflow which scheduled this activity + description: Namespace of the workflow which scheduled this activity. required: true schema: type: string - name: activityId in: path - description: Id of the activity to confirm is cancelled + description: The ID of the activity to target. required: true schema: type: string @@ -5270,7 +5888,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RespondActivityTaskCanceledByIdRequest' + $ref: '#/components/schemas/UnpauseActivityExecutionRequest' required: true responses: "200": @@ -5278,31 +5896,31 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RespondActivityTaskCanceledByIdResponse' + $ref: '#/components/schemas/UnpauseActivityExecutionResponse' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' - /namespaces/{namespace}/activities/{activityId}/terminate: + /namespaces/{namespace}/activities/{activityId}/update-options: post: tags: - WorkflowService description: |- - TerminateActivityExecution terminates an existing activity execution immediately. - - Termination does not reach the worker and the activity code cannot react to it. A terminated activity may have a - running attempt. - operationId: TerminateActivityExecution + UpdateActivityExecutionOptions is called by the client to update the options of an activity by its ID. + This API can be used to target a workflow activity or a standalone activity. + operationId: UpdateActivityExecutionOptions parameters: - name: namespace in: path + description: Namespace of the workflow which scheduled this activity required: true schema: type: string - name: activityId in: path + description: The ID of the activity to target. required: true schema: type: string @@ -5310,7 +5928,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TerminateActivityExecutionRequest' + $ref: '#/components/schemas/UpdateActivityExecutionOptionsRequest' required: true responses: "200": @@ -5318,7 +5936,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TerminateActivityExecutionResponse' + $ref: '#/components/schemas/UpdateActivityExecutionOptionsResponse' default: description: Default error response content: @@ -7488,6 +8106,13 @@ paths: * Status schema: type: string + - name: includeSystemWorkers + in: query + description: |- + When true, the response will include system workers that are created implicitly + by the server and not by the user. By default, system workers are excluded. + schema: + type: boolean responses: "200": description: OK @@ -8050,8 +8675,196 @@ paths: required: true schema: type: string - - name: execution.workflow_id + - name: execution.workflow_id + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TriggerWorkflowRuleRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TriggerWorkflowRuleResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /namespaces/{namespace}/workflows/{workflowId}: + post: + tags: + - WorkflowService + description: |- + StartWorkflowExecution starts a new workflow execution. + + It will create the execution with a `WORKFLOW_EXECUTION_STARTED` event in its history and + also schedule the first workflow task. Returns `WorkflowExecutionAlreadyStarted`, if an + instance already exists with same workflow id. + operationId: StartWorkflowExecution + parameters: + - name: namespace + in: path + required: true + schema: + type: string + - name: workflowId + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/StartWorkflowExecutionRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/StartWorkflowExecutionResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/complete: + post: + tags: + - WorkflowService + description: |- + See `RespondActivityTaskCompleted`. This version allows clients to record completions by + namespace/workflow id/activity id instead of task token. + + (-- api-linter: core::0136::prepositions=disabled + aip.dev/not-precedent: "By" is used to indicate request type. --) + operationId: RespondActivityTaskCompletedById + parameters: + - name: namespace + in: path + description: Namespace of the workflow which scheduled this activity + required: true + schema: + type: string + - name: workflowId + in: path + description: Id of the workflow which scheduled this activity, leave empty to target a standalone activity + required: true + schema: + type: string + - name: activityId + in: path + description: Id of the activity to complete + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RespondActivityTaskCompletedByIdRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/RespondActivityTaskCompletedByIdResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/fail: + post: + tags: + - WorkflowService + description: |- + See `RecordActivityTaskFailed`. This version allows clients to record failures by + namespace/workflow id/activity id instead of task token. + + (-- api-linter: core::0136::prepositions=disabled + aip.dev/not-precedent: "By" is used to indicate request type. --) + operationId: RespondActivityTaskFailedById + parameters: + - name: namespace + in: path + description: Namespace of the workflow which scheduled this activity + required: true + schema: + type: string + - name: workflowId + in: path + description: Id of the workflow which scheduled this activity, leave empty to target a standalone activity + required: true + schema: + type: string + - name: activityId + in: path + description: Id of the activity to fail + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RespondActivityTaskFailedByIdRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/RespondActivityTaskFailedByIdResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/heartbeat: + post: + tags: + - WorkflowService + description: |- + See `RecordActivityTaskHeartbeat`. This version allows clients to record heartbeats by + namespace/workflow id/activity id instead of task token. + + (-- api-linter: core::0136::prepositions=disabled + aip.dev/not-precedent: "By" is used to indicate request type. --) + operationId: RecordActivityTaskHeartbeatById + parameters: + - name: namespace + in: path + description: Namespace of the workflow which scheduled this activity + required: true + schema: + type: string + - name: workflowId + in: path + description: Id of the workflow which scheduled this activity, leave empty to target a standalone activity + required: true + schema: + type: string + - name: activityId in: path + description: Id of the activity we're heartbeating required: true schema: type: string @@ -8059,7 +8872,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TriggerWorkflowRuleRequest' + $ref: '#/components/schemas/RecordActivityTaskHeartbeatByIdRequest' required: true responses: "200": @@ -8067,32 +8880,52 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TriggerWorkflowRuleResponse' + $ref: '#/components/schemas/RecordActivityTaskHeartbeatByIdResponse' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' - /namespaces/{namespace}/workflows/{workflowId}: + /namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/pause: post: tags: - WorkflowService description: |- - StartWorkflowExecution starts a new workflow execution. + PauseActivityExecution pauses the execution of an activity specified by its ID. + This API can be used to target a workflow activity or a standalone activity - It will create the execution with a `WORKFLOW_EXECUTION_STARTED` event in its history and - also schedule the first workflow task. Returns `WorkflowExecutionAlreadyStarted`, if an - instance already exists with same workflow id. - operationId: StartWorkflowExecution + Pausing an activity means: + - If the activity is currently waiting for a retry or is running and subsequently fails, + it will not be rescheduled until it is unpaused. + - If the activity is already paused, calling this method will have no effect. + - If the activity is running and finishes successfully, the activity will be completed. + - If the activity is running and finishes with failure: + * if there is no retry left - the activity will be completed. + * if there are more retries left - the activity will be paused. + For long-running activities: + - activities in paused state will send a cancellation with "activity_paused" set to 'true' in response to 'RecordActivityTaskHeartbeat'. + + Returns a `NotFound` error if there is no pending activity with the provided ID + operationId: PauseActivityExecution parameters: - name: namespace in: path + description: Namespace of the workflow which scheduled this activity. required: true schema: type: string - name: workflowId in: path + description: |- + If provided, pause a workflow activity (or activities) for the given workflow ID. + If empty, targets a standalone activity. + required: true + schema: + type: string + - name: activityId + in: path + description: The ID of the activity to target. required: true schema: type: string @@ -8100,7 +8933,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/StartWorkflowExecutionRequest' + $ref: '#/components/schemas/PauseActivityExecutionRequest' required: true responses: "200": @@ -8108,40 +8941,47 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/StartWorkflowExecutionResponse' + $ref: '#/components/schemas/PauseActivityExecutionResponse' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' - /namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/complete: + /namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/reset: post: tags: - WorkflowService description: |- - See `RespondActivityTaskCompleted`. This version allows clients to record completions by - namespace/workflow id/activity id instead of task token. + ResetActivityExecution resets the execution of an activity specified by its ID. + This API can be used to target a workflow activity or a standalone activity. - (-- api-linter: core::0136::prepositions=disabled - aip.dev/not-precedent: "By" is used to indicate request type. --) - operationId: RespondActivityTaskCompletedById + Resetting an activity means: + * number of attempts will be reset to 0. + * activity timeouts will be reset. + * if the activity is waiting for retry, and it is not paused or 'keep_paused' is not provided: + it will be scheduled immediately (* see 'jitter' flag) + + Returns a `NotFound` error if there is no pending activity with the provided ID or type. + operationId: ResetActivityExecution parameters: - name: namespace in: path - description: Namespace of the workflow which scheduled this activity + description: Namespace of the workflow which scheduled this activity. required: true schema: type: string - name: workflowId in: path - description: Id of the workflow which scheduled this activity, leave empty to target a standalone activity + description: |- + If provided, targets a workflow activity for the given workflow ID. + If empty, targets a standalone activity. required: true schema: type: string - name: activityId in: path - description: Id of the activity to complete + description: The ID of the activity to target. required: true schema: type: string @@ -8149,7 +8989,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RespondActivityTaskCompletedByIdRequest' + $ref: '#/components/schemas/ResetActivityExecutionRequest' required: true responses: "200": @@ -8157,24 +8997,24 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RespondActivityTaskCompletedByIdResponse' + $ref: '#/components/schemas/ResetActivityExecutionResponse' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' - /namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/fail: + /namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/resolve-as-canceled: post: tags: - WorkflowService description: |- - See `RecordActivityTaskFailed`. This version allows clients to record failures by + See `RespondActivityTaskCanceled`. This version allows clients to record failures by namespace/workflow id/activity id instead of task token. (-- api-linter: core::0136::prepositions=disabled aip.dev/not-precedent: "By" is used to indicate request type. --) - operationId: RespondActivityTaskFailedById + operationId: RespondActivityTaskCanceledById parameters: - name: namespace in: path @@ -8190,7 +9030,7 @@ paths: type: string - name: activityId in: path - description: Id of the activity to fail + description: Id of the activity to confirm is cancelled required: true schema: type: string @@ -8198,7 +9038,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RespondActivityTaskFailedByIdRequest' + $ref: '#/components/schemas/RespondActivityTaskCanceledByIdRequest' required: true responses: "200": @@ -8206,40 +9046,45 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RespondActivityTaskFailedByIdResponse' + $ref: '#/components/schemas/RespondActivityTaskCanceledByIdResponse' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' - /namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/heartbeat: + /namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/unpause: post: tags: - WorkflowService description: |- - See `RecordActivityTaskHeartbeat`. This version allows clients to record heartbeats by - namespace/workflow id/activity id instead of task token. + UnpauseActivityExecution unpauses the execution of an activity specified by its ID. + This API can be used to target a workflow activity or a standalone activity. - (-- api-linter: core::0136::prepositions=disabled - aip.dev/not-precedent: "By" is used to indicate request type. --) - operationId: RecordActivityTaskHeartbeatById + If activity is not paused, this call will have no effect. + If the activity was paused while waiting for retry, it will be scheduled immediately (* see 'jitter' flag). + Once the activity is unpaused, all timeout timers will be regenerated. + + Returns a `NotFound` error if there is no pending activity with the provided ID + operationId: UnpauseActivityExecution parameters: - name: namespace in: path - description: Namespace of the workflow which scheduled this activity + description: Namespace of the workflow which scheduled this activity. required: true schema: type: string - name: workflowId in: path - description: Id of the workflow which scheduled this activity, leave empty to target a standalone activity + description: |- + If provided, targets a workflow activity for the given workflow ID. + If empty, targets a standalone activity. required: true schema: type: string - name: activityId in: path - description: Id of the activity we're heartbeating + description: The ID of the activity to target. required: true schema: type: string @@ -8247,7 +9092,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RecordActivityTaskHeartbeatByIdRequest' + $ref: '#/components/schemas/UnpauseActivityExecutionRequest' required: true responses: "200": @@ -8255,24 +9100,21 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RecordActivityTaskHeartbeatByIdResponse' + $ref: '#/components/schemas/UnpauseActivityExecutionResponse' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' - /namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/resolve-as-canceled: + /namespaces/{namespace}/workflows/{workflowId}/activities/{activityId}/update-options: post: tags: - WorkflowService description: |- - See `RespondActivityTaskCanceled`. This version allows clients to record failures by - namespace/workflow id/activity id instead of task token. - - (-- api-linter: core::0136::prepositions=disabled - aip.dev/not-precedent: "By" is used to indicate request type. --) - operationId: RespondActivityTaskCanceledById + UpdateActivityExecutionOptions is called by the client to update the options of an activity by its ID. + This API can be used to target a workflow activity or a standalone activity. + operationId: UpdateActivityExecutionOptions parameters: - name: namespace in: path @@ -8282,13 +9124,15 @@ paths: type: string - name: workflowId in: path - description: Id of the workflow which scheduled this activity, leave empty to target a standalone activity + description: |- + If provided, targets a workflow activity for the given workflow ID. + If empty, targets a standalone activity. required: true schema: type: string - name: activityId in: path - description: Id of the activity to confirm is cancelled + description: The ID of the activity to target. required: true schema: type: string @@ -8296,7 +9140,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RespondActivityTaskCanceledByIdRequest' + $ref: '#/components/schemas/UpdateActivityExecutionOptionsRequest' required: true responses: "200": @@ -8304,7 +9148,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RespondActivityTaskCanceledByIdResponse' + $ref: '#/components/schemas/UpdateActivityExecutionOptionsResponse' default: description: Default error response content: @@ -12077,6 +12921,9 @@ components: pollerAutoscaling: type: boolean description: True if the namespace supports poller autoscaling + workerCommands: + type: boolean + description: True if the namespace supports worker commands (server-to-worker communication via control queues). description: Namespace capability details. Should contain what features are enabled in a namespace. NamespaceInfo_Limits: type: object @@ -12729,6 +13576,38 @@ components: PatchScheduleResponse: type: object properties: {} + PauseActivityExecutionRequest: + type: object + properties: + namespace: + type: string + description: Namespace of the workflow which scheduled this activity. + workflowId: + type: string + description: |- + If provided, pause a workflow activity (or activities) for the given workflow ID. + If empty, targets a standalone activity. + activityId: + type: string + description: The ID of the activity to target. + runId: + type: string + description: Run ID of the workflow or standalone activity. + identity: + type: string + description: The identity of the client who initiated this request. + reason: + type: string + description: Reason to pause the activity. + resourceId: + type: string + description: Resource ID for routing. Contains "workflow:{workflow_id}" for workflow activities or "activity:{activity_id}" for standalone activities. + requestId: + type: string + description: Used to de-dupe pause requests. + PauseActivityExecutionResponse: + type: object + properties: {} PauseActivityRequest: type: object properties: @@ -12753,9 +13632,14 @@ components: reason: type: string description: Reason to pause the activity. + requestId: + type: string + description: Used to de-dupe pause requests. + description: Deprecated. Use `PauseActivityExecutionRequest`. PauseActivityResponse: type: object properties: {} + description: Deprecated. Use `PauseActivityExecutionResponse`. PauseInfo_Manual: type: object properties: @@ -13885,6 +14769,52 @@ components: Indicate if the request is still buffered. If so, the event ID is not known and its value will be an invalid event ID. description: RequestIdInfo contains details of a request ID. + ResetActivityExecutionRequest: + type: object + properties: + namespace: + type: string + description: Namespace of the workflow which scheduled this activity. + workflowId: + type: string + description: |- + If provided, targets a workflow activity for the given workflow ID. + If empty, targets a standalone activity. + activityId: + type: string + description: The ID of the activity to target. + runId: + type: string + description: Run ID of the workflow or standalone activity. + identity: + type: string + description: The identity of the client who initiated this request. + resetHeartbeat: + type: boolean + description: |- + Indicates that activity should reset heartbeat details. + This flag will be applied only to the new instance of the activity. + keepPaused: + type: boolean + description: If activity is paused, it will remain paused after reset + jitter: + pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ + type: string + description: |- + If set, and activity is in backoff, the activity will start at a random time within the specified jitter duration. + (unless it is paused and keep_paused is set) + restoreOriginalOptions: + type: boolean + description: |- + If set, the activity options will be restored to the defaults. + Default options are then options activity was created with. + They are part of the first schedule event. + resourceId: + type: string + description: Resource ID for routing. Contains "workflow:{workflow_id}" for workflow activities or "activity:{activity_id}" for standalone activities. + ResetActivityExecutionResponse: + type: object + properties: {} ResetActivityRequest: type: object properties: @@ -13926,11 +14856,14 @@ components: description: |- If set, the activity options will be restored to the defaults. Default options are then options activity was created with. - They are part of the first SCHEDULE event. - description: 'NOTE: keep in sync with temporal.api.batch.v1.BatchOperationResetActivities' + They are part of the first schedule event. + description: |- + NOTE: keep in sync with temporal.api.batch.v1.BatchOperationResetActivities + Deprecated. Use `ResetActivityExecutionRequest`. ResetActivityResponse: type: object properties: {} + description: Deprecated. Use `ResetActivityExecutionRequest`. ResetOptions: type: object properties: @@ -15557,6 +16490,14 @@ components: allOf: - $ref: '#/components/schemas/Priority' description: Priority metadata + timeSkippingConfig: + allOf: + - $ref: '#/components/schemas/TimeSkippingConfig' + description: The propagated time-skipping configuration for the child workflow. + initialSkippedDuration: + pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ + type: string + description: Propagate the duration skipped to the child workflow. StartNexusOperationExecutionRequest: type: object properties: @@ -16243,10 +17184,7 @@ components: properties: enabled: type: boolean - description: "Enables or disables time skipping for this workflow execution.\n By default, this field is propagated to transitively related workflows (child workflows/start-as-new/reset) \n at the time they are started.\n Changes made after a transitively related workflow has started are not propagated." - disablePropagation: - type: boolean - description: If set, the enabled field is not propagated to transitively related workflows. + description: Enables or disables time skipping for this workflow execution. maxSkippedDuration: pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ type: string @@ -16257,19 +17195,7 @@ components: description: |- Maximum elapsed time since time skipping was enabled. This includes both skipped time and real time elapsing. - maxTargetTime: - type: string - description: |- - Absolute virtual timestamp at which time skipping is disabled. - Time skipping will not advance beyond this point. - format: date-time - description: |- - Configuration for time skipping during a workflow execution. - When enabled, virtual time advances automatically whenever there is no in-flight work. - In-flight work includes activities, child workflows, Nexus operations, signal/cancel external workflow operations, - and possibly other features added in the future. - User timers are not classified as in-flight work and will be skipped over. - When time advances, it skips to the earlier of the next user timer or the configured bound, if either exists. + description: "Configuration for time skipping during a workflow execution.\n When enabled, virtual time advances automatically whenever there is no in-flight work.\n In-flight work includes activities, child workflows, Nexus operations, signal/cancel external workflow operations,\n and possibly other features added in the future.\n User timers are not classified as in-flight work and will be skipped over.\n When time advances, it skips to the earlier of the next user timer or the configured bound, if either exists.\n \n Propagation behavior of time skipping:\n The enabled flag, bound fields, and accumulated skipped duration are propagated to related executions as follows:\n (1) Child workflows and continue-as-new: both the configuration and the accumulated skipped duration are\n inherited from the current execution. The configured bound is shared between the inherited skipped\n duration and any additional duration skipped by the new run.\n (2) Retry and cron: the configuration and accumulated skipped duration are inherited as recorded when the\n current workflow started; the accumulated skipped duration of the current run is not propagated.\n (3) Reset: the new run retains the time-skipping configuration of the current execution. Because reset replays\n all events up to the reset point and re-applies any UpdateWorkflowExecutionOptions changes made after that\n point, the resulting run ends up with the same final time-skipping configuration as the previous run." TimeoutFailureInfo: type: object properties: @@ -16386,6 +17312,45 @@ components: applied: type: boolean description: True is the rule was applied, based on the rule conditions (predicate/visibility_query). + UnpauseActivityExecutionRequest: + type: object + properties: + namespace: + type: string + description: Namespace of the workflow which scheduled this activity. + workflowId: + type: string + description: |- + If provided, targets a workflow activity for the given workflow ID. + If empty, targets a standalone activity. + activityId: + type: string + description: The ID of the activity to target. + runId: + type: string + description: Run ID of the workflow or standalone activity. + identity: + type: string + description: The identity of the client who initiated this request. + resetAttempts: + type: boolean + description: Providing this flag will also reset the number of attempts. + resetHeartbeat: + type: boolean + description: Providing this flag will also reset the heartbeat details. + reason: + type: string + description: Reason to unpause the activity. + jitter: + pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ + type: string + description: If set, the activity will start at a random time within the specified jitter duration. + resourceId: + type: string + description: Resource ID for routing. Contains "workflow:{workflow_id}" for workflow activities or "activity:{activity_id}" for standalone activities. + UnpauseActivityExecutionResponse: + type: object + properties: {} UnpauseActivityRequest: type: object properties: @@ -16418,9 +17383,11 @@ components: pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ type: string description: If set, the activity will start at a random time within the specified jitter duration. + description: Deprecated. Use `UnpauseActivityExecutionRequest`. UnpauseActivityResponse: type: object properties: {} + description: Deprecated. Use `UnpauseActivityExecutionResponse`. UnpauseWorkflowExecutionRequest: type: object properties: @@ -16446,6 +17413,52 @@ components: type: object properties: {} description: Response to a successful UnpauseWorkflowExecution request. + UpdateActivityExecutionOptionsRequest: + type: object + properties: + namespace: + type: string + description: Namespace of the workflow which scheduled this activity + workflowId: + type: string + description: |- + If provided, targets a workflow activity for the given workflow ID. + If empty, targets a standalone activity. + activityId: + type: string + description: The ID of the activity to target. + runId: + type: string + description: Run ID of the workflow or standalone activity. + identity: + type: string + description: The identity of the client who initiated this request + activityOptions: + allOf: + - $ref: '#/components/schemas/ActivityOptions' + description: Activity options. Partial updates are accepted and controlled by update_mask + updateMask: + type: string + description: Controls which fields from `activity_options` will be applied + format: field-mask + restoreOriginal: + type: boolean + description: |- + If set, the activity options will be restored to the default. + Default options are then options activity was created with. + They are part of the first schedule event. + This flag cannot be combined with any other option; if you supply + restore_original together with other options, the request will be rejected. + resourceId: + type: string + description: Resource ID for routing. Contains "workflow:{workflow_id}" for workflow activities or "activity:{activity_id}" for standalone activities. + UpdateActivityExecutionOptionsResponse: + type: object + properties: + activityOptions: + allOf: + - $ref: '#/components/schemas/ActivityOptions' + description: Activity options after an update UpdateActivityOptionsRequest: type: object properties: @@ -16481,10 +17494,12 @@ components: description: |- If set, the activity options will be restored to the default. Default options are then options activity was created with. - They are part of the first SCHEDULE event. + They are part of the first schedule event. This flag cannot be combined with any other option; if you supply restore_original together with other options, the request will be rejected. - description: 'NOTE: keep in sync with temporal.api.batch.v1.BatchOperationUpdateActivityOptions' + description: |- + NOTE: keep in sync with temporal.api.batch.v1.BatchOperationUpdateActivityOptions + Deprecated. Use `UpdateActivityExecutionOptionsRequest`. UpdateActivityOptionsResponse: type: object properties: @@ -16492,6 +17507,7 @@ components: allOf: - $ref: '#/components/schemas/ActivityOptions' description: Activity options after an update + description: Deprecated. Use `UpdateActivityExecutionOptionsResponse`. UpdateDeploymentMetadata: type: object properties: @@ -18219,7 +19235,10 @@ components: timeSkippingConfig: allOf: - $ref: '#/components/schemas/TimeSkippingConfig' - description: "Time-skipping configuration for this workflow execution.\n If not set, the time-skipping conf will not get updated upon request, \n i.e. the existing time-skipping conf will be preserved." + description: |- + Time-skipping configuration for this workflow execution. + If not set, the time-skipping configuration is not updated by this request; + the existing configuration is preserved. WorkflowExecutionOptionsUpdatedEventAttributes: type: object properties: @@ -18563,6 +19582,12 @@ components: The configuration may be updated after start via UpdateWorkflowExecutionOptions, which will be reflected in the WorkflowExecutionOptionsUpdatedEvent. + initialSkippedDuration: + pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ + type: string + description: |- + The time skipped by the previous execution that started this workflow. + It can happen in cases of child workflows and continue-as-new workflows. description: Always the first event in workflow history WorkflowExecutionTerminatedEventAttributes: type: object diff --git a/crates/common/protos/api_upstream/temporal/api/history/v1/message.proto b/crates/common/protos/api_upstream/temporal/api/history/v1/message.proto index e873412bf..e57ec8d60 100644 --- a/crates/common/protos/api_upstream/temporal/api/history/v1/message.proto +++ b/crates/common/protos/api_upstream/temporal/api/history/v1/message.proto @@ -204,6 +204,10 @@ message WorkflowExecutionStartedEventAttributes { // The configuration may be updated after start via UpdateWorkflowExecutionOptions, which // will be reflected in the WorkflowExecutionOptionsUpdatedEvent. temporal.api.workflow.v1.TimeSkippingConfig time_skipping_config = 41; + + // The time skipped by the previous execution that started this workflow. + // It can happen in cases of child workflows and continue-as-new workflows. + google.protobuf.Duration initial_skipped_duration = 42; } // Wrapper for a target deployment version that the SDK declined to upgrade to. @@ -770,6 +774,12 @@ message StartChildWorkflowExecutionInitiatedEventAttributes { bool inherit_build_id = 19 [deprecated = true]; // Priority metadata temporal.api.common.v1.Priority priority = 20; + + // The propagated time-skipping configuration for the child workflow. + temporal.api.workflow.v1.TimeSkippingConfig time_skipping_config = 21; + + // Propagate the duration skipped to the child workflow. + google.protobuf.Duration initial_skipped_duration = 30; } message StartChildWorkflowExecutionFailedEventAttributes { diff --git a/crates/common/protos/api_upstream/temporal/api/namespace/v1/message.proto b/crates/common/protos/api_upstream/temporal/api/namespace/v1/message.proto index cded0e372..1e07f6cd3 100644 --- a/crates/common/protos/api_upstream/temporal/api/namespace/v1/message.proto +++ b/crates/common/protos/api_upstream/temporal/api/namespace/v1/message.proto @@ -50,6 +50,8 @@ message NamespaceInfo { bool worker_poll_complete_on_shutdown = 8; // True if the namespace supports poller autoscaling bool poller_autoscaling = 9; + // True if the namespace supports worker commands (server-to-worker communication via control queues). + bool worker_commands = 10; } // Namespace configured limits diff --git a/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto b/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto index c7374abc7..f4012c73d 100644 --- a/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +++ b/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto @@ -86,6 +86,11 @@ message ListNamespacesResponse { message DescribeNamespaceRequest { string namespace = 1; string id = 2; + // If true, the server may serve the response from an eventually-consistent + // source instead of reading through to persistence. Defaults to false, + // which preserves read-after-write consistency. SDKs should set this when + // fetching namespace capabilities on worker/client startup. + bool weak_consistency = 3; } message DescribeNamespaceResponse { @@ -199,6 +204,7 @@ message StartWorkflowExecutionRequest { temporal.api.common.v1.Priority priority = 27; // Deployment Options of the worker who will process the eager task. Passed when `request_eager_execution=true`. temporal.api.deployment.v1.WorkerDeploymentOptions eager_worker_deployment_options = 28; + // Time-skipping configuration. If not set, time skipping is disabled. temporal.api.workflow.v1.TimeSkippingConfig time_skipping_config = 29; } @@ -2065,6 +2071,7 @@ message ExecuteMultiOperationResponse { } // NOTE: keep in sync with temporal.api.batch.v1.BatchOperationUpdateActivityOptions +// Deprecated. Use `UpdateActivityExecutionOptionsRequest`. message UpdateActivityOptionsRequest { // Namespace of the workflow which scheduled this activity string namespace = 1; @@ -2092,17 +2099,56 @@ message UpdateActivityOptionsRequest { // If set, the activity options will be restored to the default. // Default options are then options activity was created with. - // They are part of the first SCHEDULE event. + // They are part of the first schedule event. // This flag cannot be combined with any other option; if you supply // restore_original together with other options, the request will be rejected. bool restore_original = 8; } +message UpdateActivityExecutionOptionsRequest { + // Namespace of the workflow which scheduled this activity + string namespace = 1; + + // If provided, targets a workflow activity for the given workflow ID. + // If empty, targets a standalone activity. + string workflow_id = 2; + // The ID of the activity to target. + string activity_id = 3; + // Run ID of the workflow or standalone activity. + string run_id = 4; + + // The identity of the client who initiated this request + string identity = 5; + + // Activity options. Partial updates are accepted and controlled by update_mask + temporal.api.activity.v1.ActivityOptions activity_options = 6; + + // Controls which fields from `activity_options` will be applied + google.protobuf.FieldMask update_mask = 7; + + // If set, the activity options will be restored to the default. + // Default options are then options activity was created with. + // They are part of the first schedule event. + // This flag cannot be combined with any other option; if you supply + // restore_original together with other options, the request will be rejected. + bool restore_original = 8; + + // Resource ID for routing. Contains "workflow:{workflow_id}" for workflow activities or "activity:{activity_id}" for standalone activities. + string resource_id = 9; +} + +// Deprecated. Use `UpdateActivityExecutionOptionsResponse`. message UpdateActivityOptionsResponse { // Activity options after an update temporal.api.activity.v1.ActivityOptions activity_options = 1; } +message UpdateActivityExecutionOptionsResponse { + // Activity options after an update + temporal.api.activity.v1.ActivityOptions activity_options = 1; +} + +// Deprecated. Use `PauseActivityExecutionRequest`. message PauseActivityRequest { // Namespace of the workflow which scheduled this activity. string namespace = 1; @@ -2123,11 +2169,45 @@ message PauseActivityRequest { // Reason to pause the activity. string reason = 6; + + // Used to de-dupe pause requests. + string request_id = 7; + } +message PauseActivityExecutionRequest { + // Namespace of the workflow which scheduled this activity. + string namespace = 1; + + // If provided, pause a workflow activity (or activities) for the given workflow ID. + // If empty, targets a standalone activity. + string workflow_id = 2; + // The ID of the activity to target. + string activity_id = 3; + // Run ID of the workflow or standalone activity. + string run_id = 4; + + // The identity of the client who initiated this request. + string identity = 5; + + // Reason to pause the activity. + string reason = 6; + + // Resource ID for routing. Contains "workflow:{workflow_id}" for workflow activities or "activity:{activity_id}" for standalone activities. + string resource_id = 7; + + // Used to de-dupe pause requests. + string request_id = 8; +} + +// Deprecated. Use `PauseActivityExecutionResponse`. message PauseActivityResponse { } +message PauseActivityExecutionResponse { +} + +// Deprecated. Use `UnpauseActivityExecutionRequest`. message UnpauseActivityRequest { // Namespace of the workflow which scheduled this activity. string namespace = 1; @@ -2157,10 +2237,46 @@ message UnpauseActivityRequest { google.protobuf.Duration jitter = 9; } +message UnpauseActivityExecutionRequest { + // Namespace of the workflow which scheduled this activity. + string namespace = 1; + + // If provided, targets a workflow activity for the given workflow ID. + // If empty, targets a standalone activity. + string workflow_id = 2; + // The ID of the activity to target. + string activity_id = 3; + // Run ID of the workflow or standalone activity. + string run_id = 4; + + // The identity of the client who initiated this request. + string identity = 5; + + // Providing this flag will also reset the number of attempts. + bool reset_attempts = 6; + + // Providing this flag will also reset the heartbeat details. + bool reset_heartbeat = 7; + + // Reason to unpause the activity. + string reason = 8; + + // If set, the activity will start at a random time within the specified jitter duration. + google.protobuf.Duration jitter = 9; + + // Resource ID for routing. Contains "workflow:{workflow_id}" for workflow activities or "activity:{activity_id}" for standalone activities. + string resource_id = 10; +} + +// Deprecated. Use `UnpauseActivityExecutionResponse`. message UnpauseActivityResponse { } +message UnpauseActivityExecutionResponse { +} + // NOTE: keep in sync with temporal.api.batch.v1.BatchOperationResetActivities +// Deprecated. Use `ResetActivityExecutionRequest`. message ResetActivityRequest { // Namespace of the workflow which scheduled this activity. string namespace = 1; @@ -2193,14 +2309,52 @@ message ResetActivityRequest { // If set, the activity options will be restored to the defaults. // Default options are then options activity was created with. - // They are part of the first SCHEDULE event. + // They are part of the first schedule event. + bool restore_original_options = 9; +} + +message ResetActivityExecutionRequest { + // Namespace of the workflow which scheduled this activity. + string namespace = 1; + + // If provided, targets a workflow activity for the given workflow ID. + // If empty, targets a standalone activity. + string workflow_id = 2; + // The ID of the activity to target. + string activity_id = 3; + // Run ID of the workflow or standalone activity. + string run_id = 4; + + // The identity of the client who initiated this request. + string identity = 5; + + // Indicates that activity should reset heartbeat details. + // This flag will be applied only to the new instance of the activity. + bool reset_heartbeat = 6; + + // If activity is paused, it will remain paused after reset + bool keep_paused = 7; + + // If set, and activity is in backoff, the activity will start at a random time within the specified jitter duration. + // (unless it is paused and keep_paused is set) + google.protobuf.Duration jitter = 8; + + // If set, the activity options will be restored to the defaults. + // Default options are then options activity was created with. + // They are part of the first schedule event. bool restore_original_options = 9; + // Resource ID for routing. Contains "workflow:{workflow_id}" for workflow activities or "activity:{activity_id}" for standalone activities. + string resource_id = 10; } +// Deprecated. Use `ResetActivityExecutionRequest`. message ResetActivityResponse { } +message ResetActivityExecutionResponse { +} + // Keep the parameters in sync with: // - temporal.api.batch.v1.BatchOperationUpdateWorkflowExecutionOptions. // - temporal.api.workflow.v1.PostResetOperation.UpdateWorkflowOptions. @@ -2781,6 +2935,10 @@ message ListWorkersRequest { //* StartTime //* Status string query = 4; + + // When true, the response will include system workers that are created implicitly + // by the server and not by the user. By default, system workers are excluded. + bool include_system_workers = 5; } message ListWorkersResponse { diff --git a/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/service.proto b/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/service.proto index 6a4287100..b804f3f03 100644 --- a/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +++ b/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/service.proto @@ -1527,6 +1527,10 @@ service WorkflowService { body: "*" } }; + option (temporal.api.protometa.v1.request_header) = { + header: "temporal-resource-id" + value: "workflow:{execution.workflow_id}" + }; } // WorkerHeartbeat receive heartbeat request from the worker. @@ -1862,6 +1866,141 @@ service WorkflowService { // aip.dev/not-precedent: Activity deletion not exposed to HTTP, users should use cancel or terminate. --) rpc DeleteActivityExecution (DeleteActivityExecutionRequest) returns (DeleteActivityExecutionResponse) {} + // PauseActivityExecution pauses the execution of an activity specified by its ID. + // This API can be used to target a workflow activity or a standalone activity + // + // Pausing an activity means: + // - If the activity is currently waiting for a retry or is running and subsequently fails, + // it will not be rescheduled until it is unpaused. + // - If the activity is already paused, calling this method will have no effect. + // - If the activity is running and finishes successfully, the activity will be completed. + // - If the activity is running and finishes with failure: + // * if there is no retry left - the activity will be completed. + // * if there are more retries left - the activity will be paused. + // For long-running activities: + // - activities in paused state will send a cancellation with "activity_paused" set to 'true' in response to 'RecordActivityTaskHeartbeat'. + // + // Returns a `NotFound` error if there is no pending activity with the provided ID + rpc PauseActivityExecution (PauseActivityExecutionRequest) returns (PauseActivityExecutionResponse) { + option (google.api.http) = { + // Standalone activity + post: "/namespaces/{namespace}/activities/{activity_id}/pause" + body: "*" + additional_bindings { + post: "/api/v1/namespaces/{namespace}/activities/{activity_id}/pause" + body: "*" + } + // Workflow activity + additional_bindings { + post: "/namespaces/{namespace}/workflows/{workflow_id}/activities/{activity_id}/pause" + body: "*" + } + additional_bindings { + post: "/api/v1/namespaces/{namespace}/workflows/{workflow_id}/activities/{activity_id}/pause" + body: "*" + } + }; + option (temporal.api.protometa.v1.request_header) = { + header: "temporal-resource-id" + value: "{resource_id}" + }; + } + + // ResetActivityExecution resets the execution of an activity specified by its ID. + // This API can be used to target a workflow activity or a standalone activity. + // + // Resetting an activity means: + // * number of attempts will be reset to 0. + // * activity timeouts will be reset. + // * if the activity is waiting for retry, and it is not paused or 'keep_paused' is not provided: + // it will be scheduled immediately (* see 'jitter' flag) + // + // Returns a `NotFound` error if there is no pending activity with the provided ID or type. + rpc ResetActivityExecution (ResetActivityExecutionRequest) returns (ResetActivityExecutionResponse) { + option (google.api.http) = { + // Standalone activity + post: "/namespaces/{namespace}/activities/{activity_id}/reset" + body: "*" + additional_bindings { + post: "/api/v1/namespaces/{namespace}/activities/{activity_id}/reset" + body: "*" + } + // Workflow activity + additional_bindings { + post: "/namespaces/{namespace}/workflows/{workflow_id}/activities/{activity_id}/reset" + body: "*" + } + additional_bindings { + post: "/api/v1/namespaces/{namespace}/workflows/{workflow_id}/activities/{activity_id}/reset" + body: "*" + } + }; + option (temporal.api.protometa.v1.request_header) = { + header: "temporal-resource-id" + value: "{resource_id}" + }; + } + + // UnpauseActivityExecution unpauses the execution of an activity specified by its ID. + // This API can be used to target a workflow activity or a standalone activity. + // + // If activity is not paused, this call will have no effect. + // If the activity was paused while waiting for retry, it will be scheduled immediately (* see 'jitter' flag). + // Once the activity is unpaused, all timeout timers will be regenerated. + // + // Returns a `NotFound` error if there is no pending activity with the provided ID + rpc UnpauseActivityExecution (UnpauseActivityExecutionRequest) returns (UnpauseActivityExecutionResponse) { + option (google.api.http) = { + // Standalone activity + post: "/namespaces/{namespace}/activities/{activity_id}/unpause" + body: "*" + additional_bindings { + post: "/api/v1/namespaces/{namespace}/activities/{activity_id}/unpause" + body: "*" + } + // Workflow activity + additional_bindings { + post: "/namespaces/{namespace}/workflows/{workflow_id}/activities/{activity_id}/unpause" + body: "*" + } + additional_bindings { + post: "/api/v1/namespaces/{namespace}/workflows/{workflow_id}/activities/{activity_id}/unpause" + body: "*" + } + }; + option (temporal.api.protometa.v1.request_header) = { + header: "temporal-resource-id" + value: "{resource_id}" + }; + } + + // UpdateActivityExecutionOptions is called by the client to update the options of an activity by its ID. + // This API can be used to target a workflow activity or a standalone activity. + rpc UpdateActivityExecutionOptions (UpdateActivityExecutionOptionsRequest) returns (UpdateActivityExecutionOptionsResponse) { + option (google.api.http) = { + // Standalone activity + post: "/namespaces/{namespace}/activities/{activity_id}/update-options" + body: "*" + additional_bindings { + post: "/api/v1/namespaces/{namespace}/activities/{activity_id}/update-options" + body: "*" + } + // Workflow activity + additional_bindings { + post: "/namespaces/{namespace}/workflows/{workflow_id}/activities/{activity_id}/update-options" + body: "*" + } + additional_bindings { + post: "/api/v1/namespaces/{namespace}/workflows/{workflow_id}/activities/{activity_id}/update-options" + body: "*" + } + }; + option (temporal.api.protometa.v1.request_header) = { + header: "temporal-resource-id" + value: "{resource_id}" + }; + } + // TerminateNexusOperationExecution terminates an existing Nexus operation immediately. // // Termination happens immediately and the operation handler cannot react to it. A terminated operation will have diff --git a/crates/sdk-core-c-bridge/src/client.rs b/crates/sdk-core-c-bridge/src/client.rs index b1bdfc94a..5971e5c76 100644 --- a/crates/sdk-core-c-bridge/src/client.rs +++ b/crates/sdk-core-c-bridge/src/client.rs @@ -773,6 +773,9 @@ async fn call_workflow_service( } "PatchSchedule" => rpc_call_on_trait!(client, call, WorkflowService, patch_schedule), "PauseActivity" => rpc_call_on_trait!(client, call, WorkflowService, pause_activity), + "PauseActivityExecution" => { + rpc_call_on_trait!(client, call, WorkflowService, pause_activity_execution) + } "PauseWorkflowExecution" => { rpc_call_on_trait!(client, call, WorkflowService, pause_workflow_execution) } @@ -848,6 +851,9 @@ async fn call_workflow_service( ) } "ResetActivity" => rpc_call_on_trait!(client, call, WorkflowService, reset_activity), + "ResetActivityExecution" => { + rpc_call_on_trait!(client, call, WorkflowService, reset_activity_execution) + } "ResetStickyTaskQueue" => { rpc_call_on_trait!(client, call, WorkflowService, reset_sticky_task_queue) } @@ -988,9 +994,20 @@ async fn call_workflow_service( "UnpauseActivity" => { rpc_call_on_trait!(client, call, WorkflowService, unpause_activity) } + "UnpauseActivityExecution" => { + rpc_call_on_trait!(client, call, WorkflowService, unpause_activity_execution) + } "UnpauseWorkflowExecution" => { rpc_call_on_trait!(client, call, WorkflowService, unpause_workflow_execution) } + "UpdateActivityExecutionOptions" => { + rpc_call_on_trait!( + client, + call, + WorkflowService, + update_activity_execution_options + ) + } "UpdateActivityOptions" => { rpc_call_on_trait!(client, call, WorkflowService, update_activity_options) } From 8bc16e321d18bb0a2ec480cb79d4a8e3910fc292 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 11 May 2026 14:26:10 -0700 Subject: [PATCH 8/8] Add capability checking --- .cargo/config.toml | 2 +- crates/sdk-core/src/pollers/poll_buffer.rs | 22 +- crates/sdk-core/src/worker/client.rs | 27 +- crates/sdk-core/src/worker/client/mocks.rs | 2 +- crates/sdk-core/src/worker/heartbeat.rs | 290 ++++++++++-------- crates/sdk-core/src/worker/mod.rs | 4 + .../integ_tests/worker_heartbeat_tests.rs | 8 +- crates/sdk/src/workflows.rs | 3 +- 8 files changed, 208 insertions(+), 150 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index c183ad0d0..4e0dbc936 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,6 @@ [env] # This temporarily overrides the version of the CLI used for integration tests, locally and in CI -CLI_VERSION_OVERRIDE = "v1.6.3-serverless" +# CLI_VERSION_OVERRIDE = "v1.6.3-serverless" [alias] # Not sure why --all-features doesn't work diff --git a/crates/sdk-core/src/pollers/poll_buffer.rs b/crates/sdk-core/src/pollers/poll_buffer.rs index 8a32e06e5..0a0c34908 100644 --- a/crates/sdk-core/src/pollers/poll_buffer.rs +++ b/crates/sdk-core/src/pollers/poll_buffer.rs @@ -4,7 +4,9 @@ use crate::{ worker::{ ActivitySlotKind, NamespaceCapabilities, NexusSlotKind, PollerBehavior, SlotKind, WFTPollerShared, WorkflowSlotKind, - client::{PollActivityOptions, PollOptions, PollWorkflowOptions, WorkerClient}, + client::{ + PollActivityOptions, PollNexusOptions, PollOptions, PollWorkflowOptions, WorkerClient, + }, }, }; use backoff::{SystemClock, backoff::Backoff, exponential::ExponentialBackoff}; @@ -153,7 +155,6 @@ impl LongPollBuffer { task_queue, no_retry, timeout_override, - worker_commands_queue: false, }, PollWorkflowOptions { sticky_queue_name }, ) @@ -211,7 +212,6 @@ impl LongPollBuffer { task_queue, no_retry, timeout_override, - worker_commands_queue: false, }, PollActivityOptions { max_tasks_per_sec: options.max_tps, @@ -265,12 +265,16 @@ impl LongPollBuffer { let task_queue = task_queue.clone(); async move { client - .poll_nexus_task(PollOptions { - task_queue, - no_retry, - timeout_override, - worker_commands_queue, - }) + .poll_nexus_task( + PollOptions { + task_queue, + no_retry, + timeout_override, + }, + PollNexusOptions { + worker_commands_queue, + }, + ) .await } }; diff --git a/crates/sdk-core/src/worker/client.rs b/crates/sdk-core/src/worker/client.rs index f4f71f589..c70adc816 100644 --- a/crates/sdk-core/src/worker/client.rs +++ b/crates/sdk-core/src/worker/client.rs @@ -1,7 +1,10 @@ //! Worker-specific client needs pub(crate) mod mocks; -use crate::{protosext::legacy_query_failure, worker::WorkerVersioningStrategy}; +use crate::{ + protosext::legacy_query_failure, + worker::{WorkerVersioningStrategy, worker_control_task_queue}, +}; use parking_lot::Mutex; use prost_types::Duration as PbDuration; use std::{ @@ -136,11 +139,7 @@ impl WorkerClientBag { } fn worker_control_task_queue(&self) -> String { - format!( - "temporal-sys/worker-commands/{}/{}", - self.namespace, - self.worker_grouping_key() - ) + worker_control_task_queue(&self.namespace, &self.worker_grouping_key().to_string()) } } @@ -165,6 +164,7 @@ pub trait WorkerClient: Sync + Send { async fn poll_nexus_task( &self, poll_options: PollOptions, + nexus_options: PollNexusOptions, ) -> Result; /// Complete a workflow task async fn complete_workflow_task( @@ -274,9 +274,6 @@ pub struct PollOptions { pub no_retry: Option, /// Overrides the default RPC timeout for the poll request pub timeout_override: Option, - /// If true, poll using `TaskQueueKind::WorkerCommands`. Currently only meaningful for Nexus - /// polls issued by the shared-namespace worker. - pub worker_commands_queue: bool, } /// Additional options specific to workflow task polling #[derive(Debug, Clone)] @@ -290,6 +287,13 @@ pub struct PollActivityOptions { /// Optional rate limit (tasks per second) for activity polling pub max_tasks_per_sec: Option, } +/// Additional options specific to Nexus task polling +#[derive(Debug, Clone, Default)] +pub struct PollNexusOptions { + /// If true, poll using `TaskQueueKind::WorkerCommands` — the per-process control queue used + /// by the shared-namespace worker to receive server-to-worker commands. + pub worker_commands_queue: bool, +} #[async_trait::async_trait] impl WorkerClient for WorkerClientBag { @@ -383,8 +387,9 @@ impl WorkerClient for WorkerClientBag { async fn poll_nexus_task( &self, poll_options: PollOptions, + nexus_options: PollNexusOptions, ) -> Result { - let kind = if poll_options.worker_commands_queue { + let kind = if nexus_options.worker_commands_queue { TaskQueueKind::WorkerCommands } else { TaskQueueKind::Normal @@ -400,6 +405,8 @@ impl WorkerClient for WorkerClientBag { identity: self.identity(), worker_version_capabilities: self.worker_version_capabilities(), deployment_options: self.deployment_options(), + // TODO: Piggyback worker heartbeats here if this is the system nexus worker and reset + // heartbeating ticker when done worker_heartbeat: Vec::new(), worker_instance_key: self.worker_instance_key.to_string(), poller_group_id: Default::default(), diff --git a/crates/sdk-core/src/worker/client/mocks.rs b/crates/sdk-core/src/worker/client/mocks.rs index 8df2f4a43..76cfeb40e 100644 --- a/crates/sdk-core/src/worker/client/mocks.rs +++ b/crates/sdk-core/src/worker/client/mocks.rs @@ -81,7 +81,7 @@ mockall::mock! { -> impl Future> + Send + 'b where 'a: 'b, Self: 'b; - fn poll_nexus_task<'a, 'b>(&self, poll_options: PollOptions) + fn poll_nexus_task<'a, 'b>(&self, poll_options: PollOptions, nexus_options: PollNexusOptions) -> impl Future> + Send + 'b where 'a: 'b, Self: 'b; diff --git a/crates/sdk-core/src/worker/heartbeat.rs b/crates/sdk-core/src/worker/heartbeat.rs index df4315760..1c055c7d8 100644 --- a/crates/sdk-core/src/worker/heartbeat.rs +++ b/crates/sdk-core/src/worker/heartbeat.rs @@ -1,6 +1,9 @@ use crate::{ WorkerClient, WorkerConfig, - worker::{PollError, PollerBehavior, TaskPollers, WorkerTelemetry, WorkerVersioningStrategy}, + worker::{ + PollError, PollerBehavior, TaskPollers, Worker, WorkerTelemetry, WorkerVersioningStrategy, + worker_control_task_queue, + }, }; use parking_lot::RwLock; use prost::Message; @@ -45,111 +48,147 @@ impl SharedNamespaceWorker { heartbeat_interval: Duration, telemetry: Option, ) -> Result { - let config = WorkerConfig::builder() - .namespace(namespace.clone()) - .task_queue(format!( - "temporal-sys/worker-commands/{namespace}/{}", - client.worker_grouping_key(), - )) - .task_types(WorkerTaskTypes::nexus_only()) - .max_outstanding_nexus_tasks(5_usize) - .versioning_strategy(WorkerVersioningStrategy::None { - build_id: "1.0".to_owned(), - }) - .nexus_task_poller_behavior(PollerBehavior::SimpleMaximum(1_usize)) - .build() - .expect("internal shared namespace worker options are valid"); - let worker = crate::worker::Worker::new_with_pollers( - config, - None, - client.clone(), - TaskPollers::Real, - telemetry, - None, - true, - )?; - let reset_notify = Arc::new(Notify::new()); let cancel = CancellationToken::new(); - let cancel_clone = cancel.clone(); - - let client_clone = client; - let namespace_clone = namespace.clone(); - let callbacks_map = Arc::new(RwLock::new(HashMap::::new())); - let callbacks_map_clone = callbacks_map.clone(); - - tokio::spawn(async move { - match client_clone.describe_namespace().await { - Ok(namespace_resp) => { - if namespace_resp - .namespace_info - .and_then(|info| info.capabilities) - .map(|caps| caps.worker_heartbeats) - != Some(true) - { - debug!( - "Worker heartbeating configured for runtime, but server version does not support it." - ); - worker.shutdown().await; + + tokio::spawn({ + let client = client.clone(); + let namespace = namespace.clone(); + let callbacks_map = callbacks_map.clone(); + let cancel = cancel.clone(); + async move { + let worker_commands_supported = match client.describe_namespace().await { + Ok(namespace_resp) => { + let caps = namespace_resp + .namespace_info + .and_then(|info| info.capabilities); + if caps.as_ref().map(|c| c.worker_heartbeats) != Some(true) { + debug!( + "Worker heartbeating configured for runtime, but server version does not support it." + ); + return; + } + caps.map(|c| c.worker_commands).unwrap_or(false) + } + Err(e) => { + warn!(error=?e, "Network error while describing namespace for heartbeat capabilities"); return; } + }; + + // Returns true if the heartbeat loop should stop. + async fn tick_heartbeat( + client: &Arc, + namespace: &str, + callbacks_map: &Arc>>, + ) -> bool { + let mut hb_to_send = Vec::new(); + let hb_callbacks: Vec<_> = callbacks_map + .read() + .values() + .map(|cb| cb.heartbeat.clone()) + .collect(); + for heartbeat_callback in hb_callbacks { + let mut heartbeat = heartbeat_callback(); + // All of these heartbeat details rely on a client. To avoid circular + // dependencies, this must be populated from within SharedNamespaceWorker + // to get info from the current client + client.set_heartbeat_client_fields(&mut heartbeat); + hb_to_send.push(heartbeat); + } + if let Err(e) = client + .record_worker_heartbeat(namespace.to_owned(), hb_to_send) + .await + { + if matches!(e.code(), tonic::Code::Unimplemented) { + return true; + } + warn!(error=?e, "Network error while sending worker heartbeat"); + } + false } - Err(e) => { - warn!(error=?e, "Network error while describing namespace for heartbeat capabilities"); - worker.shutdown().await; - return; + + // Returns true if the polling loop should stop. + async fn handle_nexus_poll( + worker: &Worker, + callbacks_map: &Arc>>, + nexus_result: Result, + ) -> bool { + match nexus_result { + Ok(task) => { + handle_worker_command_task(worker, callbacks_map, task).await; + false + } + Err(PollError::ShutDown) => true, + Err(e) => { + warn!(error=?e, "Error polling nexus task for worker commands"); + false + } + } } - } - let mut ticker = tokio::time::interval(heartbeat_interval); - loop { - tokio::select! { - _ = ticker.tick() => { - let mut hb_to_send = Vec::new(); - let hb_callbacks: Vec<_> = { - callbacks_map_clone.read().values() - .map(|cb| cb.heartbeat.clone()) - .collect() - }; - for heartbeat_callback in hb_callbacks { - let mut heartbeat = heartbeat_callback(); - // All of these heartbeat details rely on a client. To avoid circular - // dependencies, this must be populated from within SharedNamespaceWorker - // to get info from the current client - client_clone.set_heartbeat_client_fields(&mut heartbeat); - hb_to_send.push(heartbeat); + + if worker_commands_supported { + let config = WorkerConfig::builder() + .namespace(namespace.clone()) + .task_queue(worker_control_task_queue( + &namespace, + &client.worker_grouping_key().to_string(), + )) + .task_types(WorkerTaskTypes::nexus_only()) + .max_outstanding_nexus_tasks(5_usize) + .versioning_strategy(WorkerVersioningStrategy::None { + build_id: "1.0".to_owned(), + }) + .nexus_task_poller_behavior(PollerBehavior::SimpleMaximum(1_usize)) + .build() + .expect("internal shared namespace worker options are valid"); + match Worker::new_with_pollers( + config, + None, + client.clone(), + TaskPollers::Real, + telemetry, + None, + true, + ) { + Ok(worker) => { + tokio::spawn({ + let cm = callbacks_map.clone(); + let cancel = cancel.clone(); + async move { + loop { + tokio::select! { + nexus_result = worker.poll_nexus_task() => { + if handle_nexus_poll(&worker, &cm, nexus_result).await { + break; + } + } + _ = cancel.cancelled() => break, + } + } + worker.shutdown().await; + } + }); } - if let Err(e) = client_clone.record_worker_heartbeat(namespace_clone.clone(), hb_to_send).await { - if matches!(e.code(), tonic::Code::Unimplemented) { - worker.shutdown().await; - return; - } - warn!(error=?e, "Network error while sending worker heartbeat"); + Err(e) => { + warn!(error=?e, "Failed to build worker for nexus command polling"); } } - nexus_result = worker.poll_nexus_task() => { - match nexus_result { - Ok(task) => { - handle_worker_command_task( - &worker, - &callbacks_map_clone, - task, - ).await; - } - Err(PollError::ShutDown) => { + } else { + debug!("Server does not support the worker_commands capability"); + } + + let mut ticker = tokio::time::interval(heartbeat_interval); + loop { + tokio::select! { + _ = ticker.tick() => { + if tick_heartbeat(&client, &namespace, &callbacks_map).await { break; } - Err(e) => { - warn!(error=?e, "Error polling nexus task for worker commands"); - } } - } - _ = reset_notify.notified() => { - ticker.reset(); - } - _ = cancel_clone.cancelled() => { - worker.shutdown().await; - return; + _ = reset_notify.notified() => ticker.reset(), + _ = cancel.cancelled() => break, } } } @@ -189,7 +228,7 @@ impl SharedNamespaceWorkerTrait for SharedNamespaceWorker { } async fn handle_worker_command_task( - worker: &crate::worker::Worker, + worker: &Worker, callbacks_map: &Arc>>, task: NexusTask, ) { @@ -306,9 +345,9 @@ mod tests { use temporalio_common::protos::temporal::api::{ common::v1::Payload, namespace::v1::{NamespaceInfo, namespace_info::Capabilities}, - nexus::v1::{Request, StartOperationRequest, request}, - nexusservices::workerservice::v1::ExecuteCommandsRequest, - worker::v1::{CancelActivityCommand, WorkerCommand, worker_command}, + nexus::v1::{Request, StartOperationRequest, request, response, start_operation_response}, + nexusservices::workerservice::v1::{ExecuteCommandsRequest, ExecuteCommandsResponse}, + worker::v1::{CancelActivityCommand, WorkerCommand, worker_command, worker_command_result}, workflowservice::v1::{ DescribeNamespaceResponse, PollNexusTaskQueueResponse, RecordWorkerHeartbeatResponse, RespondNexusTaskCompletedResponse, @@ -324,7 +363,7 @@ mod tests { mock.expect_poll_workflow_task() .returning(move |_namespace, _task_queue| Ok(Default::default())); mock.expect_poll_nexus_task() - .returning(move |_poll_options| Ok(Default::default())); + .returning(move |_poll_options, _nexus_options| Ok(Default::default())); mock.expect_record_worker_heartbeat().times(3).returning( move |_namespace, worker_heartbeat| { assert_eq!(1, worker_heartbeat.len()); @@ -429,28 +468,36 @@ mod tests { .returning(|| ("test-core".to_string(), "0.0.0".to_string())); mock.expect_identity() .returning(|| "test-identity".to_string()); - mock.expect_worker_grouping_key().returning(Uuid::new_v4); + let worker_grouping_key = Uuid::new_v4(); + mock.expect_worker_grouping_key() + .returning(move || worker_grouping_key); mock.expect_worker_instance_key().returning(Uuid::new_v4); mock.expect_set_heartbeat_client_fields() .returning(|_hb| {}); let activity_task_token = vec![1, 2, 3, 4]; - let completed_response = Arc::new(Mutex::new( - None::, - )); - let completed_response_clone = completed_response.clone(); + let (completion_tx, completion_rx) = tokio::sync::oneshot::channel(); + let completion_tx = Mutex::new(Some(completion_tx)); let poll_returned_command = Arc::new(AtomicBool::new(false)); let poll_returned_command_clone = poll_returned_command.clone(); let at_clone = activity_task_token.clone(); + let expected_task_queue = format!( + "temporal-sys/worker-commands/{}/{worker_grouping_key}", + crate::test_help::NAMESPACE + ); mock.expect_poll_nexus_task() - .returning(move |poll_options| { - if poll_options - .task_queue - .starts_with("temporal-sys/worker-commands/") - && !poll_returned_command_clone.swap(true, Ordering::SeqCst) - { + .returning(move |poll_options, nexus_options| { + assert!( + nexus_options.worker_commands_queue, + "shared namespace worker must poll the worker-commands queue" + ); + assert_eq!( + poll_options.task_queue, expected_task_queue, + "shared namespace worker must poll its own control task queue" + ); + if !poll_returned_command_clone.swap(true, Ordering::SeqCst) { Ok(make_execute_commands_nexus_response( vec![99], vec![WorkerCommand { @@ -467,12 +514,12 @@ mod tests { }); mock.expect_complete_nexus_task() .returning(move |_task_token, response| { - *completed_response_clone.lock().unwrap() = Some(response); + if let Some(tx) = completion_tx.lock().unwrap().take() { + let _ = tx.send(response); + } Ok(RespondNexusTaskCompletedResponse {}) }); - mock.expect_poll_workflow_task() - .returning(move |_namespace, _task_queue| Ok(Default::default())); mock.expect_record_worker_heartbeat() .returning(move |_namespace, _worker_heartbeat| Ok(RecordWorkerHeartbeatResponse {})); mock.expect_describe_namespace().returning(move || { @@ -480,6 +527,7 @@ mod tests { namespace_info: Some(NamespaceInfo { capabilities: Some(Capabilities { worker_heartbeats: true, + worker_commands: true, ..Capabilities::default() }), ..NamespaceInfo::default() @@ -504,22 +552,12 @@ mod tests { ) .unwrap(); - // Give time for the SharedNamespaceWorker to poll and process the nexus task - tokio::time::sleep(Duration::from_millis(500)).await; + let response = tokio::time::timeout(Duration::from_secs(5), completion_rx) + .await + .expect("nexus task was not completed in time") + .expect("completion sender was dropped"); worker.drain_activity_poller_and_shutdown().await; - // Verify the nexus task was completed with a valid ExecuteCommandsResponse - let response = completed_response - .lock() - .unwrap() - .take() - .expect("Nexus task should have been completed"); - - use temporalio_common::protos::temporal::api::{ - nexus::v1::{response, start_operation_response}, - nexusservices::workerservice::v1::ExecuteCommandsResponse, - worker::v1::worker_command_result, - }; let start_op = match response.variant { Some(response::Variant::StartOperation(s)) => s, other => panic!("Expected StartOperation response, got {:?}", other), diff --git a/crates/sdk-core/src/worker/mod.rs b/crates/sdk-core/src/worker/mod.rs index f443d8b99..46fd616d0 100644 --- a/crates/sdk-core/src/worker/mod.rs +++ b/crates/sdk-core/src/worker/mod.rs @@ -2252,6 +2252,10 @@ where }) } +fn worker_control_task_queue(namespace: &str, grouping_key: &str) -> String { + format!("temporal-sys/worker-commands/{namespace}/{grouping_key}") +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/sdk-core/tests/integ_tests/worker_heartbeat_tests.rs b/crates/sdk-core/tests/integ_tests/worker_heartbeat_tests.rs index 9b40fdf7e..536d610df 100644 --- a/crates/sdk-core/tests/integ_tests/worker_heartbeat_tests.rs +++ b/crates/sdk-core/tests/integ_tests/worker_heartbeat_tests.rs @@ -81,6 +81,7 @@ async fn list_worker_heartbeats(client: &Client, query: impl Into) -> Ve page_size: 200, next_page_token: Vec::new(), query: query.into(), + include_system_workers: false, } .into_request(), ) @@ -232,6 +233,7 @@ async fn docker_worker_heartbeat_basic(#[values("otel", "prom", "no_metrics")] b page_size: 100, next_page_token: Vec::new(), query: String::new(), + include_system_workers: false, } .into_request(), ) @@ -272,6 +274,7 @@ async fn docker_worker_heartbeat_basic(#[values("otel", "prom", "no_metrics")] b page_size: 100, next_page_token: Vec::new(), query: String::new(), + include_system_workers: false, } .into_request(), ) @@ -280,7 +283,7 @@ async fn docker_worker_heartbeat_basic(#[values("otel", "prom", "no_metrics")] b .into_inner(); #[allow(deprecated)] #[allow(deprecated)] - let hb = workers_list + let hb = workers_list .workers_info .iter() .find_map(|wi| { @@ -400,6 +403,7 @@ async fn docker_worker_heartbeat_tuner() { page_size: 100, next_page_token: Vec::new(), query: String::new(), + include_system_workers: false, } .into_request(), ) @@ -1068,6 +1072,7 @@ async fn worker_heartbeat_no_runtime_heartbeat() { page_size: 100, next_page_token: Vec::new(), query: String::new(), + include_system_workers: false, } .into_request(), ) @@ -1138,6 +1143,7 @@ async fn worker_heartbeat_skip_client_worker_set_check() { page_size: 100, next_page_token: Vec::new(), query: String::new(), + include_system_workers: false, } .into_request(), ) diff --git a/crates/sdk/src/workflows.rs b/crates/sdk/src/workflows.rs index 4e290314e..7ddae60d2 100644 --- a/crates/sdk/src/workflows.rs +++ b/crates/sdk/src/workflows.rs @@ -56,8 +56,7 @@ /// /// ```no_run /// use std::time::Duration; -/// use temporalio_sdk::workflows::select; -/// use temporalio_sdk::WorkflowContext; +/// use temporalio_sdk::{WorkflowContext, workflows::select}; /// /// # async fn hidden(ctx: &mut WorkflowContext<()>) { /// select! {