# HG changeset patch # User Tuomo Valkonen # Date 1517703740 0 # Node ID b141bed9e7180985c888efb21e84bf87caccd724 # Parent 281bab8361c89bb6b971ddba857b6d4409790229 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. diff -r 281bab8361c8 -r b141bed9e718 borgend/backup.py --- 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: diff -r 281bab8361c8 -r b141bed9e718 borgend/dreamtime.py --- 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):