バグの直し方¶ ↑
: subtitle
実例を添えて
: author
Kenji Okimoto
: institution
株式会社クリアコード
: date
2012-09-16
: theme
.
: allotted-time
30m
自己紹介¶ ↑
-
okkez (おっきーと読みます)
-
Ruby 歴8年くらい
-
るりま (2006-11から)
-
Hiki (2008-09から)
-
CRuby コミッタ (2011-12から)
自己紹介¶ ↑
-
クリアコード (2010-11から)
-
大規模メールシステム n(milter manager)
-
GLib, GTK+ アプリケーション
-
Ruby, C/C++, etc.
-
お品書き¶ ↑
-
((‘note:よく知らないプログラムの’))バグの直し方
-
実例紹介
バグの直し方¶ ↑
-
バグに気付く
-
再現方法を記録する
-
問題箇所を絞り込む
-
((‘tag:x-small:問題を修正する’))
-
((‘tag:x-small:修正できたことを確認する’))
-
壊していないことを確認する
再現方法を記録する¶ ↑
-
問題を再現できる
-
テストプログラムを作成する
-
操作をメモする
-
データを作成する
-
問題箇所を絞り込む¶ ↑
対象のプログラムを
-
よく知っている場合
-
怪しい部分の目星を付ける
-
-
((*よく知らない場合*))
-
((*地道に作業して問題箇所を特定する*))
-
問題箇所を絞り込む¶ ↑
# image # src = flow.svg # relative_height = 100
property¶ ↑
: enable-title-on-image
false
ポイント!!¶ ↑
# blockquote プログラムの変更を少しずつ行うことによって、一歩ずつ着実に問題の解決に 向かっていく
ポイント!!¶ ↑
# blockquote プログラムの変更を行うたびに、「今回の変更では何を調べたいのか」を意識 して作業する
問題箇所を特定できたら¶ ↑
-
問題を修正する
修正できたことを確認¶ ↑
-
再現方法を実行
-
問題が再現しないことを確認
-
((*修正できた!!*))
実例紹介¶ ↑
デモ¶ ↑
-
((‘note:バグに気付く’))
-
((‘note:再現方法を記録する’))
-
((*問題箇所を絞り込む*))
-
問題を修正する
-
((‘note:修正できたことを確認する’))
-
((‘note:壊していないことを確認する’))
事前情報¶ ↑
-
Solaris10 で発生
-
Ruby 1.9.2-p180 で発生
-
mtrace や valgrind が使えない
-
spawn だけでなく fork でも発生
-
-
慣れている Debian では発生しない
再現コード¶ ↑
# coderay ruby ARGV[0].to_i.times do |n| fork{ exit! } #spawn{ exit! } GC.start if n % 100 == 0 end
((‘note:fork()でもspawn()でもメモリリークする’))
便利スクリプト¶ ↑
# coderay bash #!/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
((‘note:便利スクリプトがあると便利!!’))
Step 1¶ ↑
spawn のコールグラフ
spawn -> rb_f_spawn -> rb_spawn_process -> rb_fork_err
Step 1¶ ↑
fork のコールグラフ
fork -> rb_f_fork -> rb_fork -> rb_fork_err
Step 2¶ ↑
spawn でも fork でも ((rb_fork_err))nを呼んでいる
Step 3 - 処理を確認する¶ ↑
rb_fork_err の中身を確認
before_fork() fork() if (!pid) { ... } after_fork()
((‘note:if(!pid){…}は子プロセスの処理なので無視する’))
Step 3 - 処理を変更する¶ ↑
-
before_fork() の前で return -1
-
before_fork() の後で return -1
-
fork() の後で return pid
-
after_fork() の後で return pid
-
修正前と同じ
-
Step 4¶ ↑
-
((‘tag:x-small:before_fork() の前’)) … ビルドエラー
-
((‘tag:x-small:before_fork() の後’)) … ビルドエラー
-
((‘tag:x-small:fork() の後で’)) … メモリリークしない!
((*after_fork()*)) に問題があることがわかった!!
Step 5 - after_fork()¶ ↑
-
GET_THREAD()->thrown_errinfo = 0
-
値をセットしているだけ
-
-
rb_thread_reset_timer_thread()
-
値をセットしてるだけ
-
Step 5 - after_fork()¶ ↑
-
rb_thread_start_timer_thread()
-
rb_thread_create_timer_thread()
-
-
forked_child = 0
-
値をセットしてるだけ
-
-
rb_disable_interrupt()
-
値をセットしてるだけ
-
Step 6¶ ↑
-
pthread_attr_init(3) を読む
-
pthread_attr_destroy() もある
-
-
pthread_create(3) を読む
-
pthread_create()の引数がわかる
-
Step 7¶ ↑
pthread_createn の第2引数をn ((NULL))にすると…
Step 7¶ ↑
メモリリークnしなくなる!!
Step 8¶ ↑
((pthread_attr_init))n してるけどn ((pthread_attr_destroy))n してないことに気付く
Step 9¶ ↑
((pthread_attr_destroy))n を追加してn試すと…
Step 10¶ ↑
((*メモリリーク*))n ((*しなくなる*))
パッチ¶ ↑
# coderay diff 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 */ }
Step 11¶ ↑
# blockquote パッチを作ったら\nmake test-all\nを流してE,Fが増えていないことを確認する
まとめ¶ ↑
-
バグに気付く
-
((*再現方法を記録する*))
-
((*問題箇所を絞り込む*))
-
((‘tag:x-small:問題を修正する’))
-
((‘tag:x-small:修正できたことを確認する’))
-
壊していないことを確認する
ポイント!!¶ ↑
問題箇所はn((*一歩ずつ着実に*))n絞り込む
ポイント!!¶ ↑
# blockquote 問題箇所を特定できたらバグの修正は((*八割以上*))できたも同然です
次の一歩¶ ↑
-
水平展開
-
似たようなバグがあるかも。。。
-