new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Temporal · Develop · all subjects

workflows/message-passing

179 notes in this subject, read out of this brain and free to use. This is page 3 of 3.

Workflow does not exist error

When the Workflow does not exist, you receive a `Temporalio::Error::RPCError` exception whose `code` is a `NOT_FOUND` constant defined in `Code`.

Client cannot contact server error

When the Client cannot contact the server, you receive a `Temporalio::Error::RPCError` exception whose `code` is an `UNAVAILABLE` constant defined in `Code` after some retries.

Workflow finished during Update handling error

If the Workflow finishes while an Update handler execution is in progress, you receive a `Temporalio::Error::RPCError` exception with message 'workflow execution already completed'. This happens if the Workflow was canceled or failed, completed normally, or continued-as-new and the Workflow author did not wait for handlers to be finished.

Obtain Update information in handler

Use `Temporalio::Workflow.current_update_info` to obtain information about the current Update. This includes the Update ID, which can be useful for deduplication when using Continue-As-New.

Async Signal and Update handlers

Signal and Update handlers can be defined as async fn, allowing them to use await with Activities, Child Workflows, Timers, and other async operations. Handler executions and the main Workflow method run concurrently with switching at await calls. Async handlers require careful design to avoid concurrent state mutation issues.

Ensure handlers complete before Workflow finishes

When using async Signal or Update handlers, the main Workflow method can return or continue-as-new while a handler is still waiting on an async task. Use ctx.wait_condition() in the main Workflow to ensure all handlers finish before the Workflow completes, preventing interruption of crucial work and client errors when retrieving Update results.

Message handler serialization requirements

Parameters and return values of message handlers and the main Workflow function must be serializable. Prefer structs to multiple input parameters to allow for forward-compatible changes.

Update validator method signature

Update validators are defined as methods that take &self and &WorkflowContextView as parameters, accept input by reference, and return Result<(), Box<dyn std::error::Error + Send + Sync>>. They use the #[update_validator(handler_name)] macro attribute to associate with their update handler.

Signal-With-Start pattern

Signal-With-Start allows sending a Signal as part of starting a Workflow. Use WorkflowStartOptions with the start_signal() method passing a WorkflowStartSignal containing the signal name and input payloads. This ensures the signal is received when the Workflow starts.

Send Update from client with execute_update

Updates are sent from a client using wf_handle.execute_update() with the update method reference, input parameters, and WorkflowExecuteUpdateOptions. This call is synchronous and waits for the Update to complete before returning the result.

Start Update from client with start_update

Updates can be started with wf_handle.start_update() which returns an UpdateHandle as soon as the Update is accepted, without waiting for completion. This is useful for async Update handlers that perform long-running asynchronous operations. Use the UpdateHandle later to fetch results.

Wait conditions in handlers

Use ctx.wait_condition() to block async Signal or Update handlers until certain conditions are true. You specify the condition by passing a function that returns a boolean and can optionally set a timeout. Common use cases include waiting for a Signal or Update to arrive, waiting in a handler until appropriate to continue, and waiting in the main Workflow until active handlers finish.

Update handler method signature

Update handlers are defined as methods that take &mut self and &mut SyncWorkflowContext<Self> as parameters, accept input, and return a serializable value. They use the #[update] macro attribute. Update handlers can be async or synchronous.

Query handler method signature

Query handlers are defined as methods that take &self and &WorkflowContextView as parameters and return a serializable value. They use the #[query] macro attribute. Query handlers cannot perform state mutations or async operations.

Workflow Rust macro attributes for message handlers

Message handlers are defined as methods on the Workflow struct using macro attributes: #[query] for Query handlers, #[signal] for Signal handlers, #[update] for Update handlers, and #[update_validator(handler_name)] for Update validators. Handlers are registered with the Workflow runtime through these attributes.

Signal handler method signature

Signal handlers are defined as methods that take &mut self and &mut SyncWorkflowContext<Self> as parameters. They use the #[signal] macro attribute and do not return values. They can trigger async work depending on SDK capabilities.

Query handlers: synchronous state retrieval

