Repository navigation
Fix #21: start the fetch lease when the instance is claimed; run the queue-race validations - #35
Merged
Affan Dar (affandar) merged 4 commits intoOct 12, 2026
Conversation
fetch_orchestration_item set the lease to p_now_ms + p_lock_timeout_ms. The caller reads p_now_ms before the call, before any connection-pool wait and before the advisory-lock wait inside the call. When those waits were longer than the lock timeout, the item was returned with an already-expired lease, so every ack, including the max_attempts poison ack, failed with 'Invalid lock token' and the message was retried forever (microsoft/duroxide#46). Migration 0025 redefines the procedure as in 0019 with one change: the lease is GREATEST(p_now_ms + time spent in the call, clock_timestamp()) + p_lock_timeout_ms, computed when the instance is claimed. The first term covers waits inside the call, the second a p_now_ms that was already stale on arrival. Locking, candidate selection and predicates are unchanged. tests/fetch_lease_regression_tests.rs reproduces both cases: a caller timestamp that is 10 s stale, and 16 dispatchers on a hot instance at max_attempts whose poison ack must commit. Fixes #21 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Affan Dar <1480118+affandar@users.noreply.github.com>
Wraps the new duroxide provider validations (race replay, continue-as-new transition delivery for both version stamps, duplicate starts, hidden-start attempt preservation, delayed-abandon locked rows) so PostgreSQL runs them. Builds against the duroxide queue-race fixes branch until they are released. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Affan Dar <1480118+affandar@users.noreply.github.com>
This was referenced Oct 11, 2026
Point the temporary [patch.crates-io] entry at the v0.1.32 tag instead of the queue-race-fixes branch, and require duroxide 0.1.32 in both manifests. CI built an older commit of that branch, which lacks race_replay::test_continue_as_new_poisoned_successor_is_own_execution. Remove the patch once duroxide 0.1.32 is on crates.io.
duroxide 0.1.32 is published, so remove the temporary [patch.crates-io] entry that built against the v0.1.32 tag. Record the duroxide 0.1.30 -> 0.1.32 bump in the changelog.
Affan Dar (affandar)
marked this pull request as ready for review
October 12, 2026 12:20
Affan Dar (affandar)
approved these changes
Oct 12, 2026
Affan Dar (affandar)
added a commit
to microsoft/duroxide-node
that referenced
this pull request
Oct 12, 2026
* Update duroxide to 0.1.31 (race fixes) duroxide 0.1.31 fixes dropped queue reads and signal waits, the continue-as-new carry of unread queue messages, inputs that arrive between two executions, duplicate starts, and the KV key order used by prune. The SDK compiles unchanged: it does not use TurnResult or any other changed public item. - Require duroxide 0.1.31. - Temporarily patch duroxide and duroxide-pg to their unreleased fix branches (microsoft/duroxide#67, microsoft/duroxide-pg#35). Remove the patch and pin the released crates before release. - Document the new contract: a read that loses a race takes no message, continueAsNew carries exactly the unread messages, getKvAllKeys returns keys in key order. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Affan Dar <1480118+affandar@users.noreply.github.com> * test: race dequeueEvent against a timer; KV key order Add regression tests for the duroxide 0.1.31 fixes, ported from the Node reproduction of the queue/timer race: - A drain loop races ctx.dequeueEvent() against a 20 ms timer while 300 numbered messages arrive with 0-30 ms gaps. Every message must arrive once and in order, and the timer must win at least once. - The same race with a 10 ms timer, where every timer win continues as new. Every message must still arrive once and in order, so a dropped read must not count as a consumer of the carried messages. - Eight KV keys set out of order are listed in key order and pruned, and the next turn replays the writes and the prune. Before microsoft/duroxide#57 was fixed, both used hash order, which differs between replays, so the replay failed with "nondeterministic: kv clear mismatch". Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Affan Dar <1480118+affandar@users.noreply.github.com> * Update duroxide to 0.1.32 and duroxide-pg to 0.1.36 Both are on crates.io, so remove the temporary [patch.crates-io] block that built against their fix branches. 0.1.31 was reserved and never published. Changelog: add the duroxide-pg lock fix and the shutdown change. Docs: Runtime.shutdown now returns as soon as the runtime stops; the timeout is a grace period, not a fixed delay. --------- Co-authored-by: Waldemort Agent <waldemort@noreply.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Affan Dar <1480118+affandar@users.noreply.github.com> Co-authored-by: Affan Dar <affandar@hotmail.com>
Affan Dar (affandar)
added a commit
to microsoft/duroxide-python
that referenced
this pull request
Oct 12, 2026
* Update duroxide to 0.1.31 (race fixes) duroxide 0.1.31 fixes dropped queue reads and signal waits, the continue-as-new carry of unread queue messages, inputs that arrive between two executions, duplicate starts, and the KV key order used by prune. The SDK compiles unchanged: it does not use TurnResult or any other changed public item. - Require duroxide 0.1.31. - Temporarily patch duroxide and duroxide-pg to their unreleased fix branches (microsoft/duroxide#67, microsoft/duroxide-pg#35). Remove the patch and pin the released crates before release. - Document the new contract: a read that loses a race takes no message, continue_as_new carries exactly the unread messages, get_kv_all_keys returns keys in key order. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Affan Dar <1480118+affandar@users.noreply.github.com> * test: race dequeue_event against a timer; KV key order Add regression tests for the duroxide 0.1.31 fixes, the same as in duroxide-node, ported from the Node reproduction of the queue/timer race: - A drain loop races ctx.dequeue_event() against a 20 ms timer while 300 numbered messages arrive with 0-30 ms gaps. Every message must arrive once and in order, and the timer must win at least once. - The same race with a 10 ms timer, where every timer win continues as new. Every message must still arrive once and in order, so a dropped read must not count as a consumer of the carried messages. - Eight KV keys set out of order are listed in key order and pruned, and the next turn replays the writes and the prune. Before microsoft/duroxide#57 was fixed, both used hash order, which differs between replays, so the replay failed with "nondeterministic: kv clear mismatch". Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Affan Dar <1480118+affandar@users.noreply.github.com> * Update duroxide to 0.1.32 and duroxide-pg to 0.1.36 Both are on crates.io, so remove the temporary [patch.crates-io] block that built against their fix branches. 0.1.31 was reserved and never published. Changelog: add the duroxide-pg lock fix and the shutdown change. Docs: Runtime.shutdown now returns as soon as the runtime stops; the timeout is a grace period, not a fixed delay. --------- Co-authored-by: Waldemort Agent <waldemort@noreply.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Affan Dar <1480118+affandar@users.noreply.github.com> Co-authored-by: Affan Dar <affandar@hotmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Opened on behalf of Affan Dar (@affandar).
Fixes #21. This is the provider side of microsoft/duroxide#46 (see also microsoft/duroxide#47).
Requires duroxide 0.1.32, the release that contains microsoft/duroxide#67. It is on crates.io, and both manifests use it from there. There is no
[patch]entry.The SDK drafts microsoft/duroxide-node#24 and microsoft/duroxide-python#26 build against this branch. duroxide-node built from it also passed the end-to-end PilotSwarm check described in microsoft/duroxide#67.
1.
fetch_orchestration_itemnever returns an already-expired lease (migration0025)Problem
The lease was
p_now_ms + p_lock_timeout_ms. The caller readsp_now_msbefore the call: before taking a pooled connection, and before the advisory-lock wait inside the call. When those waits were longer than the lock timeout, the item was returned with a lease that had already expired. Every ack then failed withInvalid lock token. That includes the poison ack atmax_attempts, so the message was retried at the lock-timeout interval for as long as the contention lasted.Change
Migration
0025redefinesfetch_orchestration_itemexactly as in0019, with one change. The lease is computed when the instance is claimed:ack_orchestration_itemchecks the lease against the caller's clock.p_now_msthat was already stale when the call started.GREATESTcan only make the lease longer. When the database clock is ahead of the worker's, a crashed worker's item is recovered later by that skew.Nothing else changes. The locking, the candidate selection, the re-check and the predicates stay as in
0019.migrations/0025_diff.mdwas generated withscripts/generate_migration_diff.sh 25.Making the claim non-blocking, bounding the candidate loop and refreshing the caller clock in the predicates are performance improvements. They are tracked in #34.
Compatibility
CREATE OR REPLACEkeeps the function's OID and grants.0022and0024are used by open pull requests. The runner applies every pending embedded version in order, so the gap is harmless.0024also redefines this function, and the runner would apply it after0025.0025refuses a database that has it.Tests
tests/fetch_lease_regression_tests.rsreproduces both cases from #21:main0025stale_caller_timestamp_never_yields_an_expired_lease:p_now_ms10 s stale on arrival, both procedure brancheshot_instance_contention_never_yields_an_expired_lease_and_poison_commits: 16 dispatchers wait 4 s behind a session holding the instance (lock timeout 2 s); the message is atmax_attemptsInvalid lock token2. Run the queue-race provider validations on PostgreSQL
Wraps the provider validations added by microsoft/duroxide#67, so that PostgreSQL runs them. They cover race replay, continue-as-new transition delivery for both version stamps, a poisoned continue-as-new successor, duplicate starts, hidden-start attempt preservation, and the delayed-abandon locked-row contract. PostgreSQL already met them, so no provider code changes.
Test results
PostgreSQL 15.19 in a disposable container, Rust 1.97.1. The first two rows were run against duroxide 0.1.32 from crates.io. The others were run earlier, against microsoft/duroxide#67.
cargo nextest run --all-featurescargo packagecargo test --workspace-- --ignored, live Entra test excluded)0025(schemas at migration0023)0025fetch_orchestration_itemhas the new lease and the unchanged advisory lockcargo fmt --checkcargo clippy --all-targets --all-features -- -D warningsmainCommits
cd974bb80ffb5a8df07b9f15f9c6