scratch

package
v0.27.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 3, 2026 License: MIT Imports: 3 Imported by: 0

README

scratch

Shared, size-bounded buffer recycling for the SDK's service codecs.

scratch centralises the *bytes.Buffer pooling that every codec previously reimplemented: one shared sync.Pool, one cap-discard threshold, one acquire/release pair.

API

const MaxRetainedBufBytes = 256 << 10

func AcquireBuffer() *bytes.Buffer          // returns an already-Reset buffer
func ReleaseBuffer(buf *bytes.Buffer)       // repools, or drops if Cap > MaxRetainedBufBytes
func DetachBuffer(buf *bytes.Buffer) []byte // the encoded bytes: cloned + repooled, or handed over if over the cap

func AcquireReader(src []byte) *bytes.Reader // returns a reader positioned at src
func ReleaseReader(r *bytes.Reader)          // repools (no cap-discard — fixed-size)

MaxRetainedBufBytes is the codec domain's one ceiling. A codec that pools a buffer shape this package does not offer passes it to a recycler.CappedPool of its own rather than declaring another threshold.

Usage

buf := scratch.AcquireBuffer()
// ... encode into buf ...
out := slices.Clone(buf.Bytes()) // clone before release if bytes escape
scratch.ReleaseBuffer(buf)
return out

Lifetime

A buffer is caller-owned from AcquireBuffer until ReleaseBuffer or DetachBuffer. After release, neither the buffer nor any slice aliasing buf.Bytes() may be used. Clone the encoded bytes first if they must outlive the release — or let DetachBuffer do it: it clones a buffer within the cap before repooling it, and hands an over-cap one over without a copy, since the pool would drop it anyway.

A single shared pool is used deliberately: sync.Pool is per-P sharded, so consolidation raises the reuse rate without adding contention.

This package is internal to the SDK; consumers reach codecs through pkg/v1/data/codec.

Documentation

Overview

Package scratch provides shared, size-bounded recycling primitives for the service codecs. Every codec that encodes into a transient *bytes.Buffer previously declared its own sync.Pool, its own 256 KiB cap-discard constant, and its own release helper. Centralising them here gives a single source of truth for the cap-discard policy and a single shared pool whose reuse rate is higher than nine independent pools (sync.Pool is internally per-P sharded, so consolidation does not add contention).

The pooling MECHANISM lives in internal/kernel/concur/recycler (ADR 0010); scratch is a codec-domain consumer that keeps the 256 KiB threshold and the concrete *bytes.Buffer / *bytes.Reader types here.

Lifetime contract: a value returned by an Acquire* call is owned by the caller until the matching Release* call. After Release the value — and any slice aliasing it (e.g. buf.Bytes()) — MUST NOT be used; clone the encoded bytes first if they need to outlive the release.

Index

Constants

View Source
const MaxRetainedBufBytes int = 256 << 10

MaxRetainedBufBytes is the project-wide cap-discard threshold for pooled codec buffers. A buffer whose capacity exceeds this is dropped on release rather than re-pooled, so a one-off oversized payload cannot pin a large allocation for the lifetime of the pool's GC window. It bounds the shared *bytes.Buffer pool below, and it is also the ceiling a codec passes to a recycler.CappedPool of its own when it pools a buffer shape this package does not offer — so the codec domain keeps one threshold, not one per codec.

View Source
const MaxRetainedSizedBytes int = 4 << 20

MaxRetainedSizedBytes is the capacity past which the size-classed tier drops a buffer: 4 MiB. Between MaxRetainedBufBytes and it, a released buffer joins the class its capacity falls in — up to 1 MiB, up to 4 MiB — so a workload of documents past 256 KiB reuses its buffers instead of growing one on every call; past it, one oversized payload still cannot pin its allocation.

Variables

This section is empty.

Functions

func AcquireBuffer

func AcquireBuffer() *bytes.Buffer

AcquireBuffer returns a clean, zero-length *bytes.Buffer from the shared pool. The recycler reset the buffer on its previous Put, so callers append directly without re-resetting. The caller owns it until ReleaseBuffer.

func AcquireBufferSized added in v0.27.0

func AcquireBufferSized(hint int) *bytes.Buffer

AcquireBufferSized returns a clean, zero-length *bytes.Buffer with room for at least hint bytes: from the shared pool when hint is within MaxRetainedBufBytes, else from the size class hint falls in — up to 1 MiB, up to 4 MiB — whose new buffers are allocated at the class's full size, so one serves every hint of its class. A codec that knows, or has seen, the size of what it encodes passes it, and a document past 256 KiB is encoded into a recycled buffer instead of a growth chain. The caller owns it until ReleaseBufferSized, which returns it to its class.

