Files
kopia/internal/gather/gather_write_buffer_chunk.go
Jarek Kowalski daa62de3e4 chore(ci): added checklocks static analyzer (#1838)
From https://github.com/google/gvisor/tree/master/tools/checklocks

This will perform static verification that we're using
`sync.Mutex`, `sync.RWMutex` and `atomic` correctly to guard access
to certain fields.

This was mostly just a matter of adding annotations to indicate which
fields are guarded by which mutex.

In a handful of places the code had to be refactored to allow static
analyzer to do its job better or to not be confused by some
constructs.

In one place this actually uncovered a bug where a function was not
releasing a lock properly in an error case.

The check is part of `make lint` but can also be invoked by
`make check-locks`.
2022-03-19 22:42:59 -07:00

177 lines
3.7 KiB
Go

package gather
import (
"context"
"fmt"
"os"
"reflect"
"runtime"
"strings"
"sync"
"unsafe"
)
const (
mynamespace = "kopia/kopia/internal/gather"
maxCallersToTrackAllocations = 3
)
// nolint:gochecknoglobals
var (
trackChunkAllocations = os.Getenv("KOPIA_TRACK_CHUNK_ALLOC") != ""
defaultAllocator = &chunkAllocator{
name: "default",
chunkSize: 1 << 16, // nolint:gomnd
maxFreeListSize: 2048, // nolint:gomnd
}
// typicalContiguousAllocator is used for short-term buffers for encryption.
typicalContiguousAllocator = &chunkAllocator{
name: "mid-size contiguous",
chunkSize: 8<<20 + 128, // nolint:gomnd
maxFreeListSize: runtime.NumCPU(),
}
// maxContiguousAllocator is used for short-term buffers for encryption.
maxContiguousAllocator = &chunkAllocator{
name: "contiguous",
chunkSize: 16<<20 + 128, // nolint:gomnd
maxFreeListSize: runtime.NumCPU(),
}
)
type chunkAllocator struct {
name string
chunkSize int
mu sync.Mutex
// +checklocks:mu
freeList [][]byte
// +checklocks:mu
maxFreeListSize int
// +checklocks:mu
freeListHighWaterMark int
// +checklocks:mu
allocHighWaterMark int
// +checklocks:mu
allocated int
// +checklocks:mu
slicesAllocated int
// +checklocks:mu
freed int
// +checklocks:mu
activeChunks map[uintptr]string
}
// +checklocks:a.mu
func (a *chunkAllocator) trackAlloc(v []byte) []byte {
if trackChunkAllocations {
var (
pcbuf [8]uintptr
callerFrames []string
)
n := runtime.Callers(maxCallersToTrackAllocations, pcbuf[:])
frames := runtime.CallersFrames(pcbuf[0:n])
for f, ok := frames.Next(); ok; f, ok = frames.Next() {
fn := fmt.Sprintf("%v %v:%v", f.Func.Name(), f.File, f.Line)
if fn != "" && !strings.Contains(fn, mynamespace) {
callerFrames = append(callerFrames, fn)
}
}
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&v)) //nolint:gosec
if a.activeChunks == nil {
a.activeChunks = map[uintptr]string{}
}
a.activeChunks[hdr.Data] = strings.Join(callerFrames, "\n")
}
return v
}
func (a *chunkAllocator) allocChunk() []byte {
a.mu.Lock()
defer a.mu.Unlock()
a.allocated++
if tot := a.allocated - a.freed; tot > a.allocHighWaterMark {
a.allocHighWaterMark = tot
}
l := len(a.freeList)
if l == 0 {
a.slicesAllocated++
return a.trackAlloc(make([]byte, 0, a.chunkSize))
}
ch := a.freeList[l-1]
a.freeList = a.freeList[0 : l-1]
return a.trackAlloc(ch)
}
func (a *chunkAllocator) releaseChunk(s []byte) {
if cap(s) != a.chunkSize {
return
}
a.mu.Lock()
defer a.mu.Unlock()
if a.activeChunks != nil {
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s)) //nolint:gosec
delete(a.activeChunks, hdr.Data)
}
a.freed++
if len(a.freeList) < a.maxFreeListSize {
a.freeList = append(a.freeList, s[:0])
}
if len(a.freeList) > a.freeListHighWaterMark {
a.freeListHighWaterMark = len(a.freeList)
}
}
func (a *chunkAllocator) dumpStats(ctx context.Context, prefix string) {
a.mu.Lock()
defer a.mu.Unlock()
alive := a.allocated - a.freed
log(ctx).Debugw("allocator stats",
"allocator", prefix,
"chunkSize", int64(a.chunkSize),
"chunksAlloc", a.allocated,
"chunksFreed", a.freed,
"chunksAlive", alive,
"allocHighWaterMark", a.allocHighWaterMark,
"freeListHighWaterMark", a.freeListHighWaterMark,
"slicesAlloc", a.slicesAllocated,
)
for _, v := range a.activeChunks {
log(ctx).Debugf("leaked chunk from %v", v)
}
}
// DumpStats logs the allocator statistics.
func DumpStats(ctx context.Context) {
defaultAllocator.dumpStats(ctx, "default")
typicalContiguousAllocator.dumpStats(ctx, "typical-contig")
maxContiguousAllocator.dumpStats(ctx, "contig")
}