[docs]classWaiter(object):""" A low level communication utility for greenlets. Waiter is a wrapper around greenlet's ``switch()`` and ``throw()`` calls that makes them somewhat safer: * switching will occur only if the waiting greenlet is executing :meth:`get` method currently; * any error raised in the greenlet is handled inside :meth:`switch` and :meth:`throw` * if :meth:`switch`/:meth:`throw` is called before the receiver calls :meth:`get`, then :class:`Waiter` will store the value/exception. The following :meth:`get` will return the value/raise the exception. The :meth:`switch` and :meth:`throw` methods must only be called from the :class:`Hub` greenlet. The :meth:`get` method must be called from a greenlet other than :class:`Hub`. >>> from gevent.hub import Waiter >>> from gevent import get_hub >>> result = Waiter() >>> timer = get_hub().loop.timer(0.1) >>> timer.start(result.switch, 'hello from Waiter') >>> result.get() # blocks for 0.1 seconds 'hello from Waiter' >>> timer.close() If switch is called before the greenlet gets a chance to call :meth:`get` then :class:`Waiter` stores the value. >>> from gevent.time import sleep >>> result = Waiter() >>> timer = get_hub().loop.timer(0.1) >>> timer.start(result.switch, 'hi from Waiter') >>> sleep(0.2) >>> result.get() # returns immediately without blocking 'hi from Waiter' >>> timer.close() .. warning:: This is a limited and dangerous way to communicate between greenlets. It can easily leave a greenlet unscheduled forever if used incorrectly. Consider using safer classes such as :class:`gevent.event.Event`, :class:`gevent.event.AsyncResult`, or :class:`gevent.queue.Queue`. """__slots__=['hub','greenlet','value','_exception']def__init__(self,hub=None):self.hub=get_hub()ifhubisNoneelsehubself.greenlet=Noneself.value=Noneself._exception=_NONEdefclear(self):self.greenlet=Noneself.value=Noneself._exception=_NONEdef__str__(self):ifself._exceptionis_NONE:return'<%s greenlet=%s>'%(type(self).__name__,self.greenlet)ifself._exceptionisNone:return'<%s greenlet=%s value=%r>'%(type(self).__name__,self.greenlet,self.value)return'<%s greenlet=%s exc_info=%r>'%(type(self).__name__,self.greenlet,self.exc_info)
[docs]defready(self):"""Return true if and only if it holds a value or an exception"""returnself._exceptionisnot_NONE
[docs]defsuccessful(self):"""Return true if and only if it is ready and holds a value"""returnself._exceptionisNone
@propertydefexc_info(self):"Holds the exception info passed to :meth:`throw` if :meth:`throw` was called. Otherwise ``None``."ifself._exceptionisnot_NONE:returnself._exception
[docs]defswitch(self,value):""" Switch to the greenlet if one's available. Otherwise store the *value*. .. versionchanged:: 1.3b1 The *value* is no longer optional. """greenlet=self.greenletifgreenletisNone:self.value=valueself._exception=Noneelse:ifgetcurrent()isnotself.hub:# pylint:disable=undefined-variableraiseAssertionError("Can only use Waiter.switch method from the Hub greenlet")switch=greenlet.switchtry:switch(value)except:# pylint:disable=bare-exceptself.hub.handle_error(switch,*sys.exc_info())
[docs]defthrow(self,*throw_args):"""Switch to the greenlet with the exception. If there's no greenlet, store the exception."""greenlet=self.greenletifgreenletisNone:self._exception=throw_argselse:ifgetcurrent()isnotself.hub:# pylint:disable=undefined-variableraiseAssertionError("Can only use Waiter.switch method from the Hub greenlet")throw=greenlet.throwtry:throw(*throw_args)except:# pylint:disable=bare-exceptself.hub.handle_error(throw,*sys.exc_info())
[docs]defget(self):"""If a value/an exception is stored, return/raise it. Otherwise until switch() or throw() is called."""ifself._exceptionisnot_NONE:ifself._exceptionisNone:returnself.valuegetcurrent().throw(*self._exception)# pylint:disable=undefined-variableelse:ifself.greenletisnotNone:raiseConcurrentObjectUseError('This Waiter is already used by %r'%(self.greenlet,))self.greenlet=getcurrent()# pylint:disable=undefined-variabletry:returnself.hub.switch()finally:self.greenlet=None
# can also have a debugging version, that wraps the value in a tuple (self, value) in switch()# and unwraps it in wait() thus checking that switch() was indeed calledclassMultipleWaiter(Waiter):""" An internal extension of Waiter that can be used if multiple objects must be waited on, and there is a chance that in between waits greenlets might be switched out. All greenlets that switch to this waiter will have their value returned. This does not handle exceptions or throw methods. """__slots__=['_values']def__init__(self,hub=None):Waiter.__init__(self,hub)# we typically expect a relatively small number of these to be outstanding.# since we pop from the left, a deque might be slightly# more efficient, but since we're in the hub we avoid imports if# we can help it to better support monkey-patching, and delaying the import# here can be impractical (see https://github.com/gevent/gevent/issues/652)self._values=[]defswitch(self,value):self._values.append(value)Waiter.switch(self,True)defget(self):ifnotself._values:Waiter.get(self)Waiter.clear(self)returnself._values.pop(0)def_init():greenlet_init()# pylint:disable=undefined-variable_init()fromgevent._utilimportimport_c_accelimport_c_accel(globals(),'gevent.__waiter')