Skip to content

Commit 868f0b7

Browse files
Merge pull request #46 from triblespace/codex/evaluate-multiple-mutable-bytearena-buffers
Refactor ByteArena into ByteArea with SectionWriter
2 parents 430d178 + 0b7f089 commit 868f0b7

File tree

5 files changed

+180
-130
lines changed

5 files changed

+180
-130
lines changed

CHANGELOG.md

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
# Changelog
22

33
## Unreleased
4-
- added `ByteArena` for staged file writes with `Buffer::finish()` to return `Bytes`
5-
- `ByteArena::write` now accepts a zerocopy type instead of an alignment constant
6-
- `ByteArena` reuses previous pages so allocations align only to the element type
7-
- `Buffer::finish` converts the writable mapping to read-only instead of remapping
8-
- documented all fields in `ByteArena` and `Buffer`
9-
- documented ByteArena usage under advanced usage with proper heading
10-
- added `ByteArena::persist` to rename the temporary file
11-
- removed the old `ByteBuffer` type in favor of `ByteArena`
12-
- added tests covering `ByteArena` writes, typed buffers and persistence
4+
- added `ByteArea` for staged file writes with `Section::freeze()` to return `Bytes`
5+
- `SectionWriter::reserve` now accepts a zerocopy type instead of an alignment constant
6+
- `ByteArea` reuses previous pages so allocations align only to the element type
7+
- `Section::freeze` converts the writable mapping to read-only instead of remapping
8+
- simplified `ByteArea` by introducing `SectionWriter` for mutable access without
9+
interior mutability
10+
- tie `Section` lifetime to `ByteArea` to prevent dangling sections
11+
- allow multiple `ByteArea` sections at once with non-overlapping byte ranges
12+
- documented all fields in `ByteArea`, `SectionWriter` and `Section`
13+
- documented ByteArea usage under advanced usage with proper heading
14+
- added `ByteArea::persist` to rename the temporary file
15+
- removed the old `ByteBuffer` type in favor of `ByteArea`
16+
- added tests covering `ByteArea` sections, typed reserves and persistence
1317
- added test verifying alignment padding between differently aligned writes
1418
- split Kani verification into `verify.sh` and streamline `preflight.sh`
1519
- clarify that `verify.sh` runs on a dedicated system and document avoiding async code
@@ -34,6 +38,7 @@
3438
- compile-time assertion that `ALIGN` is a power of two
3539
- added `reserve_total` to `ByteBuffer` for reserving absolute capacity
3640
- fixed potential UB in `Bytes::try_unwrap_owner` for custom owners
41+
- renamed `ByteArea::writer` to `sections` for clarity
3742
- prevent dangling `data` by dropping references before unwrapping the owner
3843
- refined `Bytes::try_unwrap_owner` to cast the data slice to a pointer only
3944
when the owner type matches

README.md

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -101,26 +101,29 @@ fn read_header(file: &std::fs::File) -> std::io::Result<anybytes::view::View<Hea
101101
To map only a portion of a file use the unsafe helper
102102
`Bytes::map_file_region(file, offset, len)`.
103103

104-
### Byte Arena
104+
### Byte Area
105105

106-
Use `ByteArena` to incrementally build immutable bytes on disk:
106+
Use `ByteArea` to incrementally build immutable bytes on disk:
107107

108108
```rust
109-
use anybytes::arena::ByteArena;
109+
use anybytes::area::ByteArea;
110110

111-
let mut arena = ByteArena::new().unwrap();
112-
let mut buffer = arena.write::<u8>(4).unwrap();
113-
buffer.copy_from_slice(b"test");
114-
let bytes = buffer.finish().unwrap();
111+
let mut area = ByteArea::new().unwrap();
112+
let mut sections = area.sections();
113+
let mut section = sections.reserve::<u8>(4).unwrap();
114+
section.copy_from_slice(b"test");
115+
let bytes = section.freeze().unwrap();
115116
assert_eq!(bytes.as_ref(), b"test".as_ref());
116-
let all = arena.finish().unwrap();
117+
drop(sections);
118+
let all = area.freeze().unwrap();
117119
assert_eq!(all.as_ref(), b"test".as_ref());
118120
```
119121

120-
Call `arena.persist(path)` to keep the temporary file instead of mapping it.
122+
Call `area.persist(path)` to keep the temporary file instead of mapping it.
121123

122-
The arena only aligns allocations to the element type and may share pages
123-
between adjacent buffers to minimize wasted space.
124+
The area only aligns allocations to the element type and may share pages
125+
between adjacent sections to minimize wasted space. Multiple sections may be
126+
active simultaneously; their byte ranges do not overlap.
124127

125128
## Features
126129

