This commit is contained in:
2024-11-29 18:15:30 +00:00
parent 40aade2d8e
commit bc9415586e
5298 changed files with 1938676 additions and 80 deletions

View File

@ -0,0 +1,188 @@
import importlib
import inspect
import os
import warnings
from eventlet import patcher
from eventlet.support import greenlets as greenlet
__all__ = ["use_hub", "get_hub", "get_default_hub", "trampoline"]
threading = patcher.original('threading')
_threadlocal = threading.local()
# order is important, get_default_hub returns first available from here
builtin_hub_names = ('epolls', 'kqueue', 'poll', 'selects')
builtin_hub_modules = tuple(importlib.import_module('eventlet.hubs.' + name) for name in builtin_hub_names)
class HubError(Exception):
pass
def get_default_hub():
"""Select the default hub implementation based on what multiplexing
libraries are installed. The order that the hubs are tried is:
* epoll
* kqueue
* poll
* select
.. include:: ../../doc/source/common.txt
.. note :: |internal|
"""
for mod in builtin_hub_modules:
if mod.is_available():
return mod
raise HubError('no built-in hubs are available: {}'.format(builtin_hub_modules))
def use_hub(mod=None):
"""Use the module *mod*, containing a class called Hub, as the
event hub. Usually not required; the default hub is usually fine.
`mod` can be an actual hub class, a module, a string, or None.
If `mod` is a class, use it directly.
If `mod` is a module, use `module.Hub` class
If `mod` is a string and contains either '.' or ':'
then `use_hub` uses 'package.subpackage.module:Class' convention,
otherwise imports `eventlet.hubs.mod`.
If `mod` is None, `use_hub` uses the default hub.
Only call use_hub during application initialization,
because it resets the hub's state and any existing
timers or listeners will never be resumed.
These two threadlocal attributes are not part of Eventlet public API:
- `threadlocal.Hub` (capital H) is hub constructor, used when no hub is currently active
- `threadlocal.hub` (lowercase h) is active hub instance
"""
if mod is None:
mod = os.environ.get('EVENTLET_HUB', None)
if mod is None:
mod = get_default_hub()
if hasattr(_threadlocal, 'hub'):
del _threadlocal.hub
classname = ''
if isinstance(mod, str):
if mod.strip() == "":
raise RuntimeError("Need to specify a hub")
if '.' in mod or ':' in mod:
modulename, _, classname = mod.strip().partition(':')
else:
modulename = 'eventlet.hubs.' + mod
mod = importlib.import_module(modulename)
if hasattr(mod, 'is_available'):
if not mod.is_available():
raise Exception('selected hub is not available on this system mod={}'.format(mod))
else:
msg = '''Please provide `is_available()` function in your custom Eventlet hub {mod}.
It must return bool: whether hub supports current platform. See eventlet/hubs/{{epoll,kqueue}} for example.
'''.format(mod=mod)
warnings.warn(msg, DeprecationWarning, stacklevel=3)
hubclass = mod
if not inspect.isclass(mod):
hubclass = getattr(mod, classname or 'Hub')
_threadlocal.Hub = hubclass
def get_hub():
"""Get the current event hub singleton object.
.. note :: |internal|
"""
try:
hub = _threadlocal.hub
except AttributeError:
try:
_threadlocal.Hub
except AttributeError:
use_hub()
hub = _threadlocal.hub = _threadlocal.Hub()
return hub
# Lame middle file import because complex dependencies in import graph
from eventlet import timeout
def trampoline(fd, read=None, write=None, timeout=None,
timeout_exc=timeout.Timeout,
mark_as_closed=None):
"""Suspend the current coroutine until the given socket object or file
descriptor is ready to *read*, ready to *write*, or the specified
*timeout* elapses, depending on arguments specified.
To wait for *fd* to be ready to read, pass *read* ``=True``; ready to
write, pass *write* ``=True``. To specify a timeout, pass the *timeout*
argument in seconds.
If the specified *timeout* elapses before the socket is ready to read or
write, *timeout_exc* will be raised instead of ``trampoline()``
returning normally.
.. note :: |internal|
"""
t = None
hub = get_hub()
current = greenlet.getcurrent()
if hub.greenlet is current:
raise RuntimeError('do not call blocking functions from the mainloop')
if (read and write):
raise RuntimeError('not allowed to trampoline for reading and writing')
try:
fileno = fd.fileno()
except AttributeError:
fileno = fd
if timeout is not None:
def _timeout(exc):
# This is only useful to insert debugging
current.throw(exc)
t = hub.schedule_call_global(timeout, _timeout, timeout_exc)
try:
if read:
listener = hub.add(hub.READ, fileno, current.switch, current.throw, mark_as_closed)
elif write:
listener = hub.add(hub.WRITE, fileno, current.switch, current.throw, mark_as_closed)
try:
return hub.switch()
finally:
hub.remove(listener)
finally:
if t is not None:
t.cancel()
def notify_close(fd):
"""
A particular file descriptor has been explicitly closed. Register for any
waiting listeners to be notified on the next run loop.
"""
hub = get_hub()
hub.notify_close(fd)
def notify_opened(fd):
"""
Some file descriptors may be closed 'silently' - that is, by the garbage
collector, by an external library, etc. When the OS returns a file descriptor
from an open call (or something similar), this may be the only indication we
have that the FD has been closed and then recycled.
We let the hub know that the old file descriptor is dead; any stuck listeners
will be disabled and notified in turn.
"""
hub = get_hub()
hub.mark_as_reopened(fd)
class IOClosed(IOError):
pass

