Goでは、パニック(panic)はプログラム内の致命的なエラーを通知するために使用されます。パニックが発生した後、スタックのアンワインドが始まり、すべてのdeferred関数が呼び出されます。スタックにrecover()を呼び出す関数がある場合、それはパニックを捕まえて処理することができますが、recoverはそのゴルーチンの内部でのみ機能し、deferから呼び出されたときのみ機能します。
安全な回復のための推奨パターンは次のとおりです:
func safe(fn func()) { defer func() { if r := recover(); r != nil { fmt.Println("Recovered from panic:", r) } }() fn() } go safe(func() { panic("fail!") // プログラムの停止には至らない })
重要なことは、
panicは現在のゴルーチンの実行を最初のrecoverまで終了させるrecoverは、他のゴルーチンで発生したパニックを捕まえることができますか?
回答: いいえ、recoverはパニックが発生したゴルーチン内でのみ機能し、defer関数から呼び出された場合のみ有効です。あるゴルーチンでパニックが発生し、別のゴルーチンでrecoverが呼び出されても、捕まえることはできず、プログラムは異常終了します。
例:
func main() { go func() { panic("inside goroutine") }() time.Sleep(time.Second) recover() // 機能しない! Panicはプログラムを終了させる }
ストーリー
開発者は、main関数内でのみdefer recover()パターンを使用してエラー処理を実装した。ワーカー内でパニックが発生したとき、プログラム全体が異常終了した — グローバルrecoverがエラーを捕まえなかった。
ストーリー
プロジェクトでは、ウェブサーバーの優雅なシャットダウンのためにdefer/recoverを使用したが、それで十分だと考えていた。一つのゴルーチンのパニックがプロセス全体を終了させることが判明した。recoverの処理が正しい位置に配置されていなかったため、ワーカープール全体のリファクタリングが必要だった。
ストーリー
メッセージ処理のスレッドで、ユーザ関数の不適切な動作によりパニックが発生した。開発者は、トップレベルのrecoverがそれを「捕まえる」ことを期待したが、recoverがdeferからのみ機能することを理解していなかった。その結果、サービスは不規則にクラッシュし、キューに不正なメッセージが現れると発生した。