28
28
29
29
import utime as time
30
30
import utimeq
31
+ import ucollections
31
32
from uasyncio import *
32
33
33
34
class PriorityEventLoop (PollEventLoop ):
34
- def __init__ (self , len = 42 , lpqlen = 42 ):
35
- super ().__init__ (len )
35
+ def __init__ (self , runq_len = 16 , waitq_len = 16 , lpqlen = 42 ):
36
+ super ().__init__ (runq_len , waitq_len )
36
37
self ._max_overdue_ms = 0
37
38
self .lpq = utimeq .utimeq (lpqlen )
38
- self .hp_tasks = None
39
- self .create_task (self .lp_monitor ())
40
-
41
- # Monitor low priority tasks. If one can be scheduled, remove from LP queue
42
- # and queue it for scheduling.
43
- # If a normal task is ready we only queue an LP one which is due by more
44
- # than the max_overdue_ms threshold.
45
- # If no normal task is ready we queue the most overdue LP task.
46
- # Readiness is determined by whether it is actually due. An option would be
47
- # to check if it's due in less than N ms. This would reduce competition at
48
- # the cost of the application having to consider tight loops which pend for
49
- # < N ms.
50
- async def lp_monitor (self ):
51
- this_task = [0 , 0 , 0 ]
52
- while True :
53
- if self .lpq :
54
- tnow = self .time ()
55
- t = self .lpq .peektime ()
56
- tim = time .ticks_diff (t , tnow )
57
- to_run = self ._max_overdue_ms > 0 and tim < - self ._max_overdue_ms
58
- if not to_run : # No overdue LP task. Are any normal tasks due?
59
- can_run = True # If q is empty can run an LP task
60
- if self .q :
61
- t = self .q .peektime ()
62
- can_run = time .ticks_diff (t , tnow ) > 0 # No normal task is ready -
63
- to_run = can_run and tim <= 0 # run if so and an LP one is ready
64
- if to_run :
65
- self .lpq .pop (this_task )
66
- self .q .push (* this_task )
67
- yield
39
+ self .hp_tasks = []
40
+
41
+ # Schedule a single low priority task if one is ready or overdue.
42
+ # The most overdue task is scheduled even if normal tasks are pending.
43
+ # The most due task is scheduled only if no normal tasks are pending.
44
+ def schedule_lp_task (self , cur_task , tnow ):
45
+ t = self .lpq .peektime ()
46
+ tim = time .ticks_diff (t , tnow )
47
+ to_run = self ._max_overdue_ms > 0 and tim < - self ._max_overdue_ms
48
+ if not to_run : # No overdue LP task.
49
+ if len (self .runq ):
50
+ return False
51
+ to_run = tim <= 0 # True if LP task is due
52
+ if to_run and self .waitq : # Set False if a normal tasks is due.
53
+ t = self .waitq .peektime ()
54
+ to_run = time .ticks_diff (t , tnow ) > 0 # No normal task is ready
55
+ if to_run :
56
+ self .lpq .pop (cur_task )
57
+ self .call_soon (cur_task [1 ], * cur_task [2 ])
58
+ return True
59
+ return False
68
60
69
61
def max_overdue_ms (self , t = None ):
70
62
if t is not None :
@@ -73,17 +65,16 @@ def max_overdue_ms(self, t=None):
73
65
74
66
# Low priority versions of call_later() call_later_ms() and call_at_()
75
67
def call_after_ms (self , delay , callback , * args ):
76
- self .call_at_lp_ (time .ticks_add (self .time (), delay ), callback , args )
68
+ self .call_at_lp_ (time .ticks_add (self .time (), delay ), callback , * args )
69
+
77
70
78
71
def call_after (self , delay , callback , * args ):
79
- self .call_at_lp_ (time .ticks_add (self .time (), int (delay * 1000 )), callback , args )
72
+ self .call_at_lp_ (time .ticks_add (self .time (), int (delay * 1000 )), callback , * args )
80
73
81
- def call_at_lp_ (self , time , callback , args = () ):
74
+ def call_at_lp_ (self , time , callback , * args ):
82
75
self .lpq .push (time , callback , args )
83
76
84
- def _schedule_hp (self , func , callback , args = ()):
85
- if self .hp_tasks is None :
86
- self .hp_tasks = []
77
+ def _schedule_hp (self , func , callback , * args ):
87
78
# If there's an empty slot, assign without allocation
88
79
for entry in self .hp_tasks : # O(N) search - but N is typically 1 or 2...
89
80
if not entry [0 ]:
@@ -97,80 +88,79 @@ def _schedule_hp(self, func, callback, args=()):
97
88
def run_forever (self ):
98
89
cur_task = [0 , 0 , 0 ]
99
90
while True :
100
- if self .q :
101
- # wait() may finish prematurely due to I/O completion,
102
- # and schedule new, earlier than before tasks to run.
103
- while 1 :
104
- # Check list of high priority tasks
105
- if self .hp_tasks is not None :
106
- hp_found = False
107
- for entry in self .hp_tasks :
108
- if entry [0 ] and entry [0 ]():
109
- hp_found = True
110
- entry [0 ] = 0
111
- cur_task [0 ] = 0
112
- cur_task [1 ] = entry [1 ] # ??? quick non-allocating copy
113
- cur_task [2 ] = entry [2 ]
114
- break
115
- if hp_found :
116
- break
117
-
118
- # Schedule any due normal task
119
- t = self .q .peektime ()
120
- tnow = self .time ()
91
+ tnow = self .time ()
92
+ # Schedule a LP task if no normal task is ready
93
+ l = len (self .lpq )
94
+ if (l and not self .schedule_lp_task (cur_task , tnow )) or l == 0 :
95
+ # Expire entries in waitq and move them to runq
96
+ while self .waitq :
97
+ t = self .waitq .peektime ()
121
98
delay = time .ticks_diff (t , tnow )
122
- if delay <= 0 :
123
- # Always call wait(), to give a chance to I/O scheduling
124
- self .wait (0 )
125
- self .q .pop (cur_task )
99
+ if delay > 0 :
126
100
break
101
+ self .waitq .pop (cur_task )
102
+ if __debug__ and DEBUG :
103
+ log .debug ("Moving from waitq to runq: %s" , cur_task [1 ])
104
+ self .call_soon (cur_task [1 ], * cur_task [2 ])
105
+
106
+ # Process runq
107
+ l = len (self .runq )
108
+ if __debug__ and DEBUG :
109
+ log .debug ("Entries in runq: %d" , l )
110
+ while l :
111
+ # Check list of high priority tasks
112
+ cb = None
113
+ for entry in self .hp_tasks :
114
+ if entry [0 ] and entry [0 ](): # Ready to run
115
+ entry [0 ] = 0
116
+ cb = entry [1 ]
117
+ args = entry [2 ]
118
+ break
119
+
120
+ if cb is None :
121
+ cb = self .runq .popleft ()
122
+ l -= 1
123
+ args = ()
124
+ if not isinstance (cb , type_gen ):
125
+ args = self .runq .popleft ()
126
+ l -= 1
127
+ if __debug__ and DEBUG :
128
+ log .info ("Next callback to run: %s" , (cb , args ))
129
+ cb (* args )
130
+ continue
127
131
128
- self .wait (delay ) # Handled in superclass
129
- t = cur_task [0 ]
130
- cb = cur_task [1 ]
131
- args = cur_task [2 ]
132
132
if __debug__ and DEBUG :
133
- log .debug ("Next coroutine to run: %s" , (t , cb , args ))
134
- # __main__.mem_info()
133
+ log .info ("Next coroutine to run: %s" , (cb , args ))
135
134
self .cur_task = cb
136
- else :
137
- self .wait (- 1 )
138
- # Assuming IO completion scheduled some tasks
139
- continue
140
- if callable (cb ):
141
- cb (* args )
142
- else :
143
135
delay = 0
144
136
func = None
145
- priority = True
137
+ low_priority = False # Assume normal priority
146
138
try :
147
- if __debug__ and DEBUG :
148
- log .debug ("Coroutine %s send args: %s" , cb , args )
149
- if args == ():
150
- ret = next (cb ) # See notes at end of code
139
+ if args is ():
140
+ ret = next (cb )
151
141
else :
152
142
ret = cb .send (* args )
153
143
if __debug__ and DEBUG :
154
- log .debug ("Coroutine %s yield result: %s" , cb , ret )
144
+ log .info ("Coroutine %s yield result: %s" , cb , ret )
155
145
if isinstance (ret , SysCall1 ):
156
146
arg = ret .arg
157
- if isinstance (ret , After ):
158
- delay = int (arg * 1000 )
159
- priority = False
160
- elif isinstance (ret , AfterMs ):
161
- delay = int (arg )
162
- priority = False
163
- elif isinstance (ret , When ):
164
- if callable (arg ):
165
- func = arg
166
- else :
167
- assert False , "Argument to 'when' must be a function or method."
168
- elif isinstance (ret , SleepMs ):
147
+ if isinstance (ret , SleepMs ):
169
148
delay = arg
149
+ if isinstance (ret , AfterMs ):
150
+ low_priority = True
151
+ if isinstance (ret , After ):
152
+ delay = int (delay * 1000 )
153
+ elif isinstance (ret , When ):
154
+ if callable (arg ):
155
+ func = arg
156
+ else :
157
+ assert False , "Argument to 'when' must be a function or method."
170
158
elif isinstance (ret , IORead ):
159
+ cb .pend_throw (False )
171
160
self .add_reader (arg , cb )
172
161
continue
173
162
elif isinstance (ret , IOWrite ):
163
+ cb .pend_throw (False )
174
164
self .add_writer (arg , cb )
175
165
continue
176
166
elif isinstance (ret , IOReadDone ):
@@ -202,18 +192,37 @@ def run_forever(self):
202
192
if __debug__ and DEBUG :
203
193
log .debug ("Coroutine cancelled: %s" , cb )
204
194
continue
205
-
206
195
if func is not None :
207
196
self ._schedule_hp (func , cb )
197
+ continue
198
+ # Currently all syscalls don't return anything, so we don't
199
+ # need to feed anything to the next invocation of coroutine.
200
+ # If that changes, need to pass that value below.
201
+ if low_priority :
202
+ self .call_after_ms (delay , cb )
203
+ elif delay :
204
+ self .call_later_ms (delay , cb )
208
205
else :
209
- # Currently all syscalls don't return anything, so we don't
210
- # need to feed anything to the next invocation of coroutine.
211
- # If that changes, need to pass that value below.
212
- if priority :
213
- self .call_later_ms (delay , cb )
214
- else :
215
- self .call_after_ms (delay , cb )
206
+ self .call_soon (cb )
216
207
208
+ # Wait until next waitq task or I/O availability
209
+ delay = 0
210
+ if not self .runq :
211
+ delay = - 1
212
+ tnow = self .time ()
213
+ if self .waitq :
214
+ t = self .waitq .peektime ()
215
+ delay = time .ticks_diff (t , tnow )
216
+ if delay < 0 :
217
+ delay = 0
218
+ if self .lpq :
219
+ t = self .lpq .peektime ()
220
+ lpdelay = time .ticks_diff (t , tnow )
221
+ if lpdelay < 0 :
222
+ lpdelay = 0
223
+ if lpdelay < delay or delay < 0 :
224
+ delay = lpdelay
225
+ self .wait (delay )
217
226
218
227
# Low priority
219
228
class AfterMs (SleepMs ):
@@ -232,7 +241,7 @@ class When(SleepMs):
232
241
233
242
import uasyncio .core
234
243
uasyncio .core ._event_loop_class = PriorityEventLoop
235
- def get_event_loop (len = 42 , lpqlen = 42 ):
244
+ def get_event_loop (runq_len = 16 , waitq_len = 16 , lpqlen = 16 ):
236
245
if uasyncio .core ._event_loop is None : # Add a q entry for lp_monitor()
237
- uasyncio .core ._event_loop = uasyncio .core ._event_loop_class (len + 1 , lpqlen )
246
+ uasyncio .core ._event_loop = uasyncio .core ._event_loop_class (runq_len , waitq_len , lpqlen )
238
247
return uasyncio .core ._event_loop
0 commit comments