LCOV - code coverage report
Current view: top level - pebble/sstable/block - buffer_pool.go (source / functions) Hit Total Coverage
Test: 2025-01-07 08:17Z 28edac9f - tests only.lcov Lines: 101 103 98.1 %
Date: 2025-01-07 08:17:39 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
       2             : // of this source code is governed by a BSD-style license that can be found in
       3             : // the LICENSE file.
       4             : 
       5             : package block
       6             : 
       7             : import (
       8             :         "github.com/cockroachdb/errors"
       9             :         "github.com/cockroachdb/pebble/internal/base"
      10             :         "github.com/cockroachdb/pebble/internal/cache"
      11             :         "github.com/cockroachdb/pebble/internal/invariants"
      12             : )
      13             : 
      14             : // Alloc allocates a new Value for a block of length n (excluding the block
      15             : // trailer, but including an associated Metadata). If bufferPool is non-nil,
      16             : // Alloc allocates the buffer from the pool. Otherwise it allocates it from the
      17             : // block cache.
      18           1 : func Alloc(n int, p *BufferPool) Value {
      19           1 :         if p != nil {
      20           1 :                 return Value{buf: p.Alloc(MetadataSize + n)}
      21           1 :         }
      22           1 :         return Value{v: cache.Alloc(MetadataSize + n)}
      23             : }
      24             : 
      25             : // Value is a block buffer, either backed by the block cache or a BufferPool.
      26             : type Value struct {
      27             :         // buf.Valid() returns true if backed by a BufferPool.
      28             :         buf Buf
      29             :         // v is non-nil if backed by the block cache.
      30             :         v *cache.Value
      31             : }
      32             : 
      33             : // getInternalBuf gets the underlying buffer which includes the Metadata and the
      34             : // block.
      35           1 : func (b Value) getInternalBuf() []byte {
      36           1 :         if b.buf.Valid() {
      37           1 :                 return b.buf.p.pool[b.buf.i].b
      38           1 :         }
      39           1 :         return b.v.Buf()
      40             : }
      41             : 
      42             : // BlockData returns the byte slice for the block data.
      43           1 : func (b Value) BlockData() []byte {
      44           1 :         return b.getInternalBuf()[MetadataSize:]
      45           1 : }
      46             : 
      47             : // GetMetadata returns the block metadata.
      48           1 : func (b Value) BlockMetadata() *Metadata {
      49           1 :         return (*Metadata)(b.getInternalBuf())
      50           1 : }
      51             : 
      52             : // MakeHandle constructs a BufferHandle from the Value. If the Value is not
      53             : // backed by a buffer pool, MakeHandle inserts the value into the block cache,
      54             : // returning a handle to the now resident value.
      55             : func (b Value) MakeHandle(
      56             :         crh cache.ReadHandle, cacheID cache.ID, fileNum base.DiskFileNum, offset uint64,
      57           1 : ) BufferHandle {
      58           1 :         if b.buf.Valid() {
      59           1 :                 if invariants.Enabled && crh.Valid() {
      60           0 :                         panic("cache.ReadHandle was valid")
      61             :                 }
      62           1 :                 return BufferHandle{b: b.buf}
      63             :         }
      64           1 :         return BufferHandle{h: crh.SetReadValue(b.v)}
      65             : }
      66             : 
      67             : func (b Value) SetInCacheForTesting(
      68             :         c *cache.Cache, cacheID cache.ID, fileNum base.DiskFileNum, offset uint64,
      69           1 : ) cache.Handle {
      70           1 :         return c.Set(cacheID, fileNum, offset, b.v)
      71           1 : }
      72             : 
      73             : // Release releases the handle.
      74           1 : func (b Value) Release() {
      75           1 :         if b.buf.Valid() {
      76           1 :                 b.buf.Release()
      77           1 :         } else {
      78           1 :                 cache.Free(b.v)
      79           1 :         }
      80             : }
      81             : 
      82             : // Truncate truncates the block to n bytes.
      83           1 : func (b Value) Truncate(n int) {
      84           1 :         n += MetadataSize
      85           1 :         if b.buf.Valid() {
      86           1 :                 b.buf.p.pool[b.buf.i].b = b.buf.p.pool[b.buf.i].b[:n]
      87           1 :         } else {
      88           1 :                 b.v.Truncate(n)
      89           1 :         }
      90             : }
      91             : 
      92             : // A BufferHandle is a handle to manually-managed memory. The handle may point
      93             : // to a block in the block cache (h.Get() != nil), or a buffer that exists
      94             : // outside the block cache allocated from a BufferPool (b.Valid()).
      95             : type BufferHandle struct {
      96             :         h cache.Handle
      97             :         b Buf
      98             : }
      99             : 
     100             : // CacheBufferHandle constructs a BufferHandle from a block cache Handle.
     101           1 : func CacheBufferHandle(h cache.Handle) BufferHandle {
     102           1 :         return BufferHandle{h: h}
     103           1 : }
     104             : 
     105             : // Valid returns true if the BufferHandle holds a value.
     106           1 : func (bh BufferHandle) Valid() bool {
     107           1 :         return bh.h.Valid() || bh.b.Valid()
     108           1 : }
     109             : 
     110           1 : func (bh BufferHandle) rawBuffer() []byte {
     111           1 :         if bh.h.Valid() {
     112           1 :                 return bh.h.RawBuffer()
     113           1 :         }
     114           1 :         return bh.b.p.pool[bh.b.i].b
     115             : }
     116             : 
     117             : // BlockMetadata returns the buffer for the block metadata.
     118           1 : func (bh BufferHandle) BlockMetadata() *Metadata {
     119           1 :         return (*Metadata)(bh.rawBuffer())
     120           1 : }
     121             : 
     122             : // BlockData retrieves the buffer for the block data.
     123           1 : func (bh BufferHandle) BlockData() []byte {
     124           1 :         return (bh.rawBuffer())[MetadataSize:]
     125           1 : }
     126             : 
     127             : // Release releases the buffer, either back to the block cache or BufferPool. It
     128             : // is okay to call Release on a zero-value BufferHandle (to no effect).
     129           1 : func (bh BufferHandle) Release() {
     130           1 :         bh.h.Release()
     131           1 :         bh.b.Release()
     132           1 : }
     133             : 
     134             : // A BufferPool holds a pool of buffers for holding sstable blocks. An initial
     135             : // size of the pool is provided on Init, but a BufferPool will grow to meet the
     136             : // largest working set size. It'll never shrink. When a buffer is released, the
     137             : // BufferPool recycles the buffer for future allocations.
     138             : //
     139             : // A BufferPool should only be used for short-lived allocations with
     140             : // well-understood working set sizes to avoid excessive memory consumption.
     141             : //
     142             : // BufferPool is not thread-safe.
     143             : type BufferPool struct {
     144             :         // pool contains all the buffers held by the pool, including buffers that
     145             :         // are in-use. For every i < len(pool): pool[i].v is non-nil.
     146             :         pool []AllocedBuffer
     147             : }
     148             : 
     149             : // AllocedBuffer is an allocated memory buffer.
     150             : type AllocedBuffer struct {
     151             :         v *cache.Value
     152             :         // b holds the current byte slice. It's backed by v, but may be a subslice
     153             :         // of v's memory while the buffer is in-use [ len(b) ≤ len(v.Buf()) ].
     154             :         //
     155             :         // If the buffer is not currently in-use, b is nil. When being recycled, the
     156             :         // BufferPool.Alloc will reset b to be a subslice of v.Buf().
     157             :         b []byte
     158             : }
     159             : 
     160             : // Init initializes the pool with an initial working set buffer size of
     161             : // `initialSize`.
     162           1 : func (p *BufferPool) Init(initialSize int) {
     163           1 :         *p = BufferPool{
     164           1 :                 pool: make([]AllocedBuffer, 0, initialSize),
     165           1 :         }
     166           1 : }
     167             : 
     168             : // InitPreallocated is like Init but for internal sstable package use in
     169             : // instances where a pre-allocated slice of []allocedBuffer already exists. It's
     170             : // used to avoid an extra allocation initializing BufferPool.pool.
     171           1 : func (p *BufferPool) InitPreallocated(pool []AllocedBuffer) {
     172           1 :         *p = BufferPool{
     173           1 :                 pool: pool[:0],
     174           1 :         }
     175           1 : }
     176             : 
     177             : // Release releases all buffers held by the pool and resets the pool to an
     178             : // uninitialized state.
     179           1 : func (p *BufferPool) Release() {
     180           1 :         for i := range p.pool {
     181           1 :                 if p.pool[i].b != nil {
     182           0 :                         panic(errors.AssertionFailedf("Release called on a BufferPool with in-use buffers"))
     183             :                 }
     184           1 :                 cache.Free(p.pool[i].v)
     185             :         }
     186           1 :         *p = BufferPool{}
     187             : }
     188             : 
     189             : // Alloc allocates a new buffer of size n. If the pool already holds a buffer at
     190             : // least as large as n, the pooled buffer is used instead.
     191             : //
     192             : // Alloc is O(MAX(N,M)) where N is the largest number of concurrently in-use
     193             : // buffers allocated and M is the initialSize passed to Init.
     194           1 : func (p *BufferPool) Alloc(n int) Buf {
     195           1 :         unusableBufferIdx := -1
     196           1 :         for i := 0; i < len(p.pool); i++ {
     197           1 :                 if p.pool[i].b == nil {
     198           1 :                         if len(p.pool[i].v.Buf()) >= n {
     199           1 :                                 p.pool[i].b = p.pool[i].v.Buf()[:n]
     200           1 :                                 return Buf{p: p, i: i}
     201           1 :                         }
     202           1 :                         unusableBufferIdx = i
     203             :                 }
     204             :         }
     205             : 
     206             :         // If we would need to grow the size of the pool to allocate another buffer,
     207             :         // but there was a slot available occupied by a buffer that's just too
     208             :         // small, replace the too-small buffer.
     209           1 :         if len(p.pool) == cap(p.pool) && unusableBufferIdx >= 0 {
     210           1 :                 i := unusableBufferIdx
     211           1 :                 cache.Free(p.pool[i].v)
     212           1 :                 p.pool[i].v = cache.Alloc(n)
     213           1 :                 p.pool[i].b = p.pool[i].v.Buf()
     214           1 :                 return Buf{p: p, i: i}
     215           1 :         }
     216             : 
     217             :         // Allocate a new buffer.
     218           1 :         v := cache.Alloc(n)
     219           1 :         p.pool = append(p.pool, AllocedBuffer{v: v, b: v.Buf()[:n]})
     220           1 :         return Buf{p: p, i: len(p.pool) - 1}
     221             : }
     222             : 
     223             : // A Buf holds a reference to a manually-managed, pooled byte buffer.
     224             : type Buf struct {
     225             :         p *BufferPool
     226             :         // i holds the index into p.pool where the buffer may be found. This scheme
     227             :         // avoids needing to allocate the handle to the buffer on the heap at the
     228             :         // cost of copying two words instead of one.
     229             :         i int
     230             : }
     231             : 
     232             : // Valid returns true if the buf holds a valid buffer.
     233           1 : func (b Buf) Valid() bool {
     234           1 :         return b.p != nil
     235           1 : }
     236             : 
     237             : // Release releases the buffer back to the pool.
     238           1 : func (b *Buf) Release() {
     239           1 :         if b.p == nil {
     240           1 :                 return
     241           1 :         }
     242             :         // Clear the allocedBuffer's byte slice. This signals the allocated buffer
     243             :         // is no longer in use and a future call to BufferPool.Alloc may reuse this
     244             :         // buffer.
     245           1 :         b.p.pool[b.i].b = nil
     246           1 :         b.p = nil
     247             : }

Generated by: LCOV version 1.14