| package handler |
|
|
| import ( |
| "context" |
| "runtime" |
| "sync/atomic" |
| "testing" |
| "time" |
| ) |
|
|
| |
| func TestWrapReleaseOnDone_NoGoroutineLeak(t *testing.T) { |
| |
| runtime.GC() |
| time.Sleep(100 * time.Millisecond) |
| initialGoroutines := runtime.NumGoroutine() |
|
|
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
|
|
| var releaseCount int32 |
| release := wrapReleaseOnDone(ctx, func() { |
| atomic.AddInt32(&releaseCount, 1) |
| }) |
|
|
| |
| release() |
|
|
| |
| time.Sleep(200 * time.Millisecond) |
|
|
| |
| if count := atomic.LoadInt32(&releaseCount); count != 1 { |
| t.Errorf("expected release count to be 1, got %d", count) |
| } |
|
|
| |
| runtime.GC() |
| time.Sleep(100 * time.Millisecond) |
|
|
| |
| finalGoroutines := runtime.NumGoroutine() |
| if finalGoroutines > initialGoroutines+2 { |
| t.Errorf("goroutine leak detected: initial=%d, final=%d, leaked=%d", |
| initialGoroutines, finalGoroutines, finalGoroutines-initialGoroutines) |
| } |
| } |
|
|
| |
| func TestWrapReleaseOnDone_ContextCancellation(t *testing.T) { |
| ctx, cancel := context.WithCancel(context.Background()) |
|
|
| var releaseCount int32 |
| _ = wrapReleaseOnDone(ctx, func() { |
| atomic.AddInt32(&releaseCount, 1) |
| }) |
|
|
| |
| cancel() |
|
|
| |
| time.Sleep(100 * time.Millisecond) |
|
|
| |
| if count := atomic.LoadInt32(&releaseCount); count != 1 { |
| t.Errorf("expected release count to be 1, got %d", count) |
| } |
| } |
|
|
| |
| func TestWrapReleaseOnDone_MultipleCallsOnlyReleaseOnce(t *testing.T) { |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
|
|
| var releaseCount int32 |
| release := wrapReleaseOnDone(ctx, func() { |
| atomic.AddInt32(&releaseCount, 1) |
| }) |
|
|
| |
| release() |
| release() |
| release() |
|
|
| |
| time.Sleep(100 * time.Millisecond) |
|
|
| |
| if count := atomic.LoadInt32(&releaseCount); count != 1 { |
| t.Errorf("expected release count to be 1, got %d", count) |
| } |
| } |
|
|
| |
| func TestWrapReleaseOnDone_NilReleaseFunc(t *testing.T) { |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
|
|
| release := wrapReleaseOnDone(ctx, nil) |
|
|
| if release != nil { |
| t.Error("expected nil release function when releaseFunc is nil") |
| } |
| } |
|
|
| |
| func TestWrapReleaseOnDone_ConcurrentCalls(t *testing.T) { |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
|
|
| var releaseCount int32 |
| release := wrapReleaseOnDone(ctx, func() { |
| atomic.AddInt32(&releaseCount, 1) |
| }) |
|
|
| |
| const numGoroutines = 10 |
| for i := 0; i < numGoroutines; i++ { |
| go release() |
| } |
|
|
| |
| time.Sleep(200 * time.Millisecond) |
|
|
| |
| if count := atomic.LoadInt32(&releaseCount); count != 1 { |
| t.Errorf("expected release count to be 1, got %d", count) |
| } |
| } |
|
|
| |
| func BenchmarkWrapReleaseOnDone(b *testing.B) { |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
|
|
| b.ResetTimer() |
| for i := 0; i < b.N; i++ { |
| release := wrapReleaseOnDone(ctx, func() {}) |
| release() |
| } |
| } |
|
|