Files
go-kv/wal/sequence.go
T
dailz cf913b1d52 feat: initialize project structure and error types
- go.mod with github.com/dailz/go-kv, Go 1.26.3, testify
- config/config.go with WalConfig, Validate() with checked arithmetic
- errors.go with sentinel errors (ErrCommitUnknown, ErrWriteStopped, etc.)
- wal/constants.go with all WAL format constants and enums
- wal/header.go with WAL File Header encode/decode (CRC32 IEEE)
- wal/record.go with Physical Record codec, block boundary, SplitIntoRecords
- wal/entry.go with WAL Entry codec (varint keys/values, OpType, ValueKind)
- wal/sequence.go with SequenceManager (atomic, CAS, overflow-safe)
- manifest/manifest.go with MANIFEST stub (Load/Save atomic)
- manifest/current.go with CURRENT file (WriteCurrent/ReadCurrent)
- Comprehensive tests for all modules
- .golangci.yml configuration
2026-06-12 13:23:27 +08:00

88 lines
2.4 KiB
Go

package wal
import (
"sync/atomic"
"github.com/dailz/go-kv"
)
// SequenceManager manages monotonic sequence number allocation for the WAL.
// Invariant: durableSequence <= publishedSequence <= nextSequence
type SequenceManager struct {
nextSequence atomic.Uint64
publishedSequence atomic.Uint64
durableSequence atomic.Uint64
exhausted atomic.Bool
}
// NewSequenceManager creates a SequenceManager initialised from a recovered
// sequence number. All three watermarks start at recoveredSequence.
func NewSequenceManager(recoveredSequence uint64) *SequenceManager {
sm := &SequenceManager{}
sm.nextSequence.Store(recoveredSequence)
sm.publishedSequence.Store(recoveredSequence)
sm.durableSequence.Store(recoveredSequence)
return sm
}
// AllocateBatch atomically reserves [base, base+count-1] sequence numbers.
// Returns ErrSequenceExhausted if count is 0 or the allocation would overflow uint64.
func (sm *SequenceManager) AllocateBatch(count uint32) (baseSequence uint64, err error) {
if count == 0 {
return 0, go_kv.ErrSequenceExhausted
}
for {
if sm.exhausted.Load() {
return 0, go_kv.ErrSequenceExhausted
}
base := sm.nextSequence.Load()
last := base + uint64(count) - 1
if last < base {
return 0, go_kv.ErrSequenceExhausted
}
newNext := last + 1
if !sm.nextSequence.CompareAndSwap(base, newNext) {
continue
}
if newNext == 0 {
sm.exhausted.Store(true)
}
return base, nil
}
}
// Publish advances the publishedSequence watermark to seq (only forward).
func (sm *SequenceManager) Publish(seq uint64) {
for {
current := sm.publishedSequence.Load()
if seq <= current {
return
}
if sm.publishedSequence.CompareAndSwap(current, seq) {
return
}
}
}
// MarkDurable advances the durableSequence watermark to seq (only forward).
func (sm *SequenceManager) MarkDurable(seq uint64) {
for {
current := sm.durableSequence.Load()
if seq <= current {
return
}
if sm.durableSequence.CompareAndSwap(current, seq) {
return
}
}
}
// Published returns the current publishedSequence watermark.
func (sm *SequenceManager) Published() uint64 { return sm.publishedSequence.Load() }
// Durable returns the current durableSequence watermark.
func (sm *SequenceManager) Durable() uint64 { return sm.durableSequence.Load() }
// NextSequence returns the next sequence number to be allocated.
func (sm *SequenceManager) NextSequence() uint64 { return sm.nextSequence.Load() }