169 lines
5.5 KiB
Python
169 lines
5.5 KiB
Python
|
"""
|
||
|
Asyncio-based hub, originally implemented by Miguel Grinberg.
|
||
|
"""
|
||
|
|
||
|
import asyncio
|
||
|
try:
|
||
|
import concurrent.futures.thread
|
||
|
concurrent_imported = True
|
||
|
except RuntimeError:
|
||
|
# This happens in weird edge cases where asyncio hub is started at
|
||
|
# shutdown. Not much we can do if this happens.
|
||
|
concurrent_imported = False
|
||
|
import os
|
||
|
import sys
|
||
|
|
||
|
from eventlet.hubs import hub
|
||
|
from eventlet.patcher import original
|
||
|
|
||
|
|
||
|
def is_available():
|
||
|
"""
|
||
|
Indicate whether this hub is available, since some hubs are
|
||
|
platform-specific.
|
||
|
|
||
|
Python always has asyncio, so this is always ``True``.
|
||
|
"""
|
||
|
return True
|
||
|
|
||
|
|
||
|
class Hub(hub.BaseHub):
|
||
|
"""An Eventlet hub implementation on top of an asyncio event loop."""
|
||
|
|
||
|
def __init__(self):
|
||
|
super().__init__()
|
||
|
# Make sure asyncio thread pools use real threads:
|
||
|
if concurrent_imported:
|
||
|
concurrent.futures.thread.threading = original("threading")
|
||
|
concurrent.futures.thread.queue = original("queue")
|
||
|
|
||
|
# Make sure select/poll/epoll/kqueue are usable by asyncio:
|
||
|
import selectors
|
||
|
selectors.select = original("select")
|
||
|
|
||
|
# Make sure DNS lookups use normal blocking API (which asyncio will run
|
||
|
# in a thread):
|
||
|
import asyncio.base_events
|
||
|
asyncio.base_events.socket = original("socket")
|
||
|
|
||
|
# The presumption is that eventlet is driving the event loop, so we
|
||
|
# want a new one we control.
|
||
|
self.loop = asyncio.new_event_loop()
|
||
|
asyncio.set_event_loop(self.loop)
|
||
|
self.sleep_event = asyncio.Event()
|
||
|
|
||
|
def add_timer(self, timer):
|
||
|
"""
|
||
|
Register a ``Timer``.
|
||
|
|
||
|
Typically not called directly by users.
|
||
|
"""
|
||
|
super().add_timer(timer)
|
||
|
self.sleep_event.set()
|
||
|
|
||
|
def _file_cb(self, cb, fileno):
|
||
|
"""
|
||
|
Callback called by ``asyncio`` when a file descriptor has an event.
|
||
|
"""
|
||
|
try:
|
||
|
cb(fileno)
|
||
|
except self.SYSTEM_EXCEPTIONS:
|
||
|
raise
|
||
|
except:
|
||
|
self.squelch_exception(fileno, sys.exc_info())
|
||
|
self.sleep_event.set()
|
||
|
|
||
|
def add(self, evtype, fileno, cb, tb, mark_as_closed):
|
||
|
"""
|
||
|
Add a file descriptor of given event type to the ``Hub``. See the
|
||
|
superclass for details.
|
||
|
|
||
|
Typically not called directly by users.
|
||
|
"""
|
||
|
try:
|
||
|
os.fstat(fileno)
|
||
|
except OSError:
|
||
|
raise ValueError('Invalid file descriptor')
|
||
|
already_listening = self.listeners[evtype].get(fileno) is not None
|
||
|
listener = super().add(evtype, fileno, cb, tb, mark_as_closed)
|
||
|
if not already_listening:
|
||
|
if evtype == hub.READ:
|
||
|
self.loop.add_reader(fileno, self._file_cb, cb, fileno)
|
||
|
else:
|
||
|
self.loop.add_writer(fileno, self._file_cb, cb, fileno)
|
||
|
return listener
|
||
|
|
||
|
def remove(self, listener):
|
||
|
"""
|
||
|
Remove a listener from the ``Hub``. See the superclass for details.
|
||
|
|
||
|
Typically not called directly by users.
|
||
|
"""
|
||
|
super().remove(listener)
|
||
|
evtype = listener.evtype
|
||
|
fileno = listener.fileno
|
||
|
if not self.listeners[evtype].get(fileno):
|
||
|
if evtype == hub.READ:
|
||
|
self.loop.remove_reader(fileno)
|
||
|
else:
|
||
|
self.loop.remove_writer(fileno)
|
||
|
|
||
|
def remove_descriptor(self, fileno):
|
||
|
"""
|
||
|
Remove a file descriptor from the ``asyncio`` loop.
|
||
|
|
||
|
Typically not called directly by users.
|
||
|
"""
|
||
|
have_read = self.listeners[hub.READ].get(fileno)
|
||
|
have_write = self.listeners[hub.WRITE].get(fileno)
|
||
|
super().remove_descriptor(fileno)
|
||
|
if have_read:
|
||
|
self.loop.remove_reader(fileno)
|
||
|
if have_write:
|
||
|
self.loop.remove_writer(fileno)
|
||
|
|
||
|
def run(self, *a, **kw):
|
||
|
"""
|
||
|
Start the ``Hub`` running. See the superclass for details.
|
||
|
"""
|
||
|
async def async_run():
|
||
|
if self.running:
|
||
|
raise RuntimeError("Already running!")
|
||
|
try:
|
||
|
self.running = True
|
||
|
self.stopping = False
|
||
|
while not self.stopping:
|
||
|
while self.closed:
|
||
|
# We ditch all of these first.
|
||
|
self.close_one()
|
||
|
self.prepare_timers()
|
||
|
if self.debug_blocking:
|
||
|
self.block_detect_pre()
|
||
|
self.fire_timers(self.clock())
|
||
|
if self.debug_blocking:
|
||
|
self.block_detect_post()
|
||
|
self.prepare_timers()
|
||
|
wakeup_when = self.sleep_until()
|
||
|
if wakeup_when is None:
|
||
|
sleep_time = self.default_sleep()
|
||
|
else:
|
||
|
sleep_time = wakeup_when - self.clock()
|
||
|
if sleep_time > 0:
|
||
|
try:
|
||
|
await asyncio.wait_for(self.sleep_event.wait(),
|
||
|
sleep_time)
|
||
|
except asyncio.TimeoutError:
|
||
|
pass
|
||
|
self.sleep_event.clear()
|
||
|
else:
|
||
|
await asyncio.sleep(0)
|
||
|
else:
|
||
|
self.timers_canceled = 0
|
||
|
del self.timers[:]
|
||
|
del self.next_timers[:]
|
||
|
finally:
|
||
|
self.running = False
|
||
|
self.stopping = False
|
||
|
|
||
|
self.loop.run_until_complete(async_run())
|