環境
- Ruby 2.5.0
- byebug 10.0.2
byebugにはスレッドをデバッグするために thread
というコマンドがあり、以下の機能がある。
デバッガーでthread(短縮形はth) コマンドを使用すると、スレッド実行中にスレッドのリスト表示/停止/再開/切り替えを行えます。このコマンドには以下のささやかなオプションがあります。
threadは現在のスレッドを表示します。
thread listはすべてのスレッドのリストをステータス付きで表示します。現在実行中のスレッドは「+」記号と数字で示されます。
thread stop nはスレッド n を停止します。
thread resume nはスレッド n を再開します。
thread switch nは現在のスレッドコンテキストを n に切り替えます。
https://railsguides.jp/debugging_rails_applications.html#%E3%82%B9%E3%83%AC%E3%83%83%E3%83%89
使用例
# デッドロックするスクリプト
require "byebug"
threads = []
m1 = Mutex.new
m2 = Mutex.new
threads << Thread.new do
m1.synchronize do
puts "thread1: enter m1.synchronize"
sleep 1
m2.synchronize do
puts "thread1: enter m2.synchronize"
end
end
end
threads << Thread.new do
m2.synchronize do
puts "thread2: enter m2.synchronize"
sleep 1
m1.synchronize do
puts "thread2: enter m1.synchronize"
end
end
end
# 普通、スレッドの終了待ちするときは threads.each {|t| t.join } のように書くと思うが、デバッグするためにこの待ち方をしている
while threads.any? {|t| t.alive? }
puts "waiting..."
sleep 1
#byebug
end
puts "end"
上記のスクリプトは2個のスレッドを作成しており、それぞれm1, m2
m2, m1
の順でmutexロックを取得しようとするので、デッドロックする。実際に実行すると次のような出力になる:
$ ruby hoge.rb
waiting...
thread1: enter m1.synchronize
thread2: enter m2.synchronize
waiting...
waiting...
waiting...
waiting...
waiting...
(永久にwaiting...が続く)
これをデバッグするため、スクリプトの# byebug
の行のコメントアウトを外して実行する。
$ ruby hoge.rb
waiting...
thread1: enter m1.synchronize
thread2: enter m2.synchronize
[21, 30] in /Users/val00362/hoge.rb
21: puts "thread2: enter m1.synchronize"
22: end
23: end
24: end
25:
=> 26: while threads.any? {|t| t.alive? }
27: puts "waiting..."
28: sleep 1
29: byebug
30: end
(byebug) th list
+ 1 #<Thread:0x00007fd8c107dd38 run> /Users/val00362/hoge.rb:26
2 #<Thread:0x00007fd8c08a16f0@hoge.rb:8 sleep_forever> hoge.rb:11
3 #<Thread:0x00007fd8c08a1538@hoge.rb:17 sleep_forever> hoge.rb:20
(byebug) th sw 2
2 #<Thread:0x00007fd8c08a16f0@hoge.rb:8 sleep_forever> hoge.rb:11
waiting...
[21, 30] in /Users/val00362/hoge.rb
21: puts "thread2: enter m1.synchronize"
22: end
23: end
24: end
25:
=> 26: while threads.any? {|t| t.alive? }
27: puts "waiting..."
28: sleep 1
29: byebug
30: end
(byebug) bt
--> #0 block in block in <main> at /Users/val00362/hoge.rb:26
ͱ-- #1 Array.any?(*args) at /Users/val00362/hoge.rb:26
#2 <main> at /Users/val00362/hoge.rb:26
メインスレッドのbyebug
のところでデバッガが起動するので、th list
でスレッド一覧表示、th sw 2
でスレッド切り替え、bt
でバックトレース表示(今どこを実行中か確認)している。