diff options
author | Damien George <damien@micropython.org> | 2020-12-01 14:22:16 +1100 |
---|---|---|
committer | Damien George <damien@micropython.org> | 2020-12-02 12:31:37 +1100 |
commit | b505971069333a373d9f0c12138b64dab83fed72 (patch) | |
tree | db72998cd1785d5906ebc817ac20573f7a6330bd /extmod/uasyncio/funcs.py | |
parent | 309dfe39e07d3c3c8ba873ed09116cea9f6f52c0 (diff) |
extmod/uasyncio: Fix cancellation handling of wait_for.
This commit switches the roles of the helper task from a cancellation task
to a runner task, to get the correct semantics for cancellation of
wait_for.
Some uasyncio tests are now disabled for the native emitter due to issues
with native code generation of generators and yield-from.
Fixes #5797.
Signed-off-by: Damien George <damien@micropython.org>
Diffstat (limited to 'extmod/uasyncio/funcs.py')
-rw-r--r-- | extmod/uasyncio/funcs.py | 52 |
1 files changed, 36 insertions, 16 deletions
diff --git a/extmod/uasyncio/funcs.py b/extmod/uasyncio/funcs.py index d30675231..93f4fd256 100644 --- a/extmod/uasyncio/funcs.py +++ b/extmod/uasyncio/funcs.py @@ -9,24 +9,44 @@ async def wait_for(aw, timeout, sleep=core.sleep): if timeout is None: return await aw - def cancel(aw, timeout, sleep): - await sleep(timeout) - aw.cancel() + def runner(waiter, aw): + nonlocal status, result + try: + result = await aw + s = True + except BaseException as er: + s = er + if status is None: + # The waiter is still waiting, set status for it and cancel it. + status = s + waiter.cancel() + + # Run aw in a separate runner task that manages its exceptions. + status = None + result = None + runner_task = core.create_task(runner(core.cur_task, aw)) - cancel_task = core.create_task(cancel(aw, timeout, sleep)) try: - ret = await aw - except core.CancelledError: - # Ignore CancelledError from aw, it's probably due to timeout - pass - finally: - # Cancel the "cancel" task if it's still active (optimisation instead of cancel_task.cancel()) - if cancel_task.coro is not cancel_task: - core._task_queue.remove(cancel_task) - if cancel_task.coro is cancel_task: - # Cancel task ran to completion, ie there was a timeout - raise core.TimeoutError - return ret + # Wait for the timeout to elapse. + await sleep(timeout) + except core.CancelledError as er: + if status is True: + # aw completed successfully and cancelled the sleep, so return aw's result. + return result + elif status is None: + # This wait_for was cancelled externally, so cancel aw and re-raise. + status = True + runner_task.cancel() + raise er + else: + # aw raised an exception, propagate it out to the caller. + raise status + + # The sleep finished before aw, so cancel aw and raise TimeoutError. + status = True + runner_task.cancel() + await runner_task + raise core.TimeoutError def wait_for_ms(aw, timeout): |