mirror of
https://github.com/tokio-rs/bytes.git
synced 2026-08-13 00:00:32 +02:00
RingBuf: Fix edge cases with mark/reset.
- This fixes #25 and fixes #26. - Move the mark state into a 'Mark' enum. - Store both 'pos' and 'len' inside the mark because both are needed to distinguish between full/empty states. This fixes 'reset' setting 'len' to zero when the buffer was full when marked. - Clear the mark when the length of the marked input range exceeds the capacity. Fixes bugs with the mark not being cleared correctly on writes. - Add test cases.
This commit is contained in:
+27
-10
@@ -1,6 +1,11 @@
|
||||
use {alloc, Buf, MutBuf};
|
||||
use std::{cmp, fmt, io, ptr};
|
||||
|
||||
enum Mark {
|
||||
NoMark,
|
||||
At { pos: usize, len: usize },
|
||||
}
|
||||
|
||||
/// Buf backed by a continous chunk of memory. Maintains a read cursor and a
|
||||
/// write cursor. When reads and writes reach the end of the allocated buffer,
|
||||
/// wraps around to the start.
|
||||
@@ -9,7 +14,7 @@ pub struct RingBuf {
|
||||
cap: usize, // Capacity of the buffer
|
||||
pos: usize, // Offset of read cursor
|
||||
len: usize, // Number of bytes to read
|
||||
mark: Option<usize>, // Marked read position
|
||||
mark: Mark, // Marked read position
|
||||
}
|
||||
|
||||
// TODO: There are most likely many optimizations that can be made
|
||||
@@ -22,7 +27,7 @@ impl RingBuf {
|
||||
cap: 0,
|
||||
pos: 0,
|
||||
len: 0,
|
||||
mark: None,
|
||||
mark: Mark::NoMark,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +41,7 @@ impl RingBuf {
|
||||
cap: capacity,
|
||||
pos: 0,
|
||||
len: 0,
|
||||
mark: None,
|
||||
mark: Mark::NoMark,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +63,7 @@ impl RingBuf {
|
||||
/// buffer multiple times. The mark will be cleared if it is overwritten
|
||||
/// during a write.
|
||||
pub fn mark(&mut self) {
|
||||
self.mark = Some(self.pos);
|
||||
self.mark = Mark::At { pos: self.pos, len: self.len };
|
||||
}
|
||||
|
||||
/// Resets the read position to the previously marked position.
|
||||
@@ -70,9 +75,14 @@ impl RingBuf {
|
||||
///
|
||||
/// This method will panic if no mark has been set,
|
||||
pub fn reset(&mut self){
|
||||
let mark = self.mark.take().expect("no mark set");
|
||||
self.len = (self.len + self.pos + self.cap - mark) % self.cap;
|
||||
self.pos = mark;
|
||||
match self.mark {
|
||||
Mark::NoMark => panic!("no mark set"),
|
||||
Mark::At {pos, len} => {
|
||||
self.pos = pos;
|
||||
self.len = len;
|
||||
self.mark = Mark::NoMark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_remaining(&self) -> usize {
|
||||
@@ -97,9 +107,16 @@ impl RingBuf {
|
||||
fn advance_writer(&mut self, mut cnt: usize) {
|
||||
cnt = cmp::min(cnt, self.write_remaining());
|
||||
self.len += cnt;
|
||||
if let Some(mark) = self.mark {
|
||||
if (self.pos + self.len) % self.cap > mark {
|
||||
self.mark = None;
|
||||
|
||||
// Adjust the mark to account for bytes written.
|
||||
if let Mark::At { ref mut len, .. } = self.mark {
|
||||
*len += cnt;
|
||||
}
|
||||
|
||||
// Clear the mark if we've written past it.
|
||||
if let Mark::At { len, .. } = self.mark {
|
||||
if len > self.cap {
|
||||
self.mark = Mark::NoMark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,3 +89,34 @@ fn test_wrap_reset() {
|
||||
buf.write(&[1, 2, 3, 4]).unwrap();
|
||||
buf.reset();
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Test that writes across a mark/reset are preserved.
|
||||
fn test_mark_write() {
|
||||
use std::io::{Read, Write};
|
||||
|
||||
let mut buf = RingBuf::new(8);
|
||||
buf.write(&[1, 2, 3, 4, 5, 6, 7]).unwrap();
|
||||
buf.mark();
|
||||
buf.write(&[8]).unwrap();
|
||||
buf.reset();
|
||||
|
||||
let mut buf2 = [0; 8];
|
||||
buf.read(&mut buf2).unwrap();
|
||||
assert_eq!(buf2, [1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Test that "RingBuf::reset" does not reset the length of a
|
||||
// full buffer to zero.
|
||||
fn test_reset_full() {
|
||||
use bytes::traits::MutBuf;
|
||||
use std::io::Write;
|
||||
|
||||
let mut buf = RingBuf::new(8);
|
||||
buf.write(&[1, 2, 3, 4, 5, 6, 7, 8]).unwrap();
|
||||
assert_eq!(MutBuf::remaining(&buf), 0);
|
||||
buf.mark();
|
||||
buf.reset();
|
||||
assert_eq!(MutBuf::remaining(&buf), 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user