Skip to content

Commit 4a8aa48

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. Additionally, if an error is encountered in the poll loop, it will be injected into the main task. Signed-off-by: Jared Hancock <jared@greezybacon.me>
1 parent 8159dcc commit 4a8aa48

File tree

1 file changed

+36
-23
lines changed

1 file changed

+36
-23
lines changed

extmod/asyncio/core.py

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from time import ticks_ms as ticks, ticks_diff, ticks_add
55
import sys, select
6+
from select import POLLIN, POLLOUT
67

78
# Import TaskQueue and Task, preferring built-in C code over Python code
89
try:
@@ -54,7 +55,8 @@ def __next__(self):
5455
# Use a SingletonGenerator to do it without allocating on the heap
5556
def sleep_ms(t, sgen=SingletonGenerator()):
5657
assert sgen.state is None
57-
sgen.state = ticks_add(ticks(), max(0, t))
58+
now = ticks()
59+
sgen.state = ticks_add(now, t) if t > 0 else now
5860
return sgen
5961

6062

@@ -66,7 +68,6 @@ def sleep(t):
6668
################################################################################
6769
# Queue and poller for stream IO
6870

69-
7071
class IOQueue:
7172
def __init__(self):
7273
self.poller = select.poll()
@@ -77,13 +78,13 @@ def _enqueue(self, s, idx):
7778
entry = [None, None, s]
7879
entry[idx] = cur_task
7980
self.map[id(s)] = entry
80-
self.poller.register(s, select.POLLIN if idx == 0 else select.POLLOUT)
81+
self.poller.register(s, POLLIN if idx == 0 else POLLOUT)
8182
else:
8283
sm = self.map[id(s)]
8384
assert sm[idx] is None
8485
assert sm[1 - idx] is not None
8586
sm[idx] = cur_task
86-
self.poller.modify(s, select.POLLIN | select.POLLOUT)
87+
self.poller.modify(s, POLLIN | POLLOUT)
8788
# Link task to this IOQueue so it can be removed if needed
8889
cur_task.data = self
8990

@@ -114,20 +115,20 @@ def wait_io_event(self, dt):
114115
for s, ev in self.poller.ipoll(dt):
115116
sm = self.map[id(s)]
116117
# print('poll', s, sm, ev)
117-
if ev & ~select.POLLOUT and sm[0] is not None:
118+
if ev & ~POLLOUT and sm[0] is not None:
118119
# POLLIN or error
119120
_task_queue.push(sm[0])
120121
sm[0] = None
121-
if ev & ~select.POLLIN and sm[1] is not None:
122+
if ev & ~POLLIN and sm[1] is not None:
122123
# POLLOUT or error
123124
_task_queue.push(sm[1])
124125
sm[1] = None
125126
if sm[0] is None and sm[1] is None:
126127
self._dequeue(s)
127128
elif sm[0] is None:
128-
self.poller.modify(s, select.POLLOUT)
129+
self.poller.modify(s, POLLOUT)
129130
else:
130-
self.poller.modify(s, select.POLLIN)
131+
self.poller.modify(s, POLLIN)
131132

132133

133134
################################################################################
@@ -153,24 +154,36 @@ def run_until_complete(main_task=None):
153154
global cur_task
154155
excs_all = (CancelledError, Exception) # To prevent heap allocation in loop
155156
excs_stop = (CancelledError, StopIteration) # To prevent heap allocation in loop
157+
queue_peek = _task_queue.peek
158+
queue_pop = _task_queue.pop
159+
wait_io_event = _io_queue.wait_io_event
156160
while True:
157-
# Wait until the head of _task_queue is ready to run
158-
dt = 1
159-
while dt > 0:
160-
dt = -1
161-
t = _task_queue.peek()
162-
if t:
163-
# A task waiting on _task_queue; "ph_key" is time to schedule task at
164-
dt = max(0, ticks_diff(t.ph_key, ticks()))
165-
elif not _io_queue.map:
166-
# No tasks can be woken so finished running
167-
cur_task = None
168-
return
169-
# print('(poll {})'.format(dt), len(_io_queue.map))
170-
_io_queue.wait_io_event(dt)
161+
try:
162+
# Wait until the head of _task_queue is ready to run
163+
dt = 1
164+
while dt > 0:
165+
dt = -1
166+
t = queue_peek()
167+
if t:
168+
# A task waiting on _task_queue; "ph_key" is time to schedule task at
169+
dt = ticks_diff(t.ph_key, ticks())
170+
dt = dt if dt > 0 else 0
171+
elif not _io_queue.map:
172+
# No tasks can be woken so finished running
173+
cur_task = None
174+
return
175+
# print('(poll {})'.format(dt), len(_io_queue.map))
176+
wait_io_event(dt)
177+
except BaseException as exc:
178+
try:
179+
if main_task:
180+
main_task.coro.throw(exc)
181+
except StopIteration:
182+
pass
183+
raise
171184

172185
# Get next task to run and continue it
173-
t = _task_queue.pop()
186+
t = queue_pop()
174187
cur_task = t
175188
try:
176189
# Continue running the coroutine, it's responsible for rescheduling itself

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