blackbull.server.connection_protocol¶
blackbull.server.connection_protocol
¶
One connection buffer and transport callbacks.
BufferReader owns receive policy; ConnectionProtocol owns socket operations. Published state has exactly one writer, its owner. See docs/about/internals.md.
BufferReader
¶
Bases: AbstractReader
Read resident bytes before waiting for more.
Own high-water pause decisions, including the starving-reader exception. Only one coroutine may wait for input at a time.
discard(max_bytes, deadline)
async
¶
Read and throw away up to max_bytes, until EOF or deadline.
The read side of the lingering close. Both bounds are the caller's to
set and neither is optional; docs/about/internals.md §Rejecting
requires lingering says what each one refuses.
Returns the number of bytes discarded. Errors are the caller's to absorb: this runs on the teardown path, where the response is already on the wire.
fill(n)
async
¶
Wait until n bytes are resident, consuming nothing.
maybe_pause()
¶
A delivery crossed the high-water mark: decide whether to pause.
Never while this reader is waiting: it is starved, not behind, so the condition backpressure exists to prevent is not the one in play.
wait_for_data()
async
¶
Park after releasing the high-water pause.
A starving reader must resume input even if its requested slice exceeds the watermark. The reader publishes read_offer around this wait; a paused transport cannot otherwise supply the bytes needed to unblock it.
ConnectionProtocol
¶
Bases: BufferedProtocol
Own transport callbacks and wakeups for one connection.
BufferReader decides pause/resume policy; this object applies it.
peer_closed
property
¶
Return transport EOF; buffered bytes may still remain, so this differs from reader.at_eof.
drain()
async
¶
Wait only while the transport is above its write high-water mark.
linger_close(max_bytes=65536, timeout=0.25)
async
¶
Close after briefly discarding whatever the peer is still sending.
Reads and discards up to max_bytes for at most timeout seconds,
then closes. A connection that wrote a response and may still have
unread bytes — resident, or nothing arrived and no EOF — lingers
once; otherwise its buffer and EOF decide.
The Internals page explains why a rejection has to close this way and
why both bounds are needed. nginx calls it lingering_close.
pause_reading()
¶
Stop the transport, because the reader asked. Idempotent.
resume_reading()
¶
Let the transport read again, because the reader asked. Idempotent.
wait_for_arrival()
async
¶
Park until the next arrival, EOF, or connection loss.
The bare rendezvous; the decision to wait, and the backpressure release
that goes with it, are BufferReader.wait_for_data's.
One waiter only: a connection is driven by a single actor coroutine, so a second concurrent reader is a bug rather than a case to support.
writelines(parts)
¶
Vectored write — the upper branch of the send-path size gate.
Delegated to the transport, never joined here. A backing object that
offers only write serves small responses and fails large ones;
docs/about/internals.md §Send-path invariant is the obligation.