Skip to content

Commit

Permalink
feat(timeout): unify and enhance timeout middleware
Browse files Browse the repository at this point in the history
- Combine classic context-based timeout with a Goroutine + channel approach
- Support custom error list without additional parameters
- Return fiber.ErrRequestTimeout for timeouts or listed errors
  • Loading branch information
ReneWerner87 committed Jan 7, 2025
1 parent a95ffd8 commit f559893
Showing 1 changed file with 38 additions and 10 deletions.
48 changes: 38 additions & 10 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 {
return func(ctx fiber.Ctx) error {
timeoutContext, cancel := context.WithTimeout(ctx.Context(), t)
// 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(c fiber.Ctx) error {
// Create a context with a timeout
ctx, cancel := context.WithTimeout(c.Context(), timeout)
defer cancel()
ctx.SetContext(timeoutContext)
if err := h(ctx); err != nil {
if errors.Is(err, context.DeadlineExceeded) {

// Attach the new context to the Fiber context
c.SetContext(ctx)

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

// Execute the handler in a separate Goroutine
go func() {
done <- h(c)
}()

// Wait for either the timeout or the handler to finish
select {
case <-ctx.Done():
// Triggered if the timeout occurs or the context is canceled
if errors.Is(ctx.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
}
return nil
}
}

0 comments on commit f559893

Please sign in to comment.