Skip to content

Commit schedule changes with their checkpoint, and share the step helper - #658

Draft
devhawk wants to merge 9 commits into
transactional-fork-rewind-delayfrom
transactional-schedules-and-messages
Draft

devhawk wants to merge 9 commits into
transactional-fork-rewind-delayfrom
transactional-schedules-and-messages

Conversation

@devhawk

@devhawk devhawk commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator

Stacked on the fork, rewind and delay PR. This finishes moving DBOS's steps onto runTransactionalStep.

Schedules

createSchedule, pauseSchedule, resumeSchedule and deleteSchedule now commit with their checkpoint. A refusal is recorded as the step's error, so a create refused because the name is taken replays that refusal even once the holder is gone.

Operations that were already atomic

Send, setEvent, writeStream and closeStream already committed their effect with their checkpoint. So did the recv claim, the debounce bounce and the stranded-debouncer takeover. Each did it with its own copy of the check-run-record transaction. They now share the helper, and gain from it:

  • Failures are recorded. A send refused because its destination does not exist replays that refusal even once the destination is created.
  • A lost commit acknowledgement is handled. The retry replays the checkpoint instead of running the work again.
  • The transaction runs at READ COMMITTED.

Some details:

  • runRecordedStep is the variant for work that says what its checkpoint holds. The recv claim records the message as it was stored, and the takeover names the workflow it created as the step's child, both as before.
  • recv is split into its wait and its claim. Only the claim is the step's transaction and only it is retried, now on connection failures as well as conflicts. It re-checks the step inside that transaction.
  • Older checkpoints still replay. These operations wrote checkpoints with neither output nor error, and those now replay as a null output instead of being rejected, so steps recorded by earlier releases replay unchanged.
  • An empty send still records nothing.

Testing

New tests:

  • ScheduleCheckpointTest: a refused create replays its refusal.
  • InternalStepTransactionTest: a send to a missing workflow replays its refusal, and a lost commit acknowledgement replays the claimed message.

Targeted runs:

  • Messaging, events, streams, fork and rewind: 181 of 182 passed. The failure was SendBulkTest.testSendBulkEmpty, fixed by keeping an empty send unrecorded; after the fix, SendBulkTest and InternalStepTransactionTest pass, 18 of 18.
  • Debouncer, recv and messaging: 262 of 262.
  • Schedules: 147 of 147.

spotlessCheck, compileTestJava and javadoc pass.

🤖 Generated with Claude Code

createSchedule, pauseSchedule, resumeSchedule and deleteSchedule now
run through runTransactionalStep, so each commits its effect and its
step checkpoint in one transaction under the step name it already used,
and a refusal is recorded as the step's error. A create refused because
the name is taken therefore replays that refusal even once the holder
is gone, rather than creating the schedule on recovery. Called from
inside a step, each commits only while the caller still owns its
workflow; from a client or the Conductor, it runs in a plain
transaction.

The DAO methods take the caller's connection instead of opening their
own.
…elper

send, sendBulk, setEvent, writeStream and closeStream already committed
their effect and their checkpoint together, each with its own copy of
the check-run-record transaction. They now share runTransactionalStep,
and so gain what it adds: a failure is recorded as the step's error, so
a send refused because its destination does not exist replays that
refusal even once the destination is created; a retry after a lost
commit acknowledgement replays the checkpoint; and the transaction runs
at READ COMMITTED.

A checkpoint with neither output nor error, which is what these wrote,
now replays as a null output rather than being rejected, so steps
recorded by earlier releases replay unchanged. An empty send still
records nothing, as before.

Their DAO methods are reduced to the write on the caller's connection.
The recv claim, the debounce bounce and the stranded-debouncer takeover
each had their own copy of the check-run-record transaction. They now
share the helper, and so gain failure recording, a replay of the
checkpoint after a lost commit acknowledgement, and READ COMMITTED.

runRecordedStep is the helper for work that says what its checkpoint
holds; runTransactionalStep is now the case that serializes the value
the work returns. The recv claim records the message as it was stored,
and the takeover names the workflow it created as the step's child, as
before.

recv is split into its wait and its claim. Only the claim is the step's
transaction and only the claim is retried, now on connection failures
as well as conflicts, and it re-checks the step inside that
transaction. A timed-out recv records null through the helper too.
@devhawk
devhawk added this pull request to stack #659 October 30, 2026 17:34
@devhawk
devhawk marked this pull request as draft October 30, 2026 17:39
… and a takeover collision as nothing taken over

send, sendBulk, setEvent, writeStream and closeStream record neither an
output nor a serialization again, as they did before they ran through
the step helper, instead of a serialized null tagged with this
application's serializer, which other readers of the step would have to
decode for a step that returns nothing. runRecordedStep takes the
step's start time, and a recv records the start of its wait, so a recv
that waited shows the wait in its step's duration again.

A stranded-debouncer takeover whose create collides with whatever else
holds the key now rolls back to a savepoint taken before the takeover,
undoing the cancel with it, and records that it took nothing over. The
caller already treated the collision as exactly that; recording it as
an error made a replay depend on the serializer restoring the
exception's class.

The recv javadoc no longer claims a retried wait restarts its timeout,
whose deadline is durable, and debounceDelayedWorkflow's javadoc no
longer describes a caller it does not take.

New tests: a send's row replays with no output, a timed-out recv's step
spans its wait, a schedule create that fails before commit leaves
neither schedule nor checkpoint, and a colliding takeover inside a
workflow records no error and replays as null.
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