google.golang.org/grpc@v1.72.2/mem/buffers.go (about) 1 /* 2 * 3 * Copyright 2024 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 // Package mem provides utilities that facilitate memory reuse in byte slices 20 // that are used as buffers. 21 // 22 // # Experimental 23 // 24 // Notice: All APIs in this package are EXPERIMENTAL and may be changed or 25 // removed in a later release. 26 package mem 27 28 import ( 29 "fmt" 30 "sync" 31 "sync/atomic" 32 ) 33 34 // A Buffer represents a reference counted piece of data (in bytes) that can be 35 // acquired by a call to NewBuffer() or Copy(). A reference to a Buffer may be 36 // released by calling Free(), which invokes the free function given at creation 37 // only after all references are released. 38 // 39 // Note that a Buffer is not safe for concurrent access and instead each 40 // goroutine should use its own reference to the data, which can be acquired via 41 // a call to Ref(). 42 // 43 // Attempts to access the underlying data after releasing the reference to the 44 // Buffer will panic. 45 type Buffer interface { 46 // ReadOnlyData returns the underlying byte slice. Note that it is undefined 47 // behavior to modify the contents of this slice in any way. 48 ReadOnlyData() []byte 49 // Ref increases the reference counter for this Buffer. 50 Ref() 51 // Free decrements this Buffer's reference counter and frees the underlying 52 // byte slice if the counter reaches 0 as a result of this call. 53 Free() 54 // Len returns the Buffer's size. 55 Len() int 56 57 split(n int) (left, right Buffer) 58 read(buf []byte) (int, Buffer) 59 } 60 61 var ( 62 bufferPoolingThreshold = 1 << 10 63 64 bufferObjectPool = sync.Pool{New: func() any { return new(buffer) }} 65 refObjectPool = sync.Pool{New: func() any { return new(atomic.Int32) }} 66 ) 67 68 // IsBelowBufferPoolingThreshold returns true if the given size is less than or 69 // equal to the threshold for buffer pooling. This is used to determine whether 70 // to pool buffers or allocate them directly. 71 func IsBelowBufferPoolingThreshold(size int) bool { 72 return size <= bufferPoolingThreshold 73 } 74 75 type buffer struct { 76 origData *[]byte 77 data []byte 78 refs *atomic.Int32 79 pool BufferPool 80 } 81 82 func newBuffer() *buffer { 83 return bufferObjectPool.Get().(*buffer) 84 } 85 86 // NewBuffer creates a new Buffer from the given data, initializing the reference 87 // counter to 1. The data will then be returned to the given pool when all 88 // references to the returned Buffer are released. As a special case to avoid 89 // additional allocations, if the given buffer pool is nil, the returned buffer 90 // will be a "no-op" Buffer where invoking Buffer.Free() does nothing and the 91 // underlying data is never freed. 92 // 93 // Note that the backing array of the given data is not copied. 94 func NewBuffer(data *[]byte, pool BufferPool) Buffer { 95 // Use the buffer's capacity instead of the length, otherwise buffers may 96 // not be reused under certain conditions. For example, if a large buffer 97 // is acquired from the pool, but fewer bytes than the buffering threshold 98 // are written to it, the buffer will not be returned to the pool. 99 if pool == nil || IsBelowBufferPoolingThreshold(cap(*data)) { 100 return (SliceBuffer)(*data) 101 } 102 b := newBuffer() 103 b.origData = data 104 b.data = *data 105 b.pool = pool 106 b.refs = refObjectPool.Get().(*atomic.Int32) 107 b.refs.Add(1) 108 return b 109 } 110 111 // Copy creates a new Buffer from the given data, initializing the reference 112 // counter to 1. 113 // 114 // It acquires a []byte from the given pool and copies over the backing array 115 // of the given data. The []byte acquired from the pool is returned to the 116 // pool when all references to the returned Buffer are released. 117 func Copy(data []byte, pool BufferPool) Buffer { 118 if IsBelowBufferPoolingThreshold(len(data)) { 119 buf := make(SliceBuffer, len(data)) 120 copy(buf, data) 121 return buf 122 } 123 124 buf := pool.Get(len(data)) 125 copy(*buf, data) 126 return NewBuffer(buf, pool) 127 } 128 129 func (b *buffer) ReadOnlyData() []byte { 130 if b.refs == nil { 131 panic("Cannot read freed buffer") 132 } 133 return b.data 134 } 135 136 func (b *buffer) Ref() { 137 if b.refs == nil { 138 panic("Cannot ref freed buffer") 139 } 140 b.refs.Add(1) 141 } 142 143 func (b *buffer) Free() { 144 if b.refs == nil { 145 panic("Cannot free freed buffer") 146 } 147 148 refs := b.refs.Add(-1) 149 switch { 150 case refs > 0: 151 return 152 case refs == 0: 153 if b.pool != nil { 154 b.pool.Put(b.origData) 155 } 156 157 refObjectPool.Put(b.refs) 158 b.origData = nil 159 b.data = nil 160 b.refs = nil 161 b.pool = nil 162 bufferObjectPool.Put(b) 163 default: 164 panic("Cannot free freed buffer") 165 } 166 } 167 168 func (b *buffer) Len() int { 169 return len(b.ReadOnlyData()) 170 } 171 172 func (b *buffer) split(n int) (Buffer, Buffer) { 173 if b.refs == nil { 174 panic("Cannot split freed buffer") 175 } 176 177 b.refs.Add(1) 178 split := newBuffer() 179 split.origData = b.origData 180 split.data = b.data[n:] 181 split.refs = b.refs 182 split.pool = b.pool 183 184 b.data = b.data[:n] 185 186 return b, split 187 } 188 189 func (b *buffer) read(buf []byte) (int, Buffer) { 190 if b.refs == nil { 191 panic("Cannot read freed buffer") 192 } 193 194 n := copy(buf, b.data) 195 if n == len(b.data) { 196 b.Free() 197 return n, nil 198 } 199 200 b.data = b.data[n:] 201 return n, b 202 } 203 204 func (b *buffer) String() string { 205 return fmt.Sprintf("mem.Buffer(%p, data: %p, length: %d)", b, b.ReadOnlyData(), len(b.ReadOnlyData())) 206 } 207 208 // ReadUnsafe reads bytes from the given Buffer into the provided slice. 209 // It does not perform safety checks. 210 func ReadUnsafe(dst []byte, buf Buffer) (int, Buffer) { 211 return buf.read(dst) 212 } 213 214 // SplitUnsafe modifies the receiver to point to the first n bytes while it 215 // returns a new reference to the remaining bytes. The returned Buffer 216 // functions just like a normal reference acquired using Ref(). 217 func SplitUnsafe(buf Buffer, n int) (left, right Buffer) { 218 return buf.split(n) 219 } 220 221 type emptyBuffer struct{} 222 223 func (e emptyBuffer) ReadOnlyData() []byte { 224 return nil 225 } 226 227 func (e emptyBuffer) Ref() {} 228 func (e emptyBuffer) Free() {} 229 230 func (e emptyBuffer) Len() int { 231 return 0 232 } 233 234 func (e emptyBuffer) split(int) (left, right Buffer) { 235 return e, e 236 } 237 238 func (e emptyBuffer) read([]byte) (int, Buffer) { 239 return 0, e 240 } 241 242 // SliceBuffer is a Buffer implementation that wraps a byte slice. It provides 243 // methods for reading, splitting, and managing the byte slice. 244 type SliceBuffer []byte 245 246 // ReadOnlyData returns the byte slice. 247 func (s SliceBuffer) ReadOnlyData() []byte { return s } 248 249 // Ref is a noop implementation of Ref. 250 func (s SliceBuffer) Ref() {} 251 252 // Free is a noop implementation of Free. 253 func (s SliceBuffer) Free() {} 254 255 // Len is a noop implementation of Len. 256 func (s SliceBuffer) Len() int { return len(s) } 257 258 func (s SliceBuffer) split(n int) (left, right Buffer) { 259 return s[:n], s[n:] 260 } 261 262 func (s SliceBuffer) read(buf []byte) (int, Buffer) { 263 n := copy(buf, s) 264 if n == len(s) { 265 return n, nil 266 } 267 return n, s[n:] 268 }