• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rom8726 / chaoskit / 19373958347

14 Nov 2025 06:27PM UTC coverage: 43.843% (-1.0%) from 44.804%
19373958347

push

github

rom8726
Added InfiniteLoopValidator to detect infinite loops in step execution, implemented step wrapping mechanism in `executor`, and provided robust infinity loop detection example with documentation.

7 of 105 new or added lines in 4 files covered. (6.67%)

2 existing lines in 1 file now uncovered.

1766 of 4028 relevant lines covered (43.84%)

825.09 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/validators/infinity_loop.go
1
package validators
2

3
import (
4
        "context"
5
        "errors"
6
        "fmt"
7
        "log/slog"
8
        "sync"
9
        "time"
10

11
        "github.com/rom8726/chaoskit"
12
)
13

14
// InfiniteLoopValidator detects infinite loops by applying a timeout to step execution
15
type InfiniteLoopValidator struct {
16
        name            string
17
        timeout         time.Duration
18
        mu              sync.Mutex
19
        detectionsCount int64
20
}
21

22
// NoInfiniteLoop creates an infinite loop detector validator
NEW
23
func NoInfiniteLoop(timeout time.Duration) *InfiniteLoopValidator {
×
NEW
24
        return &InfiniteLoopValidator{
×
NEW
25
                name:    fmt.Sprintf("no_infinite_loop_%v", timeout),
×
NEW
26
                timeout: timeout,
×
NEW
27
        }
×
NEW
28
}
×
29

NEW
30
func (v *InfiniteLoopValidator) Name() string {
×
NEW
31
        return v.name
×
NEW
32
}
×
33

NEW
34
func (v *InfiniteLoopValidator) Severity() chaoskit.ValidationSeverity {
×
NEW
35
        return chaoskit.SeverityCritical
×
NEW
36
}
×
37

38
// Validate is called after each iteration - no validation here,
39
// as actual detection happens at executor level through WrapStep
NEW
40
func (v *InfiniteLoopValidator) Validate(ctx context.Context, target chaoskit.Target) error {
×
NEW
41
        return nil
×
NEW
42
}
×
43

44
// WrapStep wraps a step in a goroutine with timeout
45
// This allows detecting hung steps that exceed the timeout
46
func (v *InfiniteLoopValidator) WrapStep(
47
        step chaoskit.Step,
NEW
48
) func(ctx context.Context, target chaoskit.Target) error {
×
NEW
49
        return func(ctx context.Context, target chaoskit.Target) error {
×
NEW
50
                // Channel for result
×
NEW
51
                done := make(chan error, 1)
×
NEW
52

×
NEW
53
                // Context with timeout
×
NEW
54
                timeoutCtx, cancel := context.WithTimeout(ctx, v.timeout)
×
NEW
55
                defer cancel()
×
NEW
56

×
NEW
57
                // Launch step in a separate goroutine
×
NEW
58
                go func() {
×
NEW
59
                        defer func() {
×
NEW
60
                                if r := recover(); r != nil {
×
NEW
61
                                        select {
×
NEW
62
                                        case done <- fmt.Errorf("panic in step: %v", r):
×
NEW
63
                                        case <-timeoutCtx.Done():
×
64
                                                // Timeout already occurred, goroutine will be abandoned
65
                                        }
66
                                }
67
                        }()
68

69
                        // Execute step with timeout context
NEW
70
                        err := step.Execute(timeoutCtx, target)
×
NEW
71
                        select {
×
NEW
72
                        case done <- err:
×
NEW
73
                        case <-timeoutCtx.Done():
×
74
                                // Timeout already occurred, result is no longer needed
75
                        }
76
                }()
77

78
                // Wait for either completion or timeout
NEW
79
                select {
×
NEW
80
                case err := <-done:
×
NEW
81
                        // Step completed successfully within timeout
×
NEW
82
                        return err
×
83

NEW
84
                case <-timeoutCtx.Done():
×
NEW
85
                        // Timeout expired, step hasn't completed yet
×
NEW
86
                        // Check if it was actually a timeout (not context cancellation)
×
NEW
87
                        if !errors.Is(timeoutCtx.Err(), context.DeadlineExceeded) {
×
NEW
88
                                // Context was cancelled, not a timeout
×
NEW
89
                                return timeoutCtx.Err()
×
NEW
90
                        }
×
91

NEW
92
                        v.mu.Lock()
×
NEW
93
                        v.detectionsCount++
×
NEW
94
                        detections := v.detectionsCount
×
NEW
95
                        v.mu.Unlock()
×
NEW
96

×
NEW
97
                        chaoskit.GetLogger(ctx).Error("infinite loop detected",
×
NEW
98
                                slog.String("validator", v.name),
×
NEW
99
                                slog.String("step", step.Name()),
×
NEW
100
                                slog.Duration("timeout", v.timeout),
×
NEW
101
                                slog.Int64("total_detections", detections))
×
NEW
102

×
NEW
103
                        // Wait a bit to see if goroutine completes (graceful shutdown)
×
NEW
104
                        select {
×
NEW
105
                        case <-done:
×
NEW
106
                                // Goroutine completed after timeout - still consider it a detection
×
NEW
107
                                chaoskit.GetLogger(ctx).Debug("step completed after timeout",
×
NEW
108
                                        slog.String("validator", v.name),
×
NEW
109
                                        slog.String("step", step.Name()))
×
NEW
110
                        case <-time.After(100 * time.Millisecond):
×
NEW
111
                                // Goroutine is still hanging - it will remain as a zombie goroutine
×
NEW
112
                                chaoskit.GetLogger(ctx).Warn("step goroutine still running after timeout",
×
NEW
113
                                        slog.String("validator", v.name),
×
NEW
114
                                        slog.String("step", step.Name()))
×
115
                        }
116

NEW
117
                        return fmt.Errorf("infinite loop detected in step %s: exceeded timeout %v",
×
NEW
118
                                step.Name(), v.timeout)
×
119
                }
120
        }
121
}
122

123
// GetDetectionsCount returns the number of detected infinite loops
NEW
124
func (v *InfiniteLoopValidator) GetDetectionsCount() int64 {
×
NEW
125
        v.mu.Lock()
×
NEW
126
        defer v.mu.Unlock()
×
NEW
127

×
NEW
128
        return v.detectionsCount
×
NEW
129
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc