Skip to content

fix(backend): break outer loop on client write failure for accesses/TPS - #116

Open
ayushsingh82 wants to merge 1 commit into
monad-developers:mainfrom
ayushsingh82:fix/client-write-task-outer-break
Open

fix(backend): break outer loop on client write failure for accesses/TPS#116
ayushsingh82 wants to merge 1 commit into
monad-developers:mainfrom
ayushsingh82:fix/client-write-task-outer-break

Conversation

@ayushsingh82

@ayushsingh82 ayushsingh82 commented Aug 8, 2026

Copy link
Copy Markdown

Summary

client_write_task in backend/src/lib/server.rs sends three kinds of buffered messages to a WebSocket client each iteration: events_buf, accesses_buf, and tps_buf. When send_message() fails for events_buf, the code correctly breaks the outer loop, terminating the task for that (now-dead) connection.

But for accesses_buf and tps_buf, the send happens inside a for loop over the buffered items, and the break on failure only exits that inner for loop — not the outer task loop:

if !accesses_buf.is_empty() {
    for accesses in std::mem::take(&mut accesses_buf) {
        let server_msg = ServerMessage::TopAccesses(accesses);
        if let Err(e) = send_message(&mut ws_sender, server_msg).await {
            error!("Failed to send accesses to {}: {}", addr, e);
            break; // only exits this `for`, outer `loop` keeps going
        }
    }
}

A send_message failure here means the client's WebSocket write is broken (e.g. disconnected). Once that happens, the task should exit — same intent as the events_buf path right above it — but instead it falls through and keeps looping, repeatedly trying (and failing) to serve a dead connection instead of terminating.

Changes

  • Label the outer loop as 'outer and change the two nested breaks (in the accesses_buf and tps_buf send loops) to break 'outer, so a write failure on any buffer terminates the task consistently.
  • No other behavior changes; the unlabeled breaks elsewhere in the function (broadcast receiver error, events send failure) are unaffected since they weren't nested in a for loop to begin with.

Test plan

  • Reproduced the exact control-flow bug in an isolated Rust snippet (bare break inside a for inside a loop only exits the for) and confirmed the labeled-loop fix resolves it.
  • cargo fmt --check passes on the crate.
  • cargo check / cargo clippy / cargo test — could not run locally on macOS; monad-event-ring's build script requires cmake and Linux hugetlbfs headers not available in this environment (same constraint noted in fix(backend): prevent panic when txn_idx exceeds fixed buffer size #115). Deferred to CI.

Greptile Summary

This PR corrects WebSocket writer termination after TopAccesses or TPS delivery fails.

  • Labels the outer client-write loop.
  • Exits that loop directly when either nested send loop encounters an error.
  • Makes accesses and TPS failure handling consistent with the existing terminal behavior for event-send failures.

Confidence Score: 5/5

The PR appears safe to merge because the changed breaks now terminate work for a connection whose WebSocket writer has already failed.

The labeled breaks correct the nested-loop control flow without changing successful delivery behavior or introducing a reachable lifecycle failure.

Important Files Changed

Filename Overview
backend/src/lib/server.rs Correctly changes nested-loop breaks to terminate the client writer after a failed WebSocket write; no actionable regression was identified.

Reviews (1): Last reviewed commit: "fix(backend): break outer loop on client..." | Re-trigger Greptile

Motivation:

In client_write_task, a failed send_message() for the accesses_buf or
tps_buf messages only broke the inner `for` loop over the buffered
items, not the outer task loop. This is inconsistent with the
events_buf send right above it, which correctly breaks the outer loop
on failure.

A send failure here means the client is gone (write error on the
WebSocket, e.g. broken pipe after disconnect). Once that happens, the
task should stop - but with the inner break, it fell through to the
next iteration of the outer loop and kept trying (and failing) to
serve the same dead connection indefinitely instead of exiting, unlike
every other failure path in this function.

Modifications:

Label the outer loop and change the two nested breaks to `break
'outer'` so a write failure on any buffer (events, accesses, or TPS)
terminates the task the same way.

Result:

client_write_task now exits promptly on any write failure, regardless
of which buffer triggered it, matching its behavior for the other
error paths in the same function (broadcast receiver errors, events
send failures).
@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

@ayushsingh82 is attempting to deploy a commit to the MF Flagship Team on Vercel.

A member of the Team first needs to authorize it.

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