Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(timeout): unify and enhance timeout middleware #3275

Merged
merged 7 commits into from
Jan 8, 2025
44 changes: 36 additions & 8 deletions middleware/timeout/timeout.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,51 @@ import (
"github.com/gofiber/fiber/v3"
)

// New implementation of timeout middleware. Set custom errors(context.DeadlineExceeded vs) for get fiber.ErrRequestTimeout response.
func New(h fiber.Handler, t time.Duration, tErrs ...error) fiber.Handler {
// New sets a request timeout, runs the handler in a separate Goroutine, and
// returns fiber.ErrRequestTimeout when the timeout or any of the specified errors occur.
func New(h fiber.Handler, timeout time.Duration, tErrs ...error) fiber.Handler {
return func(ctx fiber.Ctx) error {
timeoutContext, cancel := context.WithTimeout(ctx.Context(), t)
// Create a context with a timeout
timeoutContext, cancel := context.WithTimeout(ctx.Context(), timeout)
defer cancel()

// Attach the new context to the Fiber context
ctx.SetContext(timeoutContext)
if err := h(ctx); err != nil {
if errors.Is(err, context.DeadlineExceeded) {

// Channel to capture the handler's result (error)
done := make(chan error, 1)

// Execute the handler in a separate Goroutine
go func() {
done <- h(ctx)
}()
ReneWerner87 marked this conversation as resolved.
Show resolved Hide resolved

// 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
}
for i := range tErrs {
if errors.Is(err, tErrs[i]) {
// For other context cancellations, we can still treat them the same
return fiber.ErrRequestTimeout

case err := <-done:
// If the handler returned an error
if err != nil {
// Check if it's a deadline exceeded error
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
}
}
}
// Otherwise, return the handler's error or nil
return err
}
ReneWerner87 marked this conversation as resolved.
Show resolved Hide resolved
return nil
}
}
Loading