# HG changeset patch # User Tuomo Valkonen # Date 1518035941 0 # Node ID 6993964140bd0e854b145d85a7ec07780b379e40 # Parent 173d9d7048b6914077da608a3729da9c658211d6 Time snapshot fixes. Python's default arguments are purely idiotic (aka. pythonic): generated only once. This makes sense in a purely functional language, which Python lightyears away from, but severely limits their usefulness in an imperative language. Decorators also seem clumsy for this, as one would have to tell the number of positional arguments for things to work nice, being able to pass the snapshot both positionally and as keyword. No luck. So have to do things the old-fashioned hard way. diff -r 173d9d7048b6 -r 6993964140bd borgend/dreamtime.py --- a/borgend/dreamtime.py Tue Feb 06 20:55:53 2018 +0000 +++ b/borgend/dreamtime.py Wed Feb 07 20:39:01 2018 +0000 @@ -57,72 +57,88 @@ _, sleeping=self.dreamtime_sleeping() return sleeping +# Python's default arguments are purely idiotic (aka. pythonic): generated +# only once. This makes sense in a purely functional language, which Python +# lightyears away from, but severely limits their usefulness in an imperative +# language. Decorators also seem clumsy for this, as one would have to tell +# the number of positional arguments for things to work nice, being able to +# pass the snapshot both positionally and as keyword. No luck. +# So have to do things the old-fashioned hard way. +def ensure_snapshot(snapshot): + if not snapshot: + return Snapshot() + else: + return snapshot + # The main Time class, for time advancing in various paces class Time: def __init__(self, when): self._value=when - def _monotonic(self, snapshot): - raise NotImplementedError - - def _realtime(self, snapshot): - raise NotImplementedError - @staticmethod def _now(snapshot): raise NotImplementedError - @classmethod - def now(cls): - return cls(cls._now(Snapshot())) + def remaining(self, snapshot=None): + snapshot=ensure_snapshot(snapshot) + return self._value-self._now(snapshot) + + # Mostly equal to remaining() but can be ∞ (math.inf) for DreamTime. + def horizon(self, snapshot=None): + snapshot=ensure_snapshot(snapshot) + return self.remaining(snapshot) + + def realtime(self, snapshot=None): + snapshot=ensure_snapshot(snapshot) + return snapshot.realtime()+self.remaining(snapshot) + + def monotonic(self, snapshot=None): + snapshot=ensure_snapshot(snapshot) + return snapshot.monotonic()+self.remaining(snapshot) @classmethod - def from_realtime(cls, realtime, snapshot=Snapshot()): + def now(cls, snapshot=None): + snapshot=ensure_snapshot(snapshot) + return cls(cls._now(snapshot)) + + @classmethod + def from_realtime(cls, realtime, snapshot=None): + snapshot=ensure_snapshot(snapshot) return cls(realtime-snapshot.realtime()+cls._now(snapshot)) @classmethod - def from_monotonic(cls, monotonic, snapshot=Snapshot()): + def from_monotonic(cls, monotonic, snapshot=None): + snapshot=ensure_snapshot(snapshot) return cls(monotonic-snapshot.monotonic()+cls._now(snapshot)) @classmethod - def after(cls, seconds, snapshot=Snapshot()): + def after(cls, seconds, snapshot=None): + snapshot=ensure_snapshot(snapshot) return cls(cls._now(snapshot)+seconds) @classmethod - def after_other(cls, other, seconds, snapshot=Snapshot()): + def after_other(cls, other, seconds, snapshot=None): + snapshot=ensure_snapshot(snapshot) if isinstance(other, cls): return cls(other._value+seconds) else: - return cls.from_monotonic(other._monotonic(snapshot)+seconds, + return cls.from_monotonic(other.monotonic(snapshot)+seconds, snapshot) - def datetime(self, snapshot=Snapshot()): - return datetime.datetime.fromtimestamp(self._realtime(snapshot)) - - def seconds_to(self, snapshot=Snapshot()): - return self._value-self._now(snapshot) - - def isoformat(self): - return self.datetime().isoformat() + def datetime(self, snapshot=None): + snapshot=ensure_snapshot(snapshot) + return datetime.datetime.fromtimestamp(self.realtime(snapshot)) - def realtime(self, snapshot=Snapshot()): - return self._realtime(snapshot) - - def monotonic(self, snapshot=Snapshot()): - return self._monotonic(snapshot) - - # Counted from the monotonic epoch, how far is this event? Usually should - # equal self.monotonic(), but Dreamtime can be stopped by system sleep, - # and will return ∞ (math.inf). - def horizon(self, snapshot=Snapshot()): - return self._monotonic(snapshot) + def isoformat(self, snapshot=None): + snapshot=ensure_snapshot(snapshot) + return self.datetime(snapshot).isoformat() def __compare(self, other, fn): if isinstance(other, self.__class__): return fn(self._value, other._value) else: snapshot=Snapshot() - return fn(self._monotonic(snapshot), other._monotonic(snapshot)) + return fn(self.monotonic(snapshot), other.monotonic(snapshot)) def __lt__(self, other): return self.__compare(other, lambda x, y: x < y) @@ -139,61 +155,37 @@ def __eq__(self, other): return self.__compare(other, lambda x, y: x == y) + class RealTime(Time): - def _realtime(self, snapshot): - return self._value - - def _monotonic(self, snapshot): - return self._value+(snapshot.monotonic()-snapshot.realtime()) - @staticmethod def _now(snapshot): return snapshot.realtime() -class MonotonicTime(Time): - def _realtime(self, snapshot): - return self._value+(snapshot.realtime()-snapshot.monotonic()) - - def _monotonic(self, snapshot): + def realtime(self, snapshot=None): return self._value + +class MonotonicTime(Time): @staticmethod def _now(snapshot): return snapshot.monotonic() -# class Infinity(Time): -# def __init__(self): -# super().__init__(math.inf) -# -# def _realtime(self, snapshot): -# return math.inf -# -# def _monotonic(self, snapshot): -# return math.inf -# -# @staticmethod -# def _now(snapshot): -# return 0 + def monotonic(self, snapshot=None): + return self._value + class DreamTime(Time): - def _realtime(self, snapshot): - return self._value+(snapshot.realtime()-snapshot.dreamtime()) + @staticmethod + def _now(snapshot): + return snapshot.dreamtime() - # Important: monotonic is "static" within a wakeup period - # and does not need to call time.monotonic(), as it gets compared - # to a specific time.monotonic() realisation - def _monotonic(self, snapshot): - return self._value+(snapshot.monotonic()-snapshot.dreamtime()) - - def horizon(self, snapshot=Snapshot()): + def horizon(self, snapshot=None): + snapshot=ensure_snapshot(snapshot) if snapshot.sleeping(): return math.inf else: - return self._monotonic(snapshot) + return self.remaining(snapshot) - @staticmethod - def _now(snapshot): - return snapshot.dreamtime() if platform.system()=='Darwin': @@ -238,6 +230,7 @@ now_m=time.monotonic() self.__dreamtime_last_sleep=(self.__dreamtime_last_sleep+now_m -self.__monotonic_last_wakeup) + #logger.debug("Sleeping; monotonic time now: %f; dreamtime_last_sleep: %f" % (now_m, self.__dreamtime_last_sleep)) callbacks=self.__callbacks.copy() do_callbacks(callbacks, False) @@ -249,9 +242,10 @@ self.__monotonic_last_wakeup=time.monotonic() now=time.time() slept=max(0, now-self.__sleeptime) - logger.info("Slept %f seconds" % slept) self.__slept=self.__slept+slept self.__sleeptime=None + logger.info("Slept %f seconds", slept) + #logger.debug("Slept %f seconds; total: %f; monotonic time now: %f" % (slept, self.__slept, self.__monotonic_last_wakeup)) callbacks=self.__callbacks.copy() do_callbacks(callbacks, True) @@ -270,7 +264,9 @@ sleeping=False now=snapshot.monotonic() now_dreamtime=(self.__dreamtime_last_sleep - +now-self.__monotonic_last_wakeup); + +now-self.__monotonic_last_wakeup) + #logger.debug("Dreamtime request: last_sleep: %f now: %f; last wakeup: %f => now %f dreamtime " + # % (self.__dreamtime_last_sleep, now, self.__monotonic_last_wakeup, now_dreamtime)) return now_dreamtime, sleeping # Weirdo (Py)ObjC naming to stop it form choking up diff -r 173d9d7048b6 -r 6993964140bd borgend/scheduler.py --- a/borgend/scheduler.py Tue Feb 06 20:55:53 2018 +0000 +++ b/borgend/scheduler.py Wed Feb 07 20:39:01 2018 +0000 @@ -154,28 +154,26 @@ with self._cond: while not self._terminate: snapshot = dreamtime.Snapshot() - now = snapshot.monotonic() if not self._list: timeout = None - delta = None + delta = math.inf nextname = None else: nextname=self._list.name - delta = self._list.when.horizon(snapshot)-now + delta = self._list.when.horizon(snapshot) if delta==math.inf: timeout=None else: timeout = min(self.precision, delta) if not timeout or timeout>0: - logger.debug("Scheduler waiting %s seconds [next event '%s' in %s seconds]" - % (str(timeout), nextname, str(delta))) + logger.debug("Scheduler waiting %s seconds [next event '%s' in %0.2f seconds]" + % (str(timeout), nextname, delta)) self._cond.wait(timeout) snapshot = dreamtime.Snapshot() - now = snapshot.monotonic() logger.debug("Scheduler timed out") - while self._list and self._list.when.horizon(snapshot) <= now: + while self._list and self._list.when.horizon(snapshot) <= 0: ev=self._list logger.debug("Scheduler activating %s" % (ev.name or "(unknown)")) # We are only allowed to remove ev from list when ev.cond allows @@ -217,7 +215,7 @@ # cond has to be acquired on entry! def wait_until(self, when, cond, name=None): logger.info("Scheduling '%s' in %0.01f seconds / on %s [%s]" % - (name, when.seconds_to(), when.isoformat(), + (name, when.remaining(), when.isoformat(), when.__class__.__name__)) self._wait(ScheduledEvent(when, cond, name))