Skip to content

Improves backpressure resilience and prevents protocol starvation#22

Merged
zheylmun merged 9 commits into
mainfrom
feature/slow-consumer-resilience
Jun 30, 2026
Merged

Improves backpressure resilience and prevents protocol starvation#22
zheylmun merged 9 commits into
mainfrom
feature/slow-consumer-resilience

Conversation

@zheylmun

Copy link
Copy Markdown
Owner

Enhances the socket loop's ability to handle slow message consumers without causing connection drops due to protocol timeouts. Previously, a full receive channel would block the entire loop, preventing keepalives and pongs from being sent, leading to server-side disconnects.

Changes:

  • Implements a backpressure-aware inbound delivery mechanism: When the internal receive channel is full, the loop holds the single incoming data frame, ceases reading from the inbound stream (applying TCP backpressure), but continues to service outgoing messages and fire keepalives via a dedicated sub-loop. This ensures protocol liveness, zero data loss, and bounded memory usage.
  • Increases the default internal message channel size from 4 to 256. This larger buffer absorbs transient consumer slowdowns, ensuring the explicit backpressure mechanism only engages under sustained overload.
  • Updates documentation in ConnectOptions and Socketeer to clarify the role of keepalives in maintaining connection liveness during backpressure and to reflect the new default channel size.
  • Aligns the CHANNEL_SIZE default value for SocketeerRx and ReuniteError to 256.
  • Adds a new mock server (backpressure_probe_server) and integration tests to validate that the protocol remains alive and data is delivered in order even when the consumer is backpressured.

This ensures a robust connection that adapts gracefully to fluctuating consumer speeds.

zheylmun and others added 6 commits June 29, 2026 20:11
Approved design for issues #3/#9: deliver inbound frames via try_send + a
backpressure sub-loop that holds one frame and keeps keepalive/outgoing alive
(without reading inbound) so a slow consumer never starves protocol handling.
Zero data loss, bounded memory, TCP backpressure to the server. Raises the
CHANNEL_SIZE default from 4 to 256 so transient hiccups never trigger it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three TDD tasks: probe mock server + validation test; backpressure-resilient
delivery (try_send + sub-loop) with a discriminating test; raise CHANNEL_SIZE
default to 256 + docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sends a burst then replies with an 'alive' marker only if it receives a client
keepalive ping — the discriminator for protocol-liveness during backpressure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Inbound delivery now uses try_send + a backpressure sub-loop that holds one
frame and keeps keepalive/outgoing alive (without reading inbound) until the
consumer frees capacity. A slow consumer no longer stalls ping/pong and
keepalive, so the server no longer drops the connection. Zero data loss,
bounded memory, ordering preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A larger default buffer absorbs transient consumer slowdowns so backpressure
engages only under sustained overload. Documents that backpressure resilience
relies on keepalive being enabled.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- SocketeerRx and ReuniteError CHANNEL_SIZE defaults raised from 4 to 256
  to match Socketeer, keeping explicit bare-type usage consistent with
  reunite compatibility.
- Swapped # Errors / # Panics order in backpressure_probe_server to match
  file convention (Errors first).
- Rewrote keepalive_interval disabled-case sentence to accurately describe
  that the loop stops reading inbound frames when the buffer is full, and
  that a stalled consumer can park the connection indefinitely.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.83673% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.64%. Comparing base (1c3491a) to head (48d57c9).

Files with missing lines Patch % Lines
src/socket_loop.rs 88.33% 7 Missing ⚠️
src/split.rs 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #22      +/-   ##
==========================================
+ Coverage   94.14%   94.64%   +0.49%     
==========================================
  Files           7        7              
  Lines         564      635      +71     
==========================================
+ Hits          531      601      +70     
- Misses         33       34       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

zheylmun and others added 3 commits June 29, 2026 20:22
The send path already funneled through shared helpers (send_confirmed),
but the receive path was duplicated: next_message, next_raw_message, and
the Stream poll_next impl were reimplemented across Socketeer and
SocketeerRx, with the poll_next bodies byte-for-byte identical.

Extract recv_raw and poll_recv_raw into socket_loop alongside the send
helpers so both the unsplit handle and the split receive half route
through one terminal-error-aware receive path. This also makes tracing
consistent: all receive paths now emit the "Received message" trace,
where previously only Socketeer::next_message did.

Pure refactor, no public API change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Exercises the no-keepalive arm of deliver_with_backpressure: a small
channel (2) under a 5-frame burst forces backpressure with keepalives
disabled, the consumer pauses, then drains. Asserts all burst frames
arrive in order with zero loss.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Exercises the receiver.recv() arm of deliver_with_backpressure: with the
loop parked in backpressure (channel full, consumer not reading), a
confirmed send still completes, proving a slow receiver does not block
the outgoing path. The held burst then drains in full and in order.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zheylmun zheylmun merged commit 059715f into main Jun 30, 2026
11 of 14 checks passed
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