feat(db,server): T20-S2 hardening batch B1/B4/B7 (XFF trust, pool statement_timeout, shutdown drain)

This commit is contained in:
weichen
2026-09-25 04:45:49 +00:00
parent c0e556b167
commit 2abc23ac65
12 changed files with 585 additions and 6 deletions
+25 -1
View File
@@ -125,11 +125,35 @@ func main() {
if eventsH != nil {
eventsH.Stop()
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
// T20-B7:drain 窗口与 AGENT_TIMEOUT 量级对齐(默认 min(AgentTimeout, 35m)+30s 缓冲),
// 消除「5s Shutdown vs 30m 长任务」错配;可由 SHUTDOWN_DRAIN_TIMEOUT 显式覆盖。
drain := shutdownDrainTimeout(cfg)
ctx, cancel := context.WithTimeout(context.Background(), drain)
defer cancel()
fmt.Printf("等待 in-flight 请求/长任务排空(drain 上限 %s)...\n", drain)
if err := srv.Shutdown(ctx); err != nil {
fmt.Fprintf(os.Stderr, "服务停止异常: %v\n", err)
} else {
fmt.Println("服务已安全退出。")
}
}
// shutdownDrainTimeout 解析 T20-B7 drain 窗口:
// - SHUTDOWN_DRAIN_TIMEOUT 显式设置则优先(校验 >0);
// - 否则默认 min(AGENT_TIMEOUT, 35m) + 30s 缓冲(compose stop_grace_period 需 ≥ 此值)。
func shutdownDrainTimeout(cfg *config.Config) time.Duration {
if v := os.Getenv("SHUTDOWN_DRAIN_TIMEOUT"); v != "" {
if d, err := time.ParseDuration(v); err == nil && d > 0 {
return d
}
}
at := 30 * time.Minute
if cfg != nil && cfg.AgentTimeout > 0 {
at = cfg.AgentTimeout
}
cap := 35 * time.Minute
if at > cap {
at = cap
}
return at + 30*time.Second
}
+52
View File
@@ -0,0 +1,52 @@
package main
import (
"testing"
"time"
"chat-agent-system/internal/config"
)
// T20-B7 回归:drain 窗口与 AGENT_TIMEOUT 对齐。
func TestShutdownDrainTimeout_DefaultCapped35m(t *testing.T) {
cfg := &config.Config{AgentTimeout: 60 * time.Minute} // 超过上限 → 封顶 35m
got := shutdownDrainTimeout(cfg)
want := 35*time.Minute + 30*time.Second
if got != want {
t.Fatalf("expect cap %s, got %s", want, got)
}
}
func TestShutdownDrainTimeout_FollowsShortAgentTimeout(t *testing.T) {
cfg := &config.Config{AgentTimeout: 10 * time.Minute} // 短于上限 → 跟随 +30s
got := shutdownDrainTimeout(cfg)
want := 10*time.Minute + 30*time.Second
if got != want {
t.Fatalf("expect %s, got %s", want, got)
}
}
func TestShutdownDrainTimeout_EnvOverride(t *testing.T) {
t.Setenv("SHUTDOWN_DRAIN_TIMEOUT", "45s")
cfg := &config.Config{AgentTimeout: 30 * time.Minute}
if got := shutdownDrainTimeout(cfg); got != 45*time.Second {
t.Fatalf("env override expect 45s, got %s", got)
}
}
func TestShutdownDrainTimeout_InvalidEnvFallsBack(t *testing.T) {
t.Setenv("SHUTDOWN_DRAIN_TIMEOUT", "not-a-duration")
cfg := &config.Config{AgentTimeout: 30 * time.Minute}
want := 30*time.Minute + 30*time.Second
if got := shutdownDrainTimeout(cfg); got != want {
t.Fatalf("invalid env expect fallback %s, got %s", want, got)
}
}
func TestShutdownDrainTimeout_NilCfg(t *testing.T) {
got := shutdownDrainTimeout(nil)
want := 30*time.Minute + 30*time.Second
if got != want {
t.Fatalf("nil cfg expect default %s, got %s", want, got)
}
}