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.

timer.py 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. # -*- coding: utf-8 -*-
  2. """
  3. kombu.async.timer
  4. =================
  5. Timer scheduling Python callbacks.
  6. """
  7. from __future__ import absolute_import
  8. import heapq
  9. import sys
  10. from collections import namedtuple
  11. from datetime import datetime
  12. from functools import wraps
  13. from time import time
  14. from weakref import proxy as weakrefproxy
  15. from kombu.five import monotonic
  16. from kombu.log import get_logger
  17. from kombu.utils.compat import timedelta_seconds
  18. try:
  19. from pytz import utc
  20. except ImportError:
  21. utc = None
  22. DEFAULT_MAX_INTERVAL = 2
  23. EPOCH = datetime.utcfromtimestamp(0).replace(tzinfo=utc)
  24. IS_PYPY = hasattr(sys, 'pypy_version_info')
  25. logger = get_logger(__name__)
  26. __all__ = ['Entry', 'Timer', 'to_timestamp']
  27. scheduled = namedtuple('scheduled', ('eta', 'priority', 'entry'))
  28. def to_timestamp(d, default_timezone=utc):
  29. if isinstance(d, datetime):
  30. if d.tzinfo is None:
  31. d = d.replace(tzinfo=default_timezone)
  32. return timedelta_seconds(d - EPOCH)
  33. return d
  34. class Entry(object):
  35. if not IS_PYPY: # pragma: no cover
  36. __slots__ = (
  37. 'fun', 'args', 'kwargs', 'tref', 'cancelled',
  38. '_last_run', '__weakref__',
  39. )
  40. def __init__(self, fun, args=None, kwargs=None):
  41. self.fun = fun
  42. self.args = args or []
  43. self.kwargs = kwargs or {}
  44. self.tref = weakrefproxy(self)
  45. self._last_run = None
  46. self.cancelled = False
  47. def __call__(self):
  48. return self.fun(*self.args, **self.kwargs)
  49. def cancel(self):
  50. try:
  51. self.tref.cancelled = True
  52. except ReferenceError: # pragma: no cover
  53. pass
  54. def __repr__(self):
  55. return '<TimerEntry: {0}(*{1!r}, **{2!r})'.format(
  56. self.fun.__name__, self.args, self.kwargs)
  57. def __hash__(self):
  58. return hash((self.fun, repr(self.args), repr(self.kwargs)))
  59. # must not use hash() to order entries
  60. def __lt__(self, other):
  61. return id(self) < id(other)
  62. def __gt__(self, other):
  63. return id(self) > id(other)
  64. def __le__(self, other):
  65. return id(self) <= id(other)
  66. def __ge__(self, other):
  67. return id(self) >= id(other)
  68. def __eq__(self, other):
  69. return hash(self) == hash(other)
  70. def __ne__(self, other):
  71. return not self.__eq__(other)
  72. class Timer(object):
  73. """ETA scheduler."""
  74. Entry = Entry
  75. on_error = None
  76. def __init__(self, max_interval=None, on_error=None, **kwargs):
  77. self.max_interval = float(max_interval or DEFAULT_MAX_INTERVAL)
  78. self.on_error = on_error or self.on_error
  79. self._queue = []
  80. def __enter__(self):
  81. return self
  82. def __exit__(self, *exc_info):
  83. self.stop()
  84. def call_at(self, eta, fun, args=(), kwargs={}, priority=0):
  85. return self.enter_at(self.Entry(fun, args, kwargs), eta, priority)
  86. def call_after(self, secs, fun, args=(), kwargs={}, priority=0):
  87. return self.enter_after(secs, self.Entry(fun, args, kwargs), priority)
  88. def call_repeatedly(self, secs, fun, args=(), kwargs={}, priority=0):
  89. tref = self.Entry(fun, args, kwargs)
  90. @wraps(fun)
  91. def _reschedules(*args, **kwargs):
  92. last, now = tref._last_run, monotonic()
  93. lsince = (now - tref._last_run) if last else secs
  94. try:
  95. if lsince and lsince >= secs:
  96. tref._last_run = now
  97. return fun(*args, **kwargs)
  98. finally:
  99. if not tref.cancelled:
  100. last = tref._last_run
  101. next = secs - (now - last) if last else secs
  102. self.enter_after(next, tref, priority)
  103. tref.fun = _reschedules
  104. tref._last_run = None
  105. return self.enter_after(secs, tref, priority)
  106. def enter_at(self, entry, eta=None, priority=0, time=time):
  107. """Enter function into the scheduler.
  108. :param entry: Item to enter.
  109. :keyword eta: Scheduled time as a :class:`datetime.datetime` object.
  110. :keyword priority: Unused.
  111. """
  112. if eta is None:
  113. eta = time()
  114. if isinstance(eta, datetime):
  115. try:
  116. eta = to_timestamp(eta)
  117. except Exception as exc:
  118. if not self.handle_error(exc):
  119. raise
  120. return
  121. return self._enter(eta, priority, entry)
  122. def enter_after(self, secs, entry, priority=0, time=time):
  123. return self.enter_at(entry, time() + secs, priority)
  124. def _enter(self, eta, priority, entry, push=heapq.heappush):
  125. push(self._queue, scheduled(eta, priority, entry))
  126. return entry
  127. def apply_entry(self, entry):
  128. try:
  129. entry()
  130. except Exception as exc:
  131. if not self.handle_error(exc):
  132. logger.error('Error in timer: %r', exc, exc_info=True)
  133. def handle_error(self, exc_info):
  134. if self.on_error:
  135. self.on_error(exc_info)
  136. return True
  137. def stop(self):
  138. pass
  139. def __iter__(self, min=min, nowfun=time,
  140. pop=heapq.heappop, push=heapq.heappush):
  141. """This iterator yields a tuple of ``(entry, wait_seconds)``,
  142. where if entry is :const:`None` the caller should wait
  143. for ``wait_seconds`` until it polls the schedule again."""
  144. max_interval = self.max_interval
  145. queue = self._queue
  146. while 1:
  147. if queue:
  148. eventA = queue[0]
  149. now, eta = nowfun(), eventA[0]
  150. if now < eta:
  151. yield min(eta - now, max_interval), None
  152. else:
  153. eventB = pop(queue)
  154. if eventB is eventA:
  155. entry = eventA[2]
  156. if not entry.cancelled:
  157. yield None, entry
  158. continue
  159. else:
  160. push(queue, eventB)
  161. else:
  162. yield None, None
  163. def clear(self):
  164. self._queue[:] = [] # atomic, without creating a new list.
  165. def cancel(self, tref):
  166. tref.cancel()
  167. def __len__(self):
  168. return len(self._queue)
  169. def __nonzero__(self):
  170. return True
  171. @property
  172. def queue(self, _pop=heapq.heappop):
  173. """Snapshot of underlying datastructure."""
  174. events = list(self._queue)
  175. return [_pop(v) for v in [events] * len(events)]
  176. @property
  177. def schedule(self):
  178. return self