CherryPyの自動リブート機能の仕組みと利用法
昨日、CherryPyを使ってみて、ファイル更新した途端、サーバが自動的にリブートしたのに感動しました。
で、どういう仕組みで実現しているんだろ〜?と思って、ちょっとコードを追いかけてみました。
- "cherrypy.server.start()"で、呼び出されているのは、どうも Lib\site-packages\cherrypy\_cpengine.py の start() メソッド(78行)のようです。
- 自動リブートを実現しているモジュールは、Lib\site-packages\cherrypy\lib\autoreload.py のようです。
- 自動リブートを利用したいならば、関数のポインタ?を autoreload.py の main() 関数の第1引数に渡してやればOKのようです。
- autoreload.py の main関数に飛び込むと、
1. main() -> sys.exit(restart_with_reloader()) -> restart_with_reloader() -> exit_code = os.spawnve(os.P_WAIT, sys.executable, args, new_environ)
2. main() -> reloader_thread(freq) -> reloader_thread()
の順に「2つの関数が」コールされます。 - reloader_thread()関数の 27行の"mtime = os.stat(filename).st_mtime"で ファイルの最終更新日付を取得し、
33行の"if mtime > mtimes[filename]:"にて、ファイルが更新されたか否かをチェックします。
ファイルが更新されたら、34行の"sys.exit(3)"にて、一旦終了し、restart_with_reloader() 関数に一旦戻ります。
restart_with_reloader()関数はループ構造になっているので、再び 実行対象の関数が再びコールされます。
要するに、
- restart_with_reloader() からモジュールを「子プロセス」で起動し、
- 子プロセス内では、実行対象の関数を実行しつつ、ファイル更新監視用の reloader_thread() を別スレッドで起動します。
- ファイルが更新されたのを確認したら、直ちに子プロセスをシャットダウンし、
- restart_with_reloader() から再び、「子プロセス」を再起動する(1.に戻る)
という仕掛けです。
ちなみに、子プロセスは、pythonインタプリタを起動しているようですね。
百聞は一見に如かず。以下の検証コードを配置します。(ファイル名は、test.pyとします)
import time import autoreload def main(): print "start!!" for i in range(100): print i time.sleep(1) print "end!!" if __name__ == '__main__': autoreload.main(main)
このコードを実行すると、0,1,2,...という具合に1秒おきに値が+1されて表示されます。
で、実行中に test.py をファイル更新すると、再起動されます。以下のように。
$ python test.py start!! 0 1 2 3 4 start!! 0 1 2 3 4 start!! 0 1 2
ちなみに、この機能を自分のアプリケーションに利用したいならば、単に auotreload.py をもらってくればOKのようです。
いや〜、便利ですね。ふむふむ。