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:
|
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)?
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: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:
eventlet/eventlet/green/os.py
Lines 36 to 51 in 06ec828
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 calltrampolinefirst, it works fine: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.readdesigned to be blocking (even after being monkey patched)?