ふにゃるんv2

もとは、http://d.hatena.ne.jp/Wacky/

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()関数はループ構造になっているので、再び 実行対象の関数が再びコールされます。


要するに、

  1. restart_with_reloader() からモジュールを「子プロセス」で起動し、
  2. 子プロセス内では、実行対象の関数を実行しつつ、ファイル更新監視用の reloader_thread() を別スレッドで起動します。
  3. ファイルが更新されたのを確認したら、直ちに子プロセスをシャットダウンし、
  4. 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のようです。
いや〜、便利ですね。ふむふむ。