Skip to content

Commit 431b540

Browse files
authored
bpo-32528: Make asyncio.CancelledError a BaseException. (GH-13528)
This will address the common mistake many asyncio users make: an "except Exception" clause breaking Tasks cancellation. In addition to this change, we stop inheriting asyncio.TimeoutError and asyncio.InvalidStateError from their concurrent.futures.* counterparts. There's no point for these exceptions to share the inheritance chain. In 3.9 we'll focus on implementing supervisors and cancel scopes, which should allow better handling of all exceptions, including SystemExit and KeyboardInterrupt
1 parent 16cefb0 commit 431b540

16 files changed

+146
-66
lines changed

Lib/asyncio/base_events.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ def _interleave_addrinfos(addrinfos, first_address_family_count=1):
186186
def _run_until_complete_cb(fut):
187187
if not fut.cancelled():
188188
exc = fut.exception()
189-
if isinstance(exc, BaseException) and not isinstance(exc, Exception):
189+
if isinstance(exc, (SystemExit, KeyboardInterrupt)):
190190
# Issue #22429: run_forever() already finished, no need to
191191
# stop it.
192192
return
@@ -1196,7 +1196,7 @@ async def start_tls(self, transport, protocol, sslcontext, *,
11961196

11971197
try:
11981198
await waiter
1199-
except Exception:
1199+
except BaseException:
12001200
transport.close()
12011201
conmade_cb.cancel()
12021202
resume_cb.cancel()
@@ -1710,7 +1710,9 @@ def call_exception_handler(self, context):
17101710
if self._exception_handler is None:
17111711
try:
17121712
self.default_exception_handler(context)
1713-
except Exception:
1713+
except (SystemExit, KeyboardInterrupt):
1714+
raise
1715+
except BaseException:
17141716
# Second protection layer for unexpected errors
17151717
# in the default implementation, as well as for subclassed
17161718
# event loops with overloaded "default_exception_handler".
@@ -1719,7 +1721,9 @@ def call_exception_handler(self, context):
17191721
else:
17201722
try:
17211723
self._exception_handler(self, context)
1722-
except Exception as exc:
1724+
except (SystemExit, KeyboardInterrupt):
1725+
raise
1726+
except BaseException as exc:
17231727
# Exception in the user set custom exception handler.
17241728
try:
17251729
# Let's try default handler.
@@ -1728,7 +1732,9 @@ def call_exception_handler(self, context):
17281732
'exception': exc,
17291733
'context': context,
17301734
})
1731-
except Exception:
1735+
except (SystemExit, KeyboardInterrupt):
1736+
raise
1737+
except BaseException:
17321738
# Guard 'default_exception_handler' in case it is
17331739
# overloaded.
17341740
logger.error('Exception in default exception handler '

Lib/asyncio/base_subprocess.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,9 @@ async def _connect_pipes(self, waiter):
182182
for callback, data in self._pending_calls:
183183
loop.call_soon(callback, *data)
184184
self._pending_calls = None
185-
except Exception as exc:
185+
except (SystemExit, KeyboardInterrupt):
186+
raise
187+
except BaseException as exc:
186188
if waiter is not None and not waiter.cancelled():
187189
waiter.set_exception(exc)
188190
else:

Lib/asyncio/events.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ def cancelled(self):
7979
def _run(self):
8080
try:
8181
self._context.run(self._callback, *self._args)
82-
except Exception as exc:
82+
except (SystemExit, KeyboardInterrupt):
83+
raise
84+
except BaseException as exc:
8385
cb = format_helpers._format_callback_source(
8486
self._callback, self._args)
8587
msg = f'Exception in callback {cb}'

Lib/asyncio/exceptions.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,16 @@
55
'IncompleteReadError', 'LimitOverrunError',
66
'SendfileNotAvailableError')
77

8-
import concurrent.futures
9-
from . import base_futures
108

11-
12-
class CancelledError(concurrent.futures.CancelledError):
9+
class CancelledError(BaseException):
1310
"""The Future or Task was cancelled."""
1411

