Files
seaweedfs/weed/storage/blockvol/replica_barrier.go
T
Ping QiuandClaude Opus 4.6 499e244b8e feat: durable progress truth — replicaFlushedLSN in barrier (CP13-3)
Barrier response extended from 1-byte status to 9-byte payload
carrying the replica's durable WAL progress (FlushedLSN). Updated
only after successful fd.Sync(), never on receive/append/send.

Replica side: new flushedLSN field on ReplicaReceiver, advanced
only in handleBarrier after proven contiguous receipt + sync.
max() guard prevents regression.

Shipper side: new replicaFlushedLSN (authoritative) replacing
ShippedLSN (diagnostic only). Monotonic CAS update from barrier
response. hasFlushedProgress flag tracks whether replica supports
the extended protocol.

ShipperGroup: MinReplicaFlushedLSN() returns (uint64, bool) —
minimum across shippers with known progress. (0, false) for empty
groups or legacy replicas.

Backward compat: 1-byte legacy responses decoded as FlushedLSN=0.
Legacy replicas explicitly excluded from sync_all correctness.

7 new tests: roundtrip, backward compat, flush-only-after-sync,
not-on-receive, shipper update, monotonicity, group minimum.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:52:35 -07:00

111 lines
2.5 KiB
Go

package blockvol
import (
"log"
"net"
"time"
)
// handleControlConn reads barrier requests from the control channel and
// responds with barrier status after ensuring durability.
func (r *ReplicaReceiver) handleControlConn(conn net.Conn) {
defer conn.Close()
for {
select {
case <-r.stopCh:
return
default:
}
msgType, payload, err := ReadFrame(conn)
if err != nil {
select {
case <-r.stopCh:
default:
log.Printf("replica: ctrl read error: %v", err)
}
return
}
if msgType != MsgBarrierReq {
log.Printf("replica: unexpected ctrl message type 0x%02x", msgType)
continue
}
req, err := DecodeBarrierRequest(payload)
if err != nil {
log.Printf("replica: decode barrier request: %v", err)
continue
}
resp := r.handleBarrier(req)
respPayload := EncodeBarrierResponse(resp)
if err := WriteFrame(conn, MsgBarrierResp, respPayload); err != nil {
log.Printf("replica: write barrier response: %v", err)
return
}
}
}
// handleBarrier waits until all WAL entries up to req.LSN have been received,
// then fsyncs the WAL to ensure durability.
func (r *ReplicaReceiver) handleBarrier(req BarrierRequest) BarrierResponse {
// Fail fast on epoch mismatch.
localEpoch := r.vol.epoch.Load()
if req.Epoch != localEpoch {
return BarrierResponse{Status: BarrierEpochMismatch}
}
// Use a timer goroutine to wake us on deadline.
timer := time.NewTimer(r.barrierTimeout)
defer timer.Stop()
timedOut := make(chan struct{})
go func() {
select {
case <-timer.C:
close(timedOut)
// Broadcast to wake any cond.Wait blocked in the loop below.
r.mu.Lock()
r.cond.Broadcast()
r.mu.Unlock()
case <-r.stopCh:
}
}()
r.mu.Lock()
for r.receivedLSN < req.LSN {
// Check if timed out or stopped.
select {
case <-timedOut:
r.mu.Unlock()
return BarrierResponse{Status: BarrierTimeout}
case <-r.stopCh:
r.mu.Unlock()
return BarrierResponse{Status: BarrierTimeout}
default:
}
// Block on cond.Wait -- woken by applyEntry or timeout goroutine.
r.cond.Wait()
}
r.mu.Unlock()
// fsync WAL AFTER confirming all entries received.
if err := r.vol.fd.Sync(); err != nil {
log.Printf("replica: barrier fsync error: %v", err)
return BarrierResponse{Status: BarrierFsyncFailed}
}
// Advance durable progress. Only after fd.Sync() succeeds and contiguous
// receipt through req.LSN has been proven (step 2 above).
r.mu.Lock()
if req.LSN > r.flushedLSN {
r.flushedLSN = req.LSN
}
flushed := r.flushedLSN
r.mu.Unlock()
return BarrierResponse{Status: BarrierOK, FlushedLSN: flushed}
}