Query handlers retrieve state from a Workflow Execution synchronously. They are defined as methods on the Workflow struct using the #[query] macro attribute. Query handlers cannot mutate Workflow state and cannot perform async operations like executing Activities.

Signal handlers: asynchronous message handlers

Signal handlers are asynchronous messages sent to a running Workflow Execution to change its state and control its flow. They are defined as methods on the Workflow struct using the #[signal] macro attribute. Signal handlers do not return values but can trigger async work such as Activities and timers depending on SDK capabilities.

Update handlers: trackable synchronous requests with results

Update handlers are trackable synchronous requests sent to a running Workflow Execution. They can change Workflow state, control its flow, and return a result. The sender must wait until the Worker accepts or rejects the Update, and may wait further to receive a returned value or exception. Updates are defined using the #[update] macro attribute.

Query requirements and limitations

Queries do not add events to a Workflow's Event History. Queries can be sent to closed Workflow Executions within a Namespace's Workflow retention period, including Workflows that have completed, failed, or timed out. However, querying terminated Workflows is not supported. A Worker must be online and polling the Task Queue to process a Query.

Send Query from client

Queries are sent from a client using wf_handle.query() with the query method reference, input parameters, and WorkflowQueryOptions. The call returns the query result.

Send Signal from client

Signals are sent from a client using wf_handle.signal() with the signal method reference, input parameters, and WorkflowSignalOptions. The call returns when the server accepts the Signal; it does not wait for the Signal to be delivered to the Workflow Execution.

Send Signal from another Workflow

Signals can be sent from a Workflow to another Workflow Execution using ctx.external_workflow(workflow_id, run_id_option).signal(). Signals can only be sent to Workflow Executions that haven't closed.

Entity pattern signal handler setup

In the entity pattern, use setHandler to register a signal handler that pushes incoming update commands into a pendingUpdates array. This allows the workflow to queue updates asynchronously without blocking workflow execution.

Don't call Continue-As-New from Update or Signal handlers

If you use Updates or Signals, do not call Continue-As-New from the handlers. Instead, wait for your handlers to finish in your main Workflow before you run ContinueAsNew.

Racing Signals with Timers Example

Example showing a workflow that waits for user interaction via signal for up to 30 days, then sends a reminder email if no signal was received. ```ts import { defineSignal, sleep, Trigger } from '@temporalio/workflow'; const userInteraction = new Trigger<boolean>(); const completeUserInteraction = defineSignal('completeUserInteraction'); export async function yourWorkflow(userId: string) { setHandler(completeUserInteraction, () => userInteraction.resolve(true)); const userInteracted = await Promise.race([ userInteraction, sleep('30 days'), ]); if (!userInteracted) { await sendReminderEmail(userId); } } ```

Racing Signals with Timers

Use Promise.race() with Signals and Triggers to have a promise resolve at the earlier of either system time or human intervention. This pattern lets a workflow wait for either a signal or a timeout, whichever occurs first.

Send Query from Client

Use WorkflowHandle.query() to send a Query to a Workflow Execution. Example: const supportedLanguages = await handle.query(getLanguages, { includeUnsupported: false }); Sending a Query does not add events to a Workflow's Event History.

Signal handler basics

A Signal is an asynchronous message sent to a running Workflow Execution to change its state and control its flow. Signal handlers are defined using wf.defineSignal() and wf.setHandler(). A Signal handler cannot return a value; the response is sent immediately from the server without waiting for the Workflow to process the Signal. Signal handlers can be async, allowing you to use Activities, Child Workflows, durable workflow.sleep() Timers, and workflow.condition() conditions. You can send Signals to a Workflow Execution from a Temporal Client or from another Workflow Execution, but only to Workflow Executions that haven't closed. When a Signal is sent, a WorkflowExecutionSignaled Event appears in the Workflow's Event History.

Query handler basics