1512

16-
class TimeoutError(concurrent.futures.TimeoutError):
13+
class TimeoutError(Exception):
1714
"""The operation exceeded the given deadline."""
1815

1916

20-
class InvalidStateError(concurrent.futures.InvalidStateError):
17+
class InvalidStateError(Exception):
2118
"""The operation is not allowed in this state."""
2219

2320

Lib/asyncio/proactor_events.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,9 @@ def _eof_received(self):
212212

213213
try:
214214
keep_open = self._protocol.eof_received()
215-
except Exception as exc:
215+
except (SystemExit, KeyboardInterrupt):
216+
raise
217+
except BaseException as exc:
216218
self._fatal_error(
217219
exc, 'Fatal error: protocol.eof_received() call failed.')
218220
return
@@ -235,7 +237,9 @@ def _data_received(self, data):
235237
if isinstance(self._protocol, protocols.BufferedProtocol):
236238
try:
237239
protocols._feed_data_to_buffered_proto(self._protocol, data)
238-
except Exception as exc:
240+
except (SystemExit, KeyboardInterrupt):
241+
raise
242+
except BaseException as exc:
239243
self._fatal_error(exc,
240244
'Fatal error: protocol.buffer_updated() '
241245
'call failed.')
@@ -625,7 +629,9 @@ def _loop_self_reading(self, f=None):
625629
except exceptions.CancelledError:
626630
# _close_self_pipe() has been called, stop waiting for data
627631
return
628-
except Exception as exc:
632+
except (SystemExit, KeyboardInterrupt):
633+
raise
634+
except BaseException as exc:
629635
self.call_exception_handler({
630636
'message': 'Error on reading from the event loop self pipe',
631637
'exception': exc,

Lib/asyncio/selector_events.py

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -208,12 +208,14 @@ async def _accept_connection2(
208208

209209
try:
210210
await waiter
211-
except:
211+
except BaseException:
212212
transport.close()
213213
raise
214+
# It's now up to the protocol to handle the connection.
214215

215-
# It's now up to the protocol to handle the connection.
216-
except Exception as exc:
216+
except (SystemExit, KeyboardInterrupt):
217+
raise
218+
except BaseException as exc:
217219
if self._debug:
218220
context = {
219221
'message':
@@ -370,7 +372,9 @@ def _sock_recv(self, fut, sock, n):
370372
data = sock.recv(n)
371373
except (BlockingIOError, InterruptedError):
372374
return # try again next time
373-
except Exception as exc:
375+
except (SystemExit, KeyboardInterrupt):
376+
raise
377+
except BaseException as exc:
374378
fut.set_exception(exc)
375379
else:
376380
fut.set_result(data)
@@ -404,7 +408,9 @@ def _sock_recv_into(self, fut, sock, buf):
404408
nbytes = sock.recv_into(buf)
405409
except (BlockingIOError, InterruptedError):
406410
return # try again next time
407-
except Exception as exc:
411+
except (SystemExit, KeyboardInterrupt):
412+
raise
413+
except BaseException as exc:
408414
fut.set_exception(exc)
409415
else:
410416
fut.set_result(nbytes)
@@ -447,7 +453,9 @@ def _sock_sendall(self, fut, sock, view, pos):
447453
n = sock.send(view[start:])
448454
except (BlockingIOError, InterruptedError):
449455
return
450-
except Exception as exc:
456+
except (SystemExit, KeyboardInterrupt):
457+
raise
458+
except BaseException as exc:
451459
fut.set_exception(exc)
452460
return
453461

@@ -487,7 +495,9 @@ def _sock_connect(self, fut, sock, address):
487495
fut.add_done_callback(
488496
functools.partial(self._sock_write_done, fd))
489497
self.add_writer(fd, self._sock_connect_cb, fut, sock, address)
490-
except Exception as exc:
498+
except (SystemExit, KeyboardInterrupt):
499+
raise
500+
except BaseException as exc:
491501
fut.set_exception(exc)
492502
else:
493503
fut.set_result(None)
@@ -507,7 +517,9 @@ def _sock_connect_cb(self, fut, sock, address):
507517
except (BlockingIOError, InterruptedError):
508518
# socket is still registered, the callback will be retried later
509519
pass
510-
except Exception as exc:
520+
except (SystemExit, KeyboardInterrupt):
521+
raise
522+
except BaseException as exc:
511523
fut.set_exception(exc)
512524
else:
513525
fut.set_result(None)
@@ -537,7 +549,9 @@ def _sock_accept(self, fut, registered, sock):
537549
conn.setblocking(False)
538550
except (BlockingIOError, InterruptedError):
539551
self.add_reader(fd, self._sock_accept, fut, True, sock)
540-
except Exception as exc:
552+
except (SystemExit, KeyboardInterrupt):
553+
raise
554+
except BaseException as exc:
541555
fut.set_exception(exc)
542556
else:
543557
fut.set_result((conn, address))
@@ -785,7 +799,9 @@ def _read_ready__get_buffer(self):
785799
buf = self._protocol.get_buffer(-1)
786800
if not len(buf):
787801
raise RuntimeError('get_buffer() returned an empty buffer')
788-
except Exception as exc:
802+
except (SystemExit, KeyboardInterrupt):
803+
raise
804+
except BaseException as exc:
789805
self._fatal_error(
790806
exc, 'Fatal error: protocol.get_buffer() call failed.')
791807
return
@@ -794,7 +810,9 @@ def _read_ready__get_buffer(self):
794810
nbytes = self._sock.recv_into(buf)
795811
except (BlockingIOError, InterruptedError):
796812
return
797-
except Exception as exc:
813+
except (SystemExit, KeyboardInterrupt):
814+
raise
815+
except BaseException as exc:
798816
self._fatal_error(exc, 'Fatal read error on socket transport')
799817
return
800818

@@ -804,7 +822,9 @@ def _read_ready__get_buffer(self):
804822

805823
try:
806824
self._protocol.buffer_updated(nbytes)
807-
except Exception as exc:
825+
except (SystemExit, KeyboardInterrupt):
826+
raise
827+
except BaseException as exc:
808828
self._fatal_error(
809829
exc, 'Fatal error: protocol.buffer_updated() call failed.')
810830

@@ -815,7 +835,9 @@ def _read_ready__data_received(self):
815835
data = self._sock.recv(self.max_size)
816836
except (BlockingIOError, InterruptedError):
817837
return
818-
except Exception as exc:
838+
except (SystemExit, KeyboardInterrupt):
839+
raise
840+
except BaseException as exc:
819841
self._fatal_error(exc, 'Fatal read error on socket transport')
820842
return
821843

@@ -825,7 +847,9 @@ def _read_ready__data_received(self):
825847

826848
try:
827849
self._protocol.data_received(data)
828-
except Exception as exc:
850+
except (SystemExit, KeyboardInterrupt):
851+
raise
852+
except BaseException as exc:
829853
self._fatal_error(
830854
exc, 'Fatal error: protocol.data_received() call failed.')
831855

@@ -835,7 +859,9 @@ def _read_ready__on_eof(self):
835859

836860
try:
837861
keep_open = self._protocol.eof_received()
838-
except Exception as exc:
862+
except (SystemExit, KeyboardInterrupt):
863+
raise
864+
except BaseException as exc:
839865
self._fatal_error(
840866
exc, 'Fatal error: protocol.eof_received() call failed.')
841867
return
@@ -871,7 +897,9 @@ def write(self, data):
871897
n = self._sock.send(data)
872898
except (BlockingIOError, InterruptedError):
873899
pass
874-
except Exception as exc:
900+
except (SystemExit, KeyboardInterrupt):
901+
raise
902+
except BaseException as exc:
875903
self._fatal_error(exc, 'Fatal write error on socket transport')
876904
return
877905
else:
@@ -894,7 +922,9 @@ def _write_ready(self):
894922
n = self._sock.send(self._buffer)
895923
except (BlockingIOError, InterruptedError):
896924
pass
897-
except Exception as exc:
925+
except (SystemExit, KeyboardInterrupt):
926+
raise
927+
except BaseException as exc:
898928
self._loop._remove_writer(self._sock_fd)
899929
self._buffer.clear()
900930
self._fatal_error(exc, 'Fatal write error on socket transport')
@@ -970,7 +1000,9 @@ def _read_ready(self):
9701000
pass
9711001
except OSError as exc:
9721002
self._protocol.error_received(exc)
973-
except Exception as exc:
1003+
except (SystemExit, KeyboardInterrupt):
1004+
raise
1005+
except BaseException as exc:
9741006
self._fatal_error(exc, 'Fatal read error on datagram transport')
9751007
else:
9761008
self._protocol.datagram_received(data, addr)
@@ -1007,7 +1039,9 @@ def sendto(self, data, addr=None):
10071039
except OSError as exc:
10081040
self._protocol.error_received(exc)
10091041
return
1010-
except Exception as exc:
1042+
except (SystemExit, KeyboardInterrupt):
1043+
raise
1044+
except BaseException as exc:
10111045
self._fatal_error(
10121046
exc, 'Fatal write error on datagram transport')
10131047
return
@@ -1030,7 +1064,9 @@ def _sendto_ready(self):
10301064
except OSError as exc:
10311065
self._protocol.error_received(exc)
10321066
return
1033-
except Exception as exc:
1067+
except (SystemExit, KeyboardInterrupt):
1068+
raise
1069+
except BaseException as exc:
10341070
self._fatal_error(
10351071
exc, 'Fatal write error on datagram transport')
10361072
return

Lib/asyncio/sslproto.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,9 @@ def data_received(self, data):
527527

528528
try:
529529
ssldata, appdata = self._sslpipe.feed_ssldata(data)
530-
except Exception as e:
530+
except (SystemExit, KeyboardInterrupt):
531+
raise
532+
except BaseException as e:
531533
self._fatal_error(e, 'SSL error in data received')
532534
return
533535

@@ -542,7 +544,9 @@ def data_received(self, data):
542544
self._app_protocol, chunk)
543545
else:
544546
self._app_protocol.data_received(chunk)
545-
except Exception as ex:
547+
except (SystemExit, KeyboardInterrupt):
548+
raise
549+
except BaseException as ex:
546550
self._fatal_error(
547551
ex, 'application protocol failed to receive SSL data')
548552
return
@@ -628,7 +632,9 @@ def _on_handshake_complete(self, handshake_exc):
628632
raise handshake_exc
629633

630634
peercert = sslobj.getpeercert()
631-
except Exception as exc:
635+
except (SystemExit, KeyboardInterrupt):
636+
raise
637+
except BaseException as exc:
632638
if isinstance(exc, ssl.CertificateError):
633639
msg = 'SSL handshake failed on verifying the certificate'
634640
else:
@@ -691,7 +697,9 @@ def _process_write_backlog(self):
691697
# delete it and reduce the outstanding buffer size.
692698
del self._write_backlog[0]
693699
self._write_buffer_size -= len(data)
694-
except Exception as exc:
700+
except (SystemExit, KeyboardInterrupt):
701+
raise
702+
except BaseException as exc:
695703
if self._in_handshake:
696704
# Exceptions will be re-raised in _on_handshake_complete.
697705
self._on_handshake_complete(exc)

Lib/asyncio/staggered.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,9 @@ async def run_one_coro(
105105

106106
try:
107107
result = await coro_fn()
108-
except Exception as e:
108+
except (SystemExit, KeyboardInterrupt):
109+
raise
110+
except BaseException as e:
109111
exceptions[this_index] = e
110112
this_failed.set() # Kickstart the next coroutine
111113
else:

0 commit comments

Comments
 (0)