Skip to content

Commit

Permalink
[inotify] Fix Observer.unschedule hang on linux when called with wa…
Browse files Browse the repository at this point in the history
…tch of an unmounted path (#869)

* Stop InotifyBuffer thread on IN_IGNORE event on the "root" watch descriptor

* Added a test

* Updated changelog

* Not adding IN_IGNORE events to the queue no matter their source path, as was done until now

* Fixed formatting issues

* Trying to fix the CI by running mount and umount as root

* Update changelog
  • Loading branch information
IlayRosenberg committed May 15, 2022
1 parent 5747e93 commit 4baea36
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 2 deletions.
1 change: 1 addition & 0 deletions changelog.rst
Expand Up @@ -12,6 +12,7 @@ Changelog
- [watchmedo] Fix broken parsing of ``--kill-after`` argument for the ``auto-restart`` command. (`#870 <https://github.com/gorakhargosh/watchdog/issues/870>`_)
- [watchmedo] Fix broken parsing of boolean arguments. (`#855 <https://github.com/gorakhargosh/watchdog/issues/855>`_)
- [watchmedo] Fix broken parsing of commands from ``auto-restart``, and ``shell-command``. (`#855 <https://github.com/gorakhargosh/watchdog/issues/855>`_)
- [inotify] Fix hang when unscheduling watch on a path in an unmounted filesystem. (`#869 <https://github.com/gorakhargosh/watchdog/pull/869>`_)
- Thanks to our beloved contributors: @taleinat, @kianmeng, @palfrey, @IlayRosenberg, @BoboTiG

2.1.7
Expand Down
7 changes: 7 additions & 0 deletions src/watchdog/observers/inotify_buffer.py
Expand Up @@ -88,6 +88,13 @@ def run(self):
inotify_events = self._inotify.read_events()
grouped_events = self._group_events(inotify_events)
for inotify_event in grouped_events:
if not isinstance(inotify_event, tuple) and inotify_event.is_ignored:
if inotify_event.src_path == self._inotify.path:
# Watch was removed explicitly (inotify_rm_watch(2)) or automatically (file
# was deleted, or filesystem was unmounted), stop watching for events
deleted_self = True
continue

# Only add delay for unmatched move_from events
delay = not isinstance(inotify_event, tuple) and inotify_event.is_moved_from
self._queue.put(inotify_event, delay)
Expand Down
1 change: 0 additions & 1 deletion src/watchdog/observers/inotify_c.py
Expand Up @@ -323,7 +323,6 @@ def _recursive_simulate(src_path):
path = self._path_for_wd.pop(wd)
if self._wd_for_path[path] == wd:
del self._wd_for_path[path]
continue

event_list.append(inotify_event)

Expand Down
8 changes: 8 additions & 0 deletions tests/shell.py
Expand Up @@ -123,3 +123,11 @@ def msize(path):
with open(path, 'w') as w:
w.write('0')
os.utime(path, (0, 0))


def mount_tmpfs(path):
os.system(f'sudo mount -t tmpfs none {path}')


def unmount(path):
os.system(f'sudo umount {path}')
18 changes: 17 additions & 1 deletion tests/test_inotify_buffer.py
Expand Up @@ -25,7 +25,7 @@

from watchdog.observers.inotify_buffer import InotifyBuffer

from .shell import mkdir, touch, mv, rm
from .shell import mkdir, touch, mv, rm, mount_tmpfs, unmount


def wait_for_move_event(read_event):
Expand Down Expand Up @@ -116,6 +116,22 @@ def test_delete_watched_directory(p):
inotify.close()


@pytest.mark.timeout(5)
def test_unmount_watched_directory_filesystem(p):
mkdir(p('dir1'))
mount_tmpfs(p('dir1'))
mkdir(p('dir1/dir2'))
inotify = InotifyBuffer(p('dir1/dir2').encode())
unmount(p('dir1'))

# Wait for the event to be picked up
inotify.read_event()

# Ensure InotifyBuffer shuts down cleanly without raising an exception
inotify.close()
assert not inotify.is_alive()


def test_close_should_terminate_thread(p):
inotify = InotifyBuffer(p('').encode(), recursive=True)
assert inotify.is_alive()
Expand Down

0 comments on commit 4baea36

Please sign in to comment.