A Query is a synchronous operation that retrieves state from a Workflow Execution. Query handlers are defined using wf.defineQuery() and wf.setHandler(). A Query handler cannot be async, cannot perform async operations like executing an Activity, and must not mutate Workflow state. A Query handler must return a value. Sending a Query does not add events to a Workflow's Event History. You can send Queries to closed Workflow Executions within a Namespace's Workflow retention period, including Workflows that have completed, failed, or timed out, but not terminated Workflows. A Worker must be online and polling the Task Queue to process a Query.

Send Update from Client - startUpdate

Use WorkflowHandle.startUpdate() to send an Update and receive a WorkflowUpdateHandle as soon as the Update is accepted or rejected. Example: const updateHandle = await handle.startUpdate(setLanguage, { args: [Language.ENGLISH], waitForStage: WorkflowUpdateStage.ACCEPTED }); previousLanguage = await updateHandle.result(); This method allows you to receive the handle immediately and fetch results later. Use this approach with async Update handlers that perform long-running asynchronous operations like calling an Activity. startUpdate() only waits until the Worker has accepted or rejected the Update, not until all asynchronous operations are complete.

Send Update from Client - executeUpdate

Use WorkflowHandle.executeUpdate() to send an Update and wait for it to complete. Example: let previousLanguage = await handle.executeUpdate(setLanguage, { args: [Language.CHINESE] }); This call blocks and waits for the Update to complete before returning the result.

Send Signal from Workflow (External Signal)

A Workflow can send a Signal to another Workflow using getExternalWorkflowHandle(). Example: const handle = getExternalWorkflowHandle('workflow-id-123'); await handle.signal(joinSignal, { userId: 'user-1', groupId: 'group-1' }); When an External Signal is sent, a SignalExternalWorkflowExecutionInitiated Event appears in the sender's Event History and a WorkflowExecutionSignaled Event appears in the recipient's Event History. The getExternalWorkflowHandle() method ensures Workflows remain deterministic by recording these interactions as Events in the Workflow's Event History, rather than making direct network calls.

Send Signal from Client

Use WorkflowHandle.signal() to send a Signal to a Workflow Execution. Example: await handle.signal(approve, { name: 'me' }); The call returns when the server accepts the Signal; it does not wait for the Signal to be delivered to the Workflow Execution. The WorkflowExecutionSignaled Event appears in the Workflow's Event History.

Message handler definition patterns in TypeScript

Message types (Queries, Signals, Updates) should be defined as global variables using defineQuery(), defineSignal(), or defineUpdate(). Message handlers are defined by calling workflow.setHandler() inside the Workflow function. The object returned by define*() is used both to set the handler in Workflow code and to send the message in Client code. Parameters and return values of handlers and the main Workflow function must be serializable. Prefer using a single object over multiple input parameters, as a single object allows you to add fields without changing the signature.

Example: Update-With-Start

const startWorkflowOperation = new WithStartWorkflowOperation.create( transactionWorkflow, { workflowId, args: [transactionID], taskQueue: 'early-return', workflowIdConflictPolicy: 'FAIL' } ); const earlyConfirmation = await client.workflow.executeUpdateWithStart( getTransactionConfirmation, { startWorkflowOperation } ); const wfHandle = await startWorkflowOperation.workflowHandle(); const finalReport = await wfHandle.result();

Example: Ensuring handlers finish before Workflow completes

export async function myWorkflow(): Promise<MyWorkflowOutput> { await wf.condition(wf.allHandlersFinished); return workflowOutput; }

Example: Waiting for Signal to arrive

export async function greetingWorkflow(): Promise<string> { let approvedForRelease = false; let approverName: string | undefined; wf.setHandler(approve, (input) => { approvedForRelease = true; approverName = input.name; }); await wf.condition(() => approvedForRelease); }

Example: Dynamic Signal definition

import * as wf from '@temporalio/workflow'; // 'fat handler' solution wf.setHandler(`genericSignal`, (payload) => { switch (payload.taskId) { case taskAId: // do task A things break; case taskBId: // do task B things break; default: throw new Error('Unexpected task.'); } }); // 'inline definition' solution wf.setHandler(wf.defineSignal(`task-${taskAId}`), (payload) => { /* do task A things */ }); wf.setHandler(wf.defineSignal(`task-${taskBId}`), (payload) => { /* do task B things */ }); // utility 'inline definition' helper const inlineSignal = (signalName, handler) => wf.setHandler(wf.defineSignal(signalName), handler); inlineSignal(`task-${taskBId}`, (payload) => { /* do task B things */ });

