Time snapshot fixes.

Wed, 07 Feb 2018 20:39:01 +0000

author
Tuomo Valkonen <tuomov@iki.fi>
date
Wed, 07 Feb 2018 20:39:01 +0000
changeset 113
6993964140bd
parent 112
173d9d7048b6
child 114
ad9fb3dd9fec

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.

borgend/dreamtime.py file | annotate | diff | comparison | revisions
borgend/scheduler.py file | annotate | diff | comparison | revisions
--- 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
--- 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))
 

mercurial