timeout

package
v0.18.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 11, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type RequestDeadline added in v0.18.0

type RequestDeadline struct {
	Timeout time.Duration
}

RequestDeadline arms a deadline over the ENTIRE request — response headers AND the body stream — by deriving a context.WithTimeout from the request context and serving the downstream handler with it. When the deadline elapses the request context is cancelled; a transport that honors the request context (parapet's upstream transports do) then aborts the in-flight call, including a backend that has already written headers but stalls mid-body.

How it differs from Timeout

  • Timeout is a write-header deadline: it fires only until the upstream writes response headers, then stops mattering (its timeoutRW is irrelevant once headers are written). A backend that sends headers and then stalls mid-body is NOT bounded by Timeout. On expiry Timeout writes its own 504 Gateway Timeout response.
  • RequestDeadline is a TOTAL request deadline (headers + body). It is a bare context wrapper: no SSE detection, no streaming heuristics, no response buffering. It does NOT write a custom timeout response of its own — the context deadline simply propagates and the downstream handler / transport surfaces the resulting error (typically a 502/504 from pkg/upstream once the context-cancelled call fails).

Motivation

pkg/upstream's LeastConnLoadBalancer MaxConcurrent bulkhead holds a slot until the response body is closed. A backend that writes headers then stalls mid-body keeps its slot indefinitely — no http.Transport timeout covers that stall (ResponseHeaderTimeout bounds only time-to-headers; IdleConnTimeout reaps only idle pooled connections). After MaxConcurrent such stalls the target sheds all traffic permanently: the cap becomes a latch, not a limiter. Capping TOTAL request time via a request-scoped context deadline the transport honors is the in-tree mitigation, and that is exactly what RequestDeadline provides — which Timeout cannot, since it disarms once upstream headers are written.

WARNING: do not blanket-deploy

A total request deadline will KILL legitimate long-lived responses — Server-Sent Events, streaming responses, WebSocket-style upgrades, and large file downloads — because those intentionally keep the body open far longer than any sane header-to-completion bound. Do NOT apply RequestDeadline globally. Apply it PER-ROUTE (via pkg/location or pkg/block) only to endpoints whose total time genuinely should be bounded, and exclude streaming/download routes.

A Timeout (this field) of <= 0 makes ServeHandler a pass-through no-op, matching Timeout.

Example

RequestDeadline bounds the TOTAL request time (headers AND body) via the request context, so it also aborts a backend that writes headers then stalls mid-body — unlike timeout.New/Timeout, which disarms once headers are written.

Apply it PER-ROUTE, never globally: a blanket total deadline would kill legitimate long-lived responses (SSE, streaming, large downloads). Here only the /api prefix is bounded; streaming routes elsewhere are left untouched.

package main

import (
	"time"

	"github.com/moonrhythm/parapet"
	"github.com/moonrhythm/parapet/pkg/location"
	"github.com/moonrhythm/parapet/pkg/timeout"
)

func main() {
	api := location.Prefix("/api")
	api.Use(timeout.NewRequestDeadline(30 * time.Second))
	// api.Use(upstream.SingleHost(...)) — the bounded handler.

	s := parapet.New()
	s.Use(api)
}

func NewRequestDeadline added in v0.18.0

func NewRequestDeadline(d time.Duration) *RequestDeadline

NewRequestDeadline creates a RequestDeadline middleware that bounds the TOTAL request time to d.

func (RequestDeadline) ServeHandler added in v0.18.0

func (m RequestDeadline) ServeHandler(h http.Handler) http.Handler

ServeHandler implements middleware interface

type Timeout added in v0.18.0

type Timeout struct {
	TimeoutHandler http.Handler
	Timeout        time.Duration
}

Timeout sets a write header timeout. It fires only until the upstream writes response headers; a backend that writes headers then stalls mid-body is NOT bounded by it — use RequestDeadline for a total-request deadline.

Example

Replace the default 504 response with a custom one by setting TimeoutHandler.

package main

import (
	"net/http"
	"time"

	"github.com/moonrhythm/parapet"
	"github.com/moonrhythm/parapet/pkg/timeout"
)

func main() {
	m := timeout.New(5 * time.Second)
	m.TimeoutHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Retry-After", "10")
		http.Error(w, "upstream is taking too long, try again shortly", http.StatusGatewayTimeout)
	})

	s := parapet.New()
	s.Use(m)
}

func New

func New(timeout time.Duration) *Timeout

New creates timeout middleware

Example

Bound how long the downstream handler may take to start responding: if it hasn't written a header within the deadline, the request's context is cancelled and a 504 Gateway Timeout is sent.

package main

import (
	"time"

	"github.com/moonrhythm/parapet"
	"github.com/moonrhythm/parapet/pkg/timeout"
)

func main() {
	s := parapet.New()
	s.Use(timeout.New(30 * time.Second))
	// s.Use(upstream.SingleHost(...)) — the handler the deadline applies to.
}

func (Timeout) ServeHandler added in v0.18.0

func (m Timeout) ServeHandler(h http.Handler) http.Handler

ServeHandler implements middleware interface

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL