From 2c3cc1ba38ac9126343d4c41ffb1dec651f04189 Mon Sep 17 00:00:00 2001 From: xfy Date: Wed, 3 Jun 2026 01:08:50 +0800 Subject: [PATCH] fix(proxy): prevent use-after-recycle in background cache refresh Copy the request before spawning the background goroutine. The fasthttp.RequestCtx is recycled after the handler returns, so passing it to a goroutine causes data corruption under high concurrency. The caller now AcquireRequest+CopyTo before go(), and the goroutine releases it. backgroundRefresh no longer accepts ctx directly. --- internal/proxy/cache_handler.go | 10 ++-------- internal/proxy/proxy.go | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/internal/proxy/cache_handler.go b/internal/proxy/cache_handler.go index db1a9e3..f43062b 100644 --- a/internal/proxy/cache_handler.go +++ b/internal/proxy/cache_handler.go @@ -55,20 +55,14 @@ func (p *Proxy) writeCachedResponse(ctx *fasthttp.RequestCtx, entry *cache.Proxy // 该方法在独立 goroutine 中运行,不阻塞主请求流程。 // // 参数: -// - ctx: 原始 FastHTTP 请求上下文(仅用于复制请求信息) +// - req: 预复制的请求副本(调用方负责 Acquire/Release) // - target: 要刷新的后端目标 // - hashKey: 缓存哈希键 // - origKey: 缓存原始键 -func (p *Proxy) backgroundRefresh(ctx *fasthttp.RequestCtx, target *loadbalance.Target, hashKey uint64, origKey string) { - // 创建新的请求上下文副本 - req := fasthttp.AcquireRequest() +func (p *Proxy) backgroundRefresh(req *fasthttp.Request, target *loadbalance.Target, hashKey uint64, origKey string) { resp := fasthttp.AcquireResponse() - defer fasthttp.ReleaseRequest(req) defer fasthttp.ReleaseResponse(resp) - // 复制原始请求 - ctx.Request.CopyTo(req) - // 如果启用 Revalidate,添加条件请求头 if p.config.Cache.Revalidate { if entry, ok, _ := p.cache.Get(hashKey, origKey); ok { diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index 7b86a81..3b86256 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -651,14 +651,17 @@ func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) { } return } - // 过期缓存,尝试后台刷新,同时返回旧数据 - if !p.config.Cache.BackgroundUpdateDisable { - entry.Updating.Store(true) - go func() { - defer entry.Updating.Store(false) - p.backgroundRefresh(ctx, target, hashKey, origKey) - }() - } + // 过期缓存,尝试后台刷新,同时返回旧数据 + if !p.config.Cache.BackgroundUpdateDisable { + entry.Updating.Store(true) + reqCopy := fasthttp.AcquireRequest() + ctx.Request.CopyTo(reqCopy) + go func() { + defer entry.Updating.Store(false) + defer fasthttp.ReleaseRequest(reqCopy) + p.backgroundRefresh(reqCopy, target, hashKey, origKey) + }() + } upstreamAddr = upstreamCache upstreamStatus = entry.Status