event.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. from eventlet import hubs
  2. from eventlet.support import greenlets as greenlet
  3. __all__ = ['Event']
  4. class NOT_USED:
  5. def __repr__(self):
  6. return 'NOT_USED'
  7. NOT_USED = NOT_USED()
  8. class Event(object):
  9. """An abstraction where an arbitrary number of coroutines
  10. can wait for one event from another.
  11. Events are similar to a Queue that can only hold one item, but differ
  12. in two important ways:
  13. 1. calling :meth:`send` never unschedules the current greenthread
  14. 2. :meth:`send` can only be called once; create a new event to send again.
  15. They are good for communicating results between coroutines, and
  16. are the basis for how
  17. :meth:`GreenThread.wait() <eventlet.greenthread.GreenThread.wait>`
  18. is implemented.
  19. >>> from eventlet import event
  20. >>> import eventlet
  21. >>> evt = event.Event()
  22. >>> def baz(b):
  23. ... evt.send(b + 1)
  24. ...
  25. >>> _ = eventlet.spawn_n(baz, 3)
  26. >>> evt.wait()
  27. 4
  28. """
  29. _result = None
  30. _exc = None
  31. def __init__(self):
  32. self._waiters = set()
  33. self.reset()
  34. def __str__(self):
  35. params = (self.__class__.__name__, hex(id(self)),
  36. self._result, self._exc, len(self._waiters))
  37. return '<%s at %s result=%r _exc=%r _waiters[%d]>' % params
  38. def reset(self):
  39. # this is kind of a misfeature and doesn't work perfectly well,
  40. # it's better to create a new event rather than reset an old one
  41. # removing documentation so that we don't get new use cases for it
  42. assert self._result is not NOT_USED, 'Trying to re-reset() a fresh event.'
  43. self._result = NOT_USED
  44. self._exc = None
  45. def ready(self):
  46. """ Return true if the :meth:`wait` call will return immediately.
  47. Used to avoid waiting for things that might take a while to time out.
  48. For example, you can put a bunch of events into a list, and then visit
  49. them all repeatedly, calling :meth:`ready` until one returns ``True``,
  50. and then you can :meth:`wait` on that one."""
  51. return self._result is not NOT_USED
  52. def has_exception(self):
  53. return self._exc is not None
  54. def has_result(self):
  55. return self._result is not NOT_USED and self._exc is None
  56. def poll(self, notready=None):
  57. if self.ready():
  58. return self.wait()
  59. return notready
  60. # QQQ make it return tuple (type, value, tb) instead of raising
  61. # because
  62. # 1) "poll" does not imply raising
  63. # 2) it's better not to screw up caller's sys.exc_info() by default
  64. # (e.g. if caller wants to calls the function in except or finally)
  65. def poll_exception(self, notready=None):
  66. if self.has_exception():
  67. return self.wait()
  68. return notready
  69. def poll_result(self, notready=None):
  70. if self.has_result():
  71. return self.wait()
  72. return notready
  73. def wait(self):
  74. """Wait until another coroutine calls :meth:`send`.
  75. Returns the value the other coroutine passed to
  76. :meth:`send`.
  77. >>> from eventlet import event
  78. >>> import eventlet
  79. >>> evt = event.Event()
  80. >>> def wait_on():
  81. ... retval = evt.wait()
  82. ... print "waited for", retval
  83. >>> _ = eventlet.spawn(wait_on)
  84. >>> evt.send('result')
  85. >>> eventlet.sleep(0)
  86. waited for result
  87. Returns immediately if the event has already
  88. occured.
  89. >>> evt.wait()
  90. 'result'
  91. """
  92. current = greenlet.getcurrent()
  93. if self._result is NOT_USED:
  94. self._waiters.add(current)
  95. try:
  96. return hubs.get_hub().switch()
  97. finally:
  98. self._waiters.discard(current)
  99. if self._exc is not None:
  100. current.throw(*self._exc)
  101. return self._result
  102. def send(self, result=None, exc=None):
  103. """Makes arrangements for the waiters to be woken with the
  104. result and then returns immediately to the parent.
  105. >>> from eventlet import event
  106. >>> import eventlet
  107. >>> evt = event.Event()
  108. >>> def waiter():
  109. ... print 'about to wait'
  110. ... result = evt.wait()
  111. ... print 'waited for', result
  112. >>> _ = eventlet.spawn(waiter)
  113. >>> eventlet.sleep(0)
  114. about to wait
  115. >>> evt.send('a')
  116. >>> eventlet.sleep(0)
  117. waited for a
  118. It is an error to call :meth:`send` multiple times on the same event.
  119. >>> evt.send('whoops')
  120. Traceback (most recent call last):
  121. ...
  122. AssertionError: Trying to re-send() an already-triggered event.
  123. Use :meth:`reset` between :meth:`send` s to reuse an event object.
  124. """
  125. assert self._result is NOT_USED, 'Trying to re-send() an already-triggered event.'
  126. self._result = result
  127. if exc is not None and not isinstance(exc, tuple):
  128. exc = (exc, )
  129. self._exc = exc
  130. hub = hubs.get_hub()
  131. for waiter in self._waiters:
  132. hub.schedule_call_global(
  133. 0, self._do_send, self._result, self._exc, waiter)
  134. def _do_send(self, result, exc, waiter):
  135. if waiter in self._waiters:
  136. if exc is None:
  137. waiter.switch(result)
  138. else:
  139. waiter.throw(*exc)
  140. def send_exception(self, *args):
  141. """Same as :meth:`send`, but sends an exception to waiters.
  142. The arguments to send_exception are the same as the arguments
  143. to ``raise``. If a single exception object is passed in, it
  144. will be re-raised when :meth:`wait` is called, generating a
  145. new stacktrace.
  146. >>> from eventlet import event
  147. >>> evt = event.Event()
  148. >>> evt.send_exception(RuntimeError())
  149. >>> evt.wait()
  150. Traceback (most recent call last):
  151. File "<stdin>", line 1, in <module>
  152. File "eventlet/event.py", line 120, in wait
  153. current.throw(*self._exc)
  154. RuntimeError
  155. If it's important to preserve the entire original stack trace,
  156. you must pass in the entire :func:`sys.exc_info` tuple.
  157. >>> import sys
  158. >>> evt = event.Event()
  159. >>> try:
  160. ... raise RuntimeError()
  161. ... except RuntimeError:
  162. ... evt.send_exception(*sys.exc_info())
  163. ...
  164. >>> evt.wait()
  165. Traceback (most recent call last):
  166. File "<stdin>", line 1, in <module>
  167. File "eventlet/event.py", line 120, in wait
  168. current.throw(*self._exc)
  169. File "<stdin>", line 2, in <module>
  170. RuntimeError
  171. Note that doing so stores a traceback object directly on the
  172. Event object, which may cause reference cycles. See the
  173. :func:`sys.exc_info` documentation.
  174. """
  175. # the arguments and the same as for greenlet.throw
  176. return self.send(None, args)