-
Notifications
You must be signed in to change notification settings - Fork 331
Description
This is likely the root cause behind #634.
I thought that monkey patching the os module means all blocking functions are converted into non-blocking functions. However, while using Watchdog, I found that os.read()
still block. Simple example using watchdog:
import eventlet
from eventlet.greenthread import spawn, sleep
eventlet.monkey_patch()
import logging
from watchdog.observers import Observer
from watchdog.events import LoggingEventHandler
increment = 0
def heartbeat():
global increment
while True:
print('second:', increment)
increment += 1
sleep(1)
if __name__ == "__main__":
spawn(heartbeat)
sleep(2)
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
event_handler = LoggingEventHandler()
observer = Observer(generate_full_events=True)
observer.schedule(event_handler, '/home/ethan/test', recursive=True)
observer.start()
try:
while observer.is_alive():
observer.join(1)
finally:
observer.stop()
observer.join()
I expect it to print out time stamps forever ("seconds: 0", "seconds: 1", "seconds: 2", "seconds: 3" ...), but once Watchdog starts, it stops printing because Watchdog blocks on the main thread.
The issue is that Watchdog spawns a new thread and reads from the INotify API. The read calls are executed through os.read(fd, size)
here:
https://github.com/gorakhargosh/watchdog/blob/9f23b599f369cdd873c0cf3dd9e69567234d78fb/src/watchdog/observers/inotify_c.py#L302
Of course the read calls are redirected to the monkey patched version:
Lines 36 to 51 in 06ec828
def read(fd, n): | |
"""read(fd, buffersize) -> string | |
Read a file descriptor.""" | |
while True: | |
try: | |
return __original_read__(fd, n) | |
except OSError as e: | |
if get_errno(e) == errno.EPIPE: | |
return '' | |
if get_errno(e) != errno.EAGAIN: | |
raise | |
try: | |
hubs.trampoline(fd, read=True) | |
except hubs.IOClosed: | |
return '' |
I believe the issue is that the monkey patched method immediately calls the original read function, which blocks because no output is available yet, before it calls trampoline
. If I change the order of method calls to call trampoline
first, it works fine:
def read(fd, n):
while True:
try:
hubs.trampoline(fd, read=True)
except hubs.IOClosed:
return ''
try:
return __original_read__(fd, n)
except OSError as e:
if get_errno(e) == errno.EPIPE:
return ''
if get_errno(e) != errno.EAGAIN:
raise
It makes more sense to me that Eventlet waits until data is available before calling a potentially blocking function. Is this a bug with Eventlet? If not, is os.read
designed to be blocking (even after being monkey patched)?