Recover Goroutine Panic

ปกติเวลาเขียน Go เราพยายามจัดการ error ต่างๆ ไม่ค่อยอยากให้มีกรณี panic เกิดขึ้น แต่ถ้ามันเกิด แล้วไปเกิดใน goroutine ที่เราแยกออกไป จะทำยังไงให้ goroutine หลัก ไม่ตายไปด้วยละ โดยเฉพาะการทำงานกับ long running server คงไม่ดีแน่ที่ server จะ down ไปด้วยเพราะ goroutine บางตัว panic

Defer and Recover

เราสามารถใช้ defer และ recover ช่วยดักจับการเกิด panic ได้เช่น

func Boom() {
panic("Boom!")
}
func Safe() {
defer func() {
if err := recover(); err != nil {
log.Println("Panic:", err)
}
}()

Boom()
}

Safe Goroutine

เพราะฉะนั้น เวลาเราใช้ goroutine เราก็เรียกโดยให้มี defer and recover ป้องกันการ panic ที่เกิดจากการทำงานของ goroutine ได้ด้วย ทำแบบนี้

go func() {
defer func() {
if err := recover(); err != nil {
log.Println("Panic:", err)
}
}()

Boom()
}()

ถ้าขี้เกียจมาใส่ defer and recover เองก็แยกเป็น function ช่วยที่รับค่า function ไปรันอีก goroutine ก็ได้ครับแบบนี้

func GoSafe(fn func()) {
go func() {
defer func() {
if err := recover(); err != nil {
log.Println("Panic:", err)
}
}()
                fn()
}()
}
func main() {
GoSafe(func() {
Boom()
})
}

Pattern นี้ผมเห็นมาจาก package “net/http” ซึ่งเป็น standard package ไว้สร้าง http server ครับ ซึ่งเวลามี request เข้ามา จะถูกแยกเป็นการทำงานคนละ goroutine แล้วถ้าในโค้ดของ HandlerFunc มี panic ก็จะไม่ทำให้ main ของ server ตายไปด้วย

https://github.com/golang/go/blob/master/src/net/http/server.go#L1487-L1498