1
- # SPDX-FileCopyrightText: 2019 Damien P. George
1
+ # CIRCUITPY-CHANGE: SPDX
2
+ # SPDX-FileCopyrightText: 2019-2020 Damien P. George
2
3
#
3
4
# SPDX-License-Identifier: MIT
4
- #
5
- # MicroPython uasyncio module
5
+
6
+ # MicroPython asyncio module
6
7
# MIT license; Copyright (c) 2019 Damien P. George
7
8
#
9
+ # # CIRCUITPY-CHANGE: use CircuitPython version
8
10
# This code comes from MicroPython, and has not been run through black or pylint there.
9
11
# Altering these files significantly would make merging difficult, so we will not use
10
12
# pylint or black.
11
13
# pylint: skip-file
12
14
# fmt: off
13
- """
14
- Core
15
- ====
16
- """
17
15
16
+ # CIRCUITPY-CHANGE: use our ticks library
18
17
from adafruit_ticks import ticks_ms as ticks , ticks_diff , ticks_add
19
18
import sys , select
20
19
20
+ # CIRCUITPY-CHANGE: CircuitPython traceback support
21
21
try :
22
22
from traceback import print_exception
23
23
except :
26
26
# Import TaskQueue and Task, preferring built-in C code over Python code
27
27
try :
28
28
from _asyncio import TaskQueue , Task
29
+ # CIRCUITPY-CHANGE: more specific error checking
29
30
except ImportError :
30
31
from .task import TaskQueue , Task
31
32
32
33
################################################################################
33
34
# Exceptions
34
35
35
36
37
+ # CIRCUITPY-CHANGE
36
38
# Depending on the release of CircuitPython these errors may or may not
37
39
# exist in the C implementation of `_asyncio`. However, when they
38
40
# do exist, they must be preferred over the Python code.
@@ -50,6 +52,7 @@ class InvalidStateError(Exception):
50
52
51
53
52
54
class TimeoutError (Exception ):
55
+ # CIRCUITPY-CHANGE: docstring
53
56
"""Raised when waiting for a task longer than the specified timeout."""
54
57
55
58
pass
@@ -62,6 +65,7 @@ class TimeoutError(Exception):
62
65
################################################################################
63
66
# Sleep functions
64
67
68
+
65
69
# "Yield" once, then raise StopIteration
66
70
class SingletonGenerator :
67
71
def __init__ (self ):
@@ -71,11 +75,13 @@ def __init__(self):
71
75
def __iter__ (self ):
72
76
return self
73
77
78
+ # CIRCUITPY-CHANGE: provide await
74
79
def __await__ (self ):
75
80
return self
76
81
77
82
def __next__ (self ):
78
83
if self .state is not None :
84
+ # CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push()
79
85
_task_queue .push_sorted (cur_task , self .state )
80
86
self .state = None
81
87
return None
@@ -87,18 +93,21 @@ def __next__(self):
87
93
# Pause task execution for the given time (integer in milliseconds, uPy extension)
88
94
# Use a SingletonGenerator to do it without allocating on the heap
89
95
def sleep_ms (t , sgen = SingletonGenerator ()):
96
+ # CIRCUITPY-CHANGE: doc
90
97
"""Sleep for *t* milliseconds.
91
98
92
99
This is a coroutine, and a MicroPython extension.
93
100
"""
94
101
102
+ # CIRCUITPY-CHANGE: add debugging hint
95
103
assert sgen .state is None , "Check for a missing `await` in your code"
96
104
sgen .state = ticks_add (ticks (), max (0 , t ))
97
105
return sgen
98
106
99
107
100
108
# Pause task execution for the given time (in seconds)
101
109
def sleep (t ):
110
+ # CIRCUITPY-CHANGE: doc
102
111
"""Sleep for *t* seconds
103
112
104
113
This is a coroutine.
@@ -107,6 +116,7 @@ def sleep(t):
107
116
return sleep_ms (int (t * 1000 ))
108
117
109
118
119
+ # CIRCUITPY-CHANGE: see https://github.com/adafruit/Adafruit_CircuitPython_asyncio/pull/30
110
120
################################################################################
111
121
# "Never schedule" object"
112
122
# Don't re-schedule the object that awaits _never().
@@ -166,12 +176,16 @@ def _dequeue(self, s):
166
176
del self .map [id (s )]
167
177
self .poller .unregister (s )
168
178
179
+ # CIRCUITPY-CHANGE: async
169
180
async def queue_read (self , s ):
170
181
self ._enqueue (s , 0 )
182
+ # CIRCUITPY-CHANGE: do not reschedule
171
183
await _never ()
172
184
185
+ # CIRCUITPY-CHANGE: async
173
186
async def queue_write (self , s ):
174
187
self ._enqueue (s , 1 )
188
+ # CIRCUITPY-CHANGE: do not reschedule
175
189
await _never ()
176
190
177
191
def remove (self , task ):
@@ -193,10 +207,12 @@ def wait_io_event(self, dt):
193
207
# print('poll', s, sm, ev)
194
208
if ev & ~ select .POLLOUT and sm [0 ] is not None :
195
209
# POLLIN or error
210
+ # CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push()
196
211
_task_queue .push_head (sm [0 ])
197
212
sm [0 ] = None
198
213
if ev & ~ select .POLLIN and sm [1 ] is not None :
199
214
# POLLOUT or error
215
+ # CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push()
200
216
_task_queue .push_head (sm [1 ])
201
217
sm [1 ] = None
202
218
if sm [0 ] is None and sm [1 ] is None :
@@ -210,13 +226,15 @@ def wait_io_event(self, dt):
210
226
################################################################################
211
227
# Main run loop
212
228
229
+
213
230
# Ensure the awaitable is a task
214
231
def _promote_to_task (aw ):
215
232
return aw if isinstance (aw , Task ) else create_task (aw )
216
233
217
234
218
235
# Create and schedule a new task from a coroutine
219
236
def create_task (coro ):
237
+ # CIRCUITPY-CHANGE: doc
220
238
"""Create a new task from the given coroutine and schedule it to run.
221
239
222
240
Returns the corresponding `Task` object.
@@ -225,12 +243,14 @@ def create_task(coro):
225
243
if not hasattr (coro , "send" ):
226
244
raise TypeError ("coroutine expected" )
227
245
t = Task (coro , globals ())
246
+ # CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push()
228
247
_task_queue .push_head (t )
229
248
return t
230
249
231
250
232
251
# Keep scheduling tasks until there are none left to schedule
233
252
def run_until_complete (main_task = None ):
253
+ # CIRCUITPY-CHANGE: doc
234
254
"""Run the given *main_task* until it completes."""
235
255
236
256
global cur_task
@@ -247,11 +267,13 @@ def run_until_complete(main_task=None):
247
267
dt = max (0 , ticks_diff (t .ph_key , ticks ()))
248
268
elif not _io_queue .map :
249
269
# No tasks can be woken so finished running
270
+ cur_task = None
250
271
return
251
272
# print('(poll {})'.format(dt), len(_io_queue.map))
252
273
_io_queue .wait_io_event (dt )
253
274
254
275
# Get next task to run and continue it
276
+ # CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .pop()
255
277
t = _task_queue .pop_head ()
256
278
cur_task = t
257
279
try :
@@ -271,6 +293,7 @@ def run_until_complete(main_task=None):
271
293
assert t .data is None
272
294
# This task is done, check if it's the main task and then loop should stop
273
295
if t is main_task :
296
+ cur_task = None
274
297
if isinstance (er , StopIteration ):
275
298
return er .value
276
299
raise er
@@ -288,6 +311,7 @@ def run_until_complete(main_task=None):
288
311
else :
289
312
# Schedule any other tasks waiting on the completion of this task.
290
313
while t .state .peek ():
314
+ # CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push() and .pop()
291
315
_task_queue .push_head (t .state .pop_head ())
292
316
waiting = True
293
317
# "False" indicates that the task is complete and has been await'ed on.
@@ -296,19 +320,26 @@ def run_until_complete(main_task=None):
296
320
# An exception ended this detached task, so queue it for later
297
321
# execution to handle the uncaught exception if no other task retrieves
298
322
# the exception in the meantime (this is handled by Task.throw).
323
+ # CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push()
299
324
_task_queue .push_head (t )
300
325
# Save return value of coro to pass up to caller.
301
326
t .data = er
302
327
elif t .state is None :
303
328
# Task is already finished and nothing await'ed on the task,
304
329
# so call the exception handler.
330
+
331
+ # Save exception raised by the coro for later use.
332
+ t .data = exc
333
+
334
+ # Create exception context and call the exception handler.
305
335
_exc_context ["exception" ] = exc
306
336
_exc_context ["future" ] = t
307
337
Loop .call_exception_handler (_exc_context )
308
338
309
339
310
340
# Create a new task from a coroutine and run it until it finishes
311
341
def run (coro ):
342
+ # CIRCUITPY-CHANGE: doc
312
343
"""Create a new task from the given coroutine and run it until it completes.
313
344
314
345
Returns the value returned by *coro*.
@@ -325,20 +356,24 @@ async def _stopper():
325
356
pass
326
357
327
358
359
+ cur_task = None
328
360
_stop_task = None
329
361
330
362
331
363
class Loop :
364
+ # CIRCUITPY-CHANGE: doc
332
365
"""Class representing the event loop"""
333
366
334
367
_exc_handler = None
335
368
336
369
def create_task (coro ):
370
+ # CIRCUITPY-CHANGE: doc
337
371
"""Create a task from the given *coro* and return the new `Task` object."""
338
372
339
373
return create_task (coro )
340
374
341
375
def run_forever ():
376
+ # CIRCUITPY-CHANGE: doc
342
377
"""Run the event loop until `Loop.stop()` is called."""
343
378
344
379
global _stop_task
@@ -347,47 +382,56 @@ def run_forever():
347
382
# TODO should keep running until .stop() is called, even if there're no tasks left
348
383
349
384
def run_until_complete (aw ):
385
+ # CIRCUITPY-CHANGE: doc
350
386
"""Run the given *awaitable* until it completes. If *awaitable* is not a task then
351
387
it will be promoted to one.
352
388
"""
353
389
354
390
return run_until_complete (_promote_to_task (aw ))
355
391
356
392
def stop ():
393
+ # CIRCUITPY-CHANGE: doc
357
394
"""Stop the event loop"""
358
395
359
396
global _stop_task
360
397
if _stop_task is not None :
398
+ # CIRCUITPY-CHANGE: when 8.x support is discontinued, change to .push()
361
399
_task_queue .push_head (_stop_task )
362
400
# If stop() is called again, do nothing
363
401
_stop_task = None
364
402
365
403
def close ():
404
+ # CIRCUITPY-CHANGE: doc
366
405
"""Close the event loop."""
367
406
368
407
pass
369
408
370
409
def set_exception_handler (handler ):
410
+ # CIRCUITPY-CHANGE: doc
371
411
"""Set the exception handler to call when a Task raises an exception that is not
372
412
caught. The *handler* should accept two arguments: ``(loop, context)``
373
413
"""
374
414
375
415
Loop ._exc_handler = handler
376
416
377
417
def get_exception_handler ():
418
+ # CIRCUITPY-CHANGE: doc
378
419
"""Get the current exception handler. Returns the handler, or ``None`` if no
379
420
custom handler is set.
380
421
"""
381
422
382
423
return Loop ._exc_handler
383
424
384
425
def default_exception_handler (loop , context ):
426
+ # CIRCUITPY-CHANGE: doc
385
427
"""The default exception handler that is called."""
386
428
429
+ # CIRCUITPY_CHANGE: use CircuitPython traceback printing
387
430
exc = context ["exception" ]
388
431
print_exception (None , exc , exc .__traceback__ )
389
432
390
433
def call_exception_handler (context ):
434
+ # CIRCUITPY-CHANGE: doc
391
435
"""Call the current exception handler. The argument *context* is passed through
392
436
and is a dictionary containing keys:
393
437
``'message'``, ``'exception'``, ``'future'``
@@ -397,29 +441,36 @@ def call_exception_handler(context):
397
441
398
442
# The runq_len and waitq_len arguments are for legacy uasyncio compatibility
399
443
def get_event_loop (runq_len = 0 , waitq_len = 0 ):
400
- """Return the event loop used to schedule and run tasks. See `Loop`."""
444
+ # CIRCUITPY-CHANGE: doc
445
+ """Return the event loop used to schedule and run tasks. See `Loop`. Deprecated and will be removed later."""
401
446
402
447
return Loop
403
448
404
449
405
450
def current_task ():
451
+ # CIRCUITPY-CHANGE: doc
406
452
"""Return the `Task` object associated with the currently running task."""
407
453
454
+ if cur_task is None :
455
+ raise RuntimeError ("no running event loop" )
408
456
return cur_task
409
457
410
458
411
459
def new_event_loop ():
460
+ # CIRCUITPY-CHANGE: doc
412
461
"""Reset the event loop and return it.
413
462
414
463
**NOTE**: Since MicroPython only has a single event loop, this function just resets
415
464
the loop's state, it does not create a new one
416
465
"""
417
466
467
+ # CIRCUITPY-CHANGE: add _exc_context, cur_task
418
468
global _task_queue , _io_queue , _exc_context , cur_task
419
469
# TaskQueue of Task instances
420
470
_task_queue = TaskQueue ()
421
471
# Task queue and poller for stream IO
422
472
_io_queue = IOQueue ()
473
+ # CIRCUITPY-CHANGE: exception info
423
474
cur_task = None
424
475
_exc_context ['exception' ] = None
425
476
_exc_context ['future' ] = None
0 commit comments