mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-20 13:30:46 +02:00
H wiring: block_recovery.go now uses runtime.PendingCoordinator - Removed local pendingRecoveryExecution type + store/take/peek/has/cancel - ExecutePendingCatchUp/Rebuild delegate to coord.TakeCatchUp/TakeRebuild - Shutdown uses coord.CancelAll - Added CancelAll to PendingCoordinator I wiring: executeCatchUpPlan/executeRebuildPlan replaced - ExecutePendingCatchUp now calls rt.ExecuteCatchUpPlan with RecoveryManager as RecoveryCallbacks (OnCatchUpCompleted/OnRebuildCompleted) - ExecutePendingRebuild follows same pattern - Local executeCatchUpPlan/executeRebuildPlan methods removed J structural: legacy no-core branches extracted - executeLegacyCatchUp: wraps rt.ExecuteCatchUpPlan for v2Core==nil path - executeLegacyRebuild: wraps rt.ExecuteRebuildPlan for v2Core==nil path - Clear "LEGACY NO-CORE COMPATIBILITY" section with structural separation - runCatchUp/runRebuild now branch cleanly: legacy helper vs core coordinator Test updates: pendingRecoveryExecution → rt.PendingExecution, field casing, Plan type assertions. Validation: all P4, P16B, and ApplyAssignments tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
157 lines
4.6 KiB
Go
157 lines
4.6 KiB
Go
// Package runtime provides reusable recovery-coordination helpers for the
|
|
// V2 engine. These helpers are independent of weed/ host specifics and can
|
|
// be used by any adapter shell.
|
|
package runtime
|
|
|
|
import "sync"
|
|
|
|
// PendingExecution holds the state needed to execute a planned recovery
|
|
// action. The coordinator stores one pending execution per volume and
|
|
// matches it against incoming commands.
|
|
type PendingExecution struct {
|
|
VolumeID string
|
|
ReplicaID string
|
|
|
|
// CatchUpTarget is the target LSN for catch-up execution.
|
|
// Used by ExecutePendingCatchUp for fail-closed target matching.
|
|
CatchUpTarget uint64
|
|
|
|
// RebuildTargetLSN is the target LSN for rebuild execution.
|
|
// Used by ExecutePendingRebuild for fail-closed target matching.
|
|
RebuildTargetLSN uint64
|
|
|
|
// Opaque handles — the coordinator stores these but doesn't interpret them.
|
|
// The host adapter supplies concrete types (driver, plan, IO bindings).
|
|
Driver interface{}
|
|
Plan interface{}
|
|
CatchUpIO interface{}
|
|
RebuildIO interface{}
|
|
}
|
|
|
|
// CancelFunc is called when a pending execution is cancelled due to
|
|
// mismatch or supersession. The reason string explains why.
|
|
type CancelFunc func(pending *PendingExecution, reason string)
|
|
|
|
// PendingCoordinator manages pending recovery executions with fail-closed
|
|
// command matching. It is safe for concurrent use.
|
|
//
|
|
// Flow:
|
|
// 1. Store() caches a planned execution after recovery planning completes
|
|
// 2. TakeCatchUp/TakeRebuild matches an incoming command against the cached plan
|
|
// 3. If the target doesn't match, the pending plan is cancelled (fail-closed)
|
|
// 4. Cancel() explicitly cancels a pending execution
|
|
type PendingCoordinator struct {
|
|
mu sync.Mutex
|
|
pending map[string]*PendingExecution
|
|
cancelFn CancelFunc
|
|
}
|
|
|
|
// NewPendingCoordinator creates a coordinator with the given cancel callback.
|
|
// The cancel function is called when a pending execution is cancelled due to
|
|
// target mismatch or explicit cancellation.
|
|
func NewPendingCoordinator(cancelFn CancelFunc) *PendingCoordinator {
|
|
return &PendingCoordinator{
|
|
pending: make(map[string]*PendingExecution),
|
|
cancelFn: cancelFn,
|
|
}
|
|
}
|
|
|
|
// Store caches a pending execution for a volume, replacing any previous one.
|
|
func (pc *PendingCoordinator) Store(volumeID string, pe *PendingExecution) {
|
|
pc.mu.Lock()
|
|
defer pc.mu.Unlock()
|
|
pc.pending[volumeID] = pe
|
|
}
|
|
|
|
// TakeCatchUp takes the pending execution for the volume if the catch-up
|
|
// target matches. If there's a mismatch, the pending execution is cancelled
|
|
// (fail-closed) and nil is returned. If no pending execution exists, nil
|
|
// is returned.
|
|
func (pc *PendingCoordinator) TakeCatchUp(volumeID string, targetLSN uint64) *PendingExecution {
|
|
pc.mu.Lock()
|
|
pe, ok := pc.pending[volumeID]
|
|
if ok {
|
|
delete(pc.pending, volumeID)
|
|
}
|
|
pc.mu.Unlock()
|
|
|
|
if !ok || pe == nil {
|
|
return nil
|
|
}
|
|
if pe.CatchUpTarget != targetLSN {
|
|
if pc.cancelFn != nil {
|
|
pc.cancelFn(pe, "start_catchup_target_mismatch")
|
|
}
|
|
return nil
|
|
}
|
|
return pe
|
|
}
|
|
|
|
// TakeRebuild takes the pending execution for the volume if the rebuild
|
|
// target matches. Same fail-closed semantics as TakeCatchUp.
|
|
func (pc *PendingCoordinator) TakeRebuild(volumeID string, targetLSN uint64) *PendingExecution {
|
|
pc.mu.Lock()
|
|
pe, ok := pc.pending[volumeID]
|
|
if ok {
|
|
delete(pc.pending, volumeID)
|
|
}
|
|
pc.mu.Unlock()
|
|
|
|
if !ok || pe == nil {
|
|
return nil
|
|
}
|
|
if pe.RebuildTargetLSN != targetLSN {
|
|
if pc.cancelFn != nil {
|
|
pc.cancelFn(pe, "start_rebuild_target_mismatch")
|
|
}
|
|
return nil
|
|
}
|
|
return pe
|
|
}
|
|
|
|
// Has returns true if a pending execution exists for the volume.
|
|
func (pc *PendingCoordinator) Has(volumeID string) bool {
|
|
pc.mu.Lock()
|
|
defer pc.mu.Unlock()
|
|
_, ok := pc.pending[volumeID]
|
|
return ok
|
|
}
|
|
|
|
// Cancel explicitly cancels and removes the pending execution for a volume.
|
|
func (pc *PendingCoordinator) Cancel(volumeID, reason string) {
|
|
pc.mu.Lock()
|
|
pe, ok := pc.pending[volumeID]
|
|
if ok {
|
|
delete(pc.pending, volumeID)
|
|
}
|
|
pc.mu.Unlock()
|
|
|
|
if ok && pe != nil && pc.cancelFn != nil {
|
|
pc.cancelFn(pe, reason)
|
|
}
|
|
}
|
|
|
|
// Peek returns the pending execution without removing it. Returns nil if none.
|
|
func (pc *PendingCoordinator) Peek(volumeID string) *PendingExecution {
|
|
pc.mu.Lock()
|
|
defer pc.mu.Unlock()
|
|
return pc.pending[volumeID]
|
|
}
|
|
|
|
// CancelAll cancels and removes all pending executions.
|
|
func (pc *PendingCoordinator) CancelAll(reason string) {
|
|
pc.mu.Lock()
|
|
all := make(map[string]*PendingExecution, len(pc.pending))
|
|
for k, v := range pc.pending {
|
|
all[k] = v
|
|
}
|
|
pc.pending = make(map[string]*PendingExecution)
|
|
pc.mu.Unlock()
|
|
|
|
if pc.cancelFn != nil {
|
|
for _, pe := range all {
|
|
pc.cancelFn(pe, reason)
|
|
}
|
|
}
|
|
}
|