diff --git a/src/watchdog/observers/inotify_buffer.py b/src/watchdog/observers/inotify_buffer.py index 010bbb8ed..dce2ae122 100644 --- a/src/watchdog/observers/inotify_buffer.py +++ b/src/watchdog/observers/inotify_buffer.py @@ -55,7 +55,8 @@ def run(self): IN_MOVE_TO event, remove the previous added matching IN_MOVE_FROM event and add them back to the queue as a tuple. """ - while self.should_keep_running(): + deleted_self = False + while self.should_keep_running() and not deleted_self: inotify_events = self._inotify.read_events() for inotify_event in inotify_events: logger.debug("in-event %s", inotify_event) @@ -74,3 +75,7 @@ def matching_from_event(event): else: self._queue.put(inotify_event) + if inotify_event.is_delete_self and \ + inotify_event.src_path == self._inotify.path: + # Deleted the watched directory, stop watching for events + deleted_self = True diff --git a/src/watchdog/observers/inotify_c.py b/src/watchdog/observers/inotify_c.py index ec795cd29..6b4218771 100644 --- a/src/watchdog/observers/inotify_c.py +++ b/src/watchdog/observers/inotify_c.py @@ -257,8 +257,9 @@ def close(self): Closes the inotify instance and removes all associated watches. """ with self._lock: - wd = self._wd_for_path[self._path] - inotify_rm_watch(self._inotify_fd, wd) + if self._path in self._wd_for_path: + wd = self._wd_for_path[self._path] + inotify_rm_watch(self._inotify_fd, wd) os.close(self._inotify_fd) def read_events(self, event_buffer_size=DEFAULT_EVENT_BUFFER_SIZE): diff --git a/tests/test_inotify_buffer.py b/tests/test_inotify_buffer.py index 1e4c3f50b..1c794f38a 100644 --- a/tests/test_inotify_buffer.py +++ b/tests/test_inotify_buffer.py @@ -19,7 +19,7 @@ import random import pytest from tests import tmpdir, p # pytest magic -from .shell import mkdir, touch, mv +from .shell import mkdir, touch, mv, rm from watchdog.observers.api import ObservedWatch from watchdog.utils import platform @@ -104,6 +104,19 @@ def test_move_internal_batch(p): inotify.close() +@pytest.mark.timeout(5) +def test_delete_watched_directory(p): + mkdir(p('dir')) + inotify = InotifyBuffer(p('dir').encode()) + rm(p('dir'), recursive=True) + + # Wait for the event to be picked up + inotify.read_event() + + # Ensure InotifyBuffer shuts down cleanly without raising an exception + inotify.close() + + def test_close_should_terminate_thread(p): inotify = InotifyBuffer(p('').encode(), recursive=True) assert inotify.is_alive()