Luaをタイムアウト付きで実行する
環境
- Lua 5.1.4
- LuaJIT 2.0.0-beta9
動機
MySQLのUDFの中でLuaを実行したい。
このとき、無限ループするようなコードを実行すると、killクエリを投げても止まらないスレッドになってしまい、mysqldをkill -9するしかなくなる。
そのため、無限ループしてても時間がたてば強制終了されるように、タイムアウトを設定したい。
実装
startclock = os.clock() timeout = startclock + 3 debug.sethook(function () if os.clock() >= timeout then error('timeout') end end, '', 100000) while true do end
debug.sethookの第2引数に'c', 'r', 'l'以外を渡して、第3引数を渡すと、カウントフックを設定できる。
第3引数に渡した値がカウント値として設定される。
カウント値はLuaが命令を実行するたびにデクリメントされ、0になったときにカウントフックが呼ばれる。
一度カウントフックが呼ばれると、カウント値は第3引数の値にリセットされ、再度0になるまでデクリメントされる。
リファレンスマニュアルに書いてある、
count がゼロでなければ、フックは count 命令が実行されるたびに、その直後に呼ばれる。
With a count different from zero, the hook is called after every count instructions.
この説明は間違っているので注意。
パフォーマンス
カウント値が0でないときは、命令実行のたびにデクリメントが走るだけなので、それほど重くない。
debug.sethook関数に渡す第3引数を十分大きな値にしておけば、数%程度の速度低下ですむはず。(厳密には測ってないけど。)
MySQLのHandler APIを使ってタイムラインを取得するサンプルコードでは、1%~2%程度の速度低下で済んだ。
ただし、カウント値を大きくすればするほど、タイムアウト時刻の精度が落ちるので、注意。
最後に
実際に動くコードはここにあります。
https://github.com/atsumu/mylua