mirror of https://github.com/gofiber/fiber.git
🩹 Fix: goroutine leakage (#3306)
parent
04c9089f68
commit
b0bc32b534
2
app.go
2
app.go
|
@ -994,7 +994,7 @@ func (app *App) Test(req *http.Request, config ...TestConfig) (*http.Response, e
|
||||||
app.startupProcess()
|
app.startupProcess()
|
||||||
|
|
||||||
// Serve conn to server
|
// Serve conn to server
|
||||||
channel := make(chan error)
|
channel := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
var returned bool
|
var returned bool
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|
85
app_test.go
85
app_test.go
|
@ -57,6 +57,91 @@ func testErrorResponse(t *testing.T, err error, resp *http.Response, expectedBod
|
||||||
require.Equal(t, expectedBodyError, string(body), "Response body")
|
require.Equal(t, expectedBodyError, string(body), "Response body")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_App_Test_Goroutine_Leak_Compare(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
handler Handler
|
||||||
|
name string
|
||||||
|
timeout time.Duration
|
||||||
|
sleepTime time.Duration
|
||||||
|
expectLeak bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "With timeout (potential leak)",
|
||||||
|
handler: func(c Ctx) error {
|
||||||
|
time.Sleep(300 * time.Millisecond) // Simulate time-consuming operation
|
||||||
|
return c.SendString("ok")
|
||||||
|
},
|
||||||
|
timeout: 50 * time.Millisecond, // // Short timeout to ensure triggering
|
||||||
|
sleepTime: 500 * time.Millisecond, // Wait time longer than handler execution time
|
||||||
|
expectLeak: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Without timeout (no leak)",
|
||||||
|
handler: func(c Ctx) error {
|
||||||
|
return c.SendString("ok") // Return immediately
|
||||||
|
},
|
||||||
|
timeout: 0, // Disable timeout
|
||||||
|
sleepTime: 100 * time.Millisecond,
|
||||||
|
expectLeak: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
app := New()
|
||||||
|
|
||||||
|
// Record initial goroutine count
|
||||||
|
initialGoroutines := runtime.NumGoroutine()
|
||||||
|
t.Logf("[%s] Initial goroutines: %d", tc.name, initialGoroutines)
|
||||||
|
|
||||||
|
app.Get("/", tc.handler)
|
||||||
|
|
||||||
|
// Send 10 requests
|
||||||
|
numRequests := 10
|
||||||
|
for i := 0; i < numRequests; i++ {
|
||||||
|
req := httptest.NewRequest(MethodGet, "/", nil)
|
||||||
|
|
||||||
|
if tc.timeout > 0 {
|
||||||
|
_, err := app.Test(req, TestConfig{
|
||||||
|
Timeout: tc.timeout,
|
||||||
|
FailOnTimeout: true,
|
||||||
|
})
|
||||||
|
require.Error(t, err)
|
||||||
|
require.ErrorIs(t, err, os.ErrDeadlineExceeded)
|
||||||
|
} else if resp, err := app.Test(req); err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
} else {
|
||||||
|
require.Equal(t, 200, resp.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for normal goroutines to complete
|
||||||
|
time.Sleep(tc.sleepTime)
|
||||||
|
|
||||||
|
// Check final goroutine count
|
||||||
|
finalGoroutines := runtime.NumGoroutine()
|
||||||
|
leakedGoroutines := finalGoroutines - initialGoroutines
|
||||||
|
t.Logf("[%s] Final goroutines: %d (leaked: %d)",
|
||||||
|
tc.name, finalGoroutines, leakedGoroutines)
|
||||||
|
|
||||||
|
if tc.expectLeak {
|
||||||
|
// before fix: If blocking exists, leaked goroutines should be at least equal to request count
|
||||||
|
// after fix: If no blocking exists, leaked goroutines should be less than request count
|
||||||
|
if leakedGoroutines >= numRequests {
|
||||||
|
t.Errorf("[%s] Expected at least %d leaked goroutines, but got %d",
|
||||||
|
tc.name, numRequests, leakedGoroutines)
|
||||||
|
}
|
||||||
|
} else if leakedGoroutines >= numRequests { // If no blocking exists, leaked goroutines should be less than request count
|
||||||
|
t.Errorf("[%s] Expected less than %d leaked goroutines, but got %d",
|
||||||
|
tc.name, numRequests, leakedGoroutines)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_App_MethodNotAllowed(t *testing.T) {
|
func Test_App_MethodNotAllowed(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
app := New()
|
app := New()
|
||||||
|
|
Loading…
Reference in New Issue