Example: Static Signal and Query definition

import * as wf from '@temporalio/workflow'; export const unblockSignal = wf.defineSignal('unblock'); export const isBlockedQuery = wf.defineQuery<boolean>('isBlocked'); export async function unblockOrCancel(): Promise<void> { let isBlocked = true; wf.setHandler(unblockSignal, () => void (isBlocked = false)); wf.setHandler(isBlockedQuery, () => isBlocked); wf.log.info('Blocked'); try { await wf.condition(() => !isBlocked); wf.log.info('Unblocked'); } catch (err) { if (err instanceof wf.CancelledFailure) { wf.log.info('Cancelled'); } throw err; } }

Example: Async Update handler with Activity and lock

import { Mutex } from 'async-mutex'; export const setLanguageUsingActivity = wf.defineUpdate<Language, [Language]>('setLanguageUsingActivity'); export async function greetingWorkflow(): Promise<string> { const greetings: Partial<Record<Language, string>> = { [Language.CHINESE]: '你好,世界', [Language.ENGLISH]: 'Hello, world' }; let language = Language.ENGLISH; const lock = new Mutex(); wf.setHandler(setLanguageUsingActivity, async (newLanguage) => { if (!(newLanguage in greetings)) { await lock.runExclusive(async () => { if (!(newLanguage in greetings)) { const greeting = await callGreetingService(newLanguage); if (!greeting) { throw new wf.ApplicationFailure(`${newLanguage} is not supported by the greeting service`); } greetings[newLanguage] = greeting; } }); } const previousLanguage = language; language = newLanguage; return previousLanguage; }); }

Example: Update handler with validator

export const setLanguage = wf.defineUpdate<Language, [Language]>('setLanguage'); export async function greetingWorkflow(): Promise<string> { const greetings: Partial<Record<Language, string>> = { [Language.CHINESE]: '你好,世界', [Language.ENGLISH]: 'Hello, world' }; let language = Language.ENGLISH; wf.setHandler( setLanguage, (newLanguage: Language) => { const previousLanguage = language; language = newLanguage; return previousLanguage; }, { validator: (newLanguage: Language) => { if (!(newLanguage in greetings)) { throw new Error(`${newLanguage} is not supported`); } } } ); }

Example: Signal handler mutating state

export const approve = wf.defineSignal<[ApproveInput]>('approve'); export async function greetingWorkflow(): Promise<string> { let approvedForRelease = false; let approverName: string | undefined; wf.setHandler(approve, (input) => { approvedForRelease = true; approverName = input.name; }); }

Example: Query handler with conditional logic

export enum Language { ARABIC = 'ARABIC', CHINESE = 'CHINESE', ENGLISH = 'ENGLISH', FRENCH = 'FRENCH', HINDI = 'HINDI', PORTUGUESE = 'PORTUGUESE', SPANISH = 'SPANISH' }; interface GetLanguagesInput { includeUnsupported: boolean; }; export const getLanguages = wf.defineQuery<Language[], [GetLanguagesInput]>('getLanguages'); export async function greetingWorkflow(): Promise<string> { const greetings: Partial<Record<Language, string>> = { [Language.CHINESE]: '你好,世界', [Language.ENGLISH]: 'Hello, world' }; wf.setHandler(getLanguages, (input: GetLanguagesInput): Language[] => { if (input.includeUnsupported) { return Object.values(Language); } else { return Object.keys(greetings) as Language[]; } }); }

Message handler options for setHandler

The setHandler() method can take handler options to configure behavior: 1) QueryHandlerOptions: Supports description. 2) SignalHandlerOptions: Supports description and unfinishedPolicy. 3) UpdateHandlerOptions: Supports validator, description, and unfinishedPolicy. The unfinishedPolicy can silence warnings when a Workflow finishes with unfinished handler executions on a per-handler basis.

