Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Prev Previous commit
Next Next commit
deprecate timeout middleware
  • Loading branch information
aldas committed Dec 12, 2025
commit d3e5f687c8aeba0372fd98a1616063a8c1482ba7
113 changes: 113 additions & 0 deletions 113 CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,118 @@
# Changelog

## v4.15.0 - TBD

**DEPRECATION NOTICE** Timeout Middleware Deprecated - Use ContextTimeout Instead

The `middleware.Timeout` middleware has been **deprecated** due to fundamental architectural issues that cause
data races. Use `middleware.ContextTimeout` or `middleware.ContextTimeoutWithConfig` instead.

**Why is this being deprecated?**

The Timeout middleware manipulates response writers across goroutine boundaries, which causes data races that
cannot be reliably fixed without a complete architectural redesign. The middleware:

- Swaps the response writer using `http.TimeoutHandler`
- Must be the first middleware in the chain (fragile constraint)
- Can cause races with other middleware (Logger, metrics, custom middleware)
- Has been the source of multiple race condition fixes over the years

**What should you use instead?**

The `ContextTimeout` middleware (available since v4.12.0) provides timeout functionality using Go's standard
context mechanism. It is:

- Race-free by design
- Can be placed anywhere in the middleware chain
- Simpler and more maintainable
- Compatible with all other middleware

**Migration Guide:**

```go
// Before (deprecated):
e.Use(middleware.Timeout())

// After (recommended):
e.Use(middleware.ContextTimeout(30 * time.Second))
```

With configuration:
```go
// Before (deprecated):
e.Use(middleware.TimeoutWithConfig(middleware.TimeoutConfig{
Timeout: 30 * time.Second,
Skipper: func(c echo.Context) bool {
return c.Path() == "/health"
},
}))

// After (recommended):
e.Use(middleware.ContextTimeoutWithConfig(middleware.ContextTimeoutConfig{
Timeout: 30 * time.Second,
Skipper: func(c echo.Context) bool {
return c.Path() == "/health"
},
}))
```

**Important Behavioral Differences:**

1. **Handler cooperation required**: With ContextTimeout, your handlers must check `context.Done()` for cooperative
cancellation. The old Timeout middleware would send a 503 response regardless of handler cooperation, but had
data race issues.

2. **Error handling**: ContextTimeout returns errors through the standard error handling flow. Handlers that receive
`context.DeadlineExceeded` should handle it appropriately:

```go
e.GET("/long-task", func(c echo.Context) error {
ctx := c.Request().Context()

// Example: database query with context
result, err := db.QueryContext(ctx, "SELECT * FROM large_table")
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
// Handle timeout
return echo.NewHTTPError(http.StatusServiceUnavailable, "Request timeout")
}
return err
}

return c.JSON(http.StatusOK, result)
})
```

3. **Background tasks**: For long-running background tasks, use goroutines with context:

```go
e.GET("/async-task", func(c echo.Context) error {
ctx := c.Request().Context()

resultCh := make(chan Result, 1)
errCh := make(chan error, 1)

go func() {
result, err := performLongTask(ctx)
if err != nil {
errCh <- err
return
}
resultCh <- result
}()

select {
case result := <-resultCh:
return c.JSON(http.StatusOK, result)
case err := <-errCh:
return err
case <-ctx.Done():
return echo.NewHTTPError(http.StatusServiceUnavailable, "Request timeout")
}
})
```


## v4.14.0 - 2025-12-11

`middleware.Logger` has been deprecated. For request logging, use `middleware.RequestLogger` or
Expand Down
33 changes: 33 additions & 0 deletions 33 middleware/context_timeout.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,39 @@ import (
"github.com/labstack/echo/v4"
)

// ContextTimeout Middleware
//
// ContextTimeout provides request timeout functionality using Go's context mechanism.
// It is the recommended replacement for the deprecated Timeout middleware.
//
//
// Basic Usage:
//
// e.Use(middleware.ContextTimeout(30 * time.Second))
//
// With Configuration:
//
// e.Use(middleware.ContextTimeoutWithConfig(middleware.ContextTimeoutConfig{
// Timeout: 30 * time.Second,
// Skipper: middleware.DefaultSkipper,
// }))
//
// Handler Example:
//
// e.GET("/task", func(c echo.Context) error {
// ctx := c.Request().Context()
//
// result, err := performTaskWithContext(ctx)
// if err != nil {
// if errors.Is(err, context.DeadlineExceeded) {
// return echo.NewHTTPError(http.StatusServiceUnavailable, "timeout")
// }
// return err
// }
//
// return c.JSON(http.StatusOK, result)
// })

// ContextTimeoutConfig defines the config for ContextTimeout middleware.
type ContextTimeoutConfig struct {
// Skipper defines a function to skip middleware.
Expand Down
35 changes: 35 additions & 0 deletions 35 middleware/timeout.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ import (
//

// TimeoutConfig defines the config for Timeout middleware.
//
// Deprecated: Use ContextTimeoutConfig with ContextTimeout or ContextTimeoutWithConfig instead.
// The Timeout middleware has architectural issues that cause data races due to response writer
// manipulation across goroutines. It must be the first middleware in the chain, making it fragile.
// The ContextTimeout middleware provides timeout functionality using Go's context mechanism,
// which is race-free and can be placed anywhere in the middleware chain.
type TimeoutConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
Expand Down Expand Up @@ -89,11 +95,38 @@ var DefaultTimeoutConfig = TimeoutConfig{

// Timeout returns a middleware which returns error (503 Service Unavailable error) to client immediately when handler
// call runs for longer than its time limit. NB: timeout does not stop handler execution.
//
// Deprecated: Use ContextTimeout instead. This middleware has known data race issues due to response writer
// manipulation. See https://github.com/labstack/echo/blob/master/middleware/context_timeout.go for the
// recommended alternative.
//
// Example migration:
//
// // Before:
// e.Use(middleware.Timeout())
//
// // After:
// e.Use(middleware.ContextTimeout(30 * time.Second))
func Timeout() echo.MiddlewareFunc {
return TimeoutWithConfig(DefaultTimeoutConfig)
}

// TimeoutWithConfig returns a Timeout middleware with config or panics on invalid configuration.
//
// Deprecated: Use ContextTimeoutWithConfig instead. This middleware has architectural data race issues.
// See the ContextTimeout middleware for a race-free alternative that uses Go's context mechanism.
//
// Example migration:
//
// // Before:
// e.Use(middleware.TimeoutWithConfig(middleware.TimeoutConfig{
// Timeout: 30 * time.Second,
// }))
//
// // After:
// e.Use(middleware.ContextTimeoutWithConfig(middleware.ContextTimeoutConfig{
// Timeout: 30 * time.Second,
// }))
func TimeoutWithConfig(config TimeoutConfig) echo.MiddlewareFunc {
mw, err := config.ToMiddleware()
if err != nil {
Expand All @@ -103,6 +136,8 @@ func TimeoutWithConfig(config TimeoutConfig) echo.MiddlewareFunc {
}

// ToMiddleware converts Config to middleware or returns an error for invalid configuration
//
// Deprecated: Use ContextTimeoutConfig.ToMiddleware instead.
func (config TimeoutConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
if config.Skipper == nil {
config.Skipper = DefaultTimeoutConfig.Skipper
Expand Down
Morty Proxy This is a proxified and sanitized view of the page, visit original site.