|
| 1 | +package app |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "os" |
| 6 | + "runtime" |
| 7 | + "runtime/pprof" |
| 8 | + "testing" |
| 9 | + "time" |
| 10 | + |
| 11 | + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" |
| 12 | +) |
| 13 | + |
| 14 | +// TestBlockLifecycleResourceLeak repeatedly runs the failing input to reproduce resource leaks |
| 15 | +func TestBlockLifecycleResourceLeak(t *testing.T) { |
| 16 | + // The failing input from fuzzing |
| 17 | + failingTimestamp := int64(1750678259) |
| 18 | + |
| 19 | + fmt.Printf("Starting resource leak test with timestamp %d (%s)\n", failingTimestamp, time.Unix(failingTimestamp, 0)) |
| 20 | + |
| 21 | + // Track initial resources |
| 22 | + var m runtime.MemStats |
| 23 | + runtime.ReadMemStats(&m) |
| 24 | + initialGoroutines := runtime.NumGoroutine() |
| 25 | + fmt.Printf("Initial - Memory: %d MB, Goroutines: %d\n", m.Alloc/1024/1024, initialGoroutines) |
| 26 | + |
| 27 | + // Run the test multiple times to see if resources accumulate |
| 28 | + for i := 0; i < 3; i++ { // Reduced to 3 iterations for debugging |
| 29 | + fmt.Printf("\n--- Iteration %d ---\n", i+1) |
| 30 | + |
| 31 | + // Force GC before each iteration |
| 32 | + runtime.GC() |
| 33 | + |
| 34 | + // Track resources before iteration |
| 35 | + runtime.ReadMemStats(&m) |
| 36 | + beforeGoroutines := runtime.NumGoroutine() |
| 37 | + fmt.Printf("Before iteration - Memory: %d MB, Goroutines: %d\n", m.Alloc/1024/1024, beforeGoroutines) |
| 38 | + |
| 39 | + // Run the test |
| 40 | + func() { |
| 41 | + chain := Setup(t) |
| 42 | + ctx := chain.NewContext(false) |
| 43 | + ctx = ctx.WithBlockHeader(tmproto.Header{Time: time.Unix(failingTimestamp, 0)}) |
| 44 | + |
| 45 | + // Cleanup |
| 46 | + defer func() { |
| 47 | + _, _ = chain.Commit() |
| 48 | + runtime.GC() |
| 49 | + if err := chain.Close(); err != nil { |
| 50 | + fmt.Printf("DEBUG: chain.Close() error: %v\n", err) |
| 51 | + } |
| 52 | + }() |
| 53 | + |
| 54 | + // Run BeginBlocker with timeout |
| 55 | + beginDone := make(chan error, 1) |
| 56 | + go func() { |
| 57 | + if _, err := chain.BeginBlocker(ctx); err != nil { |
| 58 | + beginDone <- err |
| 59 | + return |
| 60 | + } |
| 61 | + beginDone <- nil |
| 62 | + }() |
| 63 | + |
| 64 | + select { |
| 65 | + case err := <-beginDone: |
| 66 | + if err != nil { |
| 67 | + t.Fatalf("BeginBlocker failed: %v", err) |
| 68 | + } |
| 69 | + case <-time.After(2 * time.Second): |
| 70 | + t.Fatalf("BeginBlocker timed out") |
| 71 | + } |
| 72 | + |
| 73 | + // Run EndBlocker with timeout |
| 74 | + endDone := make(chan error, 1) |
| 75 | + go func() { |
| 76 | + if _, err := chain.EndBlocker(ctx); err != nil { |
| 77 | + endDone <- err |
| 78 | + return |
| 79 | + } |
| 80 | + endDone <- nil |
| 81 | + }() |
| 82 | + |
| 83 | + select { |
| 84 | + case err := <-endDone: |
| 85 | + if err != nil { |
| 86 | + t.Fatalf("EndBlocker failed: %v", err) |
| 87 | + } |
| 88 | + case <-time.After(2 * time.Second): |
| 89 | + t.Fatalf("EndBlocker timed out") |
| 90 | + } |
| 91 | + }() |
| 92 | + |
| 93 | + // Track resources after iteration |
| 94 | + runtime.ReadMemStats(&m) |
| 95 | + afterGoroutines := runtime.NumGoroutine() |
| 96 | + fmt.Printf("After iteration - Memory: %d MB, Goroutines: %d\n", m.Alloc/1024/1024, afterGoroutines) |
| 97 | + |
| 98 | + // Check for resource leaks |
| 99 | + if afterGoroutines > beforeGoroutines { |
| 100 | + fmt.Printf("WARNING: Goroutine leak detected! +%d goroutines\n", afterGoroutines-beforeGoroutines) |
| 101 | + |
| 102 | + // Dump goroutine stack traces to see what's leaking |
| 103 | + fmt.Printf("=== GOROUTINE STACK TRACES ===\n") |
| 104 | + pprof.Lookup("goroutine").WriteTo(os.Stdout, 1) |
| 105 | + fmt.Printf("=== END STACK TRACES ===\n") |
| 106 | + } |
| 107 | + |
| 108 | + // Small delay to allow cleanup |
| 109 | + time.Sleep(100 * time.Millisecond) |
| 110 | + } |
| 111 | + |
| 112 | + // Final resource check |
| 113 | + runtime.ReadMemStats(&m) |
| 114 | + finalGoroutines := runtime.NumGoroutine() |
| 115 | + fmt.Printf("\nFinal - Memory: %d MB, Goroutines: %d\n", m.Alloc/1024/1024, finalGoroutines) |
| 116 | + |
| 117 | + // TODO: Uncomment to fail on leaks once stable |
| 118 | + // if finalGoroutines > initialGoroutines { |
| 119 | + // t.Errorf("Resource leak detected: +%d goroutines", finalGoroutines-initialGoroutines) |
| 120 | + // } |
| 121 | +} |
0 commit comments