Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/explanations/concurrent-executions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
sidebar_position: 13
title: Concurrent Executions
description: How DBOS detects concurrent executions of the same workflow and converges every observer on a single recorded outcome
---

DBOS guarantees that every workflow runs to completion: if an executor crashes or becomes unreachable, another executor recovers its `PENDING` workflows and re-executes them from their last completed step.

The component responsible for recovery, e.g., [DBOS Conductor](../production/conductor.md), detects unhealthy executors and triggers recovery of its workflows. Sometimes, for example during the rollout of a new application image, that observation can be wrong, and a "zombie" executor could still be running your workflow.

This means the same workflow instance could be running on two executors. (DBOS detects and prevents concurrent executions of the same workflow on the same executor.)

DBOS is designed so that step and workflow invariants are preserved during these situations: steps get at-least-once guarantees and workflow outcomes are persisted exactly-once.

A concurrent execution is detected at two points: when an execution checkpoints a completed step, and when it records the workflow's terminal outcome.
In both cases, DBOS detects whether a conflicting checkpoint already exists for the step/workflow, and if so, **parks** the execution, _i.e._, it waits for the workflow's recorded outcome to become visible in the database, then delivers that recorded outcome through its own handle.

When DBOS detects a concurrent execution at the step boundary, it throws an exception (`DBOSWorkflowConflictIDError` in Python, `DBOSWorkflowConflictError` in TypeScript, `DBOSWorkflowExecutionConflictException` in Java) or returns an error ([`ErrConflictingWorkflowID`](../golang/reference/workflows-steps.md#errors) in Go). Do not ignore that error: no subsequent work in the workflow will be made durable.

Further, in the case of checkpointing the outcome of a workflow, if DBOS finds that the workflow status is not `PENDING`, DBOS decides that the current execution doesn't own the outcome anymore, and parks.
2 changes: 1 addition & 1 deletion docs/golang/reference/workflows-steps.md
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,7 @@ Classify with `errors.Is`, not the `Code` field of the outermost error: DBOS wra

| Code | Sentinel | In a workflow | Meaning |
|---|---|---|---|
| `ErrorCodeConflictingID` | `ErrConflictingWorkflowID` | Return | A concurrent execution of the same workflow recorded a conflicting step checkpoint, or an operation reused a workflow ID already in use. DBOS handles this error at the workflow level: returning it parks your execution until the winning one settles, and it adopts that outcome. Swallowing it makes the two executions race step by step. See [Concurrent Execution Conflicts](../tutorials/step-tutorial.md#concurrent-execution-conflicts). |
| `ErrorCodeConflictingID` | `ErrConflictingWorkflowID` | Return | A concurrent execution of the same workflow recorded a conflicting step checkpoint, or an operation reused a workflow ID already in use. DBOS handles this error at the workflow level: returning it parks your execution until the winning one settles, and it adopts that outcome. Swallowing it makes the two executions race step by step. See [Concurrent Executions](../../explanations/concurrent-executions.md). |
| `ErrorCodeInitialization` | — | — | The DBOS context could not be initialized (invalid configuration, system database unreachable, or migrations failed). |
| `ErrorCodeNonExistentWorkflow` | `ErrNonExistentWorkflow` | Handle | The referenced workflow does not exist (e.g., `RetrieveWorkflow` or a management method with an unknown ID). |
| `ErrorCodeUnexpectedWorkflow` | `ErrUnexpectedWorkflow` | Return | A workflow ID was reused by a different workflow function or on a different queue, indicating non-determinism or conflicting ID reuse. Continuing would write your checkpoints into another workflow's history. |
Expand Down
6 changes: 6 additions & 0 deletions docs/golang/tutorials/workflow-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ A step interrupted this way returns an error matching `dbos.ErrWorkflowCancelled
The interrupted step is deliberately **not** checkpointed, so if the workflow is later resumed, that step re-executes.
(An API `CancelWorkflow`, by contrast, does not cancel the running execution's `Context`, and its cancellation errors carry no standard-library cause.)

:::note
Cancelling the context durably cancels the workflow: DBOS immediately marks it `CANCELLED` in the database, exactly as [`CancelWorkflow`](#cancelling-workflows) would.
The cancellation is then enforced at the step boundary: a step that does not watch `ctx.Done()` keeps running, but when it returns, its result — success or error — is discarded rather than checkpointed, and the step call reports the cancellation instead.
Handle cooperative cancellation in long-running steps and return early: any work done after the context is cancelled only computes a result DBOS will throw away.
:::

A durable [`Sleep`](../reference/methods.md#sleep) wakes immediately when the workflow's context is cancelled.
An API `CancelWorkflow` does not wake an in-progress sleep: the workflow sleeps out the remaining time and stops at its next durable operation.

Expand Down
Loading