Skip to content

Commit

Permalink
fix: reading inotify file descriptor after closing it. (#1081)
Browse files Browse the repository at this point in the history
  • Loading branch information
ethan-vanderheijden authored Nov 1, 2024
1 parent 73d5612 commit db698a5
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 13 deletions.
33 changes: 21 additions & 12 deletions src/watchdog/observers/inotify_c.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import struct
import threading
from ctypes import c_char_p, c_int, c_uint32
from functools import partial, reduce
from typing import TYPE_CHECKING, Any, Callable
from functools import reduce
from typing import TYPE_CHECKING

from watchdog.utils import UnsupportedLibcError

Expand Down Expand Up @@ -150,16 +150,26 @@ def __init__(self, path: bytes, *, recursive: bool = False, event_mask: int | No
self._inotify_fd = inotify_fd
self._lock = threading.Lock()
self._closed = False
self._waiting_to_read = True
self._is_reading = True
self._kill_r, self._kill_w = os.pipe()

# _check_inotify_fd will return true if we can read _inotify_fd without blocking
if hasattr(select, "poll"):
self._poller = select.poll()
self._poller.register(self._inotify_fd, select.POLLIN)
self._poller.register(self._kill_r, select.POLLIN)
self._poll: Callable[[], Any] = partial(self._poller.poll)

def do_poll() -> bool:
return any(fd == self._inotify_fd for fd, _ in self._poller.poll())

self._check_inotify_fd = do_poll
else:
self._poll = partial(select.select, (self._inotify_fd, self._kill_r))

def do_select() -> bool:
result = select.select([self._inotify_fd, self._kill_r], [], [])
return self._inotify_fd in result[0]

self._check_inotify_fd = do_select

# Stores the watch descriptor for a given path.
self._wd_for_path: dict[bytes, int] = {}
Expand Down Expand Up @@ -249,7 +259,7 @@ def close(self) -> None:
wd = self._wd_for_path[self._path]
inotify_rm_watch(self._inotify_fd, wd)

if self._waiting_to_read:
if self._is_reading:
# inotify_rm_watch() should write data to _inotify_fd and wake
# the thread, but writing to the kill channel will gaurentee this
os.write(self._kill_w, b"!")
Expand Down Expand Up @@ -291,25 +301,24 @@ def _recursive_simulate(src_path: bytes) -> list[InotifyEvent]:
events.append(e)
return events

event_buffer = None
event_buffer = b""
while True:
try:
with self._lock:
if self._closed:
return []

self._waiting_to_read = True
self._is_reading = True

self._poll()
if self._check_inotify_fd():
event_buffer = os.read(self._inotify_fd, event_buffer_size)

with self._lock:
self._waiting_to_read = False
self._is_reading = False

if self._closed:
self._close_resources()
return []

event_buffer = os.read(self._inotify_fd, event_buffer_size)
except OSError as e:
if e.errno == errno.EINTR:
continue
Expand Down
2 changes: 1 addition & 1 deletion tests/test_inotify_c.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def register(self, fd, *args, **kwargs):

def poll(self, *args, **kwargs):
if self._fake:
return None
return [(inotify_fd, select.POLLIN)]
return self._orig.poll(*args, **kwargs)

os_read_bkp = os.read
Expand Down

0 comments on commit db698a5

Please sign in to comment.