Skip to content

Update workflow status with a final result only when status is PENDING - #467

Draft
maxdml wants to merge 4 commits into
mainfrom
update-workflow-outcome-improvements
Draft

Update workflow status with a final result only when status is PENDING#467
maxdml wants to merge 4 commits into
mainfrom
update-workflow-outcome-improvements

Conversation

@maxdml

@maxdml maxdml commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Update the workflow outcome write (updateWorkflowOutcome) to apply only while the workflow_status row is still PENDING.

Also:

  • fix DLQ error handling in await workflow result
  • fix handling of DBOSWorkflowExecutionConflictException (park the workflow)

Port of dbos-inc/dbos-transact-golang#417.

@maxdml
maxdml marked this pull request as draft July 28, 2026 17:08
Comment on lines +1264 to +1269
case MAX_RECOVERY_ATTEMPTS_EXCEEDED -> {
// A workflow is dead-lettered by the attempt that pushes recovery_attempts
// past maxRetries+1, so a dead-lettered row carries maxRetries+2 attempts.
int maxRetries = Math.max(0, rs.getInt("recovery_attempts") - 2);
throw new DBOSMaxRecoveryAttemptsExceededException(workflowId, maxRetries);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctly handle DLQ errors

Comment on lines +1820 to +1827
// Another execution owns this workflow (a concurrent run recorded a step
// checkpoint, or the workflow is already active on this executor). Never
// persist the conflict: park the execution and deliver the recorded outcome
// through this run's own future.
logger.warn(
"Aborting duplicate execution of workflow. Waiting for the recorded outcome. workflowId {}",
workflowId);
return awaitWorkflowResult(workflowId);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now park the workflow when concurrent execution is detected (typically during step checkpointing)

@maxdml
maxdml marked this pull request as ready for review July 28, 2026 22:05
maxdml added 3 commits July 29, 2026 00:05
The terminal-outcome write (updateWorkflowOutcome) now applies only to a
PENDING row and reports whether it landed: a run owns its workflow's
outcome exactly as long as the row says that run is what the workflow is
doing. When the write is refused, the runner parks on awaitWorkflowResult
and delivers the recorded outcome through its own handle instead of its
locally computed result. A missing row surfaces as
DBOSNonExistentWorkflowException.

awaitWorkflowResult now throws DBOSMaxRecoveryAttemptsExceededException
for a dead-lettered row instead of polling forever.
A run that observes its own cancellation now parks on the recorded outcome
instead of rethrowing from its local view: normally the row is CANCELLED and
awaitWorkflowResult throws DBOSAwaitedWorkflowCancelledException (the same
error the handle delivered before), but a concurrent resume may have taken
the workflow back, in which case the recorded outcome is the truth. Safe to
park because checkWorkflow only throws own-cancellation after reading
CANCELLED from the DB, so the row is never left PENDING by this path.
A run that loses the execution race (a concurrent run recorded a step
checkpoint, or the workflow is already active on this executor) now parks on
awaitWorkflowResult inside the task and delivers the recorded outcome through
its own future, instead of completing the future exceptionally and relying on
the handle's getResult conversion. The getResult conversions remain as
backstops for futures produced before the park was in place.
@maxdml
maxdml force-pushed the update-workflow-outcome-improvements branch from 16ace27 to 71f6937 Compare July 28, 2026 22:05
…tcome write

The PENDING-guarded outcome update now does exactly one statement and only
reports whether the write landed. Runs that park on the recorded outcome know
their row must already exist, so awaitWorkflowResult gains an opt-in
failIfMissing that throws DBOSNonExistentWorkflowException on a missing row
instead of polling forever. Tolerant callers (unchecked retrieves, debounced
workflows whose rows appear later) keep the polling default.
@maxdml
maxdml marked this pull request as draft July 29, 2026 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant