Files
Joe Tsai b83f28ebfb util/bufpool: add a general purpose pool of buffers (#20867)
A naive sync.Pool that stores dynamically sized []byte
is incorrect since the Pool assumes that each stored item
carries approximately the same memory cost.

Different cost items should be stored in different pools.
The bufpool tracks buffer pools by powers of two
and provides a Buffer type that mimics the API of bytes.Buffer.

This is a performance optimization. Avoid using this unless
there is a clear performance gain for doing so.
Incorrect use can lead to widespread data corruption
(as is already the case with any incorrect sync.Pool use).

Updates tailscale/corp#21363

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2026-10-14 15:56:12 -07:00

161 lines
4.0 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package bufpool
import (
"io"
"sync"
)
// Buffer is like [bytes.Buffer], but grows using the global buffer pool.
// The Grow method returns an error if the buffer cannot be grown.
// A buffer must be obtained via [Get] or [Pool.Get].
type Buffer struct {
//lint:ignore U1000 intentional unused field
noShallowCopy [0]sync.Mutex
parent *Pool
// buf is the internal buf.
// - buf[:off] is the data that has already been read
// - buf[off:len(buf)] is the data ready to read
// - buf[len(buf):cap(buf)] is the available space to write into
buf []byte
off int
}
// Reset resets the buffer to be empty,
// but it retains the underlying storage for use by future writes.
func (b *Buffer) Reset() {
b.buf = b.buf[:0]
b.off = 0
}
// Len reports the length of the buffer.
func (b *Buffer) Len() int {
return len(b.buf) - b.off
}
// Cap reports the capacity of the buffer.
func (b *Buffer) Cap() int {
return cap(b.buf)
}
// Available report the unused capacity of the buffer.
func (b *Buffer) Available() int {
return cap(b.buf) - len(b.buf)
}
// AvailableBuffer returns an empty buffer with [Buffer.Available] capacity.
// This buffer is intended to be appended to and
// passed to an immediately succeeding [Buffer.Write] call.
// The buffer is only valid until the next write operation on b.
func (b *Buffer) AvailableBuffer() []byte {
return b.buf[len(b.buf):]
}
// Bytes returns the underlying buffer.
//
// The caller must ensure that the underlying buffer is no longer in use
// before putting the Buffer pack in the pool.
func (b *Buffer) Bytes() []byte {
return b.buf[b.off:]
}
// String returns the contents of the unread portion of the buffer as a string.
func (b *Buffer) String() string {
if b == nil {
return "<nil>" // special case for debugging
}
return string(b.Bytes())
}
// Grow expands the capacity of the buffer to accommodate n additional bytes
// without any further regrowth.
func (b *Buffer) Grow(n int) error {
if n < 0 {
panic("negative capacity")
}
if cap(b.buf)-len(b.buf) < n {
b2, err := b.parent.Get(len(b.buf) + n)
if err != nil {
return err
}
b2.buf = append(b2.buf[:0], b.buf[b.off:]...) // does not allocate
b.buf, b2.buf = b2.buf, b.buf
b.off = 0
b.parent.Put(b2)
}
return nil
}
// Next returns a slice containing the next n bytes from the buffer,
// advancing the buffer as if the bytes had been returned by [Buffer.Read].
//
// The caller must ensure that the underlying buffer is no longer in use
// before putting the Buffer pack in the pool.
func (b *Buffer) Next(n int) []byte {
data := b.buf[b.off : b.off+min(n, b.Len())]
b.off += len(data)
return data
}
// Read reads unread bytes from b into p. It implements [io.Reader].
func (b *Buffer) Read(p []byte) (int, error) {
if b.Len() == 0 {
return 0, io.EOF
}
n := copy(p, b.buf[b.off:])
b.off += n
if b.off >= len(b.buf) {
b.Reset()
}
return n, nil
}
// Write writes the provided bytes from p into b. It implements [io.Writer].
func (b *Buffer) Write(p []byte) (int, error) {
if err := b.Grow(len(p)); err != nil {
return 0, err
}
b.buf = b.buf[:len(b.buf)+len(p)]
copy(b.buf[len(b.buf)-len(p):], p) // noop if src and dst are the same
return len(p), nil
}
// ReadFrom reads content from r into b. It implements [io.ReaderFrom].
func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error) {
for {
// Always ensure there is at least 1 byte of available capacity.
if err := b.Grow(1); err != nil {
return n, err
}
// Read into the available buffer until io.EOF.
m, err := r.Read(b.buf[len(b.buf):cap(b.buf)])
b.buf = b.buf[:len(b.buf)+m]
n += int64(m)
if err != nil {
if err == io.EOF {
err = nil
}
return n, err
}
}
}
// WriteTo writes content from b into w. It implements [io.WriterTo].
func (b *Buffer) WriteTo(w io.Writer) (n int64, err error) {
if b.Len() > 0 {
var m int
m, err = w.Write(b.buf[b.off:])
n = int64(m)
b.off += m
}
if b.off >= len(b.buf) {
b.Reset()
}
return n, err
}