[inotify] Avoid bad file descriptor shutdown (#895)
* avoid bad file descriptor shutdown
* add to changelog
diff --git a/changelog.rst b/changelog.rst
index 7549fec..977cad8 100644
--- a/changelog.rst
+++ b/changelog.rst
@@ -11,7 +11,8 @@
- [fsevents] Fix flakey test to assert that there are no errors when stopping the emitter.
- [watchmedo] Make ``auto-restart`` restart the sub-process if it terminates. (`#896 <https://github.com/gorakhargosh/watchdog/pull/896>`__)
- [watchmedo] Avoid zombie sub-processes when running ``shell-command`` without ``--wait``. (`#405 <https://github.com/gorakhargosh/watchdog/issues/405>`__)
-- Thanks to our beloved contributors: @samschott, @taleinat
+- [inotify] Suppress occasional ``OSError: [Errno 9] Bad file descriptor`` at shutdown (`#805 <https://github.com/gorakhargosh/watchdog/issues/805>`__)
+- Thanks to our beloved contributors: @samschott, @taleinat, @altendky
2.1.8
~~~~~
diff --git a/src/watchdog/observers/inotify_c.py b/src/watchdog/observers/inotify_c.py
index 6161f4f..d942d2a 100644
--- a/src/watchdog/observers/inotify_c.py
+++ b/src/watchdog/observers/inotify_c.py
@@ -286,6 +286,8 @@
except OSError as e:
if e.errno == errno.EINTR:
continue
+ elif e.errno == errno.EBADF:
+ return []
else:
raise
break
diff --git a/tests/test_inotify_buffer.py b/tests/test_inotify_buffer.py
index da4a210..40e85cb 100644
--- a/tests/test_inotify_buffer.py
+++ b/tests/test_inotify_buffer.py
@@ -22,6 +22,7 @@
import os
import random
+import time
from watchdog.observers.inotify_buffer import InotifyBuffer
@@ -132,8 +133,28 @@
assert not inotify.is_alive()
-def test_close_should_terminate_thread(p):
- inotify = InotifyBuffer(p('').encode(), recursive=True)
+def delay_call(function, seconds):
+ def delayed(*args, **kwargs):
+ time.sleep(seconds)
+
+ return function(*args, **kwargs)
+
+ return delayed
+
+
+class InotifyBufferDelayedRead(InotifyBuffer):
+ def run(self, *args, **kwargs):
+ # Introduce a delay to trigger the race condition where the file descriptor is
+ # closed prior to a read being triggered.
+ self._inotify.read_events = delay_call(function=self._inotify.read_events, seconds=1)
+
+ return super().run(*args, **kwargs)
+
+
+@pytest.mark.parametrize(argnames="cls", argvalues=[InotifyBuffer, InotifyBufferDelayedRead])
+def test_close_should_terminate_thread(p, cls):
+ inotify = cls(p('').encode(), recursive=True)
+
assert inotify.is_alive()
inotify.close()
assert not inotify.is_alive()