|  | 1 # | 
|  | 2 # Scheduler for Borgend | 
|  | 3 # | 
|  | 4 # This module simply provide a way for other threads to until a given time | 
|  | 5 # | 
|  | 6 | 
|  | 7 import time | 
|  | 8 import borgend | 
|  | 9 from threading import Condition, Lock, Thread | 
|  | 10 | 
|  | 11 logger=borgend.logger.getChild(__name__) | 
|  | 12 | 
|  | 13 class ScheduledEvent: | 
|  | 14     def __init__(self, when, cond, name=None): | 
|  | 15         self.next=None | 
|  | 16         self.prev=None | 
|  | 17         self.when=when | 
|  | 18         self.name=name | 
|  | 19         self.cond=Condition() | 
|  | 20 | 
|  | 21     def __lt__(self, other): | 
|  | 22         return self.when < other.when | 
|  | 23 | 
|  | 24     def __gt__(self, other): | 
|  | 25         return self.when > other.when | 
|  | 26 | 
|  | 27     def insert_after(self, ev): | 
|  | 28         if not self.next: | 
|  | 29             ev.prev=self | 
|  | 30             self.next=ev | 
|  | 31             ev.next=None | 
|  | 32         elif self.next>ev: | 
|  | 33             self.insert_immediately_after(ev) | 
|  | 34         else: | 
|  | 35             self.next.insert_after(ev) | 
|  | 36 | 
|  | 37     def insert_immediately_after(self, ev): | 
|  | 38         ev.prev=self | 
|  | 39         ev.next=self.next | 
|  | 40         if ev.next: | 
|  | 41             ev.next.prev=ev | 
|  | 42         self.next=ev | 
|  | 43 | 
|  | 44     def unlink(self): | 
|  | 45         n=self.next | 
|  | 46         p=self.prev | 
|  | 47         if n: | 
|  | 48             n.prev=p | 
|  | 49         if p: | 
|  | 50             p.next=n | 
|  | 51 | 
|  | 52 class TerminableThread(Thread): | 
|  | 53     def __init__(self, *args, **kwargs): | 
|  | 54         super().__init__(*args, **kwargs) | 
|  | 55         self._terminate=False | 
|  | 56         self._cond=Condition() | 
|  | 57 | 
|  | 58     def terminate(self): | 
|  | 59         with self._cond: | 
|  | 60             _terminate=True | 
|  | 61             self._cond.notify() | 
|  | 62 | 
|  | 63 | 
|  | 64 class Scheduler(TerminableThread): | 
|  | 65     # Default to precision of 60 seconds: the scheduler thread will never | 
|  | 66     # sleep longer than that, to get quickly back on track with the schedule | 
|  | 67     # when the computer wakes up from sleep | 
|  | 68     def __init__(self, precision=60): | 
|  | 69         self.precision = precision | 
|  | 70         self.__next_event_time = None | 
|  | 71         self.__list = None | 
|  | 72         self._cond = Condition() | 
|  | 73         self._terminate = False | 
|  | 74         super().__init__(target = self.__scheduler_thread, name = 'Scheduler') | 
|  | 75         self.daemon=True | 
|  | 76 | 
|  | 77     def __scheduler_thread(self): | 
|  | 78         with self._cond: | 
|  | 79             while not self._terminate: | 
|  | 80                 now = time.monotonic() | 
|  | 81                 if not self.__list: | 
|  | 82                     timeout = None | 
|  | 83                 else: | 
|  | 84                     # Wait at most precision seconds, or until next event if it | 
|  | 85                     # comes earlier | 
|  | 86                     timeout=min(self.precision, self.__list.when-now) | 
|  | 87 | 
|  | 88                 if not timeout or timeout>0: | 
|  | 89                     self._cond.wait(timeout) | 
|  | 90                     now = time.monotonic() | 
|  | 91 | 
|  | 92                 while self.__list and self.__list.when <= now: | 
|  | 93                     ev=self.__list | 
|  | 94                     logger.info("Found schedulable event %s" % str(ev.name)) | 
|  | 95                     self.__list=ev.next | 
|  | 96                     ev.unlink() | 
|  | 97                     ev.cond.acquire() | 
|  | 98                     ev.cond.notifyAll() | 
|  | 99                     ev.cond.release() | 
|  | 100 | 
|  | 101     # cond has to be acquired on entry! | 
|  | 102     def wait_until(self, when, cond, name=None): | 
|  | 103         ev=ScheduledEvent(when, cond, name) | 
|  | 104         with self._cond: | 
|  | 105             if not self.__list: | 
|  | 106                 self.__list=ev | 
|  | 107             elif self.__list > ev: | 
|  | 108                 ev.insert_immediately_after(self.__list) | 
|  | 109                 self.__list=ev | 
|  | 110             else: | 
|  | 111                 self.__list.insert_immediately_after(ev) | 
|  | 112 | 
|  | 113             self._cond.notify() | 
|  | 114         # This will release the lock on cond, allowing scheduler thread | 
|  | 115         # to notify us if we are already to be released | 
|  | 116         cond.wait() | 
|  | 117 | 
|  | 118         # If we were woken up by some other event, not the scheduler, | 
|  | 119         # ensure the event is removed | 
|  | 120         with self._cond: | 
|  | 121             if ev==self.__list: | 
|  | 122                 self.__list=ev.next | 
|  | 123             ev.unlink() | 
|  | 124 | 
|  | 125 |