xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/sched.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1*cda5da8dSAndroid Build Coastguard Worker"""A generally useful event scheduler class.
2*cda5da8dSAndroid Build Coastguard Worker
3*cda5da8dSAndroid Build Coastguard WorkerEach instance of this class manages its own queue.
4*cda5da8dSAndroid Build Coastguard WorkerNo multi-threading is implied; you are supposed to hack that
5*cda5da8dSAndroid Build Coastguard Workeryourself, or use a single instance per application.
6*cda5da8dSAndroid Build Coastguard Worker
7*cda5da8dSAndroid Build Coastguard WorkerEach instance is parametrized with two functions, one that is
8*cda5da8dSAndroid Build Coastguard Workersupposed to return the current time, one that is supposed to
9*cda5da8dSAndroid Build Coastguard Workerimplement a delay.  You can implement real-time scheduling by
10*cda5da8dSAndroid Build Coastguard Workersubstituting time and sleep from built-in module time, or you can
11*cda5da8dSAndroid Build Coastguard Workerimplement simulated time by writing your own functions.  This can
12*cda5da8dSAndroid Build Coastguard Workeralso be used to integrate scheduling with STDWIN events; the delay
13*cda5da8dSAndroid Build Coastguard Workerfunction is allowed to modify the queue.  Time can be expressed as
14*cda5da8dSAndroid Build Coastguard Workerintegers or floating point numbers, as long as it is consistent.
15*cda5da8dSAndroid Build Coastguard Worker
16*cda5da8dSAndroid Build Coastguard WorkerEvents are specified by tuples (time, priority, action, argument, kwargs).
17*cda5da8dSAndroid Build Coastguard WorkerAs in UNIX, lower priority numbers mean higher priority; in this
18*cda5da8dSAndroid Build Coastguard Workerway the queue can be maintained as a priority queue.  Execution of the
19*cda5da8dSAndroid Build Coastguard Workerevent means calling the action function, passing it the argument
20*cda5da8dSAndroid Build Coastguard Workersequence in "argument" (remember that in Python, multiple function
21*cda5da8dSAndroid Build Coastguard Workerarguments are be packed in a sequence) and keyword parameters in "kwargs".
22*cda5da8dSAndroid Build Coastguard WorkerThe action function may be an instance method so it
23*cda5da8dSAndroid Build Coastguard Workerhas another way to reference private data (besides global variables).
24*cda5da8dSAndroid Build Coastguard Worker"""
25*cda5da8dSAndroid Build Coastguard Worker
26*cda5da8dSAndroid Build Coastguard Workerimport time
27*cda5da8dSAndroid Build Coastguard Workerimport heapq
28*cda5da8dSAndroid Build Coastguard Workerfrom collections import namedtuple
29*cda5da8dSAndroid Build Coastguard Workerfrom itertools import count
30*cda5da8dSAndroid Build Coastguard Workerimport threading
31*cda5da8dSAndroid Build Coastguard Workerfrom time import monotonic as _time
32*cda5da8dSAndroid Build Coastguard Worker
33*cda5da8dSAndroid Build Coastguard Worker__all__ = ["scheduler"]
34*cda5da8dSAndroid Build Coastguard Worker
35*cda5da8dSAndroid Build Coastguard WorkerEvent = namedtuple('Event', 'time, priority, sequence, action, argument, kwargs')
36*cda5da8dSAndroid Build Coastguard WorkerEvent.time.__doc__ = ('''Numeric type compatible with the return value of the
37*cda5da8dSAndroid Build Coastguard Workertimefunc function passed to the constructor.''')
38*cda5da8dSAndroid Build Coastguard WorkerEvent.priority.__doc__ = ('''Events scheduled for the same time will be executed
39*cda5da8dSAndroid Build Coastguard Workerin the order of their priority.''')
40*cda5da8dSAndroid Build Coastguard WorkerEvent.sequence.__doc__ = ('''A continually increasing sequence number that
41*cda5da8dSAndroid Build Coastguard Worker    separates events if time and priority are equal.''')
42*cda5da8dSAndroid Build Coastguard WorkerEvent.action.__doc__ = ('''Executing the event means executing
43*cda5da8dSAndroid Build Coastguard Workeraction(*argument, **kwargs)''')
44*cda5da8dSAndroid Build Coastguard WorkerEvent.argument.__doc__ = ('''argument is a sequence holding the positional
45*cda5da8dSAndroid Build Coastguard Workerarguments for the action.''')
46*cda5da8dSAndroid Build Coastguard WorkerEvent.kwargs.__doc__ = ('''kwargs is a dictionary holding the keyword
47*cda5da8dSAndroid Build Coastguard Workerarguments for the action.''')
48*cda5da8dSAndroid Build Coastguard Worker
49*cda5da8dSAndroid Build Coastguard Worker_sentinel = object()
50*cda5da8dSAndroid Build Coastguard Worker
51*cda5da8dSAndroid Build Coastguard Workerclass scheduler:
52*cda5da8dSAndroid Build Coastguard Worker
53*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, timefunc=_time, delayfunc=time.sleep):
54*cda5da8dSAndroid Build Coastguard Worker        """Initialize a new instance, passing the time and delay
55*cda5da8dSAndroid Build Coastguard Worker        functions"""
56*cda5da8dSAndroid Build Coastguard Worker        self._queue = []
57*cda5da8dSAndroid Build Coastguard Worker        self._lock = threading.RLock()
58*cda5da8dSAndroid Build Coastguard Worker        self.timefunc = timefunc
59*cda5da8dSAndroid Build Coastguard Worker        self.delayfunc = delayfunc
60*cda5da8dSAndroid Build Coastguard Worker        self._sequence_generator = count()
61*cda5da8dSAndroid Build Coastguard Worker
62*cda5da8dSAndroid Build Coastguard Worker    def enterabs(self, time, priority, action, argument=(), kwargs=_sentinel):
63*cda5da8dSAndroid Build Coastguard Worker        """Enter a new event in the queue at an absolute time.
64*cda5da8dSAndroid Build Coastguard Worker
65*cda5da8dSAndroid Build Coastguard Worker        Returns an ID for the event which can be used to remove it,
66*cda5da8dSAndroid Build Coastguard Worker        if necessary.
67*cda5da8dSAndroid Build Coastguard Worker
68*cda5da8dSAndroid Build Coastguard Worker        """
69*cda5da8dSAndroid Build Coastguard Worker        if kwargs is _sentinel:
70*cda5da8dSAndroid Build Coastguard Worker            kwargs = {}
71*cda5da8dSAndroid Build Coastguard Worker
72*cda5da8dSAndroid Build Coastguard Worker        with self._lock:
73*cda5da8dSAndroid Build Coastguard Worker            event = Event(time, priority, next(self._sequence_generator),
74*cda5da8dSAndroid Build Coastguard Worker                          action, argument, kwargs)
75*cda5da8dSAndroid Build Coastguard Worker            heapq.heappush(self._queue, event)
76*cda5da8dSAndroid Build Coastguard Worker        return event # The ID
77*cda5da8dSAndroid Build Coastguard Worker
78*cda5da8dSAndroid Build Coastguard Worker    def enter(self, delay, priority, action, argument=(), kwargs=_sentinel):
79*cda5da8dSAndroid Build Coastguard Worker        """A variant that specifies the time as a relative time.
80*cda5da8dSAndroid Build Coastguard Worker
81*cda5da8dSAndroid Build Coastguard Worker        This is actually the more commonly used interface.
82*cda5da8dSAndroid Build Coastguard Worker
83*cda5da8dSAndroid Build Coastguard Worker        """
84*cda5da8dSAndroid Build Coastguard Worker        time = self.timefunc() + delay
85*cda5da8dSAndroid Build Coastguard Worker        return self.enterabs(time, priority, action, argument, kwargs)
86*cda5da8dSAndroid Build Coastguard Worker
87*cda5da8dSAndroid Build Coastguard Worker    def cancel(self, event):
88*cda5da8dSAndroid Build Coastguard Worker        """Remove an event from the queue.
89*cda5da8dSAndroid Build Coastguard Worker
90*cda5da8dSAndroid Build Coastguard Worker        This must be presented the ID as returned by enter().
91*cda5da8dSAndroid Build Coastguard Worker        If the event is not in the queue, this raises ValueError.
92*cda5da8dSAndroid Build Coastguard Worker
93*cda5da8dSAndroid Build Coastguard Worker        """
94*cda5da8dSAndroid Build Coastguard Worker        with self._lock:
95*cda5da8dSAndroid Build Coastguard Worker            self._queue.remove(event)
96*cda5da8dSAndroid Build Coastguard Worker            heapq.heapify(self._queue)
97*cda5da8dSAndroid Build Coastguard Worker
98*cda5da8dSAndroid Build Coastguard Worker    def empty(self):
99*cda5da8dSAndroid Build Coastguard Worker        """Check whether the queue is empty."""
100*cda5da8dSAndroid Build Coastguard Worker        with self._lock:
101*cda5da8dSAndroid Build Coastguard Worker            return not self._queue
102*cda5da8dSAndroid Build Coastguard Worker
103*cda5da8dSAndroid Build Coastguard Worker    def run(self, blocking=True):
104*cda5da8dSAndroid Build Coastguard Worker        """Execute events until the queue is empty.
105*cda5da8dSAndroid Build Coastguard Worker        If blocking is False executes the scheduled events due to
106*cda5da8dSAndroid Build Coastguard Worker        expire soonest (if any) and then return the deadline of the
107*cda5da8dSAndroid Build Coastguard Worker        next scheduled call in the scheduler.
108*cda5da8dSAndroid Build Coastguard Worker
109*cda5da8dSAndroid Build Coastguard Worker        When there is a positive delay until the first event, the
110*cda5da8dSAndroid Build Coastguard Worker        delay function is called and the event is left in the queue;
111*cda5da8dSAndroid Build Coastguard Worker        otherwise, the event is removed from the queue and executed
112*cda5da8dSAndroid Build Coastguard Worker        (its action function is called, passing it the argument).  If
113*cda5da8dSAndroid Build Coastguard Worker        the delay function returns prematurely, it is simply
114*cda5da8dSAndroid Build Coastguard Worker        restarted.
115*cda5da8dSAndroid Build Coastguard Worker
116*cda5da8dSAndroid Build Coastguard Worker        It is legal for both the delay function and the action
117*cda5da8dSAndroid Build Coastguard Worker        function to modify the queue or to raise an exception;
118*cda5da8dSAndroid Build Coastguard Worker        exceptions are not caught but the scheduler's state remains
119*cda5da8dSAndroid Build Coastguard Worker        well-defined so run() may be called again.
120*cda5da8dSAndroid Build Coastguard Worker
121*cda5da8dSAndroid Build Coastguard Worker        A questionable hack is added to allow other threads to run:
122*cda5da8dSAndroid Build Coastguard Worker        just after an event is executed, a delay of 0 is executed, to
123*cda5da8dSAndroid Build Coastguard Worker        avoid monopolizing the CPU when other threads are also
124*cda5da8dSAndroid Build Coastguard Worker        runnable.
125*cda5da8dSAndroid Build Coastguard Worker
126*cda5da8dSAndroid Build Coastguard Worker        """
127*cda5da8dSAndroid Build Coastguard Worker        # localize variable access to minimize overhead
128*cda5da8dSAndroid Build Coastguard Worker        # and to improve thread safety
129*cda5da8dSAndroid Build Coastguard Worker        lock = self._lock
130*cda5da8dSAndroid Build Coastguard Worker        q = self._queue
131*cda5da8dSAndroid Build Coastguard Worker        delayfunc = self.delayfunc
132*cda5da8dSAndroid Build Coastguard Worker        timefunc = self.timefunc
133*cda5da8dSAndroid Build Coastguard Worker        pop = heapq.heappop
134*cda5da8dSAndroid Build Coastguard Worker        while True:
135*cda5da8dSAndroid Build Coastguard Worker            with lock:
136*cda5da8dSAndroid Build Coastguard Worker                if not q:
137*cda5da8dSAndroid Build Coastguard Worker                    break
138*cda5da8dSAndroid Build Coastguard Worker                (time, priority, sequence, action,
139*cda5da8dSAndroid Build Coastguard Worker                 argument, kwargs) = q[0]
140*cda5da8dSAndroid Build Coastguard Worker                now = timefunc()
141*cda5da8dSAndroid Build Coastguard Worker                if time > now:
142*cda5da8dSAndroid Build Coastguard Worker                    delay = True
143*cda5da8dSAndroid Build Coastguard Worker                else:
144*cda5da8dSAndroid Build Coastguard Worker                    delay = False
145*cda5da8dSAndroid Build Coastguard Worker                    pop(q)
146*cda5da8dSAndroid Build Coastguard Worker            if delay:
147*cda5da8dSAndroid Build Coastguard Worker                if not blocking:
148*cda5da8dSAndroid Build Coastguard Worker                    return time - now
149*cda5da8dSAndroid Build Coastguard Worker                delayfunc(time - now)
150*cda5da8dSAndroid Build Coastguard Worker            else:
151*cda5da8dSAndroid Build Coastguard Worker                action(*argument, **kwargs)
152*cda5da8dSAndroid Build Coastguard Worker                delayfunc(0)   # Let other threads run
153*cda5da8dSAndroid Build Coastguard Worker
154*cda5da8dSAndroid Build Coastguard Worker    @property
155*cda5da8dSAndroid Build Coastguard Worker    def queue(self):
156*cda5da8dSAndroid Build Coastguard Worker        """An ordered list of upcoming events.
157*cda5da8dSAndroid Build Coastguard Worker
158*cda5da8dSAndroid Build Coastguard Worker        Events are named tuples with fields for:
159*cda5da8dSAndroid Build Coastguard Worker            time, priority, action, arguments, kwargs
160*cda5da8dSAndroid Build Coastguard Worker
161*cda5da8dSAndroid Build Coastguard Worker        """
162*cda5da8dSAndroid Build Coastguard Worker        # Use heapq to sort the queue rather than using 'sorted(self._queue)'.
163*cda5da8dSAndroid Build Coastguard Worker        # With heapq, two events scheduled at the same time will show in
164*cda5da8dSAndroid Build Coastguard Worker        # the actual order they would be retrieved.
165*cda5da8dSAndroid Build Coastguard Worker        with self._lock:
166*cda5da8dSAndroid Build Coastguard Worker            events = self._queue[:]
167*cda5da8dSAndroid Build Coastguard Worker        return list(map(heapq.heappop, [events]*len(events)))
168