mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-09 05:18:23 +01:00
feat: Phase 3 — performance tuning, iSCSI session refactor, store integration
Phase 3 delivers five checkpoints: CP1 Engine Tuning: BlockVolConfig tunables, 256-shard DirtyMap, adaptive group commit (low-watermark immediate flush), WAL pressure handling with backpressure and ErrWALFull timeout. CP2 iSCSI Session Refactor: RX/TX goroutine split with respCh (cap 64), txLoop for serialized response writes, StatSN assignment modes. Login phase stays single-goroutine; full-duplex after login. CP3 Store Integration: BlockVolAdapter (iscsi.BlockDevice interface), BlockVolumeStore management, BlockService in volume_server_block.go, CLI flags (--block.listen/dir/iqn.prefix), sw-block-attach.sh helper. CP5 Concurrency Hardening: WAL reuse guard (LSN validation in ReadLBA), opsOutstanding counter with beginOp/endOp + Close drain, appendWithRetry shared by WriteLBA and TrimLBA, flusher LSN guard in FlushOnce. Bug fixes (P3-BUG-2–11): unbounded pending queue cap, Data-Out timeout, flusher error logging, GroupCommitter panic recovery, Close vs concurrent ops guard, target shutdown race, WAL-full retry vs Close, WRITE SAME(16) for XFS, MODE SENSE(10) + VPD 0xB0/0xB2 for Linux kernel compatibility. 797 tests passing (517 engine + 280 iSCSI), go vet clean. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
9b7be60b0c
commit
80801b0fac
37 files changed
+6569
-461
No files matched your search
@@ -2,20 +2,25 @@ package blockvol
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrGroupCommitShutdown = errors.New("blockvol: group committer shut down")
|
||||
var (
|
||||
ErrGroupCommitShutdown = errors.New("blockvol: group committer shut down")
|
||||
ErrGroupCommitPanic = errors.New("blockvol: group committer panic in syncFunc")
|
||||
)
|
||||
|
||||
// GroupCommitter batches SyncCache requests and performs a single fsync
|
||||
// for the entire batch. This amortizes the cost of fsync across many callers.
|
||||
type GroupCommitter struct {
|
||||
syncFunc func() error // called to fsync (injectable for testing)
|
||||
maxDelay time.Duration // max wait before flushing a partial batch
|
||||
maxBatch int // flush immediately when this many waiters accumulate
|
||||
onDegraded func() // called when fsync fails
|
||||
syncFunc func() error // called to fsync (injectable for testing)
|
||||
maxDelay time.Duration // max wait before flushing a partial batch
|
||||
maxBatch int // flush immediately when this many waiters accumulate
|
||||
lowWatermark int // skip delay if fewer pending (0 = always wait)
|
||||
onDegraded func() // called when fsync fails
|
||||
|
||||
mu sync.Mutex
|
||||
pending []chan error
|
||||
@@ -30,10 +35,11 @@ type GroupCommitter struct {
|
||||
|
||||
// GroupCommitterConfig configures the group committer.
|
||||
type GroupCommitterConfig struct {
|
||||
SyncFunc func() error // required: the fsync function
|
||||
MaxDelay time.Duration // default 1ms
|
||||
MaxBatch int // default 64
|
||||
OnDegraded func() // optional: called on fsync error
|
||||
SyncFunc func() error // required: the fsync function
|
||||
MaxDelay time.Duration // default 1ms
|
||||
MaxBatch int // default 64
|
||||
LowWatermark int // skip delay if fewer pending (0 = always wait)
|
||||
OnDegraded func() // optional: called on fsync error
|
||||
}
|
||||
|
||||
// NewGroupCommitter creates a new group committer. Call Run() to start it.
|
||||
@@ -48,13 +54,14 @@ func NewGroupCommitter(cfg GroupCommitterConfig) *GroupCommitter {
|
||||
cfg.OnDegraded = func() {}
|
||||
}
|
||||
return &GroupCommitter{
|
||||
syncFunc: cfg.SyncFunc,
|
||||
maxDelay: cfg.MaxDelay,
|
||||
maxBatch: cfg.MaxBatch,
|
||||
onDegraded: cfg.OnDegraded,
|
||||
notifyCh: make(chan struct{}, 1),
|
||||
stopCh: make(chan struct{}),
|
||||
done: make(chan struct{}),
|
||||
syncFunc: cfg.SyncFunc,
|
||||
maxDelay: cfg.MaxDelay,
|
||||
maxBatch: cfg.MaxBatch,
|
||||
lowWatermark: cfg.LowWatermark,
|
||||
onDegraded: cfg.OnDegraded,
|
||||
notifyCh: make(chan struct{}, 1),
|
||||
stopCh: make(chan struct{}),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,29 +77,44 @@ func (gc *GroupCommitter) Run() {
|
||||
case <-gc.notifyCh:
|
||||
}
|
||||
|
||||
// Collect batch: wait up to maxDelay for more waiters, or until maxBatch reached.
|
||||
deadline := time.NewTimer(gc.maxDelay)
|
||||
for {
|
||||
// Adaptive: if pending count is below low watermark, flush immediately
|
||||
// without waiting for more waiters.
|
||||
skipDelay := false
|
||||
if gc.lowWatermark > 0 {
|
||||
gc.mu.Lock()
|
||||
n := len(gc.pending)
|
||||
gc.mu.Unlock()
|
||||
if n >= gc.maxBatch {
|
||||
deadline.Stop()
|
||||
break
|
||||
if n < gc.lowWatermark {
|
||||
skipDelay = true
|
||||
}
|
||||
select {
|
||||
case <-gc.stopCh:
|
||||
deadline.Stop()
|
||||
gc.markStoppedAndDrain()
|
||||
return
|
||||
case <-deadline.C:
|
||||
goto flush
|
||||
case <-gc.notifyCh:
|
||||
continue
|
||||
}
|
||||
|
||||
if !skipDelay {
|
||||
// Collect batch: wait up to maxDelay for more waiters, or until maxBatch reached.
|
||||
deadline := time.NewTimer(gc.maxDelay)
|
||||
collectDone := false
|
||||
for !collectDone {
|
||||
gc.mu.Lock()
|
||||
n := len(gc.pending)
|
||||
gc.mu.Unlock()
|
||||
if n >= gc.maxBatch {
|
||||
deadline.Stop()
|
||||
collectDone = true
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-gc.stopCh:
|
||||
deadline.Stop()
|
||||
gc.markStoppedAndDrain()
|
||||
return
|
||||
case <-deadline.C:
|
||||
collectDone = true
|
||||
case <-gc.notifyCh:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flush:
|
||||
// Take all pending waiters.
|
||||
gc.mu.Lock()
|
||||
batch := gc.pending
|
||||
@@ -103,8 +125,9 @@ func (gc *GroupCommitter) Run() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Perform fsync.
|
||||
err := gc.syncFunc()
|
||||
// Perform fsync with panic recovery. A panic in syncFunc must
|
||||
// not leave waiters hung — notify them and drain stragglers.
|
||||
err := gc.callSyncFunc()
|
||||
gc.syncCount.Add(1)
|
||||
if err != nil {
|
||||
gc.onDegraded()
|
||||
@@ -114,6 +137,12 @@ func (gc *GroupCommitter) Run() {
|
||||
for _, ch := range batch {
|
||||
ch <- err
|
||||
}
|
||||
|
||||
// If syncFunc panicked, stop accepting new work.
|
||||
if errors.Is(err, ErrGroupCommitPanic) {
|
||||
gc.markStoppedAndDrain()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +181,17 @@ func (gc *GroupCommitter) SyncCount() uint64 {
|
||||
return gc.syncCount.Load()
|
||||
}
|
||||
|
||||
// callSyncFunc calls syncFunc with panic recovery. Returns ErrGroupCommitPanic
|
||||
// wrapping the panic value if syncFunc panics.
|
||||
func (gc *GroupCommitter) callSyncFunc() (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("%w: %v", ErrGroupCommitPanic, r)
|
||||
}
|
||||
}()
|
||||
return gc.syncFunc()
|
||||
}
|
||||
|
||||
// markStoppedAndDrain sets the stopped flag under mu and drains all pending
|
||||
// waiters with ErrGroupCommitShutdown. This ensures no new Submit() can
|
||||
// enqueue after we drain, closing the race window from QA-002.
|
||||
|
||||
Reference in new issue
Block a user