From 9d625b1ec81886a57bc9b8ed135ab11434d59a02 Mon Sep 17 00:00:00 2001 From: Hongbo Zhang Date: Tue, 6 Jan 2026 14:07:50 +0800 Subject: [PATCH] add documentation for internal/io_buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add README.mbt.md and docstrings with mbt check examples for the sliding-window buffer used by async I/O. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/internal/io_buffer/README.mbt.md | 70 +++++++++++++++++ src/internal/io_buffer/read_buffer.mbt | 100 +++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 src/internal/io_buffer/README.mbt.md diff --git a/src/internal/io_buffer/README.mbt.md b/src/internal/io_buffer/README.mbt.md new file mode 100644 index 00000000..3581f8cc --- /dev/null +++ b/src/internal/io_buffer/README.mbt.md @@ -0,0 +1,70 @@ +# internal/io_buffer + +Sliding-window buffer used by the async I/O stack. + +## Overview + +`@internal/io_buffer` provides a small, low-level `Buffer` type used by +`@io.ReaderBuffer` and other internal readers. The buffer tracks three pieces of +state: + +- `buf`: the underlying `FixedArray[Byte]` +- `start`: offset of the first valid byte +- `len`: number of valid bytes + +This package is intentionally minimal and does not enforce invariants beyond +these fields. It is meant for internal use. + +## API + +- `Buffer::new(size)` creates an empty buffer with the given capacity. +- `Buffer::clear` drops data and releases storage. +- `Buffer::enlarge_to(n)` ensures there is space for `n` bytes starting from + `start`, compacting or reallocating as needed. +- `Buffer::drop(n)` consumes `n` bytes from the front. + +## Examples + +```mbt check +///| +test "basic window management" { + let buf = @io_buffer.Buffer::new(6) + buf.buf[0] = b'a' + buf.buf[1] = b'b' + buf.buf[2] = b'c' + buf.len = 3 + + // Consume two bytes. + @io_buffer.Buffer::drop(buf, 2) + inspect(buf.start, content="2") + inspect(buf.len, content="1") +} +``` + +```mbt check +///| +test "compaction preserves data" { + let buf = @io_buffer.Buffer::new(5) + buf.buf[3] = b'x' + buf.buf[4] = b'y' + buf.start = 3 + buf.len = 2 + @io_buffer.Buffer::enlarge_to(buf, 4) + let view = buf.buf.unsafe_reinterpret_as_bytes()[:buf.len] + inspect(view, content="b\"xy\"") +} +``` + +```mbt check +///| +test "resize rounds up to 1024 bytes" { + let buf = @io_buffer.Buffer::new(4) + @io_buffer.Buffer::enlarge_to(buf, 7) + inspect(buf.buf.length(), content="1024") +} +``` + +## Notes + +- The reallocation size is rounded up to 1024-byte segments. +- `clear` releases storage by replacing the internal buffer with an empty array. diff --git a/src/internal/io_buffer/read_buffer.mbt b/src/internal/io_buffer/read_buffer.mbt index 118ac0fe..3698352e 100644 --- a/src/internal/io_buffer/read_buffer.mbt +++ b/src/internal/io_buffer/read_buffer.mbt @@ -13,6 +13,12 @@ // limitations under the License. ///| +/// Internal byte buffer with a sliding window. +/// +/// `buf` stores the underlying bytes, `start` is the offset of the first valid +/// byte, and `len` is the number of valid bytes available. The buffer does not +/// enforce invariants beyond these fields; callers are responsible for keeping +/// them consistent. pub(all) struct Buffer { mut buf : FixedArray[Byte] mut start : Int @@ -20,12 +26,41 @@ pub(all) struct Buffer { } ///| +/// Create a new buffer with the given capacity. +/// +/// The buffer starts empty (`start = 0`, `len = 0`). +/// +/// # Example +/// ```mbt check +/// test "new buffer is empty" { +/// let buf = Buffer::new(8) +/// inspect(buf.buf.length(), content="8") +/// inspect(buf.start, content="0") +/// inspect(buf.len, content="0") +/// } +/// ``` #as_free_fn pub fn Buffer::new(size : Int) -> Buffer { { buf: FixedArray::make(size, 0), start: 0, len: 0 } } ///| +/// Clear the buffer and release its storage. +/// +/// After `clear`, the buffer has zero capacity and no valid bytes. +/// +/// # Example +/// ```mbt check +/// test "clear resets state" { +/// let buf = Buffer::new(4) +/// buf.start = 1 +/// buf.len = 2 +/// Buffer::clear(buf) +/// inspect(buf.buf.length(), content="0") +/// inspect(buf.start, content="0") +/// inspect(buf.len, content="0") +/// } +/// ``` pub fn Buffer::clear(buf : Buffer) -> Unit { buf.buf = [] buf.start = 0 @@ -36,6 +71,45 @@ pub fn Buffer::clear(buf : Buffer) -> Unit { const SEGMENT_SIZE = 1024 ///| +/// Ensure the buffer can hold at least `n` bytes starting from `start`. +/// +/// - If there is enough space after `start`, nothing changes. +/// - If the capacity is enough but space after `start` is not, the buffer is +/// compacted so valid bytes start at offset 0. +/// - If the capacity is insufficient, a new buffer is allocated with size +/// rounded up to a 1024-byte segment. +/// +/// # Example +/// ```mbt check +/// test "enlarge compacts in-place" { +/// let buf = Buffer::new(5) +/// buf.buf[3] = b'x' +/// buf.buf[4] = b'y' +/// buf.start = 3 +/// buf.len = 2 +/// Buffer::enlarge_to(buf, 4) +/// inspect(buf.start, content="0") +/// inspect(buf.buf.length(), content="5") +/// inspect(buf.len, content="2") +/// let view = buf.buf.unsafe_reinterpret_as_bytes()[:buf.len] +/// inspect(view, content="b\"xy\"") +/// } +/// ``` +/// +/// ```mbt check +/// test "enlarge grows to segment size" { +/// let buf = Buffer::new(4) +/// buf.buf[1] = b'a' +/// buf.buf[2] = b'b' +/// buf.start = 1 +/// buf.len = 2 +/// Buffer::enlarge_to(buf, 7) +/// inspect(buf.start, content="0") +/// inspect(buf.buf.length(), content="1024") +/// let view = buf.buf.unsafe_reinterpret_as_bytes()[:buf.len] +/// inspect(view, content="b\"ab\"") +/// } +/// ``` pub fn Buffer::enlarge_to(self : Buffer, n : Int) -> Unit { let capacity = self.buf.length() if self.start + n <= capacity { @@ -55,6 +129,32 @@ pub fn Buffer::enlarge_to(self : Buffer, n : Int) -> Unit { } ///| +/// Drop `n` bytes from the front of the buffer. +/// +/// If all bytes are dropped, `start` is reset to 0. +/// +/// # Example +/// ```mbt check +/// test "drop advances window" { +/// let buf = Buffer::new(6) +/// buf.start = 1 +/// buf.len = 4 +/// Buffer::drop(buf, 2) +/// inspect(buf.start, content="3") +/// inspect(buf.len, content="2") +/// } +/// ``` +/// +/// ```mbt check +/// test "drop to empty resets start" { +/// let buf = Buffer::new(3) +/// buf.start = 2 +/// buf.len = 1 +/// Buffer::drop(buf, 1) +/// inspect(buf.start, content="0") +/// inspect(buf.len, content="0") +/// } +/// ``` pub fn Buffer::drop(self : Buffer, n : Int) -> Unit { guard n <= self.len self.len -= n