Skip to content

Commit

Permalink
🐛 [Bug-Fix] add original timeout middleware (#2367)
Browse files Browse the repository at this point in the history
* add original timeout middleware

* fix linter issues

* deprecate original timeout middleware

* update timeout middleware documentation
  • Loading branch information
hakankutluay authored Apr 9, 2023
1 parent 562d15d commit 22b407e
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 11 deletions.
19 changes: 16 additions & 3 deletions docs/api/middleware/timeout.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,29 @@ id: timeout
title: Timeout
---

Timeout middleware for [Fiber](https://github.com/gofiber/fiber). As a `fiber.Handler` wrapper, it creates a context with `context.WithTimeout` and pass it in `UserContext`.
There exist two distinct implementations of timeout middleware [Fiber](https://github.com/gofiber/fiber).

**New**

Wraps a `fiber.Handler` with a timeout. If the handler takes longer than the given duration to return, the timeout error is set and forwarded to the centralized [ErrorHandler](https://docs.gofiber.io/error-handling).

Note: This has been depreciated since it raises race conditions.

**NewWithContext**

As a `fiber.Handler` wrapper, it creates a context with `context.WithTimeout` and pass it in `UserContext`.

If the context passed executions (eg. DB ops, Http calls) takes longer than the given duration to return, the timeout error is set and forwarded to the centralized `ErrorHandler`.


It does not cancel long running executions. Underlying executions must handle timeout by using `context.Context` parameter.

## Signatures

```go
func New(handler fiber.Handler, timeout time.Duration, timeoutErrors ...error) fiber.Handler

func NewWithContext(handler fiber.Handler, timeout time.Duration, timeoutErrors ...error) fiber.Handler
```

## Examples
Expand Down Expand Up @@ -85,7 +98,7 @@ func main() {
return nil
}

app.Get("/foo/:sleepTime", timeout.New(h, 2*time.Second, ErrFooTimeOut))
app.Get("/foo/:sleepTime", timeout.NewWithContext(h, 2*time.Second, ErrFooTimeOut))
_ = app.Listen(":3000")
}

Expand Down Expand Up @@ -124,7 +137,7 @@ func main() {
return nil
}

app.Get("/foo", timeout.New(handler, 10*time.Second))
app.Get("/foo", timeout.NewWithContext(handler, 10*time.Second))
app.Listen(":3000")
}
```
47 changes: 45 additions & 2 deletions middleware/timeout/timeout.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,56 @@ package timeout
import (
"context"
"errors"
"log"
"sync"
"time"

"github.com/gofiber/fiber/v2"
)

// 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 {
var once sync.Once

// New wraps a handler and aborts the process of the handler if the timeout is reached.
//
// Deprecated: This implementation contains data race issues. Use NewWithContext instead.
// Find documentation and sample usage on https://docs.gofiber.io/api/middleware/timeout
func New(handler fiber.Handler, timeout time.Duration) fiber.Handler {
once.Do(func() {
log.Printf("[Warning] timeout contains data race issues, not ready for production!")
})

if timeout <= 0 {
return handler
}

// logic is from fasthttp.TimeoutWithCodeHandler https://github.com/valyala/fasthttp/blob/master/server.go#L418
return func(ctx *fiber.Ctx) error {
ch := make(chan struct{}, 1)

go func() {
defer func() {
if err := recover(); err != nil {
log.Printf("[Warning] recover error %v", err)
}
}()
if err := handler(ctx); err != nil {
log.Printf("[Warning] handler error %v", err)
}
ch <- struct{}{}
}()

select {
case <-ch:
case <-time.After(timeout):
return fiber.ErrRequestTimeout
}

return nil
}
}

// NewWithContext implementation of timeout middleware. Set custom errors(context.DeadlineExceeded vs) for get fiber.ErrRequestTimeout response.
func NewWithContext(h fiber.Handler, t time.Duration, tErrs ...error) fiber.Handler {
return func(ctx *fiber.Ctx) error {
timeoutContext, cancel := context.WithTimeout(ctx.UserContext(), t)
defer cancel()
Expand Down
12 changes: 6 additions & 6 deletions middleware/timeout/timeout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import (
"github.com/gofiber/fiber/v2/utils"
)

// go test -run Test_Timeout
func Test_Timeout(t *testing.T) {
// go test -run Test_WithContextTimeout
func Test_WithContextTimeout(t *testing.T) {
t.Parallel()
// fiber instance
app := fiber.New()
h := New(func(c *fiber.Ctx) error {
h := NewWithContext(func(c *fiber.Ctx) error {
sleepTime, err := time.ParseDuration(c.Params("sleepTime") + "ms")
utils.AssertEqual(t, nil, err)
if err := sleepWithContext(c.UserContext(), sleepTime, context.DeadlineExceeded); err != nil {
Expand All @@ -44,12 +44,12 @@ func Test_Timeout(t *testing.T) {

var ErrFooTimeOut = errors.New("foo context canceled")

// go test -run Test_TimeoutWithCustomError
func Test_TimeoutWithCustomError(t *testing.T) {
// go test -run Test_WithContextTimeoutWithCustomError
func Test_WithContextTimeoutWithCustomError(t *testing.T) {
t.Parallel()
// fiber instance
app := fiber.New()
h := New(func(c *fiber.Ctx) error {
h := NewWithContext(func(c *fiber.Ctx) error {
sleepTime, err := time.ParseDuration(c.Params("sleepTime") + "ms")
utils.AssertEqual(t, nil, err)
if err := sleepWithContext(c.UserContext(), sleepTime, ErrFooTimeOut); err != nil {
Expand Down

1 comment on commit 22b407e

@ReneWerner87
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 2.

Benchmark suite Current: 22b407e Previous: 562d15d Ratio
Benchmark_Ctx_Body_With_Compression 1918 ns/op 145 B/op 7 allocs/op 957.4 ns/op 144 B/op 7 allocs/op 2.00

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.