feat(ops): 优雅停机 drain —— 三 Go 服务 SIGTERM 后排空在途,不硬切
滚动更新/重启时旧的"信号到→进程退"会硬切在途工作:dispatcher 在途任务被掐断、 mcp-go 在途工具调用让 dispatcher 干等超时、gateway 在途 HTTP 请求被截断。 共享 bus 加在途追踪 + drain: - ConsumeTasks/ServeTool 各加 sync.WaitGroup 跟踪在途 goroutine,返回 drain(ctx): 先停止接新活(cc.Stop / Unsubscribe),再等在途跑完至 drain 超时。 - 关键修复:任务 handler 的 ctx 改为派生自 context.Background()(而非信号 ctx), 否则 SIGTERM 会立即取消在途任务的 ctx,drain 形同虚设。超时未跑完才由 JetStream AckWait 重投兜底(不丢任务)。 - DrainTimeout():SHUTDOWN_DRAIN_TIMEOUT 秒,默认 30s。 各服务收尾: - gateway:r.Run → http.Server + signal.NotifyContext + srv.Shutdown(drain 在途请求), 随后 defer 关 db/redis/bus(HTTP 排空后才断后端连接)。 - dispatcher:收到信号 → drain 在途任务跑完再退。 - mcp-go:收到信号 → drain 在途工具调用回完再退(dispatcher 拿到结果而非超时)。 bus 加 TestGracefulDrain(drain 等满在途任务 + 验证在途 ctx 不被取消),e2e 测试适配 新签名,四模块全绿。live:在途任务执行中 kill -TERM dispatcher → 日志「drain 在途任务」 → 11s 后 task done(511字完整生成) → 「drain 完成,退出」,任务终态 done 非 failed; gateway/mcp-go 同样优雅退出。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,13 +5,17 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sundynix/sundynix-gateway/internal/blob"
|
||||
"github.com/sundynix/sundynix-gateway/internal/nats"
|
||||
"github.com/sundynix/sundynix-gateway/internal/router"
|
||||
"github.com/sundynix/sundynix-gateway/internal/store"
|
||||
sharedbus "github.com/sundynix/sundynix-shared/bus"
|
||||
"github.com/sundynix/sundynix-shared/contract"
|
||||
"github.com/sundynix/sundynix-shared/otelx"
|
||||
"github.com/sundynix/sundynix-shared/secrets"
|
||||
@@ -94,10 +98,29 @@ func main() {
|
||||
|
||||
r := router.New(db, cache, bus, blobStore)
|
||||
addr := envOr("GATEWAY_ADDR", ":8080")
|
||||
log.Printf("[gateway] listening on %s", addr)
|
||||
if err := r.Run(addr); err != nil {
|
||||
log.Fatalf("[gateway] exit: %v", err)
|
||||
srv := &http.Server{Addr: addr, Handler: r}
|
||||
|
||||
// 后台监听;ListenAndServe 在 Shutdown 后返回 ErrServerClosed(正常退出)。
|
||||
go func() {
|
||||
log.Printf("[gateway] listening on %s", addr)
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalf("[gateway] listen: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// 优雅停机:收到信号 → 停止收新连接、drain 在途请求(至多 DrainTimeout),再退出
|
||||
// (defer 的 db/cache/bus/blob Close 随后执行,确保 HTTP 排空后才断后端连接)。
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
<-ctx.Done()
|
||||
to := sharedbus.DrainTimeout()
|
||||
log.Printf("[gateway] 收到停机信号,优雅关闭 HTTP(drain 在途请求 ≤%s)…", to)
|
||||
sctx, cancel := context.WithTimeout(context.Background(), to)
|
||||
defer cancel()
|
||||
if err := srv.Shutdown(sctx); err != nil {
|
||||
log.Printf("[gateway] HTTP 优雅关闭超时/出错: %v", err)
|
||||
}
|
||||
log.Println("[gateway] 已优雅停机")
|
||||
}
|
||||
|
||||
func envOr(key, def string) string {
|
||||
|
||||
Reference in New Issue
Block a user