src/arena.rs renamed to src/area.rs

Lines changed: 58 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
* LICENSE file in the root directory of this source tree.
77
*/
88

9-
//! Temporary byte arena backed by a file.
9+
//! Temporary byte area backed by a file.
1010
//!
11-
//! The arena allows staged writing through [`ByteArena::write`]. Each
12-
//! call returns a mutable [`Buffer`] bound to the arena so only one
13-
//! writer can exist at a time. Finalizing the buffer via
14-
//! [`Buffer::finish`] remaps the written range as immutable and
11+
//! The area offers staged writing through a [`SectionWriter`]. Each call to
12+
//! [`SectionWriter::reserve`] returns a mutable [`Section`] tied to the area's
13+
//! lifetime. Multiple sections may coexist; their byte ranges do not overlap.
14+
//! Freezing a section via [`Section::freeze`] remaps its range as immutable and
1515
//! returns [`Bytes`].
1616
1717
use std::io::{self, Seek, SeekFrom};
@@ -31,93 +31,98 @@ fn align_up(val: usize, align: usize) -> usize {
3131
(val + align - 1) & !(align - 1)
3232
}
3333

34-
/// Arena managing a temporary file.
34+
/// Area managing a temporary file.
3535
#[derive(Debug)]
36-
pub struct ByteArena {
37-
/// Temporary file backing the arena.
36+
pub struct ByteArea {
37+
/// Temporary file backing the area.
3838
file: NamedTempFile,
3939
/// Current length of initialized data in bytes.
4040
len: usize,
4141
}
4242

43-
impl ByteArena {
44-
/// Create a new empty arena.
43+
impl ByteArea {
44+
/// Create a new empty area.
4545
pub fn new() -> io::Result<Self> {
4646
let file = NamedTempFile::new()?;
4747
Ok(Self { file, len: 0 })
4848
}
4949

50-
/// Start a new write of `elems` elements of type `T`.
51-
pub fn write<'a, T>(&'a mut self, elems: usize) -> io::Result<Buffer<'a, T>>
50+
/// Obtain a handle for reserving sections.
51+
pub fn sections(&mut self) -> SectionWriter<'_> {
52+
SectionWriter { area: self }
53+
}
54+
55+
/// Freeze the area and return immutable bytes for the entire file.
56+
pub fn freeze(self) -> io::Result<Bytes> {
57+
let file = self.file.into_file();
58+
let mmap = unsafe { memmap2::MmapOptions::new().map(&file)? };
59+
Ok(Bytes::from_source(mmap))
60+
}
61+
62+
/// Persist the temporary area file to `path` and return the underlying [`File`].
63+
pub fn persist<P: AsRef<std::path::Path>>(self, path: P) -> io::Result<std::fs::File> {
64+
self.file.persist(path).map_err(Into::into)
65+
}
66+
}
67+
68+
/// RAII guard giving temporary exclusive write access.
69+
#[derive(Debug)]
70+
pub struct SectionWriter<'area> {
71+
area: &'area mut ByteArea,
72+
}
73+
74+
impl<'area> SectionWriter<'area> {
75+
/// Reserve a new section inside the area.
76+
pub fn reserve<T>(&mut self, elems: usize) -> io::Result<Section<'area, T>>
5277
where
5378
T: FromBytes + Immutable,
5479
{
5580
let page = page_size::get();
5681
let align = core::mem::align_of::<T>();
5782
let len_bytes = core::mem::size_of::<T>() * elems;
58-
let start = align_up(self.len, align);
83+
let start = align_up(self.area.len, align);
5984
let end = start + len_bytes;
60-
self.file.as_file_mut().set_len(end as u64)?;
61-
// Ensure subsequent mappings see the extended size.
62-
self.file.as_file_mut().seek(SeekFrom::Start(end as u64))?;
6385

64-
// Map must start on a page boundary; round `start` down while
65-
// keeping track of how far into the mapping the buffer begins.
6686
let aligned_offset = start & !(page - 1);
6787
let offset = start - aligned_offset;
6888
let map_len = end - aligned_offset;
6989

90+
let file = &mut self.area.file;
91+
file.as_file_mut().set_len(end as u64)?;
92+
// Ensure subsequent mappings see the extended size.
93+
file.as_file_mut().seek(SeekFrom::Start(end as u64))?;
7094
let mmap = unsafe {
7195
memmap2::MmapOptions::new()
7296
.offset(aligned_offset as u64)
7397
.len(map_len)
74-
.map_mut(self.file.as_file())?
98+
.map_mut(file.as_file())?
7599
};
76-
Ok(Buffer {
77-
arena: self,
100+
101+
self.area.len = end;
102+
103+
Ok(Section {
78104
mmap,
79-
start,
80105
offset,
81106
elems,
82107
_marker: PhantomData,
83108
})
84109
}
85-
86-
fn update_len(&mut self, end: usize) {
87-
self.len = end;
88-
}
89-
90-
/// Finalize the arena and return immutable bytes for the entire file.
91-
pub fn finish(self) -> io::Result<Bytes> {
92-
let file = self.file.into_file();
93-
let mmap = unsafe { memmap2::MmapOptions::new().map(&file)? };
94-
Ok(Bytes::from_source(mmap))
95-
}
96-
97-
/// Persist the temporary arena file to `path` and return the underlying [`File`].
98-
pub fn persist<P: AsRef<std::path::Path>>(self, path: P) -> io::Result<std::fs::File> {
99-
self.file.persist(path).map_err(Into::into)
100-
}
101110
}
102111

103-
/// Mutable buffer for writing into a [`ByteArena`].
112+
/// Mutable section reserved from a [`ByteArea`].
104113
#[derive(Debug)]
105-
pub struct Buffer<'a, T> {
106-
/// Arena that owns the underlying file.
107-
arena: &'a mut ByteArena,
114+
pub struct Section<'arena, T> {
108115
/// Writable mapping for the current allocation.
109116
mmap: memmap2::MmapMut,
110-
/// Start position of this buffer within the arena file in bytes.
111-
start: usize,
112117
/// Offset from the beginning of `mmap` to the start of the buffer.
113118
offset: usize,
114119
/// Number of elements in the buffer.
115120
elems: usize,
116-
/// Marker to tie the buffer to element type `T`.
117-
_marker: PhantomData<T>,
121+
/// Marker tying the section to the area and element type.
122+
_marker: PhantomData<(&'arena ByteArea, *mut T)>,
118123
}
119124

120-
impl<'a, T> Buffer<'a, T>
125+
impl<'arena, T> Section<'arena, T>
121126
where
122127
T: FromBytes + Immutable,
123128
{
@@ -129,21 +134,19 @@ where
129134
}
130135
}
131136

132-
/// Finalize the buffer and return immutable [`Bytes`].
133-
pub fn finish(self) -> io::Result<Bytes> {
137+
/// Freeze the section and return immutable [`Bytes`].
138+
pub fn freeze(self) -> io::Result<Bytes> {
134139
self.mmap.flush()?;
135140
let len_bytes = self.elems * core::mem::size_of::<T>();
136141
let offset = self.offset;
137-
let arena = self.arena;
138142
// Convert the writable mapping into a read-only view instead of
139143
// unmapping and remapping the region.
140144
let map = self.mmap.make_read_only()?;
141-
arena.update_len(self.start + len_bytes);
142145
Ok(Bytes::from_source(map).slice(offset..offset + len_bytes))
143146
}
144147
}
145148

146-
impl<'a, T> core::ops::Deref for Buffer<'a, T>
149+
impl<'arena, T> core::ops::Deref for Section<'arena, T>
147150
where
148151
T: FromBytes + Immutable,
149152
{
@@ -157,7 +160,7 @@ where
157160
}
158161
}
159162

