Files
orbit/examples/http-service/cmd/server/lifecycle.go

100 lines
2.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 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
}