-
Notifications
You must be signed in to change notification settings - Fork 70
Open
Description
When using Souin as a Caddy plugin, the stale-while-revalidate (SWR) feature partially works: stale content is served correctly, but the background revalidation always fails with "context canceled". This happens because Caddy cancels the request context immediately after the main response is sent, before the background goroutine can complete the upstream request.
Environment
- Souin version: Tested with PR feat(rfc): stale-while-revalidate #690 commits (
5dfcbddeand2e1f6692) - Caddy version: Latest via xcaddy
- Go version: 1.23.5
- Storage backend: Dragonfly (Redis-compatible)
- Platform: Docker/Linux
Caddyfile Configuration
cache {
ttl 20s
stale 10s
default_cache_control "public, max-age=20, stale-while-revalidate=10"
timeout {
backend 60s
cache 60s
}
storers redis
redis {
configuration {
Addrs dragonfly:6379
DB 1
}
}
}
reverse_proxy backend:3000Steps to Reproduce
- Make initial request → Cache stores response (TTL=20s, stale=10s)
- Wait 22 seconds (past TTL, within stale window)
- Make second request with
Cache-Control: max-stale=300
Expected Behavior
- Stale response returned immediately ✅
- Background revalidation fetches fresh content from upstream
- Cache updated with fresh content
Actual Behavior
- Stale response returned immediately ✅
- Background revalidation fails instantly with "context canceled" ❌
- Cache NOT updated; next request triggers another cache miss
Debug Logs
Cache-Status: Souin; hit; ttl=-3; key=GET-/--de-default; detail=REDIS
DEBUG http.handlers.cache Found at least one valid response in the REDIS storage
DEBUG http.handlers.cache Revalidate the request with the upstream server
DEBUG http.handlers.reverse_proxy selected upstream {"dial": "storefront-xxx-dev:3000", "total_upstreams": 1}
DEBUG http.handlers.reverse_proxy upstream roundtrip {"upstream": "storefront-xxx-dev:3000", "duration": 0.000108304, "request": {"remote_ip": "172.20.0.5", "remote_port": "48446", "client_ip": "172.20.0.5", "proto": "HTTP/1.1", "method": "GET", "host": "shop.apps.xxx.io", "uri": "/", "headers": {"Cache-Control": ["max-stale=300"], "Date": ["Fri, 23 Jan 2026 13:26:06 UTC"], "X-Forwarded-Server": ["4aedd6306c31"], "X-Forwarded-Host": ["shop.apps.xxx.io"], "X-Forwarded-For": ["172.20.0.5"], "Via": ["1.1 Caddy"], "User-Agent": ["Mozilla/5.0"], "X-Forwarded-Port": ["80"], "X-Forwarded-Proto": ["http"], "X-Real-Ip": ["172.20.0.5"], "Accept-Encoding": ["gzip"], "Accept": ["*/*"]}}, "error": "context canceled"}
Note the duration: 4 microseconds — the request fails instantly, not from a timeout.
Root Cause Analysis
We traced this extensively:
- The SWR goroutine in
middleware.goclones the request withcontext.Background()correctly - However, Caddy's
reverse_proxyhandler internally captures the original request's context during handler chain setup - When Souin sends the main response, Caddy cancels the original context
- The background revalidation call through
next(bgWriter, bgReq)reachesreverse_proxy, which still uses the (now canceled) original context - The upstream roundtrip fails immediately
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels