Skip to content

Rebuild a buffered local queue whose receiving block faulted (#5358) - #5359

Closed
erdtsieck wants to merge 1 commit into
JasperFx:mainfrom
erdtsieck:agent-lane-fault
Closed

erdtsieck wants to merge 1 commit into
JasperFx:mainfrom
erdtsieck:agent-lane-fault

Conversation

@erdtsieck

Copy link
Copy Markdown
Contributor

Fixes #5358

Problem

A buffered local queue whose Block<Envelope> faulted terminally (#506) rejected every later message until the process restarted. Listener-backed receivers are rebuilt by ListeningAgent / BackPressureAgent when HasFaulted is set, but nothing owns a BufferedLocalQueue that way. On the agents queue this left a node heartbeating and getting agents assigned while it could not run one agent command.

Fix

  • BufferedReceiver builds its receiving block in buildReceivingBlock(), so it can build it again.
  • When RebuildsFaultedBlock is true and the block has faulted, the next Enqueue / EnqueueAsync swaps in a new block (under a lock) and clears HasFaulted. Only BufferedLocalQueue opts in; receivers behind a listener keep being rebuilt whole by their ListeningAgent.
  • onBlockError ignores a terminal fault from a block that was already replaced, so it cannot mark the new block as faulted. For the partitioned case the check uses the outer block, as before.

Items still buffered in the faulted block are lost, like in a crash; the queue is in-memory.

DurableLocalQueue has the same gap and is not part of this PR (see the issue).

Tests

CoreTests.Transports.Local.faulted_buffered_local_queue faults the real block (the same path its consumer loop takes when an exception escapes it) and checks that a message published afterwards is handled, and that the agents queue takes envelopes again. Both fail on main with the exception from the issue. CoreTests.Runtime.WorkerQueues*, CoreTests.Runtime.Partitioning*, CoreTests.Transports.Local* and BackPressureAgentTests pass on net9.0 and net10.0.

https://claude.ai/code/session_0176D1FySkqxczVGAYmFjNK7

A buffered local queue has no ListeningAgent, so once its block faulted
terminally (JasperFx#506) every later message was rejected until the
process restarted. On the "agents" queue this left a node heartbeating
while it could not run any agent command.

The queue now replaces a faulted block on the next enqueue.

Claude-Session: https://claude.ai/code/session_0176D1FySkqxczVGAYmFjNK7
Copilot AI balanced review requested due to automatic review settings November 5, 2026 08:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Block replacement races can lose buffered messages, leave handlers running after shutdown, and violate per-group execution guarantees.

4 open issues
What changed in this PR

Addresses #5358 by allowing buffered local queues, including the agents queue, to rebuild a terminally faulted receiving block instead of rejecting messages until restart.

Changes:

  • Extracts receiving-block construction and adds opt-in recovery on the next enqueue.
  • Enables recovery for buffered local queues while retaining listener-owned recovery behavior.
  • Adds regression tests for message handling and agents queue recovery.
File Description
src/​Wolverine/​Transports/​Local/​BufferedLocalQueue.cs Enables automatic replacement of faulted receiving blocks.
src/​Wolverine/​Runtime/​WorkerQueues/​BufferedReceiver.cs Adds block reconstruction, replacement locking, and stale-fault detection.
src/​Testing/​CoreTests/​Transports/​Local/​faulted_buffered_local_queue.cs Tests recovery after terminal faults in application and agents queues.

Review effort: Balanced

Comment on lines +139 to +143
if (!HasFaulted || _drained) return _receivingBlock;

// Whatever the faulted block still held is lost, as it would be in a crash; the queue is in-memory.
var faulted = _receivingBlock;
_receivingBlock = buildReceivingBlock();
Comment on lines +143 to +146
_receivingBlock = buildReceivingBlock();
HasFaulted = false;

_ = faulted.DisposeAsync().AsTask().ContinueWith(_ => { }, TaskScheduler.Default);
Comment on lines +163 to 165
if (!ReferenceEquals(block, _receivingBlock)) return;

HasFaulted = true;

_ = faulted.DisposeAsync().AsTask().ContinueWith(_ => { }, TaskScheduler.Default);

_logger.LogWarning("The faulted local worker queue for {Uri} was replaced with a new one", Uri);
jeremydmiller added a commit that referenced this pull request Nov 5, 2026
…queues

GH-5358 Rebuild buffered and durable local queues whose receiving block faulted (supersedes #5359)
@jeremydmiller

Copy link
Copy Markdown
Member

Thank you, Anne. This is superseded by #5382, which is now merged. Your commit went in unchanged, so you're its author, and you're co-author on the follow-up commit. The follow-up added:

Closing this one in favor of #5382.

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.

A faulted buffered local queue is never rebuilt; a node with a faulted 'agents' queue keeps heartbeating but starts no agents

3 participants