2018-07-03 14:20:58 +00:00
"""
2017-01-02 14:45:56 +00:00
Implements an event loop with cooperative multitasking and async I / O . Tasks in
the form of python coroutines ( either plain generators or ` async ` functions ) are
stepped through until completion , and can get asynchronously blocked by
` yield ` ing or ` await ` ing a syscall .
2016-04-28 05:43:37 +00:00
2019-08-20 14:20:02 +00:00
See ` schedule ` , ` run ` , and syscalls ` sleep ` , ` wait ` , ` signal ` and ` race ` .
2018-07-03 14:20:58 +00:00
"""
2016-04-29 19:48:59 +00:00
2017-01-02 14:45:56 +00:00
import utime
import utimeq
2021-12-08 09:10:58 +00:00
from typing import TYPE_CHECKING
2018-07-03 14:20:26 +00:00
from trezor import io , log
2016-05-12 14:18:40 +00:00
2021-12-08 09:10:58 +00:00
if TYPE_CHECKING :
2019-07-03 13:07:04 +00:00
from typing import (
Any ,
Awaitable ,
Callable ,
Coroutine ,
Generator ,
)
2021-12-08 09:10:58 +00:00
Task = Coroutine | Generator
AwaitableTask = Task | Awaitable
2019-07-03 13:07:04 +00:00
Finalizer = Callable [ [ Task , Any ] , None ]
# function to call after every task step
2021-03-18 09:48:50 +00:00
after_step_hook : Callable [ [ ] , None ] | None = None
2019-07-03 13:07:04 +00:00
# tasks scheduled for execution in the future
_queue = utimeq . utimeq ( 64 )
# tasks paused on I/O
2021-03-18 09:48:50 +00:00
_paused : dict [ int , set [ Task ] ] = { }
2019-07-03 13:07:04 +00:00
# functions to execute after a task is finished
2021-03-18 09:48:50 +00:00
_finalizers : dict [ int , Finalizer ] = { }
2017-01-02 14:45:56 +00:00
2020-05-22 08:28:29 +00:00
# reference to the task that is currently executing
2021-03-18 09:48:50 +00:00
this_task : Task | None = None
2020-05-22 08:28:29 +00:00
2016-08-05 10:32:37 +00:00
2020-05-22 08:28:29 +00:00
class TaskClosed ( Exception ) :
pass
TASK_CLOSED = TaskClosed ( )
2019-07-03 13:07:04 +00:00
def schedule (
2020-05-18 12:59:37 +00:00
task : Task ,
value : Any = None ,
2021-03-18 09:48:50 +00:00
deadline : int | None = None ,
finalizer : Finalizer | None = None ,
2020-05-18 12:59:37 +00:00
reschedule : bool = False ,
2019-07-03 13:07:04 +00:00
) - > None :
2018-07-03 14:20:58 +00:00
"""
2017-01-02 14:45:56 +00:00
Schedule task to be executed with ` value ` on given ` deadline ` ( in
2022-03-04 11:58:24 +00:00
milliseconds ) . Does not start the event loop itself , see ` run ` .
2019-08-20 14:20:02 +00:00
Usually done in very low - level cases , see ` race ` for more user - friendly
and correct concept .
2020-05-18 12:59:37 +00:00
If ` reschedule ` is set , updates an existing entry .
2018-07-03 14:20:58 +00:00
"""
2020-05-18 12:59:37 +00:00
if reschedule :
_queue . discard ( task )
2016-11-12 11:22:05 +00:00
if deadline is None :
2020-06-02 09:02:06 +00:00
deadline = utime . ticks_ms ( )
2019-05-13 13:06:34 +00:00
if finalizer is not None :
_finalizers [ id ( task ) ] = finalizer
2017-09-16 13:00:31 +00:00
_queue . push ( deadline , task , value )
2016-08-05 10:32:37 +00:00
2019-07-03 13:07:04 +00:00
def pause ( task : Task , iface : int ) - > None :
2019-08-20 14:20:02 +00:00
"""
Block task on given message interface . Task is resumed when the interface
is activated . It is most probably wrong to call ` pause ` from user code ,
see the ` wait ` syscall for the correct concept .
"""
2017-10-02 14:18:27 +00:00
tasks = _paused . get ( iface , None )
2017-01-02 14:45:56 +00:00
if tasks is None :
2018-01-22 12:07:12 +00:00
tasks = _paused [ iface ] = set ( )
tasks . add ( task )
2016-05-16 15:10:12 +00:00
2019-07-03 13:07:04 +00:00
def finalize ( task : Task , value : Any ) - > None :
2019-08-20 14:20:02 +00:00
""" Call and remove any finalization callbacks registered for given task. """
2019-05-13 13:06:34 +00:00
fn = _finalizers . pop ( id ( task ) , None )
if fn is not None :
fn ( task , value )
2019-07-03 13:07:04 +00:00
def close ( task : Task ) - > None :
2019-08-20 14:20:02 +00:00
"""
2019-08-21 13:12:21 +00:00
Unschedule and unblock a task , close it so it can release all resources , and
2019-08-20 14:20:02 +00:00
call its finalizer .
"""
2021-10-13 13:45:53 +00:00
for iface in _paused : # pylint: disable=consider-using-dict-items
2018-01-22 12:07:12 +00:00
_paused [ iface ] . discard ( task )
_queue . discard ( task )
2017-10-10 13:32:43 +00:00
task . close ( )
2019-05-13 13:06:34 +00:00
finalize ( task , GeneratorExit ( ) )
2017-10-10 13:32:43 +00:00
2019-07-03 13:07:04 +00:00
def run ( ) - > None :
2018-07-03 14:20:58 +00:00
"""
2017-01-02 14:45:56 +00:00
Loop forever , stepping through scheduled tasks and awaiting I / O events
2019-08-21 13:12:21 +00:00
in between . Use ` schedule ` first to add a coroutine to the task queue .
2017-01-02 14:45:56 +00:00
Tasks yield back to the scheduler on any I / O , usually by calling ` await ` on
a ` Syscall ` .
2018-07-03 14:20:58 +00:00
"""
2017-01-02 14:45:56 +00:00
task_entry = [ 0 , 0 , 0 ] # deadline, task, value
2017-06-26 14:03:20 +00:00
msg_entry = [ 0 , 0 ] # iface | flags, value
2017-10-24 11:59:09 +00:00
while _queue or _paused :
2020-07-10 09:54:33 +00:00
# compute the maximum amount of time we can wait for a message
if _queue :
delay = utime . ticks_diff ( _queue . peektime ( ) , utime . ticks_ms ( ) )
else :
delay = 1000 # wait for 1 sec maximum if queue is empty
2017-09-16 13:00:31 +00:00
if io . poll ( _paused , msg_entry , delay ) :
2017-01-02 14:45:56 +00:00
# message received, run tasks paused on the interface
2017-10-02 14:18:27 +00:00
msg_tasks = _paused . pop ( msg_entry [ 0 ] , ( ) )
for task in msg_tasks :
_step ( task , msg_entry [ 1 ] )
2017-01-02 14:45:56 +00:00
else :
# timeout occurred, run the first scheduled task
2017-09-16 13:00:31 +00:00
if _queue :
_queue . pop ( task_entry )
2022-03-01 12:55:58 +00:00
_step ( task_entry [ 1 ] , task_entry [ 2 ] ) # type: ignore [Argument of type "int" cannot be assigned to parameter "task" of type "Task" in function "_step"]
2019-07-03 13:07:04 +00:00
# error: Argument 1 to "_step" has incompatible type "int"; expected "Coroutine[Any, Any, Any]"
# rationale: We use untyped lists here, because that is what the C API supports.
2017-01-02 14:45:56 +00:00
2019-07-29 14:34:30 +00:00
def clear ( ) - > None :
2019-07-11 14:52:25 +00:00
""" Clear all queue state. Any scheduled or paused tasks will be forgotten. """
_ = [ 0 , 0 , 0 ]
while _queue :
_queue . pop ( _ )
_paused . clear ( )
_finalizers . clear ( )
2019-07-03 13:07:04 +00:00
def _step ( task : Task , value : Any ) - > None :
2019-08-20 14:20:02 +00:00
"""
2019-08-21 13:12:21 +00:00
Step through the task by sending value to it . This can result in either :
2019-08-20 14:20:02 +00:00
1. The task raises an exception :
a ) StopIteration
2019-08-21 13:12:21 +00:00
- The Task is completed and we call finalize ( ) to finish it .
2019-08-20 14:20:02 +00:00
b ) Exception
2019-08-21 13:12:21 +00:00
- An error occurred . We still need to call finalize ( ) .
2019-08-20 14:20:02 +00:00
2. Task does not raise exception and returns either :
a ) Syscall
2019-08-21 13:12:21 +00:00
- Syscall . handle ( ) is called .
2019-08-20 14:20:02 +00:00
b ) None
- The Task is simply scheduled to continue .
c ) Something else
2019-08-21 13:12:21 +00:00
- This should not happen - error .
2019-08-20 14:20:02 +00:00
"""
2020-05-22 08:28:29 +00:00
global this_task
this_task = task
2016-08-05 10:32:37 +00:00
try :
2019-05-13 13:06:34 +00:00
if isinstance ( value , BaseException ) :
2021-02-03 12:43:06 +00:00
result = task . throw ( value )
2016-08-05 10:32:37 +00:00
else :
result = task . send ( value )
2019-08-20 14:20:02 +00:00
except StopIteration as e :
2017-08-21 11:22:35 +00:00
if __debug__ :
2018-07-03 14:20:58 +00:00
log . debug ( __name__ , " finish: %s " , task )
2019-05-13 13:06:34 +00:00
finalize ( task , e . value )
2016-08-05 10:32:37 +00:00
except Exception as e :
2017-08-21 11:22:35 +00:00
if __debug__ :
log . exception ( __name__ , e )
2019-05-13 13:06:34 +00:00
finalize ( task , e )
2016-06-17 18:52:36 +00:00
else :
2016-08-05 10:32:37 +00:00
if isinstance ( result , Syscall ) :
result . handle ( task )
elif result is None :
2017-09-16 13:00:31 +00:00
schedule ( task )
2016-08-05 10:32:37 +00:00
else :
2017-08-21 11:22:35 +00:00
if __debug__ :
2018-07-03 14:20:58 +00:00
log . error ( __name__ , " unknown syscall: %s " , result )
2017-01-02 14:45:56 +00:00
if after_step_hook :
after_step_hook ( )
2016-05-12 14:18:40 +00:00
2017-01-02 14:45:56 +00:00
class Syscall :
2018-07-03 14:20:58 +00:00
"""
2017-01-02 14:45:56 +00:00
When tasks want to perform any I / O , or do any sort of communication with the
scheduler , they do so through instances of a class derived from ` Syscall ` .
2018-07-03 14:20:58 +00:00
"""
2016-08-05 10:32:37 +00:00
2021-12-08 09:10:58 +00:00
def __iter__ ( self ) - > Generator :
2017-01-02 14:45:56 +00:00
# support `yield from` or `await` on syscalls
2016-08-05 10:32:37 +00:00
return ( yield self )
2016-05-30 14:15:34 +00:00
2021-12-08 09:10:58 +00:00
if TYPE_CHECKING :
def __await__ ( self ) - > Generator :
return self . __iter__ ( )
2019-07-03 13:07:04 +00:00
def handle ( self , task : Task ) - > None :
pass
2016-05-30 14:15:34 +00:00
2017-09-16 13:00:31 +00:00
class sleep ( Syscall ) :
2020-06-02 09:02:06 +00:00
""" Pause current task and resume it after given delay.
Result value is the calculated deadline .
2017-01-02 14:45:56 +00:00
Example :
2017-09-16 13:00:31 +00:00
2020-06-02 09:02:06 +00:00
>> > planned = await loop . sleep ( 1000 ) # sleep for 1s
2021-09-27 10:13:51 +00:00
>> > print ( f " missed by { utime . ticks_diff ( utime . ticks_ms ( ) , planned ) } ms " )
2018-07-03 14:20:58 +00:00
"""
2016-05-12 14:18:40 +00:00
2020-06-02 09:02:06 +00:00
def __init__ ( self , delay_ms : int ) - > None :
self . delay_ms = delay_ms
2016-05-12 14:18:40 +00:00
2019-07-03 13:07:04 +00:00
def handle ( self , task : Task ) - > None :
2020-06-02 09:02:06 +00:00
deadline = utime . ticks_add ( utime . ticks_ms ( ) , self . delay_ms )
2017-09-16 13:00:31 +00:00
schedule ( task , deadline , deadline )
2016-05-30 14:15:34 +00:00
2016-05-12 14:18:40 +00:00
2018-04-13 12:57:04 +00:00
class wait ( Syscall ) :
2018-07-03 14:20:58 +00:00
"""
2017-01-02 14:45:56 +00:00
Pause current task , and resume only after a message on ` msg_iface ` is
received . Messages are received either from an USB interface , or the
2019-08-20 14:20:02 +00:00
touch display . Result value is a tuple of message values .
2016-05-12 14:18:40 +00:00
2017-01-02 14:45:56 +00:00
Example :
2017-09-16 13:00:31 +00:00
2018-04-13 12:57:04 +00:00
>> > hid_report , = await loop . wait ( 0xABCD ) # await USB HID report
>> > event , x , y = await loop . wait ( io . TOUCH ) # await touch event
2018-07-03 14:20:58 +00:00
"""
2017-01-02 14:45:56 +00:00
2019-07-03 13:07:04 +00:00
def __init__ ( self , msg_iface : int ) - > None :
2017-01-02 14:45:56 +00:00
self . msg_iface = msg_iface
2016-04-29 23:20:57 +00:00
2019-07-03 13:07:04 +00:00
def handle ( self , task : Task ) - > None :
2017-09-16 13:00:31 +00:00
pause ( task , self . msg_iface )
2016-08-05 10:32:37 +00:00
2021-12-08 09:10:58 +00:00
_type_gen : type [ Generator ] = type ( ( lambda : ( yield ) ) ( ) )
2019-05-13 13:06:34 +00:00
2019-08-20 14:20:02 +00:00
class race ( Syscall ) :
2018-07-03 14:20:58 +00:00
"""
2019-08-20 14:20:02 +00:00
Given a list of either children tasks or syscalls , ` race ` waits until one of
them completes ( tasks are executed in parallel , syscalls are waited upon ,
directly ) . Return value of ` race ` is the return value of the child that
triggered the completion . Other running children are killed ( by cancelling
any pending schedules and raising a ` GeneratorExit ` by calling ` close ( ) ` ) .
Child that caused the completion is present in ` self . finished ` .
2017-01-02 14:45:56 +00:00
Example :
2017-09-16 13:00:31 +00:00
>> > # async def wait_for_touch(): ...
>> > # async def animate_logo(): ...
>> > touch_task = wait_for_touch ( )
>> > animation_task = animate_logo ( )
2019-08-22 15:29:21 +00:00
>> > racer = loop . race ( touch_task , animation_task )
2019-08-20 14:20:02 +00:00
>> > result = await racer
>> > if animation_task in racer . finished :
>> > print ( ' animation task returned value: ' , result )
>> > elif touch_task in racer . finished :
>> > print ( ' touch task returned value: ' , result )
2017-09-16 13:00:31 +00:00
2019-08-20 14:20:02 +00:00
Note : You should not directly ` yield ` a ` race ` instance , see logic in
` race . __iter__ ` for explanation . Always use ` await ` .
2018-07-03 14:20:58 +00:00
"""
2016-05-11 12:39:57 +00:00
2021-12-08 09:10:58 +00:00
def __init__ ( self , * children : AwaitableTask , exit_others : bool = True ) - > None :
2016-08-05 10:32:37 +00:00
self . children = children
2016-05-02 14:06:08 +00:00
self . exit_others = exit_others
2021-12-08 09:10:58 +00:00
self . finished : list [ AwaitableTask ] = [ ] # children that finished
2021-03-18 09:48:50 +00:00
self . scheduled : list [ Task ] = [ ] # scheduled wrapper tasks
2016-05-02 14:06:08 +00:00
2019-07-03 13:07:04 +00:00
def handle ( self , task : Task ) - > None :
2019-08-20 14:20:02 +00:00
"""
Schedule all children Tasks and set ` task ` as callback .
"""
2019-05-13 13:06:34 +00:00
finalizer = self . _finish
scheduled = self . scheduled
finished = self . finished
2016-04-29 23:20:57 +00:00
2019-05-13 13:06:34 +00:00
self . callback = task
scheduled . clear ( )
finished . clear ( )
for child in self . children :
2021-12-08 09:10:58 +00:00
child_task : Task
2019-05-13 13:06:34 +00:00
if isinstance ( child , _type_gen ) :
2021-12-08 09:10:58 +00:00
# child is a coroutine/generator
# i.e., async function, or function using yield (these are identical
# in micropython)
2019-05-13 13:06:34 +00:00
child_task = child
else :
2021-12-08 09:10:58 +00:00
# child is a layout -- type-wise, it is an Awaitable, but
# implementation-wise it is an Iterable and we know that its __iter__
# will return a Generator.
2022-03-01 12:55:58 +00:00
child_task = child . __iter__ ( ) # type: ignore [Cannot access member "__iter__" for type "Awaitable[Unknown]";;Cannot access member "__iter__" for type "Coroutine[Unknown, Unknown, Unknown]"]
2020-08-08 15:49:44 +00:00
schedule ( child_task , None , None , finalizer )
scheduled . append ( child_task )
2019-05-13 13:06:34 +00:00
2021-03-18 09:48:50 +00:00
def exit ( self , except_for : Task | None = None ) - > None :
2019-05-13 13:06:34 +00:00
for task in self . scheduled :
if task != except_for :
close ( task )
2019-07-03 13:07:04 +00:00
def _finish ( self , task : Task , result : Any ) - > None :
2018-06-14 13:03:09 +00:00
if not self . finished :
2019-08-20 14:20:02 +00:00
# because we create tasks for children that are not generators yet,
# we need to find the child value that the caller supplied
2019-05-13 13:06:34 +00:00
for index , child_task in enumerate ( self . scheduled ) :
if child_task is task :
child = self . children [ index ]
break
2021-12-08 09:10:58 +00:00
else :
raise RuntimeError # task not found in scheduled
2018-06-14 13:03:09 +00:00
self . finished . append ( child )
2016-05-02 14:06:08 +00:00
if self . exit_others :
2019-05-13 13:06:34 +00:00
self . exit ( task )
2018-06-14 13:03:09 +00:00
schedule ( self . callback , result )
2016-09-25 13:55:08 +00:00
2022-03-01 12:55:58 +00:00
def __iter__ ( self ) - > Task : # type: ignore [awaitable-is-generator]
2016-09-25 13:55:08 +00:00
try :
return ( yield self )
2018-02-26 23:29:00 +00:00
except : # noqa: E722
2016-10-06 12:41:50 +00:00
# exception was raised on the waiting task externally with
2017-01-02 14:45:56 +00:00
# close() or throw(), kill the children tasks and re-raise
2020-05-22 10:00:38 +00:00
# Make sure finalizers don't continue processing.
self . finished . append ( self )
2016-09-25 13:55:08 +00:00
self . exit ( )
raise
2019-07-30 15:38:43 +00:00
class chan :
"""
Two - ended channel .
The receiving end pauses until a value to be received is available . The sending end
can choose to wait until the value is received , or it can publish the value without
waiting .
Example :
>> > # in task #1:
>> > signal = loop . chan ( )
>> > while True :
>> > result = await signal . take ( )
>> > print ( " awaited result: " , result )
>> > # in task #2:
>> > signal . publish ( " Published without waiting " )
>> > print ( " publish completed " )
>> > await signal . put ( " Put with await " )
>> > print ( " put completed " )
Example Output :
publish completed
awaited result : Published without waiting
awaited result : Put with await
put completed
"""
class Put ( Syscall ) :
2019-08-22 15:29:21 +00:00
def __init__ ( self , ch : " chan " , value : Any ) - > None :
2019-07-30 15:38:43 +00:00
self . ch = ch
self . value = value
2021-03-18 09:48:50 +00:00
self . task : Task | None = None
2019-07-30 15:38:43 +00:00
def handle ( self , task : Task ) - > None :
2019-08-22 15:29:21 +00:00
self . task = task
2019-07-30 15:38:43 +00:00
self . ch . _schedule_put ( task , self . value )
class Take ( Syscall ) :
def __init__ ( self , ch : " chan " ) - > None :
self . ch = ch
2021-03-18 09:48:50 +00:00
self . task : Task | None = None
2019-07-30 15:38:43 +00:00
2019-10-02 12:40:25 +00:00
def handle ( self , task : Task ) - > None :
2019-08-22 15:29:21 +00:00
self . task = task
2019-07-30 15:38:43 +00:00
self . ch . _schedule_take ( task )
2019-10-02 12:40:25 +00:00
def __init__ ( self ) - > None :
2021-03-18 09:48:50 +00:00
self . putters : list [ tuple [ Task | None , Any ] ] = [ ]
self . takers : list [ Task ] = [ ]
2019-08-22 15:29:21 +00:00
2022-03-01 12:55:58 +00:00
def put ( self , value : Any ) - > Awaitable [ None ] : # type: ignore [awaitable-is-generator]
2019-08-22 15:29:21 +00:00
put = chan . Put ( self , value )
try :
2019-08-22 15:36:04 +00:00
return ( yield put )
2019-08-22 15:29:21 +00:00
except : # noqa: E722
2019-08-22 15:36:04 +00:00
entry = ( put . task , value )
if entry in self . putters :
self . putters . remove ( entry )
raise
2019-08-22 15:29:21 +00:00
2022-03-01 12:55:58 +00:00
def take ( self ) - > Awaitable [ Any ] : # type: ignore [awaitable-is-generator]
2019-08-22 15:29:21 +00:00
take = chan . Take ( self )
try :
2019-08-22 15:36:04 +00:00
return ( yield take )
2019-08-22 15:29:21 +00:00
except : # noqa: E722
2019-08-22 15:36:04 +00:00
if take . task in self . takers :
self . takers . remove ( take . task )
2019-08-22 15:29:21 +00:00
raise
2019-07-30 15:38:43 +00:00
def publish ( self , value : Any ) - > None :
if self . takers :
taker = self . takers . pop ( 0 )
schedule ( taker , value )
else :
self . putters . append ( ( None , value ) )
2019-10-02 12:40:25 +00:00
def _schedule_put ( self , putter : Task , value : Any ) - > bool :
2019-07-30 15:38:43 +00:00
if self . takers :
taker = self . takers . pop ( 0 )
schedule ( taker , value )
schedule ( putter )
return True
else :
self . putters . append ( ( putter , value ) )
return False
def _schedule_take ( self , taker : Task ) - > None :
if self . putters :
putter , value = self . putters . pop ( 0 )
schedule ( taker , value )
if putter is not None :
schedule ( putter )
else :
self . takers . append ( taker )
2020-05-22 08:28:29 +00:00
class spawn ( Syscall ) :
""" Spawn a task asynchronously and get an awaitable reference to it.
Abstraction over ` loop . schedule ` and ` loop . close ` . Useful when you need to start
a task in the background , but want to be able to kill it from the outside .
Examples :
1. Spawn a background task , get its result later .
>> > wire_read = loop . spawn ( read_from_wire ( ) )
>> > long_result = await long_running_operation ( )
>> > wire_result = await wire_read
2. Allow the user to kill a long - running operation :
>> > try :
>> > operation = loop . spawn ( long_running_operation ( ) )
>> > result = await operation
>> > print ( " finished with result " , result )
>> > except loop . TaskClosed :
>> > print ( " task was closed before it could finish " )
>> >
>> > # meanwhile, on the other side of town...
>> > controller . close ( )
Task is spawned only once . Multiple attempts to ` await spawned_object ` will return
the original return value ( or raise the original exception ) .
"""
def __init__ ( self , task : Task ) - > None :
self . task = task
2021-03-18 09:48:50 +00:00
self . callback : Task | None = None
self . finalizer_callback : Callable [ [ " spawn " ] , None ] | None = None
2020-05-22 08:28:29 +00:00
self . finished = False
2020-10-16 17:39:32 +00:00
self . return_value : Any = None
2020-05-22 08:28:29 +00:00
# schedule task immediately
if __debug__ :
log . debug ( __name__ , " spawn new task: %s " , task )
2021-12-08 09:10:58 +00:00
assert isinstance ( task , _type_gen )
2020-05-22 08:28:29 +00:00
schedule ( task , finalizer = self . _finalize )
def _finalize ( self , task : Task , value : Any ) - > None :
# sanity check: make sure finalizer is for our task
assert task is self . task
# sanity check: make sure finalizer is not called more than once
assert self . finished is False
# now we are truly finished
self . finished = True
if isinstance ( value , GeneratorExit ) :
# coerce GeneratorExit to a catchable TaskClosed
self . return_value = TASK_CLOSED
else :
self . return_value = value
if self . callback is not None :
schedule ( self . callback , self . return_value )
self . callback = None
if self . finalizer_callback is not None :
self . finalizer_callback ( self )
2022-03-01 12:55:58 +00:00
def __iter__ ( self ) - > Task : # type: ignore [awaitable-is-generator]
2020-05-22 08:28:29 +00:00
if self . finished :
# exit immediately if we already have a return value
if isinstance ( self . return_value , BaseException ) :
raise self . return_value
else :
return self . return_value
try :
return ( yield self )
except BaseException :
# Clear out the callback. Otherwise we would raise the exception into it,
# AND schedule it with the closing value of the child task.
self . callback = None
assert self . task is not this_task # closing parent from child :(
close ( self . task )
raise
def handle ( self , caller : Task ) - > None :
# the same spawn should not be awaited multiple times
assert self . callback is None
self . callback = caller
def close ( self ) - > None :
""" Shut down the spawned task.
If another caller is awaiting its result it will get a TaskClosed exception .
If the task was already finished , the call has no effect .
"""
if not self . finished :
if __debug__ :
log . debug ( __name__ , " close spawned task: %s " , self . task )
close ( self . task )
def set_finalizer ( self , finalizer_callback : Callable [ [ " spawn " ] , None ] ) - > None :
""" Register a finalizer callback.
The provided function is executed synchronously when the spawned task ends ,
with the spawn object as an argument .
"""
if self . finished :
finalizer_callback ( self )
self . finalizer_callback = finalizer_callback
def is_running ( self ) - > bool :
""" Check if the caller is executing from the spawned task.
Useful for checking if it is OK to call ` task . close ( ) ` . If ` task . is_running ( ) `
is True , it would be calling close on self , which will result in a ValueError .
"""
return self . task is this_task
2021-11-04 11:54:06 +00:00
class Timer ( Syscall ) :
def __init__ ( self ) - > None :
self . task : Task | None = None
2022-05-23 17:52:12 +00:00
# Event::Attach is evaluated before task is set. Use this list to
# buffer timers until task is set.
self . before_task : list [ tuple [ int , Any ] ] = [ ]
2021-11-04 11:54:06 +00:00
def handle ( self , task : Task ) - > None :
self . task = task
2022-05-23 17:52:12 +00:00
for deadline , value in self . before_task :
schedule ( self . task , value , deadline )
self . before_task . clear ( )
2021-11-04 11:54:06 +00:00
def schedule ( self , deadline : int , value : Any ) - > None :
2022-03-04 11:58:24 +00:00
deadline = utime . ticks_add ( utime . ticks_ms ( ) , deadline )
2021-11-04 11:54:06 +00:00
if self . task is not None :
schedule ( self . task , value , deadline )
2022-05-23 17:52:12 +00:00
else :
self . before_task . append ( ( deadline , value ) )