feat(proto): add batch send/recv APIs - #735
Conversation
Add Connection::send_datagrams / read_datagrams and the underlying
DatagramState::recv_many / Datagrams::send_many to amortize the
per-datagram connection-mutex cost, which is the dominant overhead at
high pps for a mesh VPN forwarding bursts of TUN packets.
- DatagramState::recv_many drains all buffered incoming datagrams in one
VecDeque drain instead of N pop_fronts.
- Datagrams::send_many queues a whole batch under one logical operation:
peer-support check once, drop-oldest backpressure pass once, oversized
datagrams skipped individually (not aborting the batch).
- Connection::send_datagrams takes the mutex once, wakes the driver once.
- Connection::read_datagrams is a ReadDatagrams future that parks on the
datagram_received notify then drains everything in one lock hold.
- New SendMany { queued, rejected } result struct.
- 2 new unit tests: batch send/recv roundtrip, oversized-skip behavior.
- bench: datagram benchmark harness (BenchOpt/DatagramOpt, DatagramCounters).
Baseline (loopback, release, 2-core): 440 MiB/s @ 1200B, 4.17M pps @ 64B.
flub
left a comment
There was a problem hiding this comment.
Probably not an unreasonable request, left some initial notes from a quick skim. I haven't looked at tests or benchmarks at all yet.
|
Please run |
Resolves flub's PR n0-computer#735 review: - Fix send_many over-allocation: evict oldest per-element before each push (not push-all-then-evict), so the outgoing deque never transiently holds the whole batch. A datagram larger than the budget but under max_size is still enqueued once the queue is emptied, matching send(drop=true). Locked in by datagram_batch_send_drop_oldest_per_element. - send_many now stops at the first oversized datagram and returns Ok(count) accepted from the front (mirrors write_chunks / sendmmsg "first K succeeded"). No longer returns Err(TooLarge); callers recover via &datagrams[count..]. Empty batch returns Ok(0) (datagram_batch_send_empty_is_ok). - Rename to the chunks-style API: send_many_datagrams / read_many_datagrams, returning ReadManyDatagrams. Drop the SendMany struct. - Take slices for consistency with read_many_chunks / write_chunks: send_many_datagrams(&[Bytes]) -> Result<usize, _>; read_many_datagrams(&mut [Bytes]) (fill-slots, returns count drained). recv_many return-count bug fixed (now reports slots filled, not bool). - Extract over_send_budget / would_exceed_send_budget helpers, deduped across send and send_many. - CI: fmt (project unstable config), clippy, rustdoc, all tests green.
- send_many takes a `drop` flag, mirroring send. Reject the whole batch with TooLarge up front if any datagram is oversized, so a size error is never a partial send. With drop = false, stop when the buffer is full and return the count queued. - Share the drop-oldest loop between send and send_many. - Rename the send-buffer helpers to is_send_buffer_exceeded and has_send_buffer_capacity, and drain in recv_many instead of popping. - Focus the docs and drop the lock/implementation details.
|
Please use the (admittedly tiny, well hidden) re-request review button whenever the PR is ready for another round of review. It is next each reviewer's name at the top right. |
|
|
||
| /// Direction of the datagram flood. | ||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| pub enum Direction { |
There was a problem hiding this comment.
Have you found the direction to be important? In theory the direction could also be done for the stream bench, but for some reason it is not. Which is what makes me wonder if this reveals anything interesting.
There was a problem hiding this comment.
Am I crazy or my comments where deleted?
The future no longer forces the caller's out slice borrow to match the connection borrow.
An unpaced flood queues datagrams far faster than the driver transmits, so drop-oldest evicts almost the whole run locally and the benchmark measures queueing speed instead of throughput. Wait for send buffer space before each send so every queued datagram reaches the wire and loss reflects the network.
…rker Derive derive_more::Display on Direction, SendMode, and Congestion so the JSON report prints them directly instead of hand-mapping strings. Replace the uni done-stream and the drain grace timeout with an in-band 1-byte DONE datagram: the sender floods, then resends the marker every 250ms until the receiver closes; the receiver counts until it sees the marker. The duplex coordination stream keeps only the S/F handshake, where F now means the peer's DONE was received.
flub
left a comment
There was a problem hiding this comment.
basically LGTM I think, but would be nice to clean up the last nit.
Thanks for you patience!
# Conflicts: # bench/src/lib.rs # noq-proto/src/connection/datagrams.rs
The flood sized its payload once from max_datagram_size() and kept it for the whole run. MTU discovery raises the estimate within a few ms of connecting and black hole detection drops it back to the minimum MTU, so a flood that sampled the raised value aborted with TooLarge as soon as the estimate came down. Duplex runs hit this most often, since the loss they generate is what triggers black hole detection. Sends now re-clamp to the current limit and carry on instead of failing.
…f/datagram-batching
Take inbound packets from a peer connection with read_many_datagrams, so a burst is drained under one lock hold instead of a wake per packet, and hand the lazy-dial backlog to send_many_datagrams in per-route runs. The batch APIs come from noq via the rayfish forks (n0-computer/noq#735, n0-computer/iroh#4383), so ray patches noq alongside iroh and netwatch. The drop-newest policy is kept by tracking the bytes already staged in a run against the connection's send buffer space.
|
Could you apply the last changes I suggest in a new commit please? I don't seem to be able to commit those myself as I was hoping. And keep it to just those so I don't have to worry about any other changes to check again. :) |
|
well, you can merge main again too, to fix the cargo deny and perf test failures as well. |
Description
Adds functions to send and receive batches of datagrams. Particularly the
locking overhead in noq stands out and is reduced. But also adds batch
APIs into noq-proto and the driver needs to be woken fewer times.
API Changes
New APIs:
Datagrams::send_manyDatagrams::recv_manyConnection::read_many_datagramsConnection::send_many_datagramsNotes & open questions
n/a
Change checklist