160-
impl<'a, T> core::ops::DerefMut for Buffer<'a, T>
163+
impl<'arena, T> core::ops::DerefMut for Section<'arena, T>
161164
where
162165
T: FromBytes + Immutable,
163166
{
@@ -169,7 +172,7 @@ where
169172
}
170173
}
171174

172-
impl<'a, T> AsRef<[T]> for Buffer<'a, T>
175+
impl<'arena, T> AsRef<[T]> for Section<'arena, T>
173176
where
174177
T: FromBytes + Immutable,
175178
{
@@ -178,7 +181,7 @@ where
178181
}
179182
}
180183

181-
impl<'a, T> AsMut<[T]> for Buffer<'a, T>
184+
impl<'arena, T> AsMut<[T]> for Section<'arena, T>
182185
where
183186
T: FromBytes + Immutable,
184187
{

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#![warn(missing_docs)]
1111

1212
#[cfg(all(feature = "mmap", feature = "zerocopy"))]
13-
pub mod arena;
13+
pub mod area;
1414
/// Core byte container types and traits.
1515
pub mod bytes;
1616
mod sources;
@@ -31,7 +31,7 @@ pub mod winnow;
3131
mod tests;
3232

3333
#[cfg(all(feature = "mmap", feature = "zerocopy"))]
34-
pub use crate::arena::{Buffer, ByteArena};
34+
pub use crate::area::{ByteArea, Section, SectionWriter};
3535
pub use crate::bytes::ByteOwner;
3636
pub use crate::bytes::ByteSource;
3737
pub use crate::bytes::Bytes;

0 commit comments

Comments
 (0)