15

谈 Golang http.Server 安全退出:容易被误用的 Shutdown()方法

 2 years ago
source link: https://www.v2ex.com/t/803612
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

V2EX  ›  Go

谈 Golang http.Server 安全退出:容易被误用的 Shutdown()方法

  nanmu42 · 18 小时 20 分钟前 · 1134 次点击
14 条回复    2021-09-23 18:14:05 +08:00

SorcererXW

SorcererXW   17 小时 47 分钟前   ❤️ 1

我的理解是不是将退出操作放在主协程,其实 server 放在另外一个协程,就能避免立即退出?

func main() {
go server.Server()
<- signal
server.Shutdown(ctx)
}

nanmu42

nanmu42   17 小时 43 分钟前

@SorcererXW 这里就见仁见智了,ListenAndServe()在 goroutine 中的话,错误处理大概率是 log.Fatal(err)这样的操作,如果服务并不是主动退出的(比如启动时立马遇到端口占用的错误),主函数 main()中的 defer 是不会执行的。我这里用了一些额外的复杂度让安全退出的逻辑更圆满了一些。

v2Geeker

v2Geeker   16 小时 52 分钟前   ❤️ 1

见识了。

我一般都是 ListenAndServe 和 Shutdown 都放在 2 个不同的 gorountine 中,用 sync.WaitGroup 的 Wait 来等待结束,于是我好像从来没意识到 Shutdown 有这样的问题。

whitedroa

whitedroa   16 小时 30 分钟前   ❤️ 1

@nanmu42 没太看懂:“错误处理大概率是 log.Fatal(err)这样的操作” 这句话是什么意思呢

“如果服务并不是主动退出的(比如启动时立马遇到端口占用的错误),主函数 main()中的 defer 是不会执行的”
这里 main 中的 defer 为什么不会执行呢,是因为其他协程 panic 导致程序直接退出吗?

nanmu42

nanmu42   15 小时 45 分钟前

@v2Geeker 我是错了将近两年,涉及好几个服务,直到线上日志观察到了问题才醒悟的。

FrankAdler

FrankAdler   15 小时 45 分钟前   ❤️ 1

@nanmu42 #2 想让 main 的 defe 能执行,就需要让 main 正常退出,那在 goroutine 里出错错误的时候,发送 sign 到 main 里等等的 chan 就行了,比如

defer func() {
log.Println("defer")
}()

server := http.Server{
Addr: fmt.Sprintf(":%d", *port),
Handler: downright.SlowHandler(*sleepSeconds),
}

quit := make(chan os.Signal, 1)

go func() {
err := server.ListenAndServe()
if err != http.ErrServerClosed {
log.Printf("ListenAndServe err: %v", err)
quit <- syscall.SIGTERM
}
}()

signal.Notify(quit, os.Interrupt)
<-quit
log.Println("waiting for shutdown finishing...")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("shutdown err: %v", err)
}
log.Println("shutdown finished")

ListenAndServe 不管出现什么级别的错误都可以处理(只要不调用 os.Exit ),毕竟 main 一直在等待新号阻塞着

FrankAdler

FrankAdler   15 小时 44 分钟前

有几个错别字,见谅。。

nanmu42

nanmu42   15 小时 41 分钟前

@whitedroa 在另一个 goroutine 里做 ListenAndServe(),它的返回值一般是用 log.Fatal()来接的,要不然就不晓得 HTTP 服务启停状态了。
log.Fatal()调用的是 os.Exit(),这个方法会造成 go 程序直接退出,main()里的 defer 函数不运行(博文里链了 godoc 链接)。
当然也可以不用 log.Fatal(),自己搞定同步,但是那样复杂度上来了。

nanmu42

nanmu42   15 小时 39 分钟前

@FrankAdler 是呢,这样可以让 main()执行完,我们的思路挺类似呢。

zouzou0208

zouzou0208   15 小时 37 分钟前   ❤️ 1

写的好,学到了,感谢感谢。之前没主要到过这个。还给出了代码,真贴心。

hhaobao

hhaobao   15 小时 7 分钟前   ❤️ 1

go 1.16 后, Notify 可以改成 NotifyContext

Yoock

Yoock   14 小时 53 分钟前   ❤️ 1

index90

index90   13 小时 45 分钟前   ❤️ 1

你这样子改,如果一个程序要启动多个 http 服务就不行了。
如果你是担心 goroutine 启动 server,server 意外退出的问题,用 errgroup 好了。
比较完善的多 routine 和退出处理,用 rungourp 一把梭

hu8245

hu8245   13 小时 1 分钟前

with context 啊

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK