Skip to content

Clarify EPIPE handling (STOP_SENDING vs RESET_STREAM) #60

@Mygod

Description

@Mygod

After #59, when the local TCP peer closes its read side and the client hits EPIPE on write, the QUIC receiver should signal STOP_SENDING to halt inbound traffic. Currently we send both STOP_SENDING and RESET_STREAM, which aborts the send direction as well. That is consistent with a "full abort on local error" policy but is not strictly correct for half-close semantics.

We should decide on desired behavior and implement it explicitly.

Why this matters

  • EPIPE means we cannot deliver inbound QUIC data to the local TCP peer.
  • It does not necessarily mean we must abort our outbound (send) direction.
  • Full resets prevent half-close behavior and may terminate valid upstream data.

Current behavior

  • On local TCP read/write errors, we call:
    • picoquic_stop_sending(...)
    • picoquic_reset_stream(...)
  • This aborts both directions and removes stream state.

Proposed behavior (if we choose half-close correctness)

  • On write error (EPIPE):
    • Send STOP_SENDING only (receiver-side abort).
    • Keep stream state so local->remote traffic can continue.
  • On read error / local close:
    • Send RESET_STREAM (sender-side abort).
    • Optionally also STOP_SENDING if we intend full abort.

Scope / likely changes

  • In crates/slipstream-client/src/streams.rs:
    • Split handling for StreamWriteError vs StreamReadError.
    • Avoid removing the stream on write errors if we want half-close semantics.
  • Update tests:
    • Add or adjust an e2e test that verifies half-close behavior for EPIPE.
    • Ensure server observes STOP_SENDING in that case.

Notes

  • In picoquic, RESET_STREAM alone does not schedule output; STOP_SENDING does.
  • We recently added STOP_SENDING to fix missing on-wire resets; this issue is about policy correctness and half-close support.
  • True half‑close isn’t fully supported today. When the target sends FIN, we mark target_fin_pending + close_after_flush and remove the stream after flushing the FIN, which drops any queued client→target data. That’s consistent with our current full‑close policy, but a real half‑close would need to keep the stream alive for the opposite direction. If we ever add half‑close, we should revisit the close_after_flush removal in StreamClosed/prepare_to_send and avoid dropping pending client→target data.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions