mirror of https://github.com/docker/cli.git
253 lines
6.2 KiB
Go
253 lines
6.2 KiB
Go
/*
|
|
*
|
|
* Copyright 2024 gRPC authors.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
*/
|
|
|
|
// Package mem provides utilities that facilitate memory reuse in byte slices
|
|
// that are used as buffers.
|
|
//
|
|
// # Experimental
|
|
//
|
|
// Notice: All APIs in this package are EXPERIMENTAL and may be changed or
|
|
// removed in a later release.
|
|
package mem
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"sync/atomic"
|
|
)
|
|
|
|
// A Buffer represents a reference counted piece of data (in bytes) that can be
|
|
// acquired by a call to NewBuffer() or Copy(). A reference to a Buffer may be
|
|
// released by calling Free(), which invokes the free function given at creation
|
|
// only after all references are released.
|
|
//
|
|
// Note that a Buffer is not safe for concurrent access and instead each
|
|
// goroutine should use its own reference to the data, which can be acquired via
|
|
// a call to Ref().
|
|
//
|
|
// Attempts to access the underlying data after releasing the reference to the
|
|
// Buffer will panic.
|
|
type Buffer interface {
|
|
// ReadOnlyData returns the underlying byte slice. Note that it is undefined
|
|
// behavior to modify the contents of this slice in any way.
|
|
ReadOnlyData() []byte
|
|
// Ref increases the reference counter for this Buffer.
|
|
Ref()
|
|
// Free decrements this Buffer's reference counter and frees the underlying
|
|
// byte slice if the counter reaches 0 as a result of this call.
|
|
Free()
|
|
// Len returns the Buffer's size.
|
|
Len() int
|
|
|
|
split(n int) (left, right Buffer)
|
|
read(buf []byte) (int, Buffer)
|
|
}
|
|
|
|
var (
|
|
bufferPoolingThreshold = 1 << 10
|
|
|
|
bufferObjectPool = sync.Pool{New: func() any { return new(buffer) }}
|
|
refObjectPool = sync.Pool{New: func() any { return new(atomic.Int32) }}
|
|
)
|
|
|
|
func IsBelowBufferPoolingThreshold(size int) bool {
|
|
return size <= bufferPoolingThreshold
|
|
}
|
|
|
|
type buffer struct {
|
|
origData *[]byte
|
|
data []byte
|
|
refs *atomic.Int32
|
|
pool BufferPool
|
|
}
|
|
|
|
func newBuffer() *buffer {
|
|
return bufferObjectPool.Get().(*buffer)
|
|
}
|
|
|
|
// NewBuffer creates a new Buffer from the given data, initializing the reference
|
|
// counter to 1. The data will then be returned to the given pool when all
|
|
// references to the returned Buffer are released. As a special case to avoid
|
|
// additional allocations, if the given buffer pool is nil, the returned buffer
|
|
// will be a "no-op" Buffer where invoking Buffer.Free() does nothing and the
|
|
// underlying data is never freed.
|
|
//
|
|
// Note that the backing array of the given data is not copied.
|
|
func NewBuffer(data *[]byte, pool BufferPool) Buffer {
|
|
if pool == nil || IsBelowBufferPoolingThreshold(len(*data)) {
|
|
return (SliceBuffer)(*data)
|
|
}
|
|
b := newBuffer()
|
|
b.origData = data
|
|
b.data = *data
|
|
b.pool = pool
|
|
b.refs = refObjectPool.Get().(*atomic.Int32)
|
|
b.refs.Add(1)
|
|
return b
|
|
}
|
|
|
|
// Copy creates a new Buffer from the given data, initializing the reference
|
|
// counter to 1.
|
|
//
|
|
// It acquires a []byte from the given pool and copies over the backing array
|
|
// of the given data. The []byte acquired from the pool is returned to the
|
|
// pool when all references to the returned Buffer are released.
|
|
func Copy(data []byte, pool BufferPool) Buffer {
|
|
if IsBelowBufferPoolingThreshold(len(data)) {
|
|
buf := make(SliceBuffer, len(data))
|
|
copy(buf, data)
|
|
return buf
|
|
}
|
|
|
|
buf := pool.Get(len(data))
|
|
copy(*buf, data)
|
|
return NewBuffer(buf, pool)
|
|
}
|
|
|
|
func (b *buffer) ReadOnlyData() []byte {
|
|
if b.refs == nil {
|
|
panic("Cannot read freed buffer")
|
|
}
|
|
return b.data
|
|
}
|
|
|
|
func (b *buffer) Ref() {
|
|
if b.refs == nil {
|
|
panic("Cannot ref freed buffer")
|
|
}
|
|
b.refs.Add(1)
|
|
}
|
|
|
|
func (b *buffer) Free() {
|
|
if b.refs == nil {
|
|
panic("Cannot free freed buffer")
|
|
}
|
|
|
|
refs := b.refs.Add(-1)
|
|
switch {
|
|
case refs > 0:
|
|
return
|
|
case refs == 0:
|
|
if b.pool != nil {
|
|
b.pool.Put(b.origData)
|
|
}
|
|
|
|
refObjectPool.Put(b.refs)
|
|
b.origData = nil
|
|
b.data = nil
|
|
b.refs = nil
|
|
b.pool = nil
|
|
bufferObjectPool.Put(b)
|
|
default:
|
|
panic("Cannot free freed buffer")
|
|
}
|
|
}
|
|
|
|
func (b *buffer) Len() int {
|
|
return len(b.ReadOnlyData())
|
|
}
|
|
|
|
func (b *buffer) split(n int) (Buffer, Buffer) {
|
|
if b.refs == nil {
|
|
panic("Cannot split freed buffer")
|
|
}
|
|
|
|
b.refs.Add(1)
|
|
split := newBuffer()
|
|
split.origData = b.origData
|
|
split.data = b.data[n:]
|
|
split.refs = b.refs
|
|
split.pool = b.pool
|
|
|
|
b.data = b.data[:n]
|
|
|
|
return b, split
|
|
}
|
|
|
|
func (b *buffer) read(buf []byte) (int, Buffer) {
|
|
if b.refs == nil {
|
|
panic("Cannot read freed buffer")
|
|
}
|
|
|
|
n := copy(buf, b.data)
|
|
if n == len(b.data) {
|
|
b.Free()
|
|
return n, nil
|
|
}
|
|
|
|
b.data = b.data[n:]
|
|
return n, b
|
|
}
|
|
|
|
// String returns a string representation of the buffer. May be used for
|
|
// debugging purposes.
|
|
func (b *buffer) String() string {
|
|
return fmt.Sprintf("mem.Buffer(%p, data: %p, length: %d)", b, b.ReadOnlyData(), len(b.ReadOnlyData()))
|
|
}
|
|
|
|
func ReadUnsafe(dst []byte, buf Buffer) (int, Buffer) {
|
|
return buf.read(dst)
|
|
}
|
|
|
|
// SplitUnsafe modifies the receiver to point to the first n bytes while it
|
|
// returns a new reference to the remaining bytes. The returned Buffer functions
|
|
// just like a normal reference acquired using Ref().
|
|
func SplitUnsafe(buf Buffer, n int) (left, right Buffer) {
|
|
return buf.split(n)
|
|
}
|
|
|
|
type emptyBuffer struct{}
|
|
|
|
func (e emptyBuffer) ReadOnlyData() []byte {
|
|
return nil
|
|
}
|
|
|
|
func (e emptyBuffer) Ref() {}
|
|
func (e emptyBuffer) Free() {}
|
|
|
|
func (e emptyBuffer) Len() int {
|
|
return 0
|
|
}
|
|
|
|
func (e emptyBuffer) split(n int) (left, right Buffer) {
|
|
return e, e
|
|
}
|
|
|
|
func (e emptyBuffer) read(buf []byte) (int, Buffer) {
|
|
return 0, e
|
|
}
|
|
|
|
type SliceBuffer []byte
|
|
|
|
func (s SliceBuffer) ReadOnlyData() []byte { return s }
|
|
func (s SliceBuffer) Ref() {}
|
|
func (s SliceBuffer) Free() {}
|
|
func (s SliceBuffer) Len() int { return len(s) }
|
|
|
|
func (s SliceBuffer) split(n int) (left, right Buffer) {
|
|
return s[:n], s[n:]
|
|
}
|
|
|
|
func (s SliceBuffer) read(buf []byte) (int, Buffer) {
|
|
n := copy(buf, s)
|
|
if n == len(s) {
|
|
return n, nil
|
|
}
|
|
return n, s[n:]
|
|
}
|