aboutsummaryrefslogtreecommitdiff
path: root/rpkid
diff options
context:
space:
mode:
Diffstat (limited to 'rpkid')
-rw-r--r--rpkid/rpki/async.py92
1 files changed, 91 insertions, 1 deletions
diff --git a/rpkid/rpki/async.py b/rpkid/rpki/async.py
index 74a23385..9bf91304 100644
--- a/rpkid/rpki/async.py
+++ b/rpkid/rpki/async.py
@@ -18,7 +18,8 @@ OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
"""
-import rpki.log
+import asyncore
+import rpki.log, rpki.sundial
class iterator(object):
"""Iteration construct for event-driven code. Takes three
@@ -54,3 +55,92 @@ class iterator(object):
except StopIteration:
if self.done_callback is not None:
self.done_callback()
+
+
+class timer(object):
+ """Timer construct for event-driven code.
+
+ This is a virtual class, users must subclass it and define an expired() method.
+ """
+
+ ## @var queue
+ # Timer queue, shared by all timer instances (there can be only one queue).
+
+ queue = []
+
+ def set(self, when):
+ """Set a timer. Argument can be a datetime, to specify an
+ absolute time, a timedelta, to specify an offset time, or None, to
+ indicate that the timer should expire immediately, which can be
+ useful in avoiding an excessively deep call stack.
+ """
+ if when is None:
+ self.when = rpki.sundial.now()
+ elif isinstance(when, rpki.sundial.timedelta):
+ self.when = rpki.sundial.now() + when
+ else:
+ self.when = when
+ assert isinstance(self.when, rpki.sundial.datetime)
+ self.queue.append(self)
+ self.queue.sort()
+
+ def __cmp__(self, other):
+ return cmp(self.when, other.when)
+
+ def cancel(self):
+ """Cancel a timer, if it was set."""
+ try:
+ self.queue.remove(self)
+ except ValueError:
+ pass
+
+ def expired(self):
+ """Handle a timer that has expired. Subclass must define this."""
+ raise NotImplementedError
+
+ @classmethod
+ def runq(cls):
+ """Run the timer queue: for each timer whose call time has passed,
+ pull the timer off the queue and call its expired() handler.
+ """
+ while cls.queue and rpki.sundial.now() >= cls.queue[0].when:
+ cls.queue.pop(0).expired()
+
+ def __repr__(self):
+ return "<%s %s>" % (self.__class__.__name__, repr(self.when))
+
+ @classmethod
+ def seconds_until_wakeup(cls):
+ """Calculate delay until next timer expires, or None if no timers
+ are set and we should wait indefinitely. Rounds up to avoid
+ spinning in select() or poll(). We could calculate fractional
+ seconds in the right units instead, but select() and poll() don't
+ even take the same units (argh!), and we're not doing anything
+ that hair-triggered, so rounding up is simplest.
+ """
+ if not cls.queue:
+ return None
+ now = rpki.sundial.now()
+ if now >= cls.queue[0].when:
+ return 0
+ else:
+ delay = cls.queue[0].when - now
+ seconds = delay.convert_to_seconds()
+ if delay.microseconds:
+ seconds += 1
+ return seconds
+
+ @classmethod
+ def clear(cls):
+ """Cancel every timer on the queue. We could just throw away the
+ queue content, but this way we can notify subclasses that provide
+ their own cancel() method.
+ """
+ while cls.queue:
+ cls.queue.pop(0).cancel()
+
+def event_loop():
+ """Replacement for asyncore.loop(), adding timer support."""
+ while asyncore.socket_map:
+ asyncore.poll(timer.seconds_until_wakeup(), asyncore.socket_map)
+ timer.runq()