package main import ( "context" "errors" "fmt" "net" "net/http" "sync/atomic" "time" ) // component 不接管进程信号;宿主负责为每一类生命周期提供独立取消和等待。 type component struct { name string run func(context.Context) error } type completion struct { name string err error } // serve 在HTTP监听已成功后启动后台组件;返回前完成HTTP→发送/执行→结果消费的关闭顺序。 func serve(parent context.Context, listener net.Listener, server *http.Server, ready *atomic.Bool, workers []component, consumer component, grace time.Duration) error { workCtx, stopWork := context.WithCancel(context.Background()) defer stopWork() eventCtx, stopEvents := context.WithCancel(context.Background()) defer stopEvents() workDone := make(chan completion, len(workers)) eventDone := make(chan completion, 1) for _, c := range workers { go func() { workDone <- completion{c.name, c.run(workCtx)} }() } go func() { eventDone <- completion{consumer.name, consumer.run(eventCtx)} }() httpDone := make(chan error, 1) ready.Store(true) go func() { httpDone <- server.Serve(listener) }() remaining := len(workers) eventsPending := true var result error add := func(c completion) { if c.err != nil { result = errors.Join(result, fmt.Errorf("%s: %w", c.name, c.err)) } } select { case <-parent.Done(): case err := <-httpDone: if !errors.Is(err, http.ErrServerClosed) { result = errors.Join(result, err) } case c := <-workDone: remaining-- if c.err == nil { c.err = errors.New("unexpected component exit") } add(c) case c := <-eventDone: eventsPending = false if c.err == nil { c.err = errors.New("unexpected component exit") } add(c) } ready.Store(false) // 用新的关闭上下文;进程信号的context此时可能已取消,不能用它调用Shutdown。 shut, cancel := context.WithTimeout(context.Background(), grace) if err := server.Shutdown(shut); err != nil { result = errors.Join(result, fmt.Errorf("http shutdown: %w", err)) _ = server.Close() } cancel() stopWork() deadline := time.NewTimer(grace) defer deadline.Stop() for remaining > 0 { select { case c := <-workDone: remaining-- add(c) case <-deadline.C: result = errors.Join(result, errors.New("background shutdown deadline exceeded")) remaining = 0 } } // 结果消费器最后停止。仍未投递的结果会在重启后由持久订阅恢复,不以本地空闲判断全局完成。 stopEvents() if eventsPending { timer := time.NewTimer(grace) defer timer.Stop() select { case c := <-eventDone: add(c) case <-timer.C: result = errors.Join(result, errors.New("consumer shutdown deadline exceeded")) } } return result }