Rabbit Slide Show

バグの直し方

2012-09-16

Description

札幌Ruby会議2012で発表した内容にデモで話す予定だったことを追加しました。

Text

Page: 1

バグの直し方
実例を添えて
Kenji Okimoto
株式会社クリアコード
2012-09-16
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 2

自己紹介
✓ okkez (おっきーと読みます)
✓ Ruby 歴8年くらい
✓ るりま (2006-11から)
✓ Hiki (2008-09から)
✓ CRuby コミッタ (2011-12から)
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 3

自己紹介
✓ クリアコード (2010-11から)
✓ 大規模メールシステム
(milter manager)
✓ GLib, GTK+ アプリケーション
✓ Ruby, C/C++, etc.
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 4

お品書き
✓ よく知らないプログラムの バグの直し方
✓ 実例紹介
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 5

バグの直し方
✓ バグに気付く
✓ 再現方法を記録する
✓ 問題箇所を絞り込む
✓ 問題を修正する
✓ 修正できたことを確認する
✓ 壊していないことを確認する
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 6

再現方法を記録する
✓ 問題を再現できる
✓ テストプログラムを作成する
✓ 操作をメモする
✓ データを作成する
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 7

問題箇所を絞り込む
対象のプログラムを
✓ よく知っている場合
✓ 怪しい部分の目星を付ける
✓ よく知らない場合
✓ 地道に作業して問題箇所を特定する
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 8

問題箇所を絞り込む
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 9

ポイント!!
プログラムの変更を少しずつ
行うことによって、一歩ずつ着
実に問題の解決に向かっていく
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 10

ポイント!!
プログラムの変更を行うたび
に、「今回の変更では何を調べ
たいのか」を意識して作業する
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 11

問題箇所を特定できたら
✓ 問題を修正する
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 12

修正できたことを確認
✓ 再現方法を実行
✓ 問題が再現しないことを確認
✓ 修正できた!!
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 13

実例紹介
✓ Ruby のメモリリーク
✓ http://bugs.ruby-lang.org/
issues/5688
✓ http://www.clear-code.com/
blog/2011/12/6.html
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 14

デモ
✓ バグに気付く
✓ 再現方法を記録する
✓ 問題箇所を絞り込む
✓ 問題を修正する
✓ 修正できたことを確認する
✓ 壊していないことを確認する
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 15

事前情報
✓ Solaris10 で発生
✓ Ruby 1.9.2-p180 で発生
✓ mtrace や valgrind が使えない
✓ spawn だけでなく fork でも発生
✓ 慣れている Debian では発生し
ない
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 16

再現コード
ARGV[0].to_i.times do |n|
fork{ exit! }
#spawn{ exit! }
GC.start if n % 100 == 0
end
fork()でもspawn()でもメモリリークする
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 17

便利スクリプト
#!/bin/bash
/tmp/a/bin/ruby ./fork-exit.rb 300000 &
pid=$!
echo fork-exit:$pid
trap "kill $pid; exit" INT TERM
count=0
ps -o pid,ppid,vsz,rss,args | head -1
prev=""
while true; do
current=`ps -p ${pid} -o pid,ppid,vsz,rss,args | grep fork-exit.rb`
if test "$current" != "$prev"; then
echo "$current"
fi
prev=$current
sleep 1
done
便利スクリプトがあると便利!!
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 18

Step 1
spawn のコールグラフ
spawn
-> rb_f_spawn
-> rb_spawn_process
-> rb_fork_err
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 19

Step 1
fork のコールグラフ
fork
-> rb_f_fork
-> rb_fork
-> rb_fork_err
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 20

Step 2
spawn でも
fork でも
rb_fork_err
を呼んでいる
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 21

Step 3 - 処理を確認する
rb_fork_err の中身を確認
before_fork()
fork()
if (!pid) { ... }
after_fork()
if(!pid){...}は子プロセスの処理なので無視する
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 22

Step 3 - 処理を変更する
✓ before_fork() の前で return -1
✓ before_fork() の後で return -1
✓ fork() の後で return pid
✓ after_fork() の後で return pid
✓ 修正前と同じ
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 23

Step 4
✓ before_fork() の前 ... ビルドエラー
✓ before_fork() の後 ... ビルドエラー
✓ fork() の後で ... メモリリークしない!
after_fork() に問題があることがわ
かった!!
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 24

Step 5 - after_fork()
✓ GET_THREAD()-
>thrown_errinfo = 0
✓ 値をセットしているだけ
✓ rb_thread_reset_timer_threa
d()
✓ 値をセットしてるだけ
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 25

Step 5 - after_fork()
✓ rb_thread_start_timer_thread
()
✓ rb_thread_create_timer_thread()
✓ forked_child = 0
✓ 値をセットしてるだけ
✓ rb_disable_interrupt()
✓ 値をセットしてるだけ
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 26

Step 6
✓ pthread_attr_init(3) を読む
✓ pthread_attr_destroy() もある
✓ pthread_create(3) を読む
✓ pthread_create()の引数がわかる
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 27

Step 7
pthread_create
の第2引数を
NULLにすると...
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 28

Step 7
メモリリーク
しなくなる!!
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 29

Step 8
pthread_attr_init
してるけど
pthread_attr_destroy
してないことに気付く
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 30

Step 9
pthread_attr_d
estroy
を追加して
試すと...
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 31

Step 10
メモリリーク
しなくなる
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 32

パッチ
diff --git a/thread_pthread.c b/thread_pthread.c
index 4746aaa..ab7bdf9 100644
--- a/thread_pthread.c
+++ b/thread_pthread.c
@@ -835,6 +835,7 @@ rb_thread_create_timer_thread(void)
}
native_cond_wait(&timer_thread_cond, &timer_thread_lock);
native_mutex_unlock(&timer_thread_lock);
+
pthread_attr_destroy(&attr);
}
rb_disable_interrupt(); /* only timer thread recieve signal */
}
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 33

Step 11
パッチを作ったら
make test-all
を流し て E , F が増え て い な い
ことを確認する
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 34

まとめ
✓ バグに気付く
✓ 再現方法を記録する
✓ 問題箇所を絞り込む
✓ 問題を修正する
✓ 修正できたことを確認する
✓ 壊していないことを確認する
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 35

ポイント!!
問題箇所は
一歩ずつ着実に
絞り込む
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 36

ポイント!!
問題箇所を特定できたらバグ
の修正は八割以上できたも同然
です
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Page: 37

次の一歩
✓ 水平展開
✓ 似たようなバグがあるかも。。。
バグの直し方 - 実例を添えて
Powered by Rabbit 2.0.5

Other slides