Code Fix

上級

recoverは同じgoroutine内のpanicしか捕まえない

recoverはそれを呼び出したgoroutine内で発生したpanicしか回復できません。別のgoroutine内でdeferとrecoverを仕込んでも、他のgoroutineのpanicは捕まえられずプログラム全体がクラッシュします。

エラーメッセージの読み方

panic: boom
panic
パニック — 実行時に回復不能な状態になったことを示します。deferとrecoverで捕まえない限り、そのままプロセスが終了します
boom
詳細メッセージ — どの値が問題だったか

このエラーが出る典型パターン

パターン1

 1  package main
 2  
 3  import (
 4  	"fmt"
 5  	"time"
 6  )
 7  
 8  func worker() {
 9  	panic("boom")
10  }
11  
12  func main() {
13  	defer func() {
14  		if r := recover(); r != nil {
15  			fmt.Println("recovered:", r)
16  		}
17  	}()
18  	go worker()             
                 ^
19  	time.Sleep(100 * time.Millisecond)
20  }
panic: boom

別のgoroutineで発生したpanicは、mainのdeferにあるrecoverでは捕まえられません。recoverはpanicと同じgoroutine内でしか効果がありません。

直し方: go worker()worker() にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  package main
 2  
 3  import (
 4  	"fmt"
 5  	"time"
 6  )
 7  
 8  func task() {
 9  	panic("fail")
10  }
11  
12  func main() {
13  	defer func() {
14  		if r := recover(); r != nil {
15  			fmt.Println("recovered:", r)
16  		}
17  	}()
18  	go task()             
                ^
19  	time.Sleep(100 * time.Millisecond)
20  }
panic: fail

goで起動したgoroutine内のpanicは、そのgoroutine自身がrecoverしない限り、プロセス全体をクラッシュさせます。

直し方: go task()task() にします。

この問題を解いてみる →

パターン3

 1  package main
 2  
 3  import (
 4  	"fmt"
 5  	"time"
 6  )
 7  
 8  func job() {
 9  	panic("error")
10  }
11  
12  func main() {
13  	defer func() {
14  		if r := recover(); r != nil {
15  			fmt.Println("recovered:", r)
16  		}
17  	}()
18  	go job()             
               ^
19  	time.Sleep(100 * time.Millisecond)
20  }
panic: error

main関数にrecoverを仕込んでおけばどのgoroutineのpanicも防げる、という思い込みは危険です。

直し方: go job()job() にします。

この問題を解いてみる →

パターン4

 1  package main
 2  
 3  import (
 4  	"fmt"
 5  	"time"
 6  )
 7  
 8  func process() {
 9  	panic("crash")
10  }
11  
12  func main() {
13  	defer func() {
14  		if r := recover(); r != nil {
15  			fmt.Println("recovered:", r)
16  		}
17  	}()
18  	go process()             
                 ^
19  	time.Sleep(100 * time.Millisecond)
20  }
panic: crash

安全にしたいなら、goroutineの中自身にdeferとrecoverのペアを用意する必要があります。

直し方: go process()process() にします。

この問題を解いてみる →

パターン5

 1  package main
 2  
 3  import (
 4  	"fmt"
 5  	"time"
 6  )
 7  
 8  func handle() {
 9  	panic("oops")
10  }
11  
12  func main() {
13  	defer func() {
14  		if r := recover(); r != nil {
15  			fmt.Println("recovered:", r)
16  		}
17  	}()
18  	go handle()             
                 ^
19  	time.Sleep(100 * time.Millisecond)
20  }
panic: oops

panicとrecoverの対応はgoroutineごとに独立しているため、goroutineをまたいだ回復はできません。

直し方: go handle()handle() にします。

この問題を解いてみる →

よくある誤解

「main関数でrecoverを仕込んでおけば、どのgoroutineのpanicも捕まえられるはず」という思い込みは誤りです。panicとrecoverはgoroutineごとに独立しており、panicしたgoroutine自身がdefer内でrecoverを呼ばない限り、そのpanicは回復できずプロセス全体を終了させます。

まとめ

recoverは同じgoroutine内のpanicしか捕まえないは上級でつまずきやすい項目です。上の5パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)