refactor(timeout): remove goroutine-based logic and improve documentation

- Switch to a synchronous approach to avoid data races with fasthttp context
- Enhance error handling for deadline and custom errors
- Update comments for clarity and maintainability
improve_timeout_mw_structure
René 2025-01-07 20:00:30 +01:00
parent 88458c522f
commit 32f1e68a94
1 changed files with 26 additions and 34 deletions

View File

@ -8,51 +8,43 @@ import (
"github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3"
) )
// New sets a request timeout, runs the handler in a separate Goroutine, and // New enforces a timeout for each incoming request. If the timeout expires or
// returns fiber.ErrRequestTimeout when the timeout or any of the specified errors occur. // any of the specified errors occur, fiber.ErrRequestTimeout is returned.
func New(h fiber.Handler, timeout time.Duration, tErrs ...error) fiber.Handler { func New(h fiber.Handler, timeout time.Duration, tErrs ...error) fiber.Handler {
return func(ctx fiber.Ctx) error { return func(ctx fiber.Ctx) error {
// Create a context with a timeout // Create a context with the specified timeout; any operation exceeding
// this deadline will be canceled automatically.
timeoutContext, cancel := context.WithTimeout(ctx.Context(), timeout) timeoutContext, cancel := context.WithTimeout(ctx.Context(), timeout)
defer cancel() defer cancel()
// Attach the new context to the Fiber context // Attach the timeout-bound context to the current Fiber context.
ctx.SetContext(timeoutContext) ctx.SetContext(timeoutContext)
// Channel to capture the handler's result (error) // Execute the wrapped handler synchronously.
done := make(chan error, 1) err := h(ctx)
// Execute the handler in a separate Goroutine // If the context has timed out, return a request timeout error.
go func() { if timeoutContext.Err() != nil && errors.Is(timeoutContext.Err(), context.DeadlineExceeded) {
done <- h(ctx)
}()
// Wait for either the timeout or the handler to finish
select {
case <-timeoutContext.Done():
// Triggered if the timeout occurs or the context is canceled
if errors.Is(timeoutContext.Err(), context.DeadlineExceeded) {
return fiber.ErrRequestTimeout return fiber.ErrRequestTimeout
} }
// For other context cancellations, we can still treat them the same
return fiber.ErrRequestTimeout
case err := <-done: // If the handler returned an error, check whether it's a deadline exceeded
// If the handler returned an error // error or any other listed timeout-triggering error.
if err != nil { if err != nil {
// Check if it's a deadline exceeded error if errors.Is(err, context.DeadlineExceeded) || isCustomError(err, tErrs) {
if errors.Is(err, context.DeadlineExceeded) {
return fiber.ErrRequestTimeout
}
// Check against any custom errors in the list
for _, timeoutErr := range tErrs {
if errors.Is(err, timeoutErr) {
return fiber.ErrRequestTimeout return fiber.ErrRequestTimeout
} }
} }
}
// Otherwise, return the handler's error or nil
return err return err
} }
} }
// isCustomError checks whether err matches any error in errList using errors.Is.
func isCustomError(err error, errList []error) bool {
for _, e := range errList {
if errors.Is(err, e) {
return true
}
}
return false
} }