gateway_helper_test.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. package handler
  2. import (
  3. "context"
  4. "runtime"
  5. "sync/atomic"
  6. "testing"
  7. "time"
  8. "github.com/stretchr/testify/require"
  9. )
  10. // TestWrapReleaseOnDone_NoGoroutineLeak 验证 wrapReleaseOnDone 修复后不会泄露 goroutine
  11. func TestWrapReleaseOnDone_NoGoroutineLeak(t *testing.T) {
  12. // 记录测试开始时的 goroutine 数量
  13. runtime.GC()
  14. time.Sleep(100 * time.Millisecond)
  15. initialGoroutines := runtime.NumGoroutine()
  16. ctx, cancel := context.WithCancel(context.Background())
  17. defer cancel()
  18. var releaseCount int32
  19. release := wrapReleaseOnDone(ctx, func() {
  20. atomic.AddInt32(&releaseCount, 1)
  21. })
  22. // 正常释放
  23. release()
  24. // 等待足够时间确保 goroutine 退出
  25. time.Sleep(200 * time.Millisecond)
  26. // 验证只释放一次
  27. if count := atomic.LoadInt32(&releaseCount); count != 1 {
  28. t.Errorf("expected release count to be 1, got %d", count)
  29. }
  30. // 强制 GC,清理已退出的 goroutine
  31. runtime.GC()
  32. time.Sleep(100 * time.Millisecond)
  33. // 验证 goroutine 数量没有增加(允许±2的误差,考虑到测试框架本身可能创建的 goroutine)
  34. finalGoroutines := runtime.NumGoroutine()
  35. if finalGoroutines > initialGoroutines+2 {
  36. t.Errorf("goroutine leak detected: initial=%d, final=%d, leaked=%d",
  37. initialGoroutines, finalGoroutines, finalGoroutines-initialGoroutines)
  38. }
  39. }
  40. // TestWrapReleaseOnDone_ContextCancellation 验证 context 取消时也能正确释放
  41. func TestWrapReleaseOnDone_ContextCancellation(t *testing.T) {
  42. ctx, cancel := context.WithCancel(context.Background())
  43. var releaseCount int32
  44. _ = wrapReleaseOnDone(ctx, func() {
  45. atomic.AddInt32(&releaseCount, 1)
  46. })
  47. // 取消 context,应该触发释放
  48. cancel()
  49. // 等待释放完成
  50. time.Sleep(100 * time.Millisecond)
  51. // 验证释放被调用
  52. if count := atomic.LoadInt32(&releaseCount); count != 1 {
  53. t.Errorf("expected release count to be 1, got %d", count)
  54. }
  55. }
  56. func TestWrapReleaseOnDone_AlreadyCancelledReleasesExactlyOnce(t *testing.T) {
  57. ctx, cancel := context.WithCancel(context.Background())
  58. cancel()
  59. var releaseCount int32
  60. release := wrapReleaseOnDone(ctx, func() {
  61. atomic.AddInt32(&releaseCount, 1)
  62. })
  63. release()
  64. require.Eventually(t, func() bool {
  65. return atomic.LoadInt32(&releaseCount) == 1
  66. }, time.Second, time.Millisecond)
  67. }
  68. // TestWrapReleaseOnDone_MultipleCallsOnlyReleaseOnce 验证多次调用 release 只释放一次
  69. func TestWrapReleaseOnDone_MultipleCallsOnlyReleaseOnce(t *testing.T) {
  70. ctx, cancel := context.WithCancel(context.Background())
  71. defer cancel()
  72. var releaseCount int32
  73. release := wrapReleaseOnDone(ctx, func() {
  74. atomic.AddInt32(&releaseCount, 1)
  75. })
  76. // 调用多次
  77. release()
  78. release()
  79. release()
  80. // 等待执行完成
  81. time.Sleep(100 * time.Millisecond)
  82. // 验证只释放一次
  83. if count := atomic.LoadInt32(&releaseCount); count != 1 {
  84. t.Errorf("expected release count to be 1, got %d", count)
  85. }
  86. }
  87. // TestWrapReleaseOnDone_NilReleaseFunc 验证 nil releaseFunc 不会 panic
  88. func TestWrapReleaseOnDone_NilReleaseFunc(t *testing.T) {
  89. ctx, cancel := context.WithCancel(context.Background())
  90. defer cancel()
  91. release := wrapReleaseOnDone(ctx, nil)
  92. if release != nil {
  93. t.Error("expected nil release function when releaseFunc is nil")
  94. }
  95. }
  96. // TestWrapReleaseOnDone_ConcurrentCalls 验证并发调用的安全性
  97. func TestWrapReleaseOnDone_ConcurrentCalls(t *testing.T) {
  98. ctx, cancel := context.WithCancel(context.Background())
  99. defer cancel()
  100. var releaseCount int32
  101. release := wrapReleaseOnDone(ctx, func() {
  102. atomic.AddInt32(&releaseCount, 1)
  103. })
  104. // 并发调用 release
  105. const numGoroutines = 10
  106. for i := 0; i < numGoroutines; i++ {
  107. go release()
  108. }
  109. // 等待所有 goroutine 完成
  110. time.Sleep(200 * time.Millisecond)
  111. // 验证只释放一次
  112. if count := atomic.LoadInt32(&releaseCount); count != 1 {
  113. t.Errorf("expected release count to be 1, got %d", count)
  114. }
  115. }
  116. // BenchmarkWrapReleaseOnDone 性能基准测试
  117. func BenchmarkWrapReleaseOnDone(b *testing.B) {
  118. ctx, cancel := context.WithCancel(context.Background())
  119. defer cancel()
  120. b.ResetTimer()
  121. for i := 0; i < b.N; i++ {
  122. release := wrapReleaseOnDone(ctx, func() {})
  123. release()
  124. }
  125. }