@@ -33,8 +33,10 @@ class TimeoutError(CancelledError):
33
33
34
34
class EventLoop :
35
35
36
- def __init__ (self , runq_len = 16 , waitq_len = 16 , ioq_len = 0 ):
36
+ def __init__ (self , runq_len = 16 , waitq_len = 16 , ioq_len = 0 , lp_len = 0 ):
37
37
self .runq = ucollections .deque ((), runq_len , True )
38
+ self ._max_overdue_ms = 0
39
+ self .lpq = utimeq .utimeq (lp_len ) if lp_len else None
38
40
self .ioq_len = ioq_len
39
41
if ioq_len :
40
42
self .ioq = ucollections .deque ((), ioq_len , True )
@@ -64,6 +66,24 @@ def _call_now(self, callback, *args): # For stream I/O only
64
66
if not isinstance (callback , type_gen ):
65
67
self .ioq .append (args )
66
68
69
+ def max_overdue_ms (self , t = None ):
70
+ if t is not None :
71
+ self ._max_overdue_ms = int (t )
72
+ return self ._max_overdue_ms
73
+
74
+ # Low priority versions of call_later() call_later_ms() and call_at_()
75
+ def call_after_ms (self , delay , callback , * args ):
76
+ self .call_at_lp_ (time .ticks_add (self .time (), delay ), callback , * args )
77
+
78
+ def call_after (self , delay , callback , * args ):
79
+ self .call_at_lp_ (time .ticks_add (self .time (), int (delay * 1000 )), callback , * args )
80
+
81
+ def call_at_lp_ (self , time , callback , * args ):
82
+ if self .lpq is not None :
83
+ self .lpq .push (time , callback , args )
84
+ else :
85
+ raise OSError ('No low priority queue exists.' )
86
+
67
87
def call_soon (self , callback , * args ):
68
88
if __debug__ and DEBUG :
69
89
log .debug ("Scheduling in runq: %s" , (callback , args ))
@@ -96,6 +116,22 @@ def run_forever(self):
96
116
while True :
97
117
# Expire entries in waitq and move them to runq
98
118
tnow = self .time ()
119
+ if self .lpq :
120
+ # Schedule a LP task if overdue or if no normal task is ready
121
+ to_run = False # Assume no LP task is to run
122
+ t = self .lpq .peektime ()
123
+ tim = time .ticks_diff (t , tnow )
124
+ to_run = self ._max_overdue_ms > 0 and tim < - self ._max_overdue_ms
125
+ if not (to_run or self .runq ): # No overdue LP task or task on runq
126
+ # zero delay tasks go straight to runq. So don't schedule LP if runq
127
+ to_run = tim <= 0 # True if LP task is due
128
+ if to_run and self .waitq : # Set False if normal tasks due.
129
+ t = self .waitq .peektime ()
130
+ to_run = time .ticks_diff (t , tnow ) > 0 # No normal task is ready
131
+ if to_run :
132
+ self .lpq .pop (cur_task )
133
+ self .call_soon (cur_task [1 ], * cur_task [2 ])
134
+
99
135
while self .waitq :
100
136
t = self .waitq .peektime ()
101
137
delay = time .ticks_diff (t , tnow )
@@ -139,6 +175,7 @@ def run_forever(self):
139
175
log .info ("Next coroutine to run: %s" , (cb , args ))
140
176
self .cur_task = cb # Stored in a bound variable for TimeoutObj
141
177
delay = 0
178
+ low_priority = False # Assume normal priority
142
179
try :
143
180
if args is ():
144
181
ret = next (cb ) # Schedule the coro, get result
@@ -150,6 +187,10 @@ def run_forever(self):
150
187
arg = ret .arg
151
188
if isinstance (ret , SleepMs ):
152
189
delay = arg
190
+ if isinstance (ret , AfterMs ):
191
+ low_priority = True
192
+ if isinstance (ret , After ):
193
+ delay = int (delay * 1000 )
153
194
elif isinstance (ret , IORead ): # coro was a StreamReader read method
154
195
cb .pend_throw (False ) # Why? I think this is for debugging. If it is scheduled other than by wait
155
196
# (which does pend_throw(None) an exception (exception doesn't inherit from Exception) is thrown
@@ -194,7 +235,9 @@ def run_forever(self):
194
235
# Currently all syscalls don't return anything, so we don't
195
236
# need to feed anything to the next invocation of coroutine.
196
237
# If that changes, need to pass that value below.
197
- if delay :
238
+ if low_priority :
239
+ self .call_after_ms (delay , cb ) # Put on lpq
240
+ elif delay :
198
241
self .call_later_ms (delay , cb )
199
242
else :
200
243
self .call_soon (cb )
@@ -209,6 +252,13 @@ def run_forever(self):
209
252
delay = time .ticks_diff (t , tnow )
210
253
if delay < 0 :
211
254
delay = 0
255
+ if self .lpq :
256
+ t = self .lpq .peektime ()
257
+ lpdelay = time .ticks_diff (t , tnow )
258
+ if lpdelay < 0 :
259
+ lpdelay = 0
260
+ if lpdelay < delay or delay < 0 :
261
+ delay = lpdelay # waitq is empty or lp task is more current
212
262
self .wait (delay )
213
263
214
264
def run_until_complete (self , coro ):
@@ -258,10 +308,10 @@ class IOWriteDone(SysCall1):
258
308
259
309
_event_loop = None
260
310
_event_loop_class = EventLoop
261
- def get_event_loop (runq_len = 16 , waitq_len = 16 , ioq_len = 0 ):
311
+ def get_event_loop (runq_len = 16 , waitq_len = 16 , ioq_len = 0 , lp_len = 0 ):
262
312
global _event_loop
263
313
if _event_loop is None :
264
- _event_loop = _event_loop_class (runq_len , waitq_len , ioq_len )
314
+ _event_loop = _event_loop_class (runq_len , waitq_len , ioq_len , lp_len )
265
315
return _event_loop
266
316
267
317
# Allow user classes to determine prior event loop instantiation.
@@ -342,6 +392,16 @@ def wait_for(coro, timeout):
342
392
def coroutine (f ):
343
393
return f
344
394
395
+ # Low priority
396
+ class AfterMs (SleepMs ):
397
+ pass
398
+
399
+ class After (AfterMs ):
400
+ pass
401
+
402
+ after_ms = AfterMs ()
403
+ after = After ()
404
+
345
405
#
346
406
# The functions below are deprecated in uasyncio, and provided only
347
407
# for compatibility with CPython asyncio
0 commit comments