Optimize RingBuffer::enqueue_many_with to an empty buffer.

This optimization makes sure that enqueueing to an empty ring buffer
would never wrap around, in turn ensuring that enqueueing to an empty
packet buffer would never use more than one metadata entry.
Right now, pushing buffer-sized packets into a packet buffer requires
at least two metadata entries, which is surprising and wasteful.
v0.7.x
Astro 2018-05-25 00:00:00 +02:00 committed by whitequark
parent 053a6f36e5
commit e5a3de210c
2 changed files with 34 additions and 20 deletions

View File

@ -3830,33 +3830,25 @@ mod test {
#[test]
fn test_buffer_wraparound_tx() {
let mut s = socket_established();
s.tx_buffer = SocketBuffer::new(vec![0; 6]);
assert_eq!(s.send_slice(b"abc"), Ok(3));
s.tx_buffer = SocketBuffer::new(vec![b'.'; 9]);
assert_eq!(s.send_slice(b"xxxyyy"), Ok(6));
assert_eq!(s.tx_buffer.dequeue_many(3), &b"xxx"[..]);
assert_eq!(s.tx_buffer.len(), 3);
// "abcdef" not contiguous in tx buffer
assert_eq!(s.send_slice(b"abcdef"), Ok(6));
recv!(s, Ok(TcpRepr {
seq_number: LOCAL_SEQ + 1,
ack_number: Some(REMOTE_SEQ + 1),
payload: &b"abc"[..],
payload: &b"yyyabc"[..],
..RECV_TEMPL
}));
send!(s, TcpRepr {
seq_number: REMOTE_SEQ + 1,
ack_number: Some(LOCAL_SEQ + 1 + 3),
..SEND_TEMPL
});
assert_eq!(s.send_slice(b"defghi"), Ok(6));
recv!(s, Ok(TcpRepr {
seq_number: LOCAL_SEQ + 1 + 3,
seq_number: LOCAL_SEQ + 1 + 6,
ack_number: Some(REMOTE_SEQ + 1),
payload: &b"def"[..],
..RECV_TEMPL
}));
// "defghi" not contiguous in tx buffer
recv!(s, Ok(TcpRepr {
seq_number: LOCAL_SEQ + 1 + 3 + 3,
ack_number: Some(REMOTE_SEQ + 1),
payload: &b"ghi"[..],
..RECV_TEMPL
}));
}
// =========================================================================================//

View File

@ -170,6 +170,12 @@ impl<'a, T: 'a> RingBuffer<'a, T> {
/// than the size of the slice passed into it.
pub fn enqueue_many_with<'b, R, F>(&'b mut self, f: F) -> (usize, R)
where F: FnOnce(&'b mut [T]) -> (usize, R) {
if self.length == 0 {
// Ring is currently empty. Reset `read_at` to optimize
// for contiguous space.
self.read_at = 0;
}
let write_at = self.get_idx(self.length);
let max_size = self.contiguous_window();
let (size, result) = f(&mut self.storage[write_at..write_at + max_size]);
@ -659,13 +665,13 @@ mod test {
ring.dequeue_many(6).copy_from_slice(b"ABCDEF");
assert_eq!(ring.write_unallocated(0, b"ghi"), 3);
assert_eq!(&ring.storage[..], b"ABCDEFghi...");
assert_eq!(ring.get_unallocated(0, 3), b"ghi");
assert_eq!(ring.write_unallocated(3, b"jklmno"), 6);
assert_eq!(&ring.storage[..], b"mnoDEFghijkl");
assert_eq!(ring.get_unallocated(3, 3), b"jkl");
assert_eq!(ring.write_unallocated(9, b"pqrstu"), 3);
assert_eq!(&ring.storage[..], b"mnopqrghijkl");
assert_eq!(ring.get_unallocated(9, 3), b"pqr");
}
#[test]
@ -721,4 +727,20 @@ mod test {
assert_eq!(no_capacity.enqueue_one(), Err(Error::Exhausted));
assert_eq!(no_capacity.contiguous_window(), 0);
}
/// Use the buffer a bit. Then empty it and put in an item of
/// maximum size. By detecting a length of 0, the implementation
/// can reset the current buffer position.
#[test]
fn test_buffer_write_wholly() {
let mut ring = RingBuffer::new(vec![b'.'; 8]);
ring.enqueue_many(2).copy_from_slice(b"xx");
ring.enqueue_many(2).copy_from_slice(b"xx");
assert_eq!(ring.len(), 4);
ring.dequeue_many(4);
assert_eq!(ring.len(), 0);
let large = ring.enqueue_many(8);
assert_eq!(large.len(), 8);
}
}