github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/internal/cache/entry_normal.go (about)

     1  // Copyright 2020 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  //go:build (!invariants && !tracing) || race
     6  // +build !invariants,!tracing race
     7  
     8  package cache
     9  
    10  import (
    11  	"runtime"
    12  	"sync"
    13  	"unsafe"
    14  
    15  	"github.com/cockroachdb/pebble/internal/invariants"
    16  	"github.com/cockroachdb/pebble/internal/manual"
    17  )
    18  
    19  const (
    20  	entrySize            = int(unsafe.Sizeof(entry{}))
    21  	entryAllocCacheLimit = 128
    22  	// Avoid using runtime.SetFinalizer in race builds as finalizers tickle a bug
    23  	// in the Go race detector in go1.15 and earlier versions. This requires that
    24  	// entries are Go allocated rather than manually allocated.
    25  	//
    26  	// If cgo is disabled we need to allocate the entries using the Go allocator
    27  	// and is violates the Go GC rules to put Go pointers (such as the entry
    28  	// pointer fields) into untyped memory (i.e. a []byte).
    29  	entriesGoAllocated = invariants.RaceEnabled || !cgoEnabled
    30  )
    31  
    32  var entryAllocPool = sync.Pool{
    33  	New: func() interface{} {
    34  		return newEntryAllocCache()
    35  	},
    36  }
    37  
    38  func entryAllocNew() *entry {
    39  	a := entryAllocPool.Get().(*entryAllocCache)
    40  	e := a.alloc()
    41  	entryAllocPool.Put(a)
    42  	return e
    43  }
    44  
    45  func entryAllocFree(e *entry) {
    46  	a := entryAllocPool.Get().(*entryAllocCache)
    47  	a.free(e)
    48  	entryAllocPool.Put(a)
    49  }
    50  
    51  type entryAllocCache struct {
    52  	entries []*entry
    53  }
    54  
    55  func newEntryAllocCache() *entryAllocCache {
    56  	c := &entryAllocCache{}
    57  	if !entriesGoAllocated {
    58  		// Note the use of a "real" finalizer here (as opposed to a build tag-gated
    59  		// no-op finalizer). Without the finalizer, objects released from the pool
    60  		// and subsequently GC'd by the Go runtime would fail to have their manually
    61  		// allocated memory freed, which results in a memory leak.
    62  		// lint:ignore SetFinalizer
    63  		runtime.SetFinalizer(c, freeEntryAllocCache)
    64  	}
    65  	return c
    66  }
    67  
    68  func freeEntryAllocCache(obj interface{}) {
    69  	c := obj.(*entryAllocCache)
    70  	for i, e := range c.entries {
    71  		c.dealloc(e)
    72  		c.entries[i] = nil
    73  	}
    74  }
    75  
    76  func (c *entryAllocCache) alloc() *entry {
    77  	n := len(c.entries)
    78  	if n == 0 {
    79  		if entriesGoAllocated {
    80  			return &entry{}
    81  		}
    82  		b := manual.New(entrySize)
    83  		return (*entry)(unsafe.Pointer(&b[0]))
    84  	}
    85  	e := c.entries[n-1]
    86  	c.entries = c.entries[:n-1]
    87  	return e
    88  }
    89  
    90  func (c *entryAllocCache) dealloc(e *entry) {
    91  	if !entriesGoAllocated {
    92  		buf := (*[manual.MaxArrayLen]byte)(unsafe.Pointer(e))[:entrySize:entrySize]
    93  		manual.Free(buf)
    94  	}
    95  }
    96  
    97  func (c *entryAllocCache) free(e *entry) {
    98  	if len(c.entries) == entryAllocCacheLimit {
    99  		c.dealloc(e)
   100  		return
   101  	}
   102  	c.entries = append(c.entries, e)
   103  }