Skip to content

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.

writing_paused property

Whether drain would wait.

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.