-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
fix up timeout middleware #3826
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,11 @@ | ||
| package timeout | ||
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "time" | ||
|
|
||
| "github.com/gofiber/fiber/v3" | ||
| utils "github.com/gofiber/utils/v2" | ||
| ) | ||
|
|
||
| // New enforces a timeout for each incoming request. It replaces the request's | ||
|
|
@@ -21,42 +22,34 @@ func New(h fiber.Handler, config ...Config) fiber.Handler { | |
|
|
||
| timeout := cfg.Timeout | ||
| if timeout <= 0 { | ||
| return runHandler(ctx, h, cfg) | ||
| return h(ctx) | ||
| } | ||
|
|
||
| parent := ctx.Context() | ||
| tCtx, cancel := context.WithTimeout(parent, timeout) | ||
| ctx.SetContext(tCtx) | ||
| defer func() { | ||
| cancel() | ||
| ctx.SetContext(parent) | ||
| err := make(chan error, 1) | ||
| go func() { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the timeout occurs first, the go routine will otherwise block forever because there is nothing to receive from the channel. Hence the buffered channel. |
||
| err <- h(ctx) | ||
| }() | ||
|
|
||
| err := runHandler(ctx, h, cfg) | ||
|
|
||
| if errors.Is(tCtx.Err(), context.DeadlineExceeded) && err == nil { | ||
| select { | ||
| case err := <-err: | ||
| if err != nil && (len(cfg.Errors) > 0 && isCustomError(err, cfg.Errors)) { | ||
| if cfg.OnTimeout != nil { | ||
| if toErr := cfg.OnTimeout(ctx); toErr != nil { | ||
| return toErr | ||
| } | ||
| } | ||
| return fiber.ErrRequestTimeout | ||
| } | ||
| return err | ||
| case <-time.After(timeout): | ||
| if cfg.OnTimeout != nil { | ||
| return cfg.OnTimeout(ctx) | ||
| err := cfg.OnTimeout(ctx) | ||
| ctx.RequestCtx().TimeoutErrorWithResponse(&ctx.RequestCtx().Response) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a problem, you have to create a brand new response object not the one from the ctx since it gets recycle. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function TimeoutErrorWithResponse already creates a new response inside There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| return err | ||
| } | ||
| ctx.RequestCtx().TimeoutErrorWithCode(utils.StatusMessage(fiber.StatusRequestTimeout), fiber.StatusRequestTimeout) | ||
| return fiber.ErrRequestTimeout | ||
| } | ||
| return err | ||
| } | ||
| } | ||
|
|
||
| // runHandler executes the handler and returns fiber.ErrRequestTimeout if it | ||
| // sees a deadline exceeded error or one of the custom "timeout-like" errors. | ||
| func runHandler(c fiber.Ctx, h fiber.Handler, cfg Config) error { | ||
| err := h(c) | ||
| if err != nil && (errors.Is(err, context.DeadlineExceeded) || (len(cfg.Errors) > 0 && isCustomError(err, cfg.Errors))) { | ||
| if cfg.OnTimeout != nil { | ||
| if toErr := cfg.OnTimeout(c); toErr != nil { | ||
| return toErr | ||
| } | ||
| } | ||
| return fiber.ErrRequestTimeout | ||
| } | ||
| return err | ||
| } | ||
|
|
||
| // isCustomError checks whether err matches any error in errList using errors.Is. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
contextpackage was removed, but it's essential for correctly handling request timeouts and cancellations in Go. The new implementation without it introduces a critical bug (leaked goroutines). Please re-add thecontextimport as it's required for the proper fix I've suggested in my other comment.