Files
seaweedfs/weed/storage/volume_io_error_test.go
T
Chris Lu 729c3bf387 fix(volume): don't nuke local data on transient IO error (#9378)
A single syscall.EIO from any read/write/delete set v.lastIoError, and
the next CollectHeartbeat then called Volume.Destroy on the replica —
removing the .dat/.idx/.vif/.sdx/.ldb/.rdb files. A brief NFS / fabric
/ controller blip hitting several replicas at once could cascade into
removal of the last healthy copy, with no recovery for non-tiered
volumes.

Now require IoErrorTolerance (3) consecutive EIOs before acting, and on
that threshold mark the volume read-only and stop announcing it to the
master so re-replication kicks in from healthy peers — never delete
the data files. The on-disk copy stays for operator inspection /
recovery.
2026-05-08 22:19:42 -07:00

62 lines
1.7 KiB
Go

package storage
import (
"errors"
"fmt"
"syscall"
"testing"
)
func TestCheckReadWriteErrorTracksConsecutiveEIO(t *testing.T) {
v := &Volume{}
// non-EIO errors must not advance the counter.
v.checkReadWriteError(errors.New("some other error"))
if got := v.lastIoErrorCount.Load(); got != 0 {
t.Fatalf("non-EIO error advanced counter: got %d", got)
}
if v.lastIoError != nil {
t.Fatalf("non-EIO error set lastIoError: %v", v.lastIoError)
}
// each EIO bumps the counter.
for i := int32(1); i <= 5; i++ {
v.checkReadWriteError(fmt.Errorf("disk failed: %w", syscall.EIO))
if got := v.lastIoErrorCount.Load(); got != i {
t.Fatalf("after %d EIO(s): counter = %d, want %d", i, got, i)
}
if v.lastIoError == nil {
t.Fatalf("after %d EIO(s): lastIoError is nil", i)
}
}
// a single success resets both fields.
v.checkReadWriteError(nil)
if got := v.lastIoErrorCount.Load(); got != 0 {
t.Fatalf("success did not reset counter: got %d", got)
}
if v.lastIoError != nil {
t.Fatalf("success did not clear lastIoError: %v", v.lastIoError)
}
}
func TestIoErrorToleranceGate(t *testing.T) {
v := &Volume{}
// below tolerance: do not act.
for i := 0; i < IoErrorTolerance-1; i++ {
v.checkReadWriteError(fmt.Errorf("eio: %w", syscall.EIO))
}
if v.lastIoErrorCount.Load() >= IoErrorTolerance {
t.Fatalf("counter %d already crossed tolerance %d after %d errors",
v.lastIoErrorCount.Load(), IoErrorTolerance, IoErrorTolerance-1)
}
// one more crosses the threshold.
v.checkReadWriteError(fmt.Errorf("eio: %w", syscall.EIO))
if got := v.lastIoErrorCount.Load(); got < IoErrorTolerance {
t.Fatalf("counter %d below tolerance %d after %d errors",
got, IoErrorTolerance, IoErrorTolerance)
}
}