macOS "sleep" signals / status are complete bollocks:

Sun, 04 Feb 2018 00:22:20 +0000

author
Tuomo Valkonen <tuomov@iki.fi>
date
Sun, 04 Feb 2018 00:22:20 +0000
changeset 100
b141bed9e718
parent 99
281bab8361c8
child 101
3068b0de12ee

macOS "sleep" signals / status are complete bollocks:
at least when plugged in, the system is actually sometimes running all
night with the lid closed and sleepNotification delivered. Therefore,
we need our timers to work in a sane manner when we should be sleeping!

This patch is a first stage of this fix, implementing stopped dreamtime.
It also has improved comparisons of different types of time, based on
snapshotting.

borgend/backup.py file | annotate | diff | comparison | revisions
borgend/dreamtime.py file | annotate | diff | comparison | revisions
--- a/borgend/backup.py	Wed Jan 31 22:41:12 2018 +0000
+++ b/borgend/backup.py	Sun Feb 04 00:22:20 2018 +0000
@@ -536,7 +536,7 @@
                 try:
                     assert(not self.current_operation)
                     self.__main_thread_wait_schedule()
-                    if (not self._terminate_or_pause() and self.scheduled_operation
+                    if ((not self._terminate_or_pause()) and self.scheduled_operation
                         and self.scheduled_operation.start_time <= MonotonicTime.now()):
                         self.__main_thread_queue_and_launch()
                 except Exception as err:
--- a/borgend/dreamtime.py	Wed Jan 31 22:41:12 2018 +0000
+++ b/borgend/dreamtime.py	Sun Feb 04 00:22:20 2018 +0000
@@ -19,113 +19,143 @@
 # Support classes for dealing with different times
 #
 
-# Return difference (delay) of "dreamtime" to monotonic time
-def dreamtime_difference():
-    if _dreamtime_monitor:
-        return _dreamtime_monitor.diff()
-    else:
-        return time.monotonic()
+# Time snapshotting to helps to create stable comparisons of different 
+# subclasses of Time.
+class Snapshot:
+    def __init__(self):
+        self._monotonic=None
+        self._realtime=None
+        self._dreamtime=None
+
+    def monotonic(self):
+        if self._monotonic is None:
+            self._monotonic=time.monotonic()
+        return self._monotonic
 
-# Return "dreamtime"
-def dreamtime():
-    return max(0, time.monotonic()-dreamtime_difference())
+    def realtime(self):
+        if self._realtime is None:
+            self._realtime=time.time()
+        return self._realtime
 
+    def dreamtime(self):
+        if self._dreamtime is None:
+            if _dreamtime_monitor:
+                self._dreamtime=_dreamtime_monitor.dreamtime(snapshot=self)
+            else:
+                self._dreamtime=self.monotonic()
+        return self._dreamtime
+
+# The main Time class, for time advancing in various paces
 class Time:
     def __init__(self, when):
         self._value=when
 
-    def realtime(self):
+    def _monotonic(self, snapshot):
         raise NotImplementedError
 
-    def monotonic(self):
+    def _realtime(self, snapshot):
         raise NotImplementedError
 
     @staticmethod
-    def _now():
+    def _now(snapshot):
         raise NotImplementedError
 
     @classmethod
     def now(cls):
-        return cls(cls._now())
+        return cls(cls._now(Snapshot()))
 
     @classmethod
-    def from_realtime(cls, realtime):
-        return cls(realtime-time.time()+cls._now())
+    def from_realtime(cls, realtime, snapshot=Snapshot()):
+        return cls(realtime-snapshot.realtime()+cls._now(snapshot))
 
     @classmethod
-    def from_monotonic(cls, monotonic):
-        return cls(monotonic-time.monotonic()+cls._now())
+    def from_monotonic(cls, monotonic, snapshot=Snapshot()):
+        return cls(monotonic-snapshot.monotonic()+cls._now(snapshot))
 
     @classmethod
-    def after(cls, seconds):
-        return cls(cls._now()+seconds)
+    def after(cls, seconds, snapshot=Snapshot()):
+        return cls(cls._now(snapshot)+seconds)
 
     @classmethod
-    def after_other(cls, other, seconds):
+    def after_other(cls, other, seconds, snapshot=Snapshot()):
         if isinstance(other, cls):
             return cls(other._value+seconds)
         else:
-            return cls.from_monotonic(other.monotonic()+seconds)
+            return cls.from_monotonic(other._monotonic(snapshot)+seconds,
+                                      snapshot)
 
     def datetime(self):
         return datetime.datetime.fromtimestamp(self.realtime())
 
     def seconds_to(self):
-        return self._value-self._now()
+        return self._value-self._now(Snapshot())
 
     def isoformat(self):
         return self.datetime().isoformat()
 
+    def realtime(self):
+        return self._realtime(Snapshot())
+
+    def monotonic(self):
+        return self._monotonic(Snapshot())
+
+    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))
+
     def __lt__(self, other):
