fix: 停机先停接受并等待循环再断开 MQTT

This commit is contained in:
Nixevol
2026-09-30 16:22:44 +08:00
parent 6a65ab8593
commit b40ef5c548
8 changed files with 345 additions and 20 deletions
+55 -10
View File
@@ -43,7 +43,20 @@ func cmdServe(_ []string) error {
setupJSONLogger(cfg.Log)
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
return runServe(ctx, cfg)
return runServe(withServeStop(ctx, stop), cfg)
}
type serveStopKey struct{}
func withServeStop(ctx context.Context, stop context.CancelFunc) context.Context {
return context.WithValue(ctx, serveStopKey{}, stop)
}
func invokeServeStop(ctx context.Context) {
stop, _ := ctx.Value(serveStopKey{}).(context.CancelFunc)
if stop != nil {
stop()
}
}
func runServe(ctx context.Context, cfg config.Config) error {
@@ -297,27 +310,59 @@ func runServe(ctx context.Context, cfg config.Config) error {
}
}
loopCtx, loopCancel := context.WithCancel(ctx)
loopCtx, loopCancel := context.WithCancel(context.Background())
defer loopCancel()
go messageLoops(loopCtx, msgApp, db, hashPool, metricsReg)
loopsDone := make(chan struct{})
go func() {
defer close(loopsDone)
messageLoops(loopCtx, msgApp, db, hashPool, metricsReg)
}()
<-ctx.Done()
invokeServeStop(ctx)
shutdownDeadline := time.Now().Add(30 * time.Second)
_ = lnSrv.StopAccept()
loopCancel()
// B-08:先对 MQTT 连接发 0x8B。HTTP Shutdown 与监听器完整停机顺序见 L-03。
shutCtx, shutCancel := context.WithTimeout(context.Background(), 5*time.Second)
_ = brk.Shutdown(shutCtx)
shutCancel()
_ = lnSrv.Close()
drainCtx, drainCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer drainCancel()
<-loopsDone
drainBudget := 10 * time.Second
drainStart := time.Now()
drainCtx, drainCancel := context.WithTimeout(context.Background(), drainBudget)
if drainErr := db.Queue.Drain(drainCtx); drainErr != nil && !errors.Is(drainErr, context.DeadlineExceeded) {
slog.Error("write queue drain", "err", drainErr)
}
drainCancel()
shutCtx, shutCancel := context.WithTimeout(context.Background(), 5*time.Second)
_ = brk.Shutdown(shutCtx)
shutCancel()
secondDrain := drainBudget - time.Since(drainStart)
if secondDrain < time.Second {
secondDrain = time.Until(shutdownDeadline)
}
if secondDrain < time.Second {
secondDrain = time.Second
}
drain2, drain2Cancel := context.WithTimeout(context.Background(), secondDrain)
if drainErr := db.Queue.Drain(drain2); drainErr != nil && !errors.Is(drainErr, context.DeadlineExceeded) {
slog.Error("write queue drain", "err", drainErr)
}
drain2Cancel()
waitRemain := time.Until(shutdownDeadline)
if waitRemain < 2*time.Second {
waitRemain = 2 * time.Second
}
waitCtx, waitCancel := context.WithTimeout(context.Background(), waitRemain)
_ = lnSrv.Wait(waitCtx)
waitCancel()
return nil
}
func messageLoops(ctx context.Context, msgApp *message.App, db *store.DB, hashPool auth.HashPool, met *metrics.Registry) {
msgApp.StartLoops(ctx)
defer msgApp.WaitLoops()
t := time.NewTicker(15 * time.Second)
defer t.Stop()
sample := func() {