Lab
winston
A backpressure policy is real only at the first queue that can fill, and a bounded setting in front of an unbounded buffer does nothing.
↓
The released design
- log() is synchronous. In 0.8.2 it pushes into one bounded crossbeam channel, and 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 behavior. Five tests in the backpressure suite were marked ignored because none passed on the new shape.
Alternatives
- Bound the unbounded channel: the strategy fires as documented, and two capacities and two policies remain to explain.
- Make 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, so 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 rather than kept as a setting that does nothing.
Where it stops holding
The position holds 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, and a caller on a single-threaded executor cannot be made to wait at all.
Up next
graphql-scalars→