- wal/batch.go: WalBatch encode/decode with FragmentCollector state machine - wal/validate.go: ValidateBatchLimits with checked arithmetic - memtable/arena.go: Arena allocator with 8-byte alignment and mutex - Comprehensive tests for all modules, all pass with -race
85 lines
2.2 KiB
Go
85 lines
2.2 KiB
Go
package memtable
|
|
|
|
import (
|
|
"errors"
|
|
"sync"
|
|
"sync/atomic"
|
|
)
|
|
|
|
// ErrArenaFull is returned when the arena cannot satisfy an allocation request.
|
|
var ErrArenaFull = errors.New("arena: insufficient capacity")
|
|
|
|
// Arena is a bump allocator backed by a fixed-size byte slice.
|
|
// It provides thread-safe allocation with 8-byte alignment.
|
|
//
|
|
// The arena is used by the memtable to store keys, values, and skip-list
|
|
// node structures. Once allocated, bytes are never freed — the entire
|
|
// arena is discarded when the memtable is flushed.
|
|
type Arena struct {
|
|
buf []byte
|
|
offset uint32 // next allocation offset (atomic for reads)
|
|
capacity uint32 // total capacity (immutable)
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// NewArena creates a new Arena with the given capacity in bytes.
|
|
// The entire buffer is allocated upfront.
|
|
func NewArena(capacity uint32) *Arena {
|
|
return &Arena{
|
|
buf: make([]byte, capacity),
|
|
capacity: capacity,
|
|
}
|
|
}
|
|
|
|
// Allocate reserves alignedSize bytes in the arena and returns the offset
|
|
// at which the caller can write. The size is aligned up to 8 bytes.
|
|
func (a *Arena) Allocate(size uint32) (uint32, error) {
|
|
alignedSize := (size + 7) &^ uint32(7)
|
|
|
|
a.mu.Lock()
|
|
rem := a.capacity - a.offset
|
|
if rem < alignedSize {
|
|
a.mu.Unlock()
|
|
return 0, ErrArenaFull
|
|
}
|
|
off := a.offset
|
|
a.offset += alignedSize
|
|
a.mu.Unlock()
|
|
|
|
return off, nil
|
|
}
|
|
|
|
// GetBytes returns a slice of the arena buffer at [offset, offset+size).
|
|
// It panics if the range is out of bounds.
|
|
func (a *Arena) GetBytes(offset, size uint32) []byte {
|
|
end := offset + size
|
|
if end > a.capacity {
|
|
panic("arena: GetBytes out of bounds")
|
|
}
|
|
return a.buf[offset:end]
|
|
}
|
|
|
|
// Remaining returns the number of bytes still available for allocation.
|
|
func (a *Arena) Remaining() uint32 {
|
|
return a.capacity - atomic.LoadUint32(&a.offset)
|
|
}
|
|
|
|
// Capacity returns the total capacity of the arena.
|
|
func (a *Arena) Capacity() uint32 {
|
|
return a.capacity
|
|
}
|
|
|
|
// Reserve checks whether the arena has at least totalSize bytes remaining
|
|
// without actually allocating. It is used by the WAL writer to verify
|
|
// capacity before appending entries.
|
|
func (a *Arena) Reserve(totalSize uint32) error {
|
|
a.mu.Lock()
|
|
rem := a.capacity - a.offset
|
|
a.mu.Unlock()
|
|
|
|
if rem < totalSize {
|
|
return ErrArenaFull
|
|
}
|
|
return nil
|
|
}
|