func AcquireReader

func AcquireReader(src []byte) *bytes.Reader

AcquireReader returns a *bytes.Reader from the shared pool, reset to read from src. The caller owns it until ReleaseReader; src must stay alive and unmodified for as long as the reader is used.

func DetachBuffer

func DetachBuffer(buf *bytes.Buffer) []byte

DetachBuffer hands the caller the bytes encoded into buf and ends the caller's ownership of buf, by the cheaper of two releases for its size. A buffer within MaxRetainedBufBytes is cloned and repooled: the clone is small, and the next encode reuses the buffer. An over-cap buffer would pay a full copy of a large payload, so it is orphaned instead — the size-classed tier is for codecs that ask AcquireBufferSized for their buffer and release it with ReleaseBufferSized: the returned slice IS its storage, now the caller's, and buf is left for the GC without a reset — a reset would not zero the bytes, but a later write through buf would overwrite them. Either way, buf must not be used after the call. A nil buf yields nil.

func ReleaseAppendWriter added in v0.27.0

func ReleaseAppendWriter(w *AppendWriter)

ReleaseAppendWriter returns w to the shared pool. The writer forgets the slice it held, so an idle writer keeps no caller's buffer alive. Callers MUST NOT use w after this call; the slice AppendWriter.Bytes returned stays theirs. A nil w is a no-op.

func ReleaseBuffer

func ReleaseBuffer(buf *bytes.Buffer)

ReleaseBuffer returns buf to the shared pool unless its capacity exceeds MaxRetainedBufBytes, in which case the recycler orphans it for the GC. Callers MUST NOT use buf — or any slice aliasing buf.Bytes() — after this call; clone first if the encoded bytes must outlive the release.

func ReleaseBufferSized added in v0.27.0

func ReleaseBufferSized(buf *bytes.Buffer)

ReleaseBufferSized returns buf to the pool its capacity belongs to: the shared pool within MaxRetainedBufBytes, the size class past it, and none past MaxRetainedSizedBytes, where the recycler orphans it for the GC. It is the release of a buffer AcquireBufferSized handed out — or of any buffer a codec grew past the shared pool's cap and means to reuse. Callers MUST NOT use buf, or any slice aliasing buf.Bytes(), after this call.

func ReleaseReader

func ReleaseReader(r *bytes.Reader)

ReleaseReader returns r to the shared pool. A bytes.Reader is fixed-size, so there is no cap-discard. Callers MUST NOT use r after this call.

Types

type AppendWriter added in v0.27.0

type AppendWriter struct {
	// contains filtered or unexported fields
}

AppendWriter is an io.Writer over a caller's slice: each Write appends to it, growing it as append grows a slice, and AppendWriter.Bytes returns the slice so far. AppendWriter.Truncate cuts it back to the length it had when the writer was acquired, so an encode that fails half-way leaves the caller's bytes as they were — the codec.Appender contract. It never fails a Write. Acquire one with AcquireAppendWriter for one encode, or keep one from NewAppendWriter; the zero AppendWriter appends to a nil slice.

func AcquireAppendWriter added in v0.27.0

func AcquireAppendWriter(dst []byte) *AppendWriter

AcquireAppendWriter returns an AppendWriter from the shared pool that appends to dst in place: what an encoder writes through it lands after dst's last byte, in dst's spare capacity while it lasts. It is how a codec's Append hands a caller's buffer to a standard-library encoder that takes an io.Writer, instead of encoding into a pooled buffer and copying the result onto dst. The caller owns the writer until ReleaseAppendWriter.

func NewAppendWriter added in v0.27.0

func NewAppendWriter(dst []byte) *AppendWriter

NewAppendWriter returns an AppendWriter appending to dst that is the caller's to keep, outside the pool: for a writer that lives as long as its owner — a stream encoder writing every record through the same one — where AcquireAppendWriter is for one encode.

func (*AppendWriter) Bytes added in v0.27.0

func (w *AppendWriter) Bytes() []byte

Bytes returns the slice so far: the bytes the writer was acquired with, then everything written since. It may be a new array when a Write outgrew the capacity of the one before.

func (*AppendWriter) Truncate added in v0.27.0

func (w *AppendWriter) Truncate() []byte

Truncate drops everything written since the writer was acquired and returns the slice cut back to its length then, so a failed encode hands the caller back the bytes it gave, unchanged.

func (*AppendWriter) Write added in v0.27.0

func (w *AppendWriter) Write(p []byte) (n int, err error)

Write appends p to the slice and reports len(p). It never returns an error, so an encoder writing through it fails only on its own account.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL