-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathharness.go
More file actions
169 lines (150 loc) · 4.6 KB
/
Copy pathharness.go
File metadata and controls
169 lines (150 loc) · 4.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package main
import (
"context"
"fmt"
"math/rand"
"sync"
"time"
)
// CrashInjectionHarness provides deterministic failure injection
// for testing concurrent workflow system recovery paths.
type CrashInjectionHarness struct {
mu sync.Mutex
Seed int64
Failures map[string]int // failure type -> count
}
type FailureType string
const (
PanicRecovery FailureType = "panic"
OOMPath FailureType = "oom"
NetworkPartition FailureType = "network_partition"
DeadlockDetection FailureType = "deadlock"
SchedulerStall FailureType = "scheduler_stall"
ResourceExhaustion FailureType = "resource_exhaustion"
)
func NewCrashInjectionHarness(seed int64) *CrashInjectionHarness {
return &CrashInjectionHarness{
Seed: seed,
Failures: make(map[string]int),
}
}
func (h *CrashInjectionHarness) RecordFailure(ft FailureType) {
h.mu.Lock()
defer h.mu.Unlock()
h.Failures[string(ft)]++
}
func (h *CrashInjectionHarness) Count(ft FailureType) int {
h.mu.Lock()
defer h.mu.Unlock()
return h.Failures[string(ft)]
}
func (h *CrashInjectionHarness) TotalFailures() int {
h.mu.Lock()
defer h.mu.Unlock()
total := 0
for _, c := range h.Failures {
total += c
}
return total
}
// 1. PanicRecoveryPath: spawns a goroutine that panics and recovers
func (h *CrashInjectionHarness) PanicRecoveryPath(shouldPanic bool) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
h.RecordFailure(PanicRecovery)
}
}()
if shouldPanic {
panic("simulated crash: worker panic")
}
return nil
}
// 2. OOMPath: simulates memory exhaustion by rejecting allocation
func (h *CrashInjectionHarness) OOMPath(simulate bool) error {
if simulate {
h.RecordFailure(OOMPath)
return fmt.Errorf("OOM: memory allocation failed for buffer of size 1048576")
}
return nil
}
// 3. NetworkPartitionPath: simulates network unavailability
func (h *CrashInjectionHarness) NetworkPartitionPath(simulate bool, target string) error {
if simulate {
h.RecordFailure(NetworkPartition)
return fmt.Errorf("network partition: cannot reach %s (connection refused)", target)
}
return nil
}
// 4. DeadlockDetectionPath: detects potential deadlocks via timeout
func (h *CrashInjectionHarness) DeadlockDetectionPath(ctx context.Context, timeout time.Duration) error {
done := make(chan struct{})
go func() {
// Simulate work that might deadlock
time.Sleep(50 * time.Millisecond)
close(done)
}()
select {
case <-done:
return nil
case <-ctx.Done():
h.RecordFailure(DeadlockDetection)
return fmt.Errorf("deadlock detected: worker timed out after %v", timeout)
case <-time.After(timeout):
h.RecordFailure(DeadlockDetection)
return fmt.Errorf("deadlock detected: operation timed out after %v", timeout)
}
}
// 5. SchedulerStallPath: simulates scheduler not making progress
func (h *CrashInjectionHarness) SchedulerStallPath(simulate bool, stallDuration time.Duration) error {
if simulate {
// Detect stall by measuring actual vs expected duration
start := time.Now()
// Simulate a goroutine that doesn't yield
done := make(chan bool)
go func() {
// Busy loop simulating scheduler stall
for time.Since(start) < stallDuration {
}
done <- true
}()
<-done
elapsed := time.Since(start)
if elapsed >= stallDuration {
h.RecordFailure(SchedulerStall)
return fmt.Errorf("scheduler stall detected: expected %v, actual %v", stallDuration, elapsed)
}
}
return nil
}
// 6. ResourceExhaustionPath: simulates resource pool exhaustion
func (h *CrashInjectionHarness) ResourceExhaustionPath(simulate bool, resource string, poolSize int) error {
if simulate {
h.RecordFailure(ResourceExhaustion)
return fmt.Errorf("resource exhaustion: %s pool exhausted (max %d connections, all in use)", resource, poolSize)
}
return nil
}
// RunDeterministicTesting runs a set of test scenarios with deterministic failure injection.
// Returns a report of all failure modes triggered.
func (h *CrashInjectionHarness) RunDeterministicTesting(seed int64, scenarios []FailureType) map[string]int {
rng := rand.New(rand.NewSource(seed))
results := make(map[string]int)
for _, s := range scenarios {
shouldFail := rng.Intn(2) == 0
switch s {
case PanicRecovery:
h.PanicRecoveryPath(shouldFail)
case OOMPath:
h.OOMPath(shouldFail)
case NetworkPartition:
h.NetworkPartitionPath(shouldFail, fmt.Sprintf("node-%d", rng.Intn(100)))
case SchedulerStall:
h.SchedulerStallPath(shouldFail, 10*time.Millisecond)
case ResourceExhaustion:
h.ResourceExhaustionPath(shouldFail, "goroutine", 100)
}
results[string(s)] = h.Failures[string(s)]
}
return results
}