Skip to content

Commit 615bf4a

Browse files
committed
asyncio: Properly cancel the main task on exception
If the main task is interrupted by e.g. a KeyboardInterrupt, then the main task needs to have the exception injected into it so it will run the exception handlers and contextmanager __aexit__ methods.
1 parent b52fe52 commit 615bf4a

File tree

1 file changed

+68
-61
lines changed

1 file changed

+68
-61
lines changed

extmod/uasyncio/core.py

Lines changed: 68 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -145,81 +145,88 @@ def create_task(coro):
145145
_task_queue.push(t)
146146
return t
147147

148-
149148
# Keep scheduling tasks until there are none left to schedule
150149
def run_until_complete(main_task=None):
151150
global cur_task
152151
excs_all = (CancelledError, Exception) # To prevent heap allocation in loop
153152
excs_stop = (CancelledError, StopIteration) # To prevent heap allocation in loop
154-
while True:
155-
# Wait until the head of _task_queue is ready to run
156-
dt = 1
157-
while dt > 0:
158-
dt = -1
153+
try:
154+
_io_queue.wait_io_event(0)
155+
while True:
156+
# Wait until the head of _task_queue is ready to run
159157
t = _task_queue.peek()
160158
if t:
161159
# A task waiting on _task_queue; "ph_key" is time to schedule task at
162-
dt = max(0, ticks_diff(t.ph_key, ticks()))
160+
dt = ticks_diff(t.ph_key, ticks())
161+
if dt > 0:
162+
_io_queue.wait_io_event(dt)
163163
elif not _io_queue.map:
164164
# No tasks can be woken so finished running
165165
return
166-
# print('(poll {})'.format(dt), len(_io_queue.map))
167-
_io_queue.wait_io_event(dt)
168-
169-
# Get next task to run and continue it
170-
t = _task_queue.pop()
171-
cur_task = t
172-
try:
173-
# Continue running the coroutine, it's responsible for rescheduling itself
174-
exc = t.data
175-
if not exc:
176-
t.coro.send(None)
177166
else:
178-
# If the task is finished and on the run queue and gets here, then it
179-
# had an exception and was not await'ed on. Throwing into it now will
180-
# raise StopIteration and the code below will catch this and run the
181-
# call_exception_handler function.
182-
t.data = None
183-
t.coro.throw(exc)
184-
except excs_all as er:
185-
# Check the task is not on any event queue
186-
assert t.data is None
187-
# This task is done, check if it's the main task and then loop should stop
188-
if t is main_task:
189-
if isinstance(er, StopIteration):
190-
return er.value
191-
raise er
192-
if t.state:
193-
# Task was running but is now finished.
194-
waiting = False
195-
if t.state is True:
196-
# "None" indicates that the task is complete and not await'ed on (yet).
197-
t.state = None
198-
elif callable(t.state):
199-
# The task has a callback registered to be called on completion.
200-
t.state(t, er)
201-
t.state = False
202-
waiting = True
167+
_io_queue.wait_io_event(-1)
168+
169+
# Get next task to run and continue it
170+
t = _task_queue.pop()
171+
cur_task = t
172+
try:
173+
# Continue running the coroutine, it's responsible for rescheduling itself
174+
exc = t.data
175+
if not exc:
176+
t.coro.send(None)
203177
else:
204-
# Schedule any other tasks waiting on the completion of this task.
205-
while t.state.peek():
206-
_task_queue.push(t.state.pop())
178+
# If the task is finished and on the run queue and gets here, then it
179+
# had an exception and was not await'ed on. Throwing into it now will
180+
# raise StopIteration and the code below will catch this and run the
181+
# call_exception_handler function.
182+
t.data = None
183+
t.coro.throw(exc)
184+
except excs_all as er:
185+
# Check the task is not on any event queue
186+
assert t.data is None
187+
# This task is done, check if it's the main task and then loop should stop
188+
if t is main_task:
189+
if isinstance(er, StopIteration):
190+
return er.value
191+
raise er
192+
if t.state:
193+
# Task was running but is now finished.
194+
waiting = False
195+
if t.state is True:
196+
# "None" indicates that the task is complete and not await'ed on (yet).
197+
t.state = None
198+
elif callable(t.state):
199+
# The task has a callback registered to be called on completion.
200+
t.state(t, er)
201+
t.state = False
207202
waiting = True
208-
# "False" indicates that the task is complete and has been await'ed on.
209-
t.state = False
210-
if not waiting and not isinstance(er, excs_stop):
211-
# An exception ended this detached task, so queue it for later
212-
# execution to handle the uncaught exception if no other task retrieves
213-
# the exception in the meantime (this is handled by Task.throw).
214-
_task_queue.push(t)
215-
# Save return value of coro to pass up to caller.
216-
t.data = er
217-
elif t.state is None:
218-
# Task is already finished and nothing await'ed on the task,
219-
# so call the exception handler.
220-
_exc_context["exception"] = exc
221-
_exc_context["future"] = t
222-
Loop.call_exception_handler(_exc_context)
203+
else:
204+
# Schedule any other tasks waiting on the completion of this task.
205+
while t.state.peek():
206+
_task_queue.push(t.state.pop())
207+
waiting = True
208+
# "False" indicates that the task is complete and has been await'ed on.
209+
t.state = False
210+
if not waiting and not isinstance(er, excs_stop):
211+
# An exception ended this detached task, so queue it for later
212+
# execution to handle the uncaught exception if no other task retrieves
213+
# the exception in the meantime (this is handled by Task.throw).
214+
_task_queue.push(t)
215+
# Save return value of coro to pass up to caller.
216+
t.data = er
217+
elif t.state is None:
218+
# Task is already finished and nothing await'ed on the task,
219+
# so call the exception handler.
220+
_exc_context["exception"] = exc
221+
_exc_context["future"] = t
222+
Loop.call_exception_handler(_exc_context)
223+
except BaseException as exc:
224+
try:
225+
if main_task:
226+
main_task.coro.throw(exc)
227+
except StopIteration:
228+
pass
229+
raise
223230

224231

225232
# Create a new task from a coroutine and run it until it finishes

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy