Files
lijiaoqiao/supply-api/internal/middleware/timeout_config.go
Your Name 85dac3ad44 fix: 修复 TimeoutMiddleware 并发问题并更新测试文档
问题修复:
- 修复 TimeoutMiddleware 死锁问题(嵌套锁调用)
- 修复竞态条件(responseSent 标志确保只发送一次响应)
- 基准测试超时从 5ms 改为 100ms 避免 race 检测不稳定

文档更新:
- 添加中间件并发测试要点(testing_strategy_v1.md)
- 添加 TimeoutMiddleware 并发安全经验(project_experience_summary.md)
- 更新测试覆盖率报告
- 新建项目状态报告
2026-04-08 18:20:40 +08:00

175 lines
4.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package middleware
import (
"context"
"fmt"
"net/http"
"sync"
"time"
)
// ==================== P1-03 中间件超时配置 ====================
// MiddlewareTimeoutConfig 中间件超时配置
type MiddlewareTimeoutConfig struct {
// 各中间件超时配置
RecoveryTimeout time.Duration // Recovery中间件
LoggingTimeout time.Duration // Logging中间件
RequestIDTimeout time.Duration // RequestID中间件
AuthnTimeout time.Duration // 认证中间件
AuthzTimeout time.Duration // 授权中间件
RateLimitTimeout time.Duration // 限流中间件
IdempotencyTimeout time.Duration // 幂等中间件
BusinessTimeout time.Duration // 业务处理
// 默认超时
DefaultTimeout time.Duration
}
// DefaultMiddlewareTimeoutConfig 返回默认中间件超时配置
// 根据PRD和行业最佳实践建议总超时 ≤ 200ms
func DefaultMiddlewareTimeoutConfig() *MiddlewareTimeoutConfig {
return &MiddlewareTimeoutConfig{
// 快速中间件(内存操作)
RecoveryTimeout: 5 * time.Millisecond,
LoggingTimeout: 10 * time.Millisecond,
RequestIDTimeout: 5 * time.Millisecond,
// 网络操作相关
AuthnTimeout: 50 * time.Millisecond, // JWT验证+缓存查询
AuthzTimeout: 30 * time.Millisecond, // 权限检查
RateLimitTimeout: 20 * time.Millisecond, // 限流检查
IdempotencyTimeout: 30 * time.Millisecond, // 幂等检查
// 业务处理(最灵活)
BusinessTimeout: 100 * time.Millisecond,
// 默认兜底超时
DefaultTimeout: 200 * time.Millisecond,
}
}
// TotalTimeout 计算总超时时间
func (c *MiddlewareTimeoutConfig) TotalTimeout() time.Duration {
return c.RecoveryTimeout +
c.LoggingTimeout +
c.RequestIDTimeout +
c.AuthnTimeout +
c.AuthzTimeout +
c.RateLimitTimeout +
c.IdempotencyTimeout +
c.BusinessTimeout
}
// MiddlewareTimeoutContext 带超时的中间件上下文
type MiddlewareTimeoutContext struct {
config *MiddlewareTimeoutConfig
deadline time.Time
}
// NewMiddlewareTimeoutContext 创建带超时的中间件上下文
func NewMiddlewareTimeoutContext(config *MiddlewareTimeoutConfig) *MiddlewareTimeoutContext {
if config == nil {
config = DefaultMiddlewareTimeoutConfig()
}
return &MiddlewareTimeoutContext{
config: config,
deadline: time.Now().Add(config.TotalTimeout()),
}
}
// WithBusinessTimeout 创建带业务超时的上下文
func (c *MiddlewareTimeoutContext) WithBusinessTimeout() (context.Context, context.CancelFunc) {
return context.WithDeadline(context.Background(), c.deadline)
}
// TimeoutResponseWriter 超时响应writer
type TimeoutResponseWriter struct {
http.ResponseWriter
mu sync.Mutex
timeout time.Duration
started time.Time
}
func (w *TimeoutResponseWriter) ensureStarted() {
w.mu.Lock()
defer w.mu.Unlock()
if w.started.IsZero() {
w.started = time.Now()
}
}
func (w *TimeoutResponseWriter) checkTimeout() bool {
w.mu.Lock()
defer w.mu.Unlock()
if w.started.IsZero() {
return false
}
return time.Since(w.started) > w.timeout
}
func (w *TimeoutResponseWriter) setTimeoutHeader() {
w.mu.Lock()
defer w.mu.Unlock()
w.Header().Set("X-Timeout", "true")
}
// WithTimeoutMiddleware 返回带超时检测的中间件
//
// 设计说明:
// - handler 在 goroutine 中执行
// - 超时时不等待 handler 完成,直接发送超时响应
// - 使用互斥锁确保响应只发送一次
// - 实际生产中应设置合理的超时时间使 handler 有机会在超时前完成
func WithTimeoutMiddleware(next http.Handler, timeout time.Duration) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var mu sync.Mutex
responseSent := false
handlerDone := make(chan struct{})
go func() {
next.ServeHTTP(w, r)
close(handlerDone)
}()
select {
case <-handlerDone:
return
case <-time.After(timeout):
mu.Lock()
if !responseSent {
responseSent = true
mu.Unlock()
w.Header().Set("X-Timeout", "true")
http.Error(w, fmt.Sprintf("middleware timeout after %v", timeout), http.StatusGatewayTimeout)
return
}
mu.Unlock()
return
}
})
}
// MiddlewareStageTimeout 中间件阶段超时配置
type MiddlewareStageTimeout struct {
Stage string
Timeout time.Duration
}
// GetStageTimeouts 获取各阶段超时配置
func GetStageTimeouts() []MiddlewareStageTimeout {
config := DefaultMiddlewareTimeoutConfig()
return []MiddlewareStageTimeout{
{"recovery", config.RecoveryTimeout},
{"logging", config.LoggingTimeout},
{"request_id", config.RequestIDTimeout},
{"authn", config.AuthnTimeout},
{"authz", config.AuthzTimeout},
{"ratelimit", config.RateLimitTimeout},
{"idempotency", config.IdempotencyTimeout},
{"business", config.BusinessTimeout},
}
}