Context
Design: docs/plans/2026-03-19-channel-based-io-driver-design.md — Phase 1/3
Problem: After stages 1-4, upload stuck at ~8 Mbps on 95 Mbps baseline. stream_send() and poll task share the same Mutex. They serialize each other regardless of the event-driven backpressure changes.
Current: Download improved +135% (15 Mbps) and +232% (LEO 11.8 Mbps) but upload unaffected.
Solution
Replace Mutex with a channel-based I/O driver. Single tokio task owns quiche Connection exclusively. Relay tasks communicate via bounded mpsc channel (capacity 64 = 4MB).
Changes Required
crates/quick-transport/src/client.rs
- Add
SendCommand enum (StreamSend, StreamRecv, OpenStream, DgramSend, GetStats, Close)
- Add
run_io_driver() async function (select! loop: channel + socket + timer)
- Add
flush_packets() helper (batch collect + UDP send + BBR pacing)
- Rewrite
stream_send(): channel send + oneshot await (no Mutex)
- Rewrite
stream_recv(): channel send + oneshot await
- Rewrite
open_stream(), stats(), close(): same pattern
- Make
poll() a no-op
- Remove
connection: Arc<Mutex<Option<QuicConnection>>> field
- Add
send_tx: mpsc::Sender<SendCommand> field
- Spawn I/O driver in
connect() after handshake completes
bins/quick-entry/src/pool.rs
- Remove
start_background_tasks() manual poll loop (I/O driver replaces it)
- Store I/O driver handles for shutdown
bins/quick-entry/src/main.rs
- Remove manual poll loop (lines 772-805)
- Health checks via periodic GetStats commands
Acceptance Criteria
Dependencies
Requires #110 (mutex split) merged — DONE.
Expected Impact
Upload: 8 Mbps → 50-80 Mbps terrestrial. GEO: 0 → 0.5+ Mbps.
Context
Design: docs/plans/2026-03-19-channel-based-io-driver-design.md — Phase 1/3
Problem: After stages 1-4, upload stuck at ~8 Mbps on 95 Mbps baseline. stream_send() and poll task share the same Mutex. They serialize each other regardless of the event-driven backpressure changes.
Current: Download improved +135% (15 Mbps) and +232% (LEO 11.8 Mbps) but upload unaffected.
Solution
Replace Mutex with a channel-based I/O driver. Single tokio task owns quiche Connection exclusively. Relay tasks communicate via bounded mpsc channel (capacity 64 = 4MB).
Changes Required
crates/quick-transport/src/client.rs
SendCommandenum (StreamSend, StreamRecv, OpenStream, DgramSend, GetStats, Close)run_io_driver()async function (select! loop: channel + socket + timer)flush_packets()helper (batch collect + UDP send + BBR pacing)stream_send(): channel send + oneshot await (no Mutex)stream_recv(): channel send + oneshot awaitopen_stream(),stats(),close(): same patternpoll()a no-opconnection: Arc<Mutex<Option<QuicConnection>>>fieldsend_tx: mpsc::Sender<SendCommand>fieldconnect()after handshake completesbins/quick-entry/src/pool.rs
start_background_tasks()manual poll loop (I/O driver replaces it)bins/quick-entry/src/main.rs
Acceptance Criteria
Dependencies
Requires #110 (mutex split) merged — DONE.
Expected Impact
Upload: 8 Mbps → 50-80 Mbps terrestrial. GEO: 0 → 0.5+ Mbps.