-        return self.monotonic() < other.monotonic()
+        return self.__compare(other, lambda x, y: x < y)
 
     def __gt__(self, other):
-        return self.monotonic() > other.monotonic()
+        return self.__compare(other, lambda x, y: x > y)
 
     def __le__(self, other):
-        return self.monotonic() <= other.monotonic()
+        return self.__compare(other, lambda x, y: x <= y)
 
     def __ge__(self, other):
-        return self.monotonic() >= other.monotonic()
+        return self.__compare(other, lambda x, y: x >= y)
 
     def __eq__(self, other):
-        return self.monotonic() == other.realtime()
+        return self.__compare(other, lambda x, y: x == y)
 
 class RealTime(Time):
-    def realtime(self):
+    def _realtime(self, snapshot):
         return self._value
 
-    def monotonic(self):
-        return self._value+(time.monotonic()-time.time())
+    def _monotonic(self, snapshot):
+        return self._value+(snapshot.monotonic()-snapshot.realtime())
 
     @staticmethod
-    def _now():
-        return time.time()
+    def _now(snapshot):
+        return snapshot.realtime()
 
 class MonotonicTime(Time):
-    def realtime(self):
-        return self._value+(time.time()-time.monotonic())
+    def _realtime(self, snapshot):
+        return self._value+(snapshot.realtime()-snapshot.monotonic())
 
-    def monotonic(self):
+    def _monotonic(self, snapshot):
         return self._value
 
     @staticmethod
-    def _now():
-        return time.monotonic()
+    def _now(snapshot):
+        return snapshot.monotonic()
 
 class DreamTime(Time):
-    def realtime(self):
-        return self._value+(time.time()-dreamtime())
+    def _realtime(self, snapshot):
+        return self._value+(snapshot.realtime()-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):
-        return self._value+dreamtime_difference()
+    def _monotonic(self, snapshot):
+        return self._value+(snapshot.monotonic()-snapshot.dreamtime())
 
     @staticmethod
-    def _now():
-        return dreamtime()
+    def _now(snapshot):
+        return snapshot.dreamtime()
 
 
 if platform.system()=='Darwin':
@@ -175,11 +205,20 @@
                 except Exception:
                     logger.exception("Error in wake notification callback")
 
-        # Return difference to time.monotonic()
-        def diff(self):
+        # Return dreamtime
+        def dreamtime(self, snapshot=Snapshot()):
             with self.__lock:
-                diff=self.__epoch+self.__slept
-            return diff
+                # macOS "sleep" signals / status are complete bollocks: at least
+                # when plugged in, the system is actually sometimes running all
+                # night with the lid closed and sleepNotification delivered.
+                # Therefore, we need our timers to work in a sane manner when
+                # we should be sleeping!
+                if self.__sleeptime is not None:
+                    now_monotonic=self.__sleeptime
+                else:
+                    now_monotonic=snapshot.monotonic()
+                now_dreamtime=max(0, now_monotonic-self.__epoch-self.__slept)
+            return now_dreamtime
 
         # Weirdo (Py)ObjC naming to stop it form choking up
         def addForObj_aCallback_(self, obj, callback):

mercurial