Update errors and troubleshooting

When sending an Update, the client may encounter these errors: 1) No Workflow Workers polling the Task Queue: The request will be retried indefinitely. 2) Update failed (WorkflowUpdateFailedError): Caused by Update rejection via validator or Update failure after acceptance (like failed Activity or failed Child Workflow). 3) Workflow Task Failure: If not accepted, receive ServiceError with code 9 FAILED_PRECONDITION. If accepted, it's durable; use WorkflowUpdateHandle to fetch result after code deploy. 4) Workflow finished during Update handler execution: Receive ServiceError with code 5 NOT_FOUND, occurring if Workflow was canceled, failed, completed, or continued-as-new without waiting for handlers.

Signal errors and troubleshooting

When sending a Signal, the client may encounter limited errors. The two main errors are: 1) The client can't contact the server: You receive a ServiceError with cause.code of gRPC status code 14 UNAVAILABLE (after retries). 2) The workflow does not exist: You receive a WorkflowNotFoundError error. Unlike Queries and Updates, Signals do not wait for a response from the Worker, so additional handler execution errors don't occur during Signal sending.

Query errors and troubleshooting

When sending a Query, the client may encounter these errors: 1) No Workflow Worker polling the Task Queue: You receive a ServiceError with cause.code of gRPC status code 9 FAILED_PRECONDITION. 2) Query failed: You receive a QueryNotFoundError exception if something goes wrong during a Query. Any error in a Query handler will trigger this error, which differs from Signal and Update requests where errors can lead to Workflow Task Failure instead. 3) The handler caused the Workflow Task to fail: This can happen if the Query handler blocks the thread too long without yielding.

Send messages without type safety

When you cannot import message type objects defined by defineQuery, defineSignal, or defineUpdate (such as when you don't have access to the Workflow Definition or it isn't written in TypeScript), you can use APIs that aren't type-safe by passing message type names as strings instead of message type objects to: client.workflow.start(), WorkflowHandle.query(), WorkflowHandle.signal(), WorkflowHandle.executeUpdate(), WorkflowHandle.startUpdate(). You can also pass Workflow IDs to client.workflow.getHandle() and getExternalWorkflowHandle() to get Workflow handles without type safety.

Getting Workflow handles for sending messages

To send Queries, Signals, or Updates, you need a WorkflowHandle object. Obtain a handle by: 1) Using client.workflow.start() and returning its handle: const handle = await client.workflow.start(greetingWorkflow, { taskQueue: 'my-task-queue', args: [myArg], workflowId: 'my-workflow-id' }); 2) Using client.workflow.getHandle() to retrieve a Workflow handle by its Workflow ID. To obtain an Update handle, use WorkflowHandle.startUpdate() or getUpdateHandle() to fetch a handle for an in-progress Update using the Update ID.

Using locks to prevent concurrent handler execution

When multiple handler instances run simultaneously, concurrent execution can lead to unpredictable behavior and race conditions. Use a lock (mutex) to coordinate access and ensure only one handler instance can execute a specific section of code at any given time. Example using async-mutex: const lock = new Mutex(); wf.setHandler(mySignal, async () => { await lock.runExclusive(async () => { const data = await myActivity(); x = data.x; await workflow.sleep(500); y = data.y; }); }); This ensures that when the event loop switches to a different handler execution or main workflow function, no other execution of this handler can run until the current execution finishes.

Waiting for conditions in handlers

Use workflow.condition() inside handlers to prevent code from proceeding until a condition becomes true. Example: await workflow.condition(() => readyForUpdateToExecute); This is useful for waiting for a Signal or Update to arrive, waiting in a handler until it is appropriate to continue, and waiting in the main Workflow until all active handlers have finished. Handlers can execute before the main Workflow method starts, so you may need to use conditions to ensure prerequisites are met.

Async handlers with Activities

