streambuf is a Go library that provides an append-only buffer with multiple independent readers.
It allows a single writer to continuously append bytes to a buffer, while any number of readers consume the data at their own pace, without interfering with each other.
The buffer can be backed by memory or by a file, making it suitable for both lightweight in-memory streaming and durable, disk-backed use cases.
Go’s standard library provides excellent primitives for streaming (io.Reader, io.Writer, bufio, channels), but it lacks a native abstraction for:
- Append-only data
- Multiple independent readers
- Late-joining readers
- Sequential, ordered reads
- Optional file-backed persistence
streambuf fills this gap by behaving like a shared, growing stream where readers maintain their own cursor.
This pattern shows up frequently in systems programming, including:
- Chat and messaging services
- Log streaming
- Fan-out pipelines
- Event feeds
- Streaming ingestion systems
- Testing and replay of streamed data
Below are quick API examples. For runnable end-to-end examples, see examples/.
func ExampleNew() {
var err error
if exampleBuffer, err = New("path/to/file"); err != nil {
log.Fatal(err)
}
}func ExampleNewReadOnly() {
var err error
// NewReadOnly constructs a read-only file-backed buffer.
// Calls to Write on this buffer return ErrCannotWriteToReadOnly.
if exampleBuffer, err = NewReadOnly("path/to/file"); err != nil {
log.Fatal(err)
}
}func ExampleNewMemory() {
exampleBuffer = NewMemory()
}func ExampleNewReadOnlyMemory() {
// NewReadOnlyMemory constructs a read-only memory-backed buffer.
// Calls to Write on this buffer return ErrCannotWriteToReadOnly.
exampleBuffer = NewReadOnlyMemory([]byte("hello world"))
}func ExampleBuffer_Write() {
if _, err := exampleBuffer.Write([]byte("hello world")); err != nil {
log.Fatal(err)
}
}func ExampleBuffer_Reader() {
var err error
if _, err = exampleBuffer.Write([]byte("hello world")); err != nil {
log.Fatal(err)
}
var (
r1 io.ReadSeekCloser
r2 io.ReadSeekCloser
r3 io.ReadSeekCloser
)
if r1, err = exampleBuffer.Reader(); err != nil {
log.Fatal(err)
}
defer r1.Close()
if r2, err = exampleBuffer.Reader(); err != nil {
log.Fatal(err)
}
defer r2.Close()
if r3, err = exampleBuffer.Reader(); err != nil {
log.Fatal(err)
}
defer r3.Close()
// Each reader is independent and maintains its own read offset.
// Reads or seeks on r1 do not affect r2 or r3.
}func ExampleBuffer_Close() {
// Close closes the backend immediately and does not wait for readers to finish.
if err := exampleBuffer.Close(); err != nil {
log.Fatal(err)
}
}func ExampleBuffer_CloseAndWait() {
// CloseAndWait blocks until the backend is closed and all readers are closed,
// or until the provided context is done.
if err := exampleBuffer.CloseAndWait(context.Background()); err != nil {
log.Fatal(err)
}
}Data is written once and never modified in place.
Writes always append to the end of the buffer.
Each reader maintains its own read position. Readers do not block or consume data from each other.
Readers may:
- Start from the beginning
- Start from the current end
- Join after data has already been written
Readers block when no data is available and resume automatically when new data is appended.
For read-only buffers (NewReadOnly, NewReadOnlyMemory), this means reaching the
current end of the preloaded data/file will also block until the buffer is closed
or the reader is closed.
If you are treating a read-only buffer as a finite snapshot, call Close() (or
CloseAndWait(...)) on the buffer after readers finish consuming data, or close
the reader directly, to unblock waiting reads and complete shutdown cleanly.
Close()closes immediately. Existing unread bytes may no longer be available to readers.CloseAndWait(ctx)closes writes and waits for readers untilctxis canceled.ctxcan be a timeout/deadline context to bound how long shutdown waits.- Terminal reads after either buffer close or reader close return
ErrIsClosed. - To preserve reader drain behavior, finish reading first, then call
CloseAndWait(or coordinate with readerClosecalls and context cancellation). - If
ctxis canceled before readers close,CloseAndWaitstill returns and the buffer stays closed; close outstanding readers afterward to finish internal wait cleanup.
streambuf supports multiple backing implementations:
- Memory-backed (
[]byte) - File-backed (using a shared file descriptor)
- Read-only memory-backed (preloaded
[]byte) - Read-only file-backed (existing file opened read-only)
Both implementations expose the same behavior and API.
This project is intentionally human-authored for all logic.
To be explicit:
- AI does not write or modify non-test code in this repository.
- AI does not make architectural or behavioral decisions.
- AI may assist with documentation, comments, and test scaffolding only.
- All implementation logic is written and reviewed by human maintainers.
These boundaries are enforced in AGENTS.md and are part of this repository's contribution discipline.
- Human maintainers: library design, implementation, and behavior decisions.
- ChatGPT Codex: documentation, test coverage support, and comments.
- Google Gemini: README artwork generation.

