Skip to content

Test that a receiver recovered mid-stream gets every message once, in order - #636

Merged
devhawk merged 3 commits into
mainfrom
recv-across-recovery-test
Oct 21, 2026
Merged

devhawk merged 3 commits into
mainfrom
recv-across-recovery-test

Conversation

@devhawk

@devhawk devhawk commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

This PR adds tests only; it doesn't change the SDK.

RecvAcrossRecoveryTest checks that a long-running receiver gets every message exactly once, in order, while it is recovered onto a new executor again and again. On DBOS Cloud, Conductor recovers a dead executor's workflows by calling recoverPendingWorkflows, and that's the call these tests make.

  • aReceiverRecoveredRepeatedlyGetsEveryMessageInOrder
    • A workflow receives 500 messages in order and fails on anything else. A DBOSClient sends them about 20 ms apart, with no idempotency keys.
    • Repeatedly, a new executor recovers all the executors it replaced. After each hand-off the test waits until a new execution has claimed the receiver and received at least one more message while messages are still arriving, and it requires at least five such mid-stream resumptions (in practice about 14 on Postgres and 21 on CockroachDB).
    • close: each executor is closed before the next one recovers it, as in a redeploy. Some messages arrive while nothing is running the receiver.
    • abandon: each executor keeps running through the hand-off, as on a host wrongly presumed dead. Its execution of the receiver is refused at its next write.
  • aRetriedSendIsDeliveredTwiceWithoutAnIdempotencyKey
    • This is the one way a message does arrive twice: a send retried after the first attempt went through.
    • Without an idempotency key, the receiver gets the message twice and fails with Unexpected message 0, expected 1.
    • With the same key on both attempts, it gets the message once.

Both tests pass on Postgres and on CockroachDB.

These came out of an intermittent DBOS Cloud chaos-test failure (dbos-inc/dbos-cloud#1392), where a Java receiver ends in ERROR. The tests show the SDK delivers correctly across recovery. That points to the chaos test retrying its non-idempotent send.

🤖 Generated with Claude Code

… order

A workflow receives 300 messages in order while a client sends them, without
idempotency keys, and it is recovered onto a new executor every 250 ms
through recoverPendingWorkflows, the call Conductor's recovery makes. In one
variant each executor is closed before the next recovers it, as a redeploy
does, so some messages arrive while nothing runs the receiver; in the other
it is left running through the hand-off and is refused at its next write.
Either way the receiver gets every message exactly once.

A second test shows the one way a message does arrive twice: a send retried
after the first attempt went through, without an idempotency key. With the
same key on both attempts, the message is delivered once.
@devhawk
devhawk requested review from kraftp, maxdml and qianl15 and a balanced review from Copilot October 21, 2026 16:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The recovery test can pass without verifying repeated mid-stream receiver resumptions.

1 open issue
What changed in this PR

Adds notification tests for receiver recovery and send idempotency without changing the SDK.

Changes:

  • Exercises ordered delivery during repeated executor replacement using close and abandon scenarios.
  • Compares retried sends with and without idempotency keys.
File Description
transact/​src/​test/​java/​dev/​dbos/​transact/​notifications/​RecvAcrossRecoveryTest.java Adds recovery and duplicate-send tests.

Review effort: Balanced

A recovered workflow waits for a queue poll, about a second, before anything
runs it again, so with a hand-off every 250 ms most new executors were
replaced before they claimed the receiver, which could sit ENQUEUED until
the sending ended and still pass. Each hand-off now waits for a new
execution to claim the receiver (its start time changes) and receive another
message while messages are still arriving, and the test asserts at least
five such resumptions. 1000 messages keep the sender going long enough.
On CockroachDB the receiver was still working through its backlog when the
fixed 60 s wait after the sending ran out, and each variant took about 90 s
of the 2-minute test timeout. The final wait now lasts while the receiver
keeps receiving and gives up after 30 s without progress, and 500 messages
sent 20 ms apart still keep the sender going through several resumptions:
about 17 s per variant on Postgres and 50 s on CockroachDB.
@devhawk

devhawk commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator Author

merging w/o review since it's only test changes

@devhawk
devhawk merged commit 87967dd into main Oct 21, 2026
12 checks passed
@devhawk
devhawk deleted the recv-across-recovery-test branch October 21, 2026 18:42
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.

2 participants