diff --git a/middleware/timeout.go b/middleware/timeout.go index d146541e6..4be557f76 100644 --- a/middleware/timeout.go +++ b/middleware/timeout.go @@ -4,6 +4,7 @@ package middleware import ( "context" + "fmt" "github.com/labstack/echo/v4" "time" ) @@ -62,6 +63,17 @@ func TimeoutWithConfig(config TimeoutConfig) echo.MiddlewareFunc { done := make(chan error, 1) go func() { + defer func() { + if r := recover(); r != nil { + err, ok := r.(error) + if !ok { + err = fmt.Errorf("panic recovered in timeout middleware: %v", r) + } + c.Logger().Error(err) + done <- err + } + }() + // This goroutine will keep running even if this middleware times out and // will be stopped when ctx.Done() is called down the next(c) call chain done <- next(c) diff --git a/middleware/timeout_test.go b/middleware/timeout_test.go index c0e945933..faecc4c53 100644 --- a/middleware/timeout_test.go +++ b/middleware/timeout_test.go @@ -175,3 +175,22 @@ func TestTimeoutTestRequestClone(t *testing.T) { assert.NoError(t, err) } + +func TestTimeoutRecoversPanic(t *testing.T) { + t.Parallel() + m := TimeoutWithConfig(TimeoutConfig{ + Timeout: 25 * time.Millisecond, + }) + + req := httptest.NewRequest(http.MethodGet, "/", nil) + rec := httptest.NewRecorder() + + e := echo.New() + c := e.NewContext(req, rec) + + err := m(func(c echo.Context) error { + panic("panic in handler") + })(c) + + assert.Error(t, err, "panic recovered in timeout middleware: panic in handler") +}