Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

hub.py 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. # -*- coding: utf-8 -*-
  2. """
  3. kombu.async.hub
  4. ===============
  5. Event loop implementation.
  6. """
  7. from __future__ import absolute_import
  8. import errno
  9. from collections import deque
  10. from contextlib import contextmanager
  11. from time import sleep
  12. from types import GeneratorType as generator
  13. from amqp import promise
  14. from kombu.five import Empty, range
  15. from kombu.log import get_logger
  16. from kombu.utils import cached_property, fileno
  17. from kombu.utils.compat import get_errno
  18. from kombu.utils.eventio import READ, WRITE, ERR, poll
  19. from .timer import Timer
  20. __all__ = ['Hub', 'get_event_loop', 'set_event_loop']
  21. logger = get_logger(__name__)
  22. _current_loop = None
  23. W_UNKNOWN_EVENT = """\
  24. Received unknown event %r for fd %r, please contact support!\
  25. """
  26. class Stop(BaseException):
  27. """Stops the event loop."""
  28. def _raise_stop_error():
  29. raise Stop()
  30. @contextmanager
  31. def _dummy_context(*args, **kwargs):
  32. yield
  33. def get_event_loop():
  34. return _current_loop
  35. def set_event_loop(loop):
  36. global _current_loop
  37. _current_loop = loop
  38. return loop
  39. class Hub(object):
  40. """Event loop object.
  41. :keyword timer: Specify timer object.
  42. """
  43. #: Flag set if reading from an fd will not block.
  44. READ = READ
  45. #: Flag set if writing to an fd will not block.
  46. WRITE = WRITE
  47. #: Flag set on error, and the fd should be read from asap.
  48. ERR = ERR
  49. #: List of callbacks to be called when the loop is exiting,
  50. #: applied with the hub instance as sole argument.
  51. on_close = None
  52. def __init__(self, timer=None):
  53. self.timer = timer if timer is not None else Timer()
  54. self.readers = {}
  55. self.writers = {}
  56. self.on_tick = set()
  57. self.on_close = set()
  58. self._ready = deque()
  59. self._running = False
  60. self._loop = None
  61. # The eventloop (in celery.worker.loops)
  62. # will merge fds in this set and then instead of calling
  63. # the callback for each ready fd it will call the
  64. # :attr:`consolidate_callback` with the list of ready_fds
  65. # as an argument. This API is internal and is only
  66. # used by the multiprocessing pool to find inqueues
  67. # that are ready to write.
  68. self.consolidate = set()
  69. self.consolidate_callback = None
  70. self.propagate_errors = ()
  71. self._create_poller()
  72. def reset(self):
  73. self.close()
  74. self._create_poller()
  75. def _create_poller(self):
  76. self.poller = poll()
  77. self._register_fd = self.poller.register
  78. self._unregister_fd = self.poller.unregister
  79. def _close_poller(self):
  80. if self.poller is not None:
  81. self.poller.close()
  82. self.poller = None
  83. self._register_fd = None
  84. self._unregister_fd = None
  85. def stop(self):
  86. self.call_soon(_raise_stop_error)
  87. def __repr__(self):
  88. return '<Hub@{0:#x}: R:{1} W:{2}>'.format(
  89. id(self), len(self.readers), len(self.writers),
  90. )
  91. def fire_timers(self, min_delay=1, max_delay=10, max_timers=10,
  92. propagate=()):
  93. timer = self.timer
  94. delay = None
  95. if timer and timer._queue:
  96. for i in range(max_timers):
  97. delay, entry = next(self.scheduler)
  98. if entry is None:
  99. break
  100. try:
  101. entry()
  102. except propagate:
  103. raise
  104. except (MemoryError, AssertionError):
  105. raise
  106. except OSError as exc:
  107. if get_errno(exc) == errno.ENOMEM:
  108. raise
  109. logger.error('Error in timer: %r', exc, exc_info=1)
  110. except Exception as exc:
  111. logger.error('Error in timer: %r', exc, exc_info=1)
  112. return min(delay or min_delay, max_delay)
  113. def _remove_from_loop(self, fd):
  114. try:
  115. self._unregister(fd)
  116. finally:
  117. self._discard(fd)
  118. def add(self, fd, callback, flags, args=(), consolidate=False):
  119. fd = fileno(fd)
  120. try:
  121. self.poller.register(fd, flags)
  122. except ValueError:
  123. self._remove_from_loop(fd)
  124. raise
  125. else:
  126. dest = self.readers if flags & READ else self.writers
  127. if consolidate:
  128. self.consolidate.add(fd)
  129. dest[fd] = None
  130. else:
  131. dest[fd] = callback, args
  132. def remove(self, fd):
  133. fd = fileno(fd)
  134. self._remove_from_loop(fd)
  135. def run_forever(self):
  136. self._running = True
  137. try:
  138. while 1:
  139. try:
  140. self.run_once()
  141. except Stop:
  142. break
  143. finally:
  144. self._running = False
  145. def run_once(self):
  146. try:
  147. next(self.loop)
  148. except StopIteration:
  149. self._loop = None
  150. def call_soon(self, callback, *args):
  151. handle = promise(callback, args)
  152. self._ready.append(handle)
  153. return handle
  154. def call_later(self, delay, callback, *args):
  155. return self.timer.call_after(delay, callback, args)
  156. def call_at(self, when, callback, *args):
  157. return self.timer.call_at(when, callback, args)
  158. def call_repeatedly(self, delay, callback, *args):
  159. return self.timer.call_repeatedly(delay, callback, args)
  160. def add_reader(self, fds, callback, *args):
  161. return self.add(fds, callback, READ | ERR, args)
  162. def add_writer(self, fds, callback, *args):
  163. return self.add(fds, callback, WRITE, args)
  164. def remove_reader(self, fd):
  165. writable = fd in self.writers
  166. on_write = self.writers.get(fd)
  167. try:
  168. self._remove_from_loop(fd)
  169. finally:
  170. if writable:
  171. cb, args = on_write
  172. self.add(fd, cb, WRITE, args)
  173. def remove_writer(self, fd):
  174. readable = fd in self.readers
  175. on_read = self.readers.get(fd)
  176. try:
  177. self._remove_from_loop(fd)
  178. finally:
  179. if readable:
  180. cb, args = on_read
  181. self.add(fd, cb, READ | ERR, args)
  182. def _unregister(self, fd):
  183. try:
  184. self.poller.unregister(fd)
  185. except (AttributeError, KeyError, OSError):
  186. pass
  187. def close(self, *args):
  188. [self._unregister(fd) for fd in self.readers]
  189. self.readers.clear()
  190. [self._unregister(fd) for fd in self.writers]
  191. self.writers.clear()
  192. self.consolidate.clear()
  193. self._close_poller()
  194. for callback in self.on_close:
  195. callback(self)
  196. def _discard(self, fd):
  197. fd = fileno(fd)
  198. self.readers.pop(fd, None)
  199. self.writers.pop(fd, None)
  200. self.consolidate.discard(fd)
  201. def create_loop(self,
  202. generator=generator, sleep=sleep, min=min, next=next,
  203. Empty=Empty, StopIteration=StopIteration,
  204. KeyError=KeyError, READ=READ, WRITE=WRITE, ERR=ERR):
  205. readers, writers = self.readers, self.writers
  206. poll = self.poller.poll
  207. fire_timers = self.fire_timers
  208. hub_remove = self.remove
  209. scheduled = self.timer._queue
  210. consolidate = self.consolidate
  211. consolidate_callback = self.consolidate_callback
  212. on_tick = self.on_tick
  213. todo = self._ready
  214. propagate = self.propagate_errors
  215. while 1:
  216. for tick_callback in on_tick:
  217. tick_callback()
  218. while todo:
  219. item = todo.popleft()
  220. if item:
  221. item()
  222. poll_timeout = fire_timers(propagate=propagate) if scheduled else 1
  223. if readers or writers:
  224. to_consolidate = []
  225. try:
  226. events = poll(poll_timeout)
  227. except ValueError: # Issue 882
  228. raise StopIteration()
  229. for fd, event in events or ():
  230. general_error = False
  231. if fd in consolidate and \
  232. writers.get(fd) is None:
  233. to_consolidate.append(fd)
  234. continue
  235. cb = cbargs = None
  236. if event & READ:
  237. try:
  238. cb, cbargs = readers[fd]
  239. except KeyError:
  240. self.remove_reader(fd)
  241. continue
  242. elif event & WRITE:
  243. try:
  244. cb, cbargs = writers[fd]
  245. except KeyError:
  246. self.remove_writer(fd)
  247. continue
  248. elif event & ERR:
  249. general_error = True
  250. else:
  251. logger.info(W_UNKNOWN_EVENT, event, fd)
  252. general_error = True
  253. if general_error:
  254. try:
  255. cb, cbargs = (readers.get(fd) or
  256. writers.get(fd))
  257. except TypeError:
  258. pass
  259. if cb is None:
  260. self.remove(fd)
  261. continue
  262. if isinstance(cb, generator):
  263. try:
  264. next(cb)
  265. except OSError as exc:
  266. if get_errno(exc) != errno.EBADF:
  267. raise
  268. hub_remove(fd)
  269. except StopIteration:
  270. pass
  271. except Exception:
  272. hub_remove(fd)
  273. raise
  274. else:
  275. try:
  276. cb(*cbargs)
  277. except Empty:
  278. pass
  279. if to_consolidate:
  280. consolidate_callback(to_consolidate)
  281. else:
  282. # no sockets yet, startup is probably not done.
  283. sleep(min(poll_timeout, 0.1))
  284. yield
  285. def repr_active(self):
  286. from .debug import repr_active
  287. return repr_active(self)
  288. def repr_events(self, events):
  289. from .debug import repr_events
  290. return repr_events(self, events)
  291. @cached_property
  292. def scheduler(self):
  293. return iter(self.timer)
  294. @property
  295. def loop(self):
  296. if self._loop is None:
  297. self._loop = self.create_loop()
  298. return self._loop