View File

@ -0,0 +1,168 @@
"""
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())

View File

@ -0,0 +1,31 @@
import errno
from eventlet import patcher, support
from eventlet.hubs import hub, poll
select = patcher.original('select')
def is_available():
return hasattr(select, 'epoll')
# NOTE: we rely on the fact that the epoll flag constants
# are identical in value to the poll constants
class Hub(poll.Hub):
def __init__(self, clock=None):
super().__init__(clock=clock)
self.poll = select.epoll()
def add(self, evtype, fileno, cb, tb, mac):
oldlisteners = bool(self.listeners[self.READ].get(fileno) or
self.listeners[self.WRITE].get(fileno))
# not super() to avoid double register()
listener = hub.BaseHub.add(self, evtype, fileno, cb, tb, mac)
try:
self.register(fileno, new=not oldlisteners)
except OSError as ex: # ignore EEXIST, #80
if support.get_errno(ex) != errno.EEXIST:
raise
return listener
def do_poll(self, seconds):
return self.poll.poll(seconds)

View File

@ -0,0 +1,495 @@
import errno
import heapq
import math
import signal
import sys
import traceback
arm_alarm = None
if hasattr(signal, 'setitimer'):
def alarm_itimer(seconds):
signal.setitimer(signal.ITIMER_REAL, seconds)
arm_alarm = alarm_itimer
else:
try:
import itimer
arm_alarm = itimer.alarm
except ImportError:
def alarm_signal(seconds):
signal.alarm(math.ceil(seconds))
arm_alarm = alarm_signal
import eventlet.hubs
from eventlet.hubs import timer
from eventlet.support import greenlets as greenlet
try:
from monotonic import monotonic
except ImportError:
from time import monotonic
g_prevent_multiple_readers = True
READ = "read"
WRITE = "write"
def closed_callback(fileno):
""" Used to de-fang a callback that may be triggered by a loop in BaseHub.wait
"""
# No-op.
pass
class FdListener:
def __init__(self, evtype, fileno, cb, tb, mark_as_closed):
""" The following are required:
cb - the standard callback, which will switch into the
listening greenlet to indicate that the event waited upon
is ready
tb - a 'throwback'. This is typically greenlet.throw, used
to raise a signal into the target greenlet indicating that
an event was obsoleted by its underlying filehandle being
repurposed.
mark_as_closed - if any listener is obsoleted, this is called
(in the context of some other client greenlet) to alert
underlying filehandle-wrapping objects that they've been
closed.
"""
assert (evtype is READ or evtype is WRITE)
self.evtype = evtype
self.fileno = fileno
self.cb = cb
self.tb = tb
self.mark_as_closed = mark_as_closed
self.spent = False
self.greenlet = greenlet.getcurrent()
def __repr__(self):
return "%s(%r, %r, %r, %r)" % (type(self).__name__, self.evtype, self.fileno,
self.cb, self.tb)
__str__ = __repr__
def defang(self):
self.cb = closed_callback
if self.mark_as_closed is not None:
self.mark_as_closed()
self.spent = True
noop = FdListener(READ, 0, lambda x: None, lambda x: None, None)
# in debug mode, track the call site that created the listener
class DebugListener(FdListener):
def __init__(self, evtype, fileno, cb, tb, mark_as_closed):
self.where_called = traceback.format_stack()
self.greenlet = greenlet.getcurrent()
super().__init__(evtype, fileno, cb, tb, mark_as_closed)
def __repr__(self):
return "DebugListener(%r, %r, %r, %r, %r, %r)\n%sEndDebugFdListener" % (
self.evtype,
self.fileno,
self.cb,
self.tb,
self.mark_as_closed,
self.greenlet,
''.join(self.where_called))
__str__ = __repr__
def alarm_handler(signum, frame):
import inspect
raise RuntimeError("Blocking detector ALARMED at" + str(inspect.getframeinfo(frame)))
class BaseHub:
""" Base hub class for easing the implementation of subclasses that are
specific to a particular underlying event architecture. """
SYSTEM_EXCEPTIONS = (KeyboardInterrupt, SystemExit)
READ = READ
WRITE = WRITE
def __init__(self, clock=None):
self.listeners = {READ: {}, WRITE: {}}
self.secondaries = {READ: {}, WRITE: {}}
self.closed = []
if clock is None:
clock = monotonic
self.clock = clock
self.greenlet = greenlet.greenlet(self.run)
self.stopping = False
self.running = False
self.timers = []
self.next_timers = []
self.lclass = FdListener
self.timers_canceled = 0
self.debug_exceptions = True
self.debug_blocking = False
self.debug_blocking_resolution = 1
def block_detect_pre(self):
# shortest alarm we can possibly raise is one second
tmp = signal.signal(signal.SIGALRM, alarm_handler)
if tmp != alarm_handler:
self._old_signal_handler = tmp
arm_alarm(self.debug_blocking_resolution)
def block_detect_post(self):
if (hasattr(self, "_old_signal_handler") and
self._old_signal_handler):
signal.signal(signal.SIGALRM, self._old_signal_handler)
signal.alarm(0)
def add(self, evtype, fileno, cb, tb, mark_as_closed):
""" Signals an intent to or write a particular file descriptor.
The *evtype* argument is either the constant READ or WRITE.
The *fileno* argument is the file number of the file of interest.
The *cb* argument is the callback which will be called when the file
is ready for reading/writing.
The *tb* argument is the throwback used to signal (into the greenlet)
that the file was closed.
The *mark_as_closed* is used in the context of the event hub to
prepare a Python object as being closed, pre-empting further
close operations from accidentally shutting down the wrong OS thread.
"""
listener = self.lclass(evtype, fileno, cb, tb, mark_as_closed)
bucket = self.listeners[evtype]
if fileno in bucket:
if g_prevent_multiple_readers:
raise RuntimeError(
"Second simultaneous %s on fileno %s "
"detected. Unless you really know what you're doing, "
"make sure that only one greenthread can %s any "
"particular socket. Consider using a pools.Pool. "
"If you do know what you're doing and want to disable "
"this error, call "
"eventlet.debug.hub_prevent_multiple_readers(False) - MY THREAD=%s; "
"THAT THREAD=%s" % (
evtype, fileno, evtype, cb, bucket[fileno]))
# store off the second listener in another structure
self.secondaries[evtype].setdefault(fileno, []).append(listener)
else:
bucket[fileno] = listener
return listener
def _obsolete(self, fileno):
""" We've received an indication that 'fileno' has been obsoleted.
Any current listeners must be defanged, and notifications to
their greenlets queued up to send.
"""
found = False
for evtype, bucket in self.secondaries.items():
if fileno in bucket:
for listener in bucket[fileno]:
found = True
self.closed.append(listener)
listener.defang()
del bucket[fileno]
# For the primary listeners, we actually need to call remove,
# which may modify the underlying OS polling objects.
for evtype, bucket in self.listeners.items():
if fileno in bucket:
listener = bucket[fileno]
found = True
self.closed.append(listener)
self.remove(listener)
listener.defang()
return found
def notify_close(self, fileno):
""" We might want to do something when a fileno is closed.
However, currently it suffices to obsolete listeners only
when we detect an old fileno being recycled, on open.
"""
pass
def remove(self, listener):
if listener.spent:
# trampoline may trigger this in its finally section.
return
fileno = listener.fileno
evtype = listener.evtype
if listener is self.listeners[evtype][fileno]:
del self.listeners[evtype][fileno]
# migrate a secondary listener to be the primary listener
if fileno in self.secondaries[evtype]:
sec = self.secondaries[evtype][fileno]
if sec:
self.listeners[evtype][fileno] = sec.pop(0)
if not sec:
del self.secondaries[evtype][fileno]
else:
self.secondaries[evtype][fileno].remove(listener)
if not self.secondaries[evtype][fileno]:
del self.secondaries[evtype][fileno]
def mark_as_reopened(self, fileno):
""" If a file descriptor is returned by the OS as the result of some
open call (or equivalent), that signals that it might be being
recycled.
Catch the case where the fd was previously in use.
"""
self._obsolete(fileno)
def remove_descriptor(self, fileno):
""" Completely remove all listeners for this fileno. For internal use
only."""
# gather any listeners we have
listeners = []
listeners.append(self.listeners[READ].get(fileno, noop))
listeners.append(self.listeners[WRITE].get(fileno, noop))
listeners.extend(self.secondaries[READ].get(fileno, ()))
listeners.extend(self.secondaries[WRITE].get(fileno, ()))
for listener in listeners:
try:
# listener.cb may want to remove(listener)
listener.cb(fileno)
except Exception:
self.squelch_generic_exception(sys.exc_info())
# NOW this fileno is now dead to all
self.listeners[READ].pop(fileno, None)
self.listeners[WRITE].pop(fileno, None)
self.secondaries[READ].pop(fileno, None)
self.secondaries[WRITE].pop(fileno, None)
def close_one(self):
""" Triggered from the main run loop. If a listener's underlying FD was
closed somehow, throw an exception back to the trampoline, which should
be able to manage it appropriately.
"""
listener = self.closed.pop()
if not listener.greenlet.dead:
# There's no point signalling a greenlet that's already dead.
listener.tb(eventlet.hubs.IOClosed(errno.ENOTCONN, "Operation on closed file"))
def ensure_greenlet(self):
if self.greenlet.dead:
# create new greenlet sharing same parent as original
new = greenlet.greenlet(self.run, self.greenlet.parent)
# need to assign as parent of old greenlet
# for those greenlets that are currently
# children of the dead hub and may subsequently
# exit without further switching to hub.
self.greenlet.parent = new
self.greenlet = new
def switch(self):
cur = greenlet.getcurrent()
assert cur is not self.greenlet, 'Cannot switch to MAINLOOP from MAINLOOP'
switch_out = getattr(cur, 'switch_out', None)
if switch_out is not None:
try:
switch_out()
except:
self.squelch_generic_exception(sys.exc_info())
self.ensure_greenlet()
try:
if self.greenlet.parent is not cur:
cur.parent = self.greenlet
except ValueError:
pass # gets raised if there is a greenlet parent cycle
return self.greenlet.switch()
def squelch_exception(self, fileno, exc_info):
traceback.print_exception(*exc_info)
sys.stderr.write("Removing descriptor: %r\n" % (fileno,))
sys.stderr.flush()
try:
self.remove_descriptor(fileno)
except Exception as e:
sys.stderr.write("Exception while removing descriptor! %r\n" % (e,))
sys.stderr.flush()
def wait(self, seconds=None):
raise NotImplementedError("Implement this in a subclass")
def default_sleep(self):
return 60.0
def sleep_until(self):
t = self.timers
if not t:
return None
return t[0][0]
def run(self, *a, **kw):
"""Run the runloop until abort is called.
"""
# accept and discard variable arguments because they will be
# supplied if other greenlets have run and exited before the
# hub's greenlet gets a chance to 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:
self.wait(sleep_time)
else:
self.wait(0)
else:
self.timers_canceled = 0
del self.timers[:]
del self.next_timers[:]
finally:
self.running = False
self.stopping = False
def abort(self, wait=False):
"""Stop the runloop. If run is executing, it will exit after
completing the next runloop iteration.
Set *wait* to True to cause abort to switch to the hub immediately and
wait until it's finished processing. Waiting for the hub will only
work from the main greenthread; all other greenthreads will become
unreachable.
"""
if self.running:
self.stopping = True
if wait:
assert self.greenlet is not greenlet.getcurrent(
), "Can't abort with wait from inside the hub's greenlet."
# schedule an immediate timer just so the hub doesn't sleep
self.schedule_call_global(0, lambda: None)
# switch to it; when done the hub will switch back to its parent,
# the main greenlet
self.switch()
def squelch_generic_exception(self, exc_info):
if self.debug_exceptions:
traceback.print_exception(*exc_info)
sys.stderr.flush()
def squelch_timer_exception(self, timer, exc_info):
if self.debug_exceptions:
traceback.print_exception(*exc_info)
sys.stderr.flush()
def add_timer(self, timer):
scheduled_time = self.clock() + timer.seconds
self.next_timers.append((scheduled_time, timer))
return scheduled_time
def timer_canceled(self, timer):
self.timers_canceled += 1
len_timers = len(self.timers) + len(self.next_timers)
if len_timers > 1000 and len_timers / 2 <= self.timers_canceled:
self.timers_canceled = 0
self.timers = [t for t in self.timers if not t[1].called]
self.next_timers = [t for t in self.next_timers if not t[1].called]
heapq.heapify(self.timers)
def prepare_timers(self):
heappush = heapq.heappush
t = self.timers
for item in self.next_timers:
if item[1].called:
self.timers_canceled -= 1
else:
heappush(t, item)
del self.next_timers[:]
def schedule_call_local(self, seconds, cb, *args, **kw):
"""Schedule a callable to be called after 'seconds' seconds have
elapsed. Cancel the timer if greenlet has exited.
seconds: The number of seconds to wait.
cb: The callable to call after the given time.
*args: Arguments to pass to the callable when called.
**kw: Keyword arguments to pass to the callable when called.
"""
t = timer.LocalTimer(seconds, cb, *args, **kw)
self.add_timer(t)
return t
def schedule_call_global(self, seconds, cb, *args, **kw):
"""Schedule a callable to be called after 'seconds' seconds have
elapsed. The timer will NOT be canceled if the current greenlet has
exited before the timer fires.
seconds: The number of seconds to wait.
cb: The callable to call after the given time.
*args: Arguments to pass to the callable when called.
**kw: Keyword arguments to pass to the callable when called.
"""
t = timer.Timer(seconds, cb, *args, **kw)
self.add_timer(t)
return t
def fire_timers(self, when):
t = self.timers
heappop = heapq.heappop
while t:
next = t[0]
exp = next[0]
timer = next[1]
if when < exp:
break
heappop(t)
try:
if timer.called:
self.timers_canceled -= 1
else:
timer()
except self.SYSTEM_EXCEPTIONS:
raise
except:
self.squelch_timer_exception(timer, sys.exc_info())
# for debugging:
def get_readers(self):
return self.listeners[READ].values()
def get_writers(self):
return self.listeners[WRITE].values()
def get_timers_count(hub):
return len(hub.timers) + len(hub.next_timers)
def set_debug_listeners(self, value):
if value:
self.lclass = DebugListener
else:
self.lclass = FdListener
def set_timer_exceptions(self, value):
self.debug_exceptions = value

View File

@ -0,0 +1,110 @@
import os
import sys
from eventlet import patcher, support
from eventlet.hubs import hub
select = patcher.original('select')
time = patcher.original('time')
def is_available():
return hasattr(select, 'kqueue')
class Hub(hub.BaseHub):
MAX_EVENTS = 100
def __init__(self, clock=None):
self.FILTERS = {
hub.READ: select.KQ_FILTER_READ,
hub.WRITE: select.KQ_FILTER_WRITE,
}
super().__init__(clock)
self._events = {}
self._init_kqueue()
def _init_kqueue(self):
self.kqueue = select.kqueue()
self._pid = os.getpid()
def _reinit_kqueue(self):
self.kqueue.close()
self._init_kqueue()
events = [e for i in self._events.values()
for e in i.values()]
self.kqueue.control(events, 0, 0)
def _control(self, events, max_events, timeout):
try:
return self.kqueue.control(events, max_events, timeout)
except OSError:
# have we forked?
if os.getpid() != self._pid:
self._reinit_kqueue()
return self.kqueue.control(events, max_events, timeout)
raise
def add(self, evtype, fileno, cb, tb, mac):
listener = super().add(evtype, fileno, cb, tb, mac)
events = self._events.setdefault(fileno, {})
if evtype not in events:
try:
event = select.kevent(fileno, self.FILTERS.get(evtype), select.KQ_EV_ADD)
self._control([event], 0, 0)
events[evtype] = event
except ValueError:
super().remove(listener)
raise
return listener
def _delete_events(self, events):
del_events = [
select.kevent(e.ident, e.filter, select.KQ_EV_DELETE)
for e in events
]
self._control(del_events, 0, 0)
def remove(self, listener):
super().remove(listener)
evtype = listener.evtype
fileno = listener.fileno
if not self.listeners[evtype].get(fileno):
event = self._events[fileno].pop(evtype, None)
if event is None:
return
try:
self._delete_events((event,))
except OSError:
pass
def remove_descriptor(self, fileno):
super().remove_descriptor(fileno)
try:
events = self._events.pop(fileno).values()
self._delete_events(events)
except KeyError:
pass
except OSError:
pass
def wait(self, seconds=None):
readers = self.listeners[self.READ]
writers = self.listeners[self.WRITE]
if not readers and not writers:
if seconds:
time.sleep(seconds)
return
result = self._control([], self.MAX_EVENTS, seconds)
SYSTEM_EXCEPTIONS = self.SYSTEM_EXCEPTIONS
for event in result:
fileno = event.ident
evfilt = event.filter
try:
if evfilt == select.KQ_FILTER_READ:
readers.get(fileno, hub.noop).cb(fileno)
if evfilt == select.KQ_FILTER_WRITE:
writers.get(fileno, hub.noop).cb(fileno)
except SYSTEM_EXCEPTIONS:
raise
except:
self.squelch_exception(fileno, sys.exc_info())

View File

@ -0,0 +1,118 @@
import errno
import sys
from eventlet import patcher, support
from eventlet.hubs import hub
select = patcher.original('select')
time = patcher.original('time')
def is_available():
return hasattr(select, 'poll')
class Hub(hub.BaseHub):
def __init__(self, clock=None):
super().__init__(clock)
self.EXC_MASK = select.POLLERR | select.POLLHUP
self.READ_MASK = select.POLLIN | select.POLLPRI
self.WRITE_MASK = select.POLLOUT
self.poll = select.poll()
def add(self, evtype, fileno, cb, tb, mac):
listener = super().add(evtype, fileno, cb, tb, mac)
self.register(fileno, new=True)
return listener
def remove(self, listener):
super().remove(listener)
self.register(listener.fileno)
def register(self, fileno, new=False):
mask = 0
if self.listeners[self.READ].get(fileno):
mask |= self.READ_MASK | self.EXC_MASK
if self.listeners[self.WRITE].get(fileno):
mask |= self.WRITE_MASK | self.EXC_MASK
try:
if mask:
if new:
self.poll.register(fileno, mask)
else:
try:
self.poll.modify(fileno, mask)
except OSError:
self.poll.register(fileno, mask)
else:
try:
self.poll.unregister(fileno)
except (KeyError, OSError):
# raised if we try to remove a fileno that was
# already removed/invalid
pass
except ValueError:
# fileno is bad, issue 74
self.remove_descriptor(fileno)
raise
def remove_descriptor(self, fileno):
super().remove_descriptor(fileno)
try:
self.poll.unregister(fileno)
except (KeyError, ValueError, OSError):
# raised if we try to remove a fileno that was
# already removed/invalid
pass
def do_poll(self, seconds):
# poll.poll expects integral milliseconds
return self.poll.poll(int(seconds * 1000.0))
def wait(self, seconds=None):
readers = self.listeners[self.READ]
writers = self.listeners[self.WRITE]
if not readers and not writers:
if seconds:
time.sleep(seconds)
return
try:
presult = self.do_poll(seconds)
except OSError as e:
if support.get_errno(e) == errno.EINTR:
return
raise
SYSTEM_EXCEPTIONS = self.SYSTEM_EXCEPTIONS
if self.debug_blocking:
self.block_detect_pre()
# Accumulate the listeners to call back to prior to
# triggering any of them. This is to keep the set
# of callbacks in sync with the events we've just
# polled for. It prevents one handler from invalidating
# another.
callbacks = set()
noop = hub.noop # shave getattr
for fileno, event in presult:
if event & self.READ_MASK:
callbacks.add((readers.get(fileno, noop), fileno))
if event & self.WRITE_MASK:
callbacks.add((writers.get(fileno, noop), fileno))
if event & select.POLLNVAL:
self.remove_descriptor(fileno)
continue
if event & self.EXC_MASK:
callbacks.add((readers.get(fileno, noop), fileno))
callbacks.add((writers.get(fileno, noop), fileno))
for listener, fileno in callbacks:
try:
listener.cb(fileno)
except SYSTEM_EXCEPTIONS:
raise
except:
self.squelch_exception(fileno, sys.exc_info())
if self.debug_blocking:
self.block_detect_post()

View File

@ -0,0 +1,4 @@
raise ImportError(
"Eventlet pyevent hub was removed because it was not maintained."
" Try version 0.22.1 or older. Sorry for the inconvenience."
)

View File

@ -0,0 +1,63 @@
import errno
import sys
from eventlet import patcher, support
from eventlet.hubs import hub
select = patcher.original('select')
time = patcher.original('time')
try:
BAD_SOCK = {errno.EBADF, errno.WSAENOTSOCK}
except AttributeError:
BAD_SOCK = {errno.EBADF}
def is_available():
return hasattr(select, 'select')
class Hub(hub.BaseHub):
def _remove_bad_fds(self):
""" Iterate through fds, removing the ones that are bad per the
operating system.
"""
all_fds = list(self.listeners[self.READ]) + list(self.listeners[self.WRITE])
for fd in all_fds:
try:
select.select([fd], [], [], 0)
except OSError as e:
if support.get_errno(e) in BAD_SOCK:
self.remove_descriptor(fd)
def wait(self, seconds=None):
readers = self.listeners[self.READ]
writers = self.listeners[self.WRITE]
if not readers and not writers:
if seconds:
time.sleep(seconds)
return
reader_fds = list(readers)
writer_fds = list(writers)
all_fds = reader_fds + writer_fds
try:
r, w, er = select.select(reader_fds, writer_fds, all_fds, seconds)
except OSError as e:
if support.get_errno(e) == errno.EINTR:
return
elif support.get_errno(e) in BAD_SOCK:
self._remove_bad_fds()
return
else:
raise
for fileno in er:
readers.get(fileno, hub.noop).cb(fileno)
writers.get(fileno, hub.noop).cb(fileno)
for listeners, events in ((readers, r), (writers, w)):
for fileno in events:
try:
listeners.get(fileno, hub.noop).cb(fileno)
except self.SYSTEM_EXCEPTIONS:
raise
except:
self.squelch_exception(fileno, sys.exc_info())

View File

@ -0,0 +1,106 @@
import traceback
import eventlet.hubs
from eventlet.support import greenlets as greenlet
import io
""" If true, captures a stack trace for each timer when constructed. This is
useful for debugging leaking timers, to find out where the timer was set up. """
_g_debug = False
class Timer:
def __init__(self, seconds, cb, *args, **kw):
"""Create a timer.
seconds: The minimum number of seconds to wait before calling
cb: The callback to call when the timer has expired
*args: The arguments to pass to cb
**kw: The keyword arguments to pass to cb
This timer will not be run unless it is scheduled in a runloop by
calling timer.schedule() or runloop.add_timer(timer).
"""
self.seconds = seconds
self.tpl = cb, args, kw
self.called = False
if _g_debug:
self.traceback = io.StringIO()
traceback.print_stack(file=self.traceback)
@property
def pending(self):
return not self.called
def __repr__(self):
secs = getattr(self, 'seconds', None)
cb, args, kw = getattr(self, 'tpl', (None, None, None))
retval = "Timer(%s, %s, *%s, **%s)" % (
secs, cb, args, kw)
if _g_debug and hasattr(self, 'traceback'):
retval += '\n' + self.traceback.getvalue()
return retval
def copy(self):
cb, args, kw = self.tpl
return self.__class__(self.seconds, cb, *args, **kw)
def schedule(self):
"""Schedule this timer to run in the current runloop.
"""
self.called = False
self.scheduled_time = eventlet.hubs.get_hub().add_timer(self)
return self
def __call__(self, *args):
if not self.called:
self.called = True
cb, args, kw = self.tpl
try:
cb(*args, **kw)
finally:
try:
del self.tpl
except AttributeError:
pass
def cancel(self):
"""Prevent this timer from being called. If the timer has already
been called or canceled, has no effect.
"""
if not self.called:
self.called = True
eventlet.hubs.get_hub().timer_canceled(self)
try:
del self.tpl
except AttributeError:
pass
# No default ordering in 3.x. heapq uses <
# FIXME should full set be added?
def __lt__(self, other):
return id(self) < id(other)
class LocalTimer(Timer):
def __init__(self, *args, **kwargs):
self.greenlet = greenlet.getcurrent()
Timer.__init__(self, *args, **kwargs)
@property
def pending(self):
if self.greenlet is None or self.greenlet.dead:
return False
return not self.called
def __call__(self, *args):
if not self.called:
self.called = True
if self.greenlet is not None and self.greenlet.dead:
return
cb, args, kw = self.tpl
cb(*args, **kw)
def cancel(self):
self.greenlet = None
Timer.cancel(self)