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
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
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() }
|
||||
Reference in New Issue
Block a user