Summary
The DropOldest overflow policy was removed from 1.0 (485ad8b) because it could not be correctly implemented with tokio::sync::mpsc. The Sender has no access to the Receiver end, so the producer cannot evict the oldest item from the channel when the buffer is full.
Problem
For real-time market data subscriptions, slow consumers need a way to receive the freshest data rather than stale data from when they first fell behind. DropNewest (current default) keeps old data and discards new — exactly the wrong behavior for price-sensitive consumers.
Constraints
send_batch() is the hot path (~50K calls/sec at scale). Zero-overhead for DropNewest and Block policies must be preserved.
RecordBatch is Clone (cheap, Arc-backed columns).
BlpError is NOT Clone (contains Box<dyn Error>).
- Multiple producers (one per topic) share a single consumer channel.
Rejected Approaches
| Approach |
Why it fails |
| VecDeque alongside mpsc |
Evicts middle-age data, not oldest. The 256-item mpsc buffer is untouched. |
| Consumer-side drain |
Fresh batches rejected by try_send are already lost. Draining gives old data. |
tokio::sync::broadcast |
BlpError is not Clone. Broadcast also clones on every recv, not just overflow. |
Recommended Approach
Replace mpsc with a custom bounded ring channel (or use a crate like ringbuf / async-ringbuf) when DropOldest is requested:
- Producer writes to a ring buffer. When full, oldest item is overwritten.
- Consumer reads from the ring buffer.
DropNewest and Block continue using mpsc (zero change to existing paths).
The channel type selection can be done at subscription construction time based on the policy enum, behind a trait abstraction.
Acceptance Criteria
Summary
The
DropOldestoverflow policy was removed from 1.0 (485ad8b) because it could not be correctly implemented withtokio::sync::mpsc. TheSenderhas no access to theReceiverend, so the producer cannot evict the oldest item from the channel when the buffer is full.Problem
For real-time market data subscriptions, slow consumers need a way to receive the freshest data rather than stale data from when they first fell behind.
DropNewest(current default) keeps old data and discards new — exactly the wrong behavior for price-sensitive consumers.Constraints
send_batch()is the hot path (~50K calls/sec at scale). Zero-overhead forDropNewestandBlockpolicies must be preserved.RecordBatchisClone(cheap, Arc-backed columns).BlpErroris NOTClone(containsBox<dyn Error>).Rejected Approaches
try_sendare already lost. Draining gives old data.tokio::sync::broadcastBlpErroris notClone. Broadcast also clones on everyrecv, not just overflow.Recommended Approach
Replace
mpscwith a custom bounded ring channel (or use a crate likeringbuf/async-ringbuf) whenDropOldestis requested:DropNewestandBlockcontinue usingmpsc(zero change to existing paths).The channel type selection can be done at subscription construction time based on the policy enum, behind a trait abstraction.
Acceptance Criteria
OverflowPolicy::DropOldestvariant restoredDropNewestandBlockcode paths