feat(docker,server): 改进容器信号处理和优雅关闭
- 使用 tini 作为 PID 1 init 进程,处理僵尸进程回收和信号转发 - 多次 SIGINT (3次) 支持强制退出 - GoroutinePool.Stop() 添加 5s 超时等待,防止无限阻塞 - GracefulUpgrade 启动 goroutine 等待子进程,避免僵尸进程 - 关闭父进程文件描述符副本,防止泄漏 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1ed56bd9c4
commit
ada7357f7d
12
Dockerfile
12
Dockerfile
@ -32,6 +32,10 @@ RUN CGO_ENABLED=0 GOOS=linux go build \
|
|||||||
-o /build/lolly \
|
-o /build/lolly \
|
||||||
main.go
|
main.go
|
||||||
|
|
||||||
|
# ---- Tini stage ----
|
||||||
|
FROM alpine:3.19 AS tini-stage
|
||||||
|
RUN apk add --no-cache tini-static
|
||||||
|
|
||||||
# ---- Runtime stage ----
|
# ---- Runtime stage ----
|
||||||
FROM scratch
|
FROM scratch
|
||||||
|
|
||||||
@ -41,11 +45,15 @@ COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
|||||||
# 二进制文件
|
# 二进制文件
|
||||||
COPY --from=builder /build/lolly /lolly
|
COPY --from=builder /build/lolly /lolly
|
||||||
|
|
||||||
|
# tini 静态版本(处理 PID 1 僵尸进程回收和信号转发)
|
||||||
|
COPY --from=tini-stage /sbin/tini-static /tini
|
||||||
|
|
||||||
# 优雅关闭:SIGQUIT 触发 30s graceful stop
|
# 优雅关闭:SIGQUIT 触发 30s graceful stop
|
||||||
STOPSIGNAL SIGQUIT
|
STOPSIGNAL SIGQUIT
|
||||||
|
|
||||||
# HTTP/1.1, HTTP/2, HTTP/3 (QUIC)
|
# HTTP/1.1, HTTP/2, HTTP/3 (QUIC)
|
||||||
EXPOSE 8080/tcp 443/tcp 443/udp
|
EXPOSE 8080/tcp 443/tcp 443/udp
|
||||||
|
|
||||||
ENTRYPOINT ["/lolly"]
|
# 使用 tini 作为 init 进程(PID 1)
|
||||||
CMD ["-c", "/etc/lolly/lolly.yaml"]
|
ENTRYPOINT ["/tini", "--"]
|
||||||
|
CMD ["/lolly", "-c", "/etc/lolly/lolly.yaml"]
|
||||||
@ -328,6 +328,9 @@ func (a *App) Run() int {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// SIGINT 计数器,用于强制退出
|
||||||
|
sigintCount := 0
|
||||||
|
|
||||||
// 等待信号或启动错误
|
// 等待信号或启动错误
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@ -335,6 +338,14 @@ func (a *App) Run() int {
|
|||||||
a.logger.Error().Err(err).Msg("服务器启动失败")
|
a.logger.Error().Err(err).Msg("服务器启动失败")
|
||||||
return 1
|
return 1
|
||||||
case sig := <-sigChan:
|
case sig := <-sigChan:
|
||||||
|
// 多次 SIGINT 强制退出
|
||||||
|
if sig == syscall.SIGINT {
|
||||||
|
sigintCount++
|
||||||
|
if sigintCount >= 3 {
|
||||||
|
a.logger.LogShutdown("收到 3 次 SIGINT,强制退出")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
if !a.handleSignal(sig) {
|
if !a.handleSignal(sig) {
|
||||||
// 返回 false 表示退出
|
// 返回 false 表示退出
|
||||||
a.logger.LogShutdown("服务器已停止")
|
a.logger.LogShutdown("服务器已停止")
|
||||||
|
|||||||
@ -126,12 +126,26 @@ func (p *GoroutinePool) Start() {
|
|||||||
|
|
||||||
// Stop 停止 Goroutine 池。
|
// Stop 停止 Goroutine 池。
|
||||||
//
|
//
|
||||||
// 取消所有 worker,等待它们退出完成。
|
// 取消所有 worker,等待它们退出完成(最多等待 5 秒)。
|
||||||
// 调用后池将不再接受新任务。
|
// 调用后池将不再接受新任务。
|
||||||
func (p *GoroutinePool) Stop() {
|
func (p *GoroutinePool) Stop() {
|
||||||
p.running.Store(false)
|
p.running.Store(false)
|
||||||
p.cancel()
|
p.cancel()
|
||||||
p.wg.Wait()
|
|
||||||
|
// 使用超时等待,防止 wg.Wait() 无限阻塞
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
p.wg.Wait()
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
// 所有 worker 正常退出
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
// 超时,强制退出(worker 会在收到 ctx.Done() 后自行退出)
|
||||||
|
// 不再等待,直接返回
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Submit 提交任务到池。
|
// Submit 提交任务到池。
|
||||||
|
|||||||
@ -223,6 +223,18 @@ func (u *UpgradeManager) GracefulUpgrade(newBinary string) error {
|
|||||||
_ = os.WriteFile(u.pidFile, []byte(fmt.Sprintf("%d", newPid)), 0644)
|
_ = os.WriteFile(u.pidFile, []byte(fmt.Sprintf("%d", newPid)), 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 启动 goroutine 等待子进程结束,避免产生僵尸进程
|
||||||
|
// cmd.Wait() 会回收子进程资源,确保不会产生 defunct 进程
|
||||||
|
go func() {
|
||||||
|
_ = cmd.Wait()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 关闭父进程中的文件描述符副本(子进程已继承)
|
||||||
|
// 避免文件描述符泄漏
|
||||||
|
for _, file := range files {
|
||||||
|
_ = file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user