Skip to content

Commit

Permalink
feat: switch to slice-ring-buffer (#5)
Browse files Browse the repository at this point in the history
* switch to slice-ring-buffer

* happy clippy

* tests: happy clippy
  • Loading branch information
dignifiedquire authored Aug 5, 2024
1 parent e0f876a commit b089996
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 45 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ edition = "2021"
memchr = "2.0"

# `slice_deque` is only supported on platforms with virtual memory
[target.'cfg(any(unix, windows))'.dependencies.slice-deque]
[target.'cfg(any(unix, windows))'.dependencies.slice-ring-buffer]
version = "0.3"
optional = true

[features]
default = ["slice-deque"]
slice-deque = ["dep:slice-ring-buffer"]
4 changes: 3 additions & 1 deletion src/buffer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ mod std_buf;
#[cfg(feature = "slice-deque")]
mod slice_deque_buf;

use std::mem::MaybeUninit;

use self::std_buf::StdBuf;

#[cfg(feature = "slice-deque")]
Expand Down Expand Up @@ -109,7 +111,7 @@ impl BufImpl {

pub fn buf_mut(&mut self)[] -> &mut [u8];

pub unsafe fn write_buf(&mut self)[] -> &mut [u8];
pub unsafe fn write_buf(&mut self)[] -> &mut [MaybeUninit<u8>];

pub unsafe fn bytes_written(&mut self, add: usize)[add];

Expand Down
11 changes: 5 additions & 6 deletions src/buffer/slice_deque_buf.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
//! Move-free buffer and reader utilizing the [`slice-deque`] crate.
//! Move-free buffer and reader utilizing the [`slice-ring-buffer`] crate.
//!
//! These types are only available on target platforms with virtual memory support,
//! namely Windows, OS X and Linux.
//!
//! [`slice-deque`]: https://crates.io/crates/slice-deque
extern crate slice_deque;
use self::slice_deque::SliceDeque;
//! [`slice-ring-buffer`]: https://crates.io/crates/slice-ring-buffer
use slice_ring_buffer::SliceRingBuffer as SliceDeque;

use std::cmp;
use std::{cmp, mem::MaybeUninit};

pub struct SliceDequeBuf {
deque: SliceDeque<u8>,
Expand Down Expand Up @@ -55,7 +54,7 @@ impl SliceDequeBuf {
&mut self.deque
}

pub unsafe fn write_buf(&mut self) -> &mut [u8] {
pub unsafe fn write_buf(&mut self) -> &mut [MaybeUninit<u8>] {
self.deque.tail_head_slice()
}

Expand Down
8 changes: 5 additions & 3 deletions src/buffer/std_buf.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::cmp;
use std::{cmp, mem::MaybeUninit};

use self::impl_::RawBuf;

Expand Down Expand Up @@ -82,8 +82,10 @@ impl StdBuf {
unsafe { &mut self.buf.as_mut_slice()[self.pos..self.end] }
}

pub unsafe fn write_buf(&mut self) -> &mut [u8] {
&mut self.buf.as_mut_slice()[self.end..]
pub unsafe fn write_buf(&mut self) -> &mut [MaybeUninit<u8>] {
let slice: &mut [u8] = &mut self.buf.as_mut_slice()[self.end..];
// SAFETY: &[T] and &[MaybeUninit<T>] have the same layout
std::mem::transmute(slice)
}

pub unsafe fn bytes_written(&mut self, amt: usize) {
Expand Down
64 changes: 42 additions & 22 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@
//! [`policy` module]:
//!
//! * Refine `BufReader`'s behavior by implementing the [`ReaderPolicy` trait] or use
//! an existing implementation like [`MinBuffered`] to ensure the buffer always contains
//! a minimum number of bytes (until the underlying reader is empty).
//! an existing implementation like [`MinBuffered`] to ensure the buffer always contains
//! a minimum number of bytes (until the underlying reader is empty).
//!
//! * Refine `BufWriter`'s behavior by implementing the [`WriterPolicy` trait]
//! or use an existing implementation like [`FlushOn`] to flush when a particular byte
//! appears in the buffer (used to implement [`LineWriter`]).
//! or use an existing implementation like [`FlushOn`] to flush when a particular byte
//! appears in the buffer (used to implement [`LineWriter`]).
//!
//! [`policy` module]: policy
//! [`ReaderPolicy` trait]: policy::ReaderPolicy
Expand Down Expand Up @@ -112,19 +112,19 @@
//! However, this has some caveats:
//!
//! * It is only available on target platforms with virtual memory support, namely fully fledged
//! OSes such as Windows and Unix-derivative platforms like Linux, OS X, BSD variants, etc.
//! OSes such as Windows and Unix-derivative platforms like Linux, OS X, BSD variants, etc.
//!
//! * The default capacity varies based on platform, and custom capacities are rounded up to a
//! multiple of their minimum size, typically the page size of the platform.
//! Windows' minimum size is comparably quite large (**64 KiB**) due to some legacy reasons,
//! so this may be less optimal than the default capacity for a normal buffer (8 KiB) for some
//! use-cases.
//! multiple of their minimum size, typically the page size of the platform.
//! Windows' minimum size is comparably quite large (**64 KiB**) due to some legacy reasons,
//! so this may be less optimal than the default capacity for a normal buffer (8 KiB) for some
//! use-cases.
//!
//! * Due to the nature of the virtual-memory trick, the virtual address space the buffer
//! allocates will be double its capacity. This means that your program will *appear* to use more
//! memory than it would if it was using a normal buffer of the same capacity. The physical memory
//! usage will be the same in both cases, but if address space is at a premium in your application
//! (32-bit targets) then this may be a concern.
//! allocates will be double its capacity. This means that your program will *appear* to use more
//! memory than it would if it was using a normal buffer of the same capacity. The physical memory
//! usage will be the same in both cases, but if address space is at a premium in your application
//! (32-bit targets) then this may be a concern.
//!
//! [ringbuf-wikipedia]: https://en.wikipedia.org/wiki/Circular_buffer#Optimization
#![warn(missing_docs)]
Expand All @@ -143,6 +143,7 @@ use std::cell::RefCell;
use std::io::prelude::*;
use std::io::SeekFrom;
use std::mem::ManuallyDrop;
use std::mem::MaybeUninit;
use std::{cmp, error, fmt, io, ptr};

use self::policy::{FlushOnNewline, ReaderPolicy, StdPolicy, WriterPolicy};
Expand Down Expand Up @@ -906,7 +907,7 @@ impl Buffer {
/// The default capacity varies based on the target platform:
///
/// * Unix-derivative platforms; Linux, OS X, BSDs, etc: **8KiB** (the default buffer size for
/// `std::io` buffered types)
/// `std::io` buffered types)
/// * Windows: **64KiB** because of legacy reasons, of course (see below)
///
/// Only available on platforms with virtual memory support and with the `slice-deque` feature
Expand All @@ -925,10 +926,10 @@ impl Buffer {
/// The capacity will be rounded up to the minimum size for the current target:
///
/// * Unix-derivative platforms; Linux, OS X, BSDs, etc: the next multiple of the page size
/// (typically 4KiB but can vary based on system configuration)
/// (typically 4KiB but can vary based on system configuration)
/// * Windows: the next muliple of **64KiB**; see [this Microsoft dev blog post][Win-why-64k]
/// for why it's 64KiB and not the page size (TL;DR: Alpha AXP needs it and it's applied on
/// all targets for consistency/portability)
/// for why it's 64KiB and not the page size (TL;DR: Alpha AXP needs it and it's applied on
/// all targets for consistency/portability)
///
/// [Win-why-64k]: https://blogs.msdn.microsoft.com/oldnewthing/20031008-00/?p=42223
///
Expand Down Expand Up @@ -1039,14 +1040,21 @@ impl Buffer {
if self.zeroed < cap {
unsafe {
let buf = self.buf.write_buf();
buf.fill(0);
// TODO: use MaybeUninit::fill once stabilized
for el in buf {
el.write(0);
}
}

self.zeroed = cap;
}

let read = {
let buf = unsafe { self.buf.write_buf() };
// SAFETY: everything upto `cap` was zeroed above
// TODO: use MaybeUninit::slice_assume_init_mut once stabilized
let buf = unsafe { &mut *(buf as *mut [MaybeUninit<u8>] as *mut [u8]) };
// TODO: use BorrowedCursor once stabilized
rdr.read(buf)?
};

Expand All @@ -1066,7 +1074,13 @@ impl Buffer {
let len = unsafe {
let buf = self.buf.write_buf();
let len = cmp::min(buf.len(), src.len());
buf[..len].copy_from_slice(&src[..len]);

// TODO: use MaybeUninit::copy_from_slice once stabilized

// SAFETY: &[T] and &[MaybeUninit<T>] have the same layout
let uninit_src: &[MaybeUninit<u8>] = std::mem::transmute(src);
buf[..len].copy_from_slice(&uninit_src[..len]);

len
};

Expand Down Expand Up @@ -1172,7 +1186,13 @@ impl Buffer {
}

unsafe {
self.buf.write_buf()[..s_len].copy_from_slice(bytes);
// TODO: use MaybeUninit::copy_from_slice once stabilized

// SAFETY: &[T] and &[MaybeUninit<T>] have the same layout
let uninit_src: &[MaybeUninit<u8>] = std::mem::transmute(bytes);
let buf = self.buf.write_buf();
buf[..s_len].copy_from_slice(uninit_src);

self.buf.bytes_written(s_len);
}
}
Expand Down Expand Up @@ -1293,9 +1313,9 @@ thread_local!(
///
/// ### Panics
/// If called from within a handler previously provided to this function.
pub fn set_drop_err_handler<F: 'static>(handler: F)
pub fn set_drop_err_handler<F>(handler: F)
where
F: Fn(&mut dyn Write, &mut Buffer, io::Error),
F: 'static + Fn(&mut dyn Write, &mut Buffer, io::Error),
{
DROP_ERR_HANDLER.with(|deh| *deh.borrow_mut() = Box::new(handler))
}
9 changes: 3 additions & 6 deletions src/ringbuf_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ fn test_buffered_reader_seek_underflow() {
self.pos = self.pos.wrapping_add(n as u64);
}
SeekFrom::End(n) => {
self.pos = u64::max_value().wrapping_add(n as u64);
self.pos = u64::MAX.wrapping_add(n as u64);
}
}
Ok(self.pos)
Expand All @@ -134,15 +134,12 @@ fn test_buffered_reader_seek_underflow() {

let mut reader = BufReader::with_capacity(5, PositionReader { pos: 0 });
assert_eq!(reader.fill_buf().ok(), Some(&[0, 1, 2, 3, 4][..]));
assert_eq!(
reader.seek(SeekFrom::End(-5)).ok(),
Some(u64::max_value() - 5)
);
assert_eq!(reader.seek(SeekFrom::End(-5)).ok(), Some(u64::MAX - 5));
assert_eq!(reader.fill_buf().ok().map(|s| s.len()), Some(5));
// the following seek will require two underlying seeks
let expected = 9223372036854775802;
assert_eq!(
reader.seek(SeekFrom::Current(i64::min_value())).ok(),
reader.seek(SeekFrom::Current(i64::MIN)).ok(),
Some(expected)
);
assert_eq!(reader.fill_buf().ok().map(|s| s.len()), Some(5));
Expand Down
9 changes: 3 additions & 6 deletions src/std_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ fn test_buffered_reader_seek_underflow() {
self.pos = self.pos.wrapping_add(n as u64);
}
SeekFrom::End(n) => {
self.pos = u64::max_value().wrapping_add(n as u64);
self.pos = u64::MAX.wrapping_add(n as u64);
}
}
Ok(self.pos)
Expand All @@ -111,15 +111,12 @@ fn test_buffered_reader_seek_underflow() {

let mut reader = BufReader::with_capacity(5, PositionReader { pos: 0 });
assert_eq!(reader.fill_buf().ok(), Some(&[0, 1, 2, 3, 4][..]));
assert_eq!(
reader.seek(SeekFrom::End(-5)).ok(),
Some(u64::max_value() - 5)
);
assert_eq!(reader.seek(SeekFrom::End(-5)).ok(), Some(u64::MAX - 5));
assert_eq!(reader.fill_buf().ok().map(|s| s.len()), Some(5));
// the following seek will require two underlying seeks
let expected = 9223372036854775802;
assert_eq!(
reader.seek(SeekFrom::Current(i64::min_value())).ok(),
reader.seek(SeekFrom::Current(i64::MIN)).ok(),
Some(expected)
);
assert_eq!(reader.fill_buf().ok().map(|s| s.len()), Some(5));
Expand Down

0 comments on commit b089996

Please sign in to comment.