Lab
winston
A backpressure policy takes effect only at the first queue that can fill.
↓
The released design
- log() is synchronous and, in 0.8.2, pushes into one bounded crossbeam channel. A background thread drains that channel into every transport.
- A BackpressureStrategy of Block, DropOldest or DropCurrent decides what happens when the channel is full. It is the caller's one control over what a slow sink costs.
Where the control stopped working
- Rebuilt on streams, four queues sat between log() and a sink: the caller's bounded channel, an unbounded channel into the async fan-out, a bounded mailbox per transport, and the stream's own queue.
- A slow sink filled its mailbox and the fan-out's send parked. The unbounded channel behind it grew without limit. The caller's channel, the only one the strategy governed, never filled.
- Block and DropOldest produced the same behaviour. Five tests in the backpressure suite were marked ignored because none passed on the new shape.
Alternatives
- Bounding the unbounded channel makes the strategy fire as documented and leaves two capacities and two policies to explain.
- Making log() async removes the handoff the channel exists for and breaks every synchronous call site, the log-crate adapter included.
The rework
- log() dispatches synchronously into a bounded mailbox per transport. Each transport carries its own OverflowPolicy of Block, DropNewest or DropOldest. A file can block for durability while the console drops for freshness.
- log() can now block. The slowest transport set to Block sets the rate for every caller.
- A blocking call inside an async task parks the executor's worker thread; on a single-threaded runtime that is a deadlock. Async callers get an opt-in log_async.
- The caller-side strategy is removed with the channel it governed.
Unbounded stages and single-threaded callers
Block reaches the caller only while at most one bounded queue stands between the caller and each sink. Any stage that can grow without bound turns Block into drop-later. A caller on a single-threaded executor cannot be made to wait at all.
Up next
graphql-scalars→