Pythonのフォルダ監視(Watchdog)は例外で停止する
環境
- Python 3.11
- Watchdog 2.1.9
要約
PythonのWatchdogパッケージでフォルダ監視を行う時に、予期せぬエラーによる監視終了を避けるには、フォルダイベントに対する処理を別スレッドで行えばよい。
現象
Pythonでフォルダ監視というと、標準ライブラリではカバーしておらずWatchdogを使う方法がメジャーだと思われる。フォルダ監視をプログラム実行中永続的に行わせるケースは多いと思われるが、Watchdogから呼び出した命令で予期せぬ例外が発生した場合、フォルダ監視は終了してしまう。
さっと検索した限り、これを問題として取り上げている記事は見つからなかった。
サンプルコード
#!/usr/bin/python3
"""例示用."""
import threading
from pathlib import Path
from typing import NoReturn
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
class TestHandler(FileSystemEventHandler):
"""フォルダ内でファイルが作成されたときの処理を規定するクラス."""
def irregular(self) -> NoReturn:
"""例外を発生させる."""
raise Exception("Exception in on_created.")
def on_created(self, event) -> None:
"""ファイル生成もしくはファイルが移動してきた時の対応."""
print(f"created: {event.src_path=}")
self.irregular()
# threading.Thread(target=self.irregular).start()
if __name__ == '__main__':
TEST_PATH = "test"
Path(TEST_PATH).mkdir(parents=True, exist_ok=True)
observer = Observer()
observer.schedule(TestHandler(), TEST_PATH, recursive=True)
observer.start()
try:
while observer.is_alive():
observer.join(1)
finally:
observer.stop()
observer.join()
TEST_PATH
内にファイルを作成すると、例外が発生してプログラムが終了する。当然、もう一つファイルを作成しても何も起こらない。
対策
サンプルコード内にコメントアウトしてあるが、self.irregular()
をthreading.Thread(target=self.irregular).start()
と変更して別スレッドで実行すれば、少なくともフォルダ監視が止まることはなくなる。
考察
例外が発生した時に、エラーに気づけるようにプログラムが停止した方がいいのか、エラーを抱えたまま動作を続けた方がいいのかについては議論が分かれることもあると思うが、永続的フォルダ監視の場合、個々のイベントへの対応が独立していて動作の継続の方が重要であるケースは多いのではと思う。
事前にエラーを網羅するのは困難であることを考えれば、動作継続が優先する時にFileSystemEventHandler
から呼び出す処理を予め別スレッドでの実行となるように書いておくのは方法論として有効だと言える。
エラーの発見は、TestHandler.irregular()
内の処理をtry ... except
で囲ってログを取るなどの方法で対処できると思われる。
余談
余談だが、上記は2023/1/28時点のWatchdog公式QuickStartのサンプルコードを参考に作成したが、while observer.is_alive():
がwhile observer.isAlive():
になっていたため動作せず、ちょっと戸惑った。
ちなみにGitHubのサンプルコードのようにwhile True:
でループすると、プログラムは終了していないのにフォルダ監視だけ終了しているという、大変エラーがわかりにくい状態になる。
改訂履歴(表現の修正などは除く)
- 2023/1/28: 初版