Async Signal and Update handlers can execute Activities by using await. Example: const greeting = await callGreetingService(newLanguage); This allows handlers to call Activities and process their results synchronously within the handler logic. An async Signal handler can execute an Activity, but using an async Update handler is preferred because it allows the client to receive a result or error once the Activity completes, letting your client track the progress of asynchronous work.

Dynamic message types in TypeScript

For flexible use cases, you can define Signals and Queries dynamically. Two approaches exist: 1) Collapse all Signals into one handler and move the ID to the payload using a switch statement. 2) Actually make the Signal name dynamic by inlining the Signal definition per handler using defineSignal() inline with unique names like `task-${taskId}`. This allows for generated IDs and dynamic Signal names.

Signal handling cost optimization

Where feasible, implement deduplication logic client-side or aggregate data into fewer Signals. Use SignalWithStart instead of separate StartWorkflow and SignalWorkflow calls when initiating Workflows with Signals.

Wait for handlers to finish before Workflow completion

When a Workflow uses async Signal or Update handlers, the main Workflow method can return or Continue-as-New while a handler is still waiting on an async task (such as an Activity result), potentially interrupting the handler before it completes crucial work and causing client errors. To ensure all handlers complete before the Workflow finishes: - In Ruby: Use `Temporalio::Workflow.wait_condition { Temporalio::Workflow.all_handlers_finished? }` before the Workflow method returns - In TypeScript/JavaScript: Use `await workflow.condition(workflow.allHandlersFinished);` before returning the workflow output Example (TypeScript): ``` await workflow.condition(workflow.allHandlersFinished); return workflowOutput; ``` By default, the Worker logs a warning when a Workflow finishes with unfinished handler executions. You can silence these warnings on a per-handler basis: - In Ruby: Pass `unfinished_policy: Temporalio::Workflow::HandlerUnfinishedPolicy::ABANDON` to the `workflow_signal` or `workflow_update` class methods - In TypeScript/JavaScript: Set `unfinishedPolicy` in SignalHandlerOptions or UpdateHandlerOptions when calling `workflow.setHandler()`

Update-With-Start API and configuration

Update-With-Start lets you send an Update that checks whether a Workflow with the specified ID already exists. If the Workflow exists, the Update is processed. If the Workflow does not exist, a new Workflow Execution is started with the given ID and the Update is processed before the main Workflow method starts to execute. API Usage: - Use executeUpdateWithStart() to start an Update and wait for the result in one call - Alternatively, use startUpdateWithStart() to start an Update and receive a WorkflowUpdateHandle, then use await updateHandle.result() to retrieve the result - In Java/Go, use Client.UpdateWithStartWorkflow or Client.NewWithStartWorkflowOperation Required Configuration: - Provide a WithStartWorkflowOperation (or StartWorkflowOperation in Java/Go) to define the Workflow that will be started if necessary and its arguments - Set WorkflowIdConflictPolicy to WORKFLOW_ID_CONFLICT_POLICY_USE_EXISTING - Provide UpdateWorkflowOptions with UpdateName and WaitForStage - A WithStartWorkflowOperation can only be used once

No Workers polling Task Queue for Updates: handling indefinite retries

When sending an Update and no Workflow Workers are polling the Task Queue, the SDK Client request will be retried indefinitely. You can use either: - `asyncio.timeout` to impose a timeout, or - a `Cancellation` in your RPC options to cancel the Update Both approaches raise a `WorkflowUpdateRPCTimeoutOrCanceledError` exception.

Signal-With-Start

Signal-With-Start allows a Client to send a Signal to a Workflow Execution, starting the Execution if it is not already running. If a Workflow is running with the given Workflow Id, it will be signaled. If not, a new Workflow will be started and immediately signaled. When using Signal-With-Start, the Signal handler is executed before the Workflow method. Signal-With-Start is limited to Client use and cannot be called from a Workflow. Usage: Call Client.workflow.signalWithStart(). Example: await client.workflow.signalWithStart(yourWorkflow, { workflowId: 'workflow-id-123', taskQueue: 'my-taskqueue', args: [{ foo: 1 }], signal: joinSignal, signalArgs: [{ userId: 'user-1', groupId: 'group-1' }] });

Give your agent this brain