92dffe8c60
- Enhanced CI/CD pipeline with coverage reporting, benchmarks, and artifact uploads - Implemented rate limiter IP validation with proxy support and spoofing protection - Added extensive Makefile test targets for coverage, benchmarks, and continuous testing - Expanded middleware chain with request validation, size limits, and suspicious activity logging
116 lines
2.7 KiB
Go
116 lines
2.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"runtime"
|
|
"time"
|
|
)
|
|
|
|
// Simulate the OLD broken version
|
|
type BrokenRateLimiter struct{}
|
|
|
|
func NewBrokenRateLimiter() *BrokenRateLimiter {
|
|
go func() {
|
|
for {
|
|
time.Sleep(1 * time.Minute)
|
|
// Cleanup... but never stops!
|
|
}
|
|
}()
|
|
return &BrokenRateLimiter{}
|
|
}
|
|
|
|
// Simulate the NEW fixed version
|
|
type FixedRateLimiter struct {
|
|
quit chan struct{}
|
|
done chan struct{}
|
|
}
|
|
|
|
func NewFixedRateLimiter() *FixedRateLimiter {
|
|
rl := &FixedRateLimiter{
|
|
quit: make(chan struct{}),
|
|
done: make(chan struct{}),
|
|
}
|
|
go rl.cleanup()
|
|
return rl
|
|
}
|
|
|
|
func (rl *FixedRateLimiter) cleanup() {
|
|
ticker := time.NewTicker(1 * time.Minute)
|
|
defer ticker.Stop()
|
|
defer close(rl.done)
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
// Cleanup
|
|
case <-rl.quit:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (rl *FixedRateLimiter) Shutdown(ctx context.Context) error {
|
|
close(rl.quit)
|
|
select {
|
|
case <-rl.done:
|
|
return nil
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
fmt.Println("========================================")
|
|
fmt.Println("GOROUTINE LEAK DEMONSTRATION")
|
|
fmt.Println("========================================")
|
|
fmt.Println()
|
|
|
|
// Test 1: Broken Version (BEFORE FIX)
|
|
fmt.Println("Test 1: BROKEN VERSION (BEFORE FIX)")
|
|
fmt.Println("------------------------------------")
|
|
before := runtime.NumGoroutine()
|
|
fmt.Printf("Goroutines at start: %d\n", before)
|
|
|
|
for i := 1; i <= 5; i++ {
|
|
_ = NewBrokenRateLimiter()
|
|
time.Sleep(10 * time.Millisecond)
|
|
count := runtime.NumGoroutine()
|
|
fmt.Printf("After creating limiter #%d: %d goroutines (+%d leaked)\n", i, count, count-before)
|
|
}
|
|
|
|
leaked := runtime.NumGoroutine() - before
|
|
fmt.Printf("\n❌ RESULT: %d goroutines LEAKED!\n", leaked)
|
|
fmt.Println()
|
|
|
|
// Test 2: Fixed Version (AFTER FIX)
|
|
fmt.Println("Test 2: FIXED VERSION (AFTER FIX)")
|
|
fmt.Println("----------------------------------")
|
|
runtime.GC() // Force garbage collection
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
before = runtime.NumGoroutine()
|
|
fmt.Printf("Goroutines at start: %d\n", before)
|
|
|
|
ctx := context.Background()
|
|
for i := 1; i <= 5; i++ {
|
|
rl := NewFixedRateLimiter()
|
|
time.Sleep(10 * time.Millisecond)
|
|
_ = rl.Shutdown(ctx)
|
|
time.Sleep(10 * time.Millisecond)
|
|
count := runtime.NumGoroutine()
|
|
fmt.Printf("After limiter #%d (created & shutdown): %d goroutines (leaked: %d)\n", i, count, count-before)
|
|
}
|
|
|
|
after := runtime.NumGoroutine()
|
|
fmt.Printf("\n✅ RESULT: %d goroutines leaked (properly cleaned up!)\n", after-before)
|
|
fmt.Println()
|
|
|
|
fmt.Println("========================================")
|
|
fmt.Println("CONCLUSION")
|
|
fmt.Println("========================================")
|
|
fmt.Println("BEFORE FIX: Each rate limiter leaked 1 goroutine")
|
|
fmt.Println("AFTER FIX: Goroutines properly cleaned up on shutdown")
|
|
fmt.Println()
|
|
}
|