-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathctx_reclaim_test.go
More file actions
165 lines (132 loc) · 5.48 KB
/
Copy pathctx_reclaim_test.go
File metadata and controls
165 lines (132 loc) · 5.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
// 🤖 GitHub Repository: https://github.com/gofiber/fiber
// 📌 API Documentation: https://docs.gofiber.io
package fiber
import (
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/valyala/fasthttp"
)
// acquireReclaimTestCtx wires up a *DefaultCtx for reclaim tests without going
// through a full request lifecycle, so each test can drive ScheduleReclaim,
// signalReleased, and ReleaseCtx in isolation.
func acquireReclaimTestCtx(t *testing.T) (*App, *DefaultCtx) {
t.Helper()
app := New()
raw := app.AcquireCtx(&fasthttp.RequestCtx{})
dc, ok := raw.(*DefaultCtx)
require.True(t, ok, "AcquireCtx must return *DefaultCtx in tests")
return app, dc
}
// TestDefaultCtx_ScheduleReclaim_HappyPath covers the dominant timed-out flow:
// after ScheduleReclaim is armed and both signals fire (handlerDone closes and
// the request handler releases the context), the context is returned to the
// pool, observable as IsAbandoned flipping back to false.
func TestDefaultCtx_ScheduleReclaim_HappyPath(t *testing.T) {
t.Parallel()
app, c := acquireReclaimTestCtx(t)
handlerDone := make(chan struct{})
c.ScheduleReclaim(handlerDone, nil)
require.True(t, c.IsAbandoned(), "ScheduleReclaim must Abandon the ctx internally")
close(handlerDone)
require.True(t, c.IsAbandoned(), "ctx must stay abandoned until ReleaseCtx fires")
app.ReleaseCtx(c)
require.Eventually(t, func() bool {
return !c.IsAbandoned()
}, time.Second, 5*time.Millisecond, "ctx must be reclaimed once both signals fire")
}
// TestDefaultCtx_ScheduleReclaim_ReleaseBeforeHandlerDone covers the reverse
// ordering: ReleaseCtx is called first, then handlerDone closes. The reclaim
// goroutine must still wait on handlerDone before pooling.
func TestDefaultCtx_ScheduleReclaim_ReleaseBeforeHandlerDone(t *testing.T) {
t.Parallel()
app, c := acquireReclaimTestCtx(t)
handlerDone := make(chan struct{})
c.ScheduleReclaim(handlerDone, nil)
app.ReleaseCtx(c)
require.True(t, c.IsAbandoned(), "ctx must stay abandoned until handlerDone closes")
close(handlerDone)
require.Eventually(t, func() bool {
return !c.IsAbandoned()
}, time.Second, 5*time.Millisecond, "ctx must be reclaimed after handlerDone closes")
}
// TestDefaultCtx_ScheduleReclaim_CancelInvoked verifies the cancel hook fires
// exactly once when the handler goroutine finishes.
func TestDefaultCtx_ScheduleReclaim_CancelInvoked(t *testing.T) {
t.Parallel()
app, c := acquireReclaimTestCtx(t)
var calls atomic.Int32
cancel := func() { calls.Add(1) }
handlerDone := make(chan struct{})
c.ScheduleReclaim(handlerDone, cancel)
close(handlerDone)
app.ReleaseCtx(c)
require.Eventually(t, func() bool {
return calls.Load() == 1 && !c.IsAbandoned()
}, time.Second, 5*time.Millisecond, "cancel must fire once and ctx must be reclaimed")
}
// TestDefaultCtx_ScheduleReclaim_NilCancel exercises the cancel==nil branch.
func TestDefaultCtx_ScheduleReclaim_NilCancel(t *testing.T) {
t.Parallel()
app, c := acquireReclaimTestCtx(t)
handlerDone := make(chan struct{})
c.ScheduleReclaim(handlerDone, nil)
close(handlerDone)
app.ReleaseCtx(c)
require.Eventually(t, func() bool {
return !c.IsAbandoned()
}, time.Second, 5*time.Millisecond, "nil cancel must not block reclamation")
}
// TestDefaultCtx_signalReleased_NoReclaim guards that calling signalReleased on
// a context that was never armed for reclamation is a safe no-op. This is the
// SSE-style path (Abandon without ScheduleReclaim) hit through ReleaseCtx.
func TestDefaultCtx_signalReleased_NoReclaim(t *testing.T) {
t.Parallel()
_, c := acquireReclaimTestCtx(t)
require.NotPanics(t, func() { c.signalReleased() })
require.NotPanics(t, func() { c.signalReleased() })
}
// TestDefaultCtx_signalReleased_Idempotent guards the sync.Once semantics: even
// if the request release path fires multiple times, the latch must close once.
func TestDefaultCtx_signalReleased_Idempotent(t *testing.T) {
t.Parallel()
app, c := acquireReclaimTestCtx(t)
handlerDone := make(chan struct{})
c.ScheduleReclaim(handlerDone, nil)
require.NotPanics(t, func() {
c.signalReleased()
c.signalReleased()
c.signalReleased()
})
close(handlerDone)
require.Eventually(t, func() bool {
return !c.IsAbandoned()
}, time.Second, 5*time.Millisecond, "ctx must still be reclaimed exactly once")
_ = app
}
// TestApp_releaseDefaultCtx_AbandonedSignalsReclaim exercises the internal
// releaseDefaultCtx path (called by defaultRequestHandler's defer) for an
// abandoned, reclaim-armed context. It mirrors what ReleaseCtx does for the
// public CustomCtx path and ensures both release entry points fire the latch.
func TestApp_releaseDefaultCtx_AbandonedSignalsReclaim(t *testing.T) {
t.Parallel()
app, c := acquireReclaimTestCtx(t)
handlerDone := make(chan struct{})
c.ScheduleReclaim(handlerDone, nil)
app.releaseDefaultCtx(c)
require.True(t, c.IsAbandoned(), "abandoned ctx must not be pooled by releaseDefaultCtx")
close(handlerDone)
require.Eventually(t, func() bool {
return !c.IsAbandoned()
}, time.Second, 5*time.Millisecond, "releaseDefaultCtx must wire signalReleased into the latch")
}
// TestApp_releaseDefaultCtx_NotAbandonedPools verifies the non-abandoned branch
// in releaseDefaultCtx still pools the ctx through the normal path.
func TestApp_releaseDefaultCtx_NotAbandonedPools(t *testing.T) {
t.Parallel()
app, c := acquireReclaimTestCtx(t)
require.False(t, c.IsAbandoned())
require.NotPanics(t, func() { app.releaseDefaultCtx(c) })
}