Skip to content

Commit d0f93d1

Browse files
committed
Merge branch 'main' of https://github.com/python/cpython
2 parents 54084e2 + e5521bc commit d0f93d1

21 files changed

+548
-92
lines changed

Doc/library/asyncio-task.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,27 @@ is also included in the exception group.
392392
The same special case is made for
393393
:exc:`KeyboardInterrupt` and :exc:`SystemExit` as in the previous paragraph.
394394

395+
Task groups are careful not to mix up the internal cancellation used to
396+
"wake up" their :meth:`~object.__aexit__` with cancellation requests
397+
for the task in which they are running made by other parties.
398+
In particular, when one task group is syntactically nested in another,
399+
and both experience an exception in one of their child tasks simultaneously,
400+
the inner task group will process its exceptions, and then the outer task group
401+
will receive another cancellation and process its own exceptions.
402+
403+
In the case where a task group is cancelled externally and also must
404+
raise an :exc:`ExceptionGroup`, it will call the parent task's
405+
:meth:`~asyncio.Task.cancel` method. This ensures that a
406+
:exc:`asyncio.CancelledError` will be raised at the next
407+
:keyword:`await`, so the cancellation is not lost.
408+
409+
Task groups preserve the cancellation count
410+
reported by :meth:`asyncio.Task.cancelling`.
411+
412+
.. versionchanged:: 3.13
413+
414+
Improved handling of simultaneous internal and external cancellations
415+
and correct preservation of cancellation counts.
395416

396417
Sleeping
397418
========
@@ -1369,6 +1390,15 @@ Task Object
13691390
catching :exc:`CancelledError`, it needs to call this method to remove
13701391
the cancellation state.
13711392

1393+
When this method decrements the cancellation count to zero,
1394+
the method checks if a previous :meth:`cancel` call had arranged
1395+
for :exc:`CancelledError` to be thrown into the task.
1396+
If it hasn't been thrown yet, that arrangement will be
1397+
rescinded (by resetting the internal ``_must_cancel`` flag).
1398+
1399+
.. versionchanged:: 3.13
1400+
Changed to rescind pending cancellation requests upon reaching zero.
1401+
13721402
.. method:: cancelling()
13731403

13741404
Return the number of pending cancellation requests to this Task, i.e.,

Doc/library/typing.rst

Lines changed: 91 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1385,22 +1385,23 @@ These can be used as types in annotations. They all support subscription using
13851385
.. versionadded:: 3.9
13861386

13871387

1388-
.. data:: TypeGuard
1388+
.. data:: TypeIs
13891389

1390-
Special typing construct for marking user-defined type guard functions.
1390+
Special typing construct for marking user-defined type predicate functions.
13911391

1392-
``TypeGuard`` can be used to annotate the return type of a user-defined
1393-
type guard function. ``TypeGuard`` only accepts a single type argument.
1394-
At runtime, functions marked this way should return a boolean.
1392+
``TypeIs`` can be used to annotate the return type of a user-defined
1393+
type predicate function. ``TypeIs`` only accepts a single type argument.
1394+
At runtime, functions marked this way should return a boolean and take at
1395+
least one positional argument.
13951396

1396-
``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static
1397+
``TypeIs`` aims to benefit *type narrowing* -- a technique used by static
13971398
type checkers to determine a more precise type of an expression within a
13981399
program's code flow. Usually type narrowing is done by analyzing
13991400
conditional code flow and applying the narrowing to a block of code. The
1400-
conditional expression here is sometimes referred to as a "type guard"::
1401+
conditional expression here is sometimes referred to as a "type predicate"::
14011402

14021403
def is_str(val: str | float):
1403-
# "isinstance" type guard
1404+
# "isinstance" type predicate
14041405
if isinstance(val, str):
14051406
# Type of ``val`` is narrowed to ``str``
14061407
...
@@ -1409,8 +1410,73 @@ These can be used as types in annotations. They all support subscription using
14091410
...
14101411

14111412
Sometimes it would be convenient to use a user-defined boolean function
1412-
as a type guard. Such a function should use ``TypeGuard[...]`` as its
1413-
return type to alert static type checkers to this intention.
1413+
as a type predicate. Such a function should use ``TypeIs[...]`` or
1414+
:data:`TypeGuard` as its return type to alert static type checkers to
1415+
this intention. ``TypeIs`` usually has more intuitive behavior than
1416+
``TypeGuard``, but it cannot be used when the input and output types
1417+
are incompatible (e.g., ``list[object]`` to ``list[int]``) or when the
1418+
function does not return ``True`` for all instances of the narrowed type.
1419+
1420+
Using ``-> TypeIs[NarrowedType]`` tells the static type checker that for a given
1421+
function:
1422+
1423+
1. The return value is a boolean.
1424+
2. If the return value is ``True``, the type of its argument
1425+
is the intersection of the argument's original type and ``NarrowedType``.
1426+
3. If the return value is ``False``, the type of its argument
1427+
is narrowed to exclude ``NarrowedType``.
1428+
1429+
For example::
1430+
1431+
from typing import assert_type, final, TypeIs
1432+
1433+
class Parent: pass
1434+
class Child(Parent): pass
1435+
@final
1436+
class Unrelated: pass
1437+
1438+
def is_parent(val: object) -> TypeIs[Parent]:
1439+
return isinstance(val, Parent)
1440+
1441+
def run(arg: Child | Unrelated):
1442+
if is_parent(arg):
1443+
# Type of ``arg`` is narrowed to the intersection
1444+
# of ``Parent`` and ``Child``, which is equivalent to
1445+
# ``Child``.
1446+
assert_type(arg, Child)
1447+
else:
1448+
# Type of ``arg`` is narrowed to exclude ``Parent``,
1449+
# so only ``Unrelated`` is left.
1450+
assert_type(arg, Unrelated)
1451+
1452+
The type inside ``TypeIs`` must be consistent with the type of the
1453+
function's argument; if it is not, static type checkers will raise
1454+
an error. An incorrectly written ``TypeIs`` function can lead to
1455+
unsound behavior in the type system; it is the user's responsibility
1456+
to write such functions in a type-safe manner.
1457+
1458+
If a ``TypeIs`` function is a class or instance method, then the type in
1459+
``TypeIs`` maps to the type of the second parameter after ``cls`` or
1460+
``self``.
1461+
1462+
In short, the form ``def foo(arg: TypeA) -> TypeIs[TypeB]: ...``,
1463+
means that if ``foo(arg)`` returns ``True``, then ``arg`` is an instance
1464+
of ``TypeB``, and if it returns ``False``, it is not an instance of ``TypeB``.
1465+
1466+
``TypeIs`` also works with type variables. For more information, see
1467+
:pep:`742` (Narrowing types with ``TypeIs``).
1468+
1469+
.. versionadded:: 3.13
1470+
1471+
1472+
.. data:: TypeGuard
1473+
1474+
Special typing construct for marking user-defined type predicate functions.
1475+
1476+
Type predicate functions are user-defined functions that return whether their
1477+
argument is an instance of a particular type.
1478+
``TypeGuard`` works similarly to :data:`TypeIs`, but has subtly different
1479+
effects on type checking behavior (see below).
14141480

14151481
Using ``-> TypeGuard`` tells the static type checker that for a given
14161482
function:
@@ -1419,6 +1485,8 @@ These can be used as types in annotations. They all support subscription using
14191485
2. If the return value is ``True``, the type of its argument
14201486
is the type inside ``TypeGuard``.
14211487

1488+
``TypeGuard`` also works with type variables. See :pep:`647` for more details.
1489+
14221490
For example::
14231491

14241492
def is_str_list(val: list[object]) -> TypeGuard[list[str]]:
@@ -1433,23 +1501,19 @@ These can be used as types in annotations. They all support subscription using
14331501
# Type of ``val`` remains as ``list[object]``.
14341502
print("Not a list of strings!")
14351503

1436-
If ``is_str_list`` is a class or instance method, then the type in
1437-
``TypeGuard`` maps to the type of the second parameter after ``cls`` or
1438-
``self``.
1439-
1440-
In short, the form ``def foo(arg: TypeA) -> TypeGuard[TypeB]: ...``,
1441-
means that if ``foo(arg)`` returns ``True``, then ``arg`` narrows from
1442-
``TypeA`` to ``TypeB``.
1443-
1444-
.. note::
1445-
1446-
``TypeB`` need not be a narrower form of ``TypeA`` -- it can even be a
1447-
wider form. The main reason is to allow for things like
1448-
narrowing ``list[object]`` to ``list[str]`` even though the latter
1449-
is not a subtype of the former, since ``list`` is invariant.
1450-
The responsibility of writing type-safe type guards is left to the user.
1451-
1452-
``TypeGuard`` also works with type variables. See :pep:`647` for more details.
1504+
``TypeIs`` and ``TypeGuard`` differ in the following ways:
1505+
1506+
* ``TypeIs`` requires the narrowed type to be a subtype of the input type, while
1507+
``TypeGuard`` does not. The main reason is to allow for things like
1508+
narrowing ``list[object]`` to ``list[str]`` even though the latter
1509+
is not a subtype of the former, since ``list`` is invariant.
1510+
* When a ``TypeGuard`` function returns ``True``, type checkers narrow the type of the
1511+
variable to exactly the ``TypeGuard`` type. When a ``TypeIs`` function returns ``True``,
1512+
type checkers can infer a more precise type combining the previously known type of the
1513+
variable with the ``TypeIs`` type. (Technically, this is known as an intersection type.)
1514+
* When a ``TypeGuard`` function returns ``False``, type checkers cannot narrow the type of
1515+
the variable at all. When a ``TypeIs`` function returns ``False``, type checkers can narrow
1516+
the type of the variable to exclude the ``TypeIs`` type.
14531517

14541518
.. versionadded:: 3.10
14551519

Doc/whatsnew/3.13.rst

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ Interpreter improvements:
8787
Performance improvements are modest -- we expect to be improving this
8888
over the next few releases.
8989

90+
New typing features:
91+
92+
* :pep:`742`: :data:`typing.TypeIs` was added, providing more intuitive
93+
type narrowing behavior.
9094

9195
New Features
9296
============
@@ -192,13 +196,6 @@ Other Language Changes
192196

193197
(Contributed by Sebastian Pipping in :gh:`115623`.)
194198

195-
* When :func:`asyncio.TaskGroup.create_task` is called on an inactive
196-
:class:`asyncio.TaskGroup`, the given coroutine will be closed (which
197-
prevents a :exc:`RuntimeWarning` about the given coroutine being
198-
never awaited).
199-
200-
(Contributed by Arthur Tacca and Jason Zhang in :gh:`115957`.)
201-
202199
* The :func:`ssl.create_default_context` API now includes
203200
:data:`ssl.VERIFY_X509_PARTIAL_CHAIN` and :data:`ssl.VERIFY_X509_STRICT`
204201
in its default flags.
@@ -296,6 +293,33 @@ asyncio
296293
with the tasks being completed.
297294
(Contributed by Justin Arthur in :gh:`77714`.)
298295

296+
* When :func:`asyncio.TaskGroup.create_task` is called on an inactive
297+
:class:`asyncio.TaskGroup`, the given coroutine will be closed (which
298+
prevents a :exc:`RuntimeWarning` about the given coroutine being
299+
never awaited).
300+
(Contributed by Arthur Tacca and Jason Zhang in :gh:`115957`.)
301+
302+
* Improved behavior of :class:`asyncio.TaskGroup` when an external cancellation
303+
collides with an internal cancellation. For example, when two task groups
304+
are nested and both experience an exception in a child task simultaneously,
305+
it was possible that the outer task group would hang, because its internal
306+
cancellation was swallowed by the inner task group.
307+
308+
In the case where a task group is cancelled externally and also must
309+
raise an :exc:`ExceptionGroup`, it will now call the parent task's
310+
:meth:`~asyncio.Task.cancel` method. This ensures that a
311+
:exc:`asyncio.CancelledError` will be raised at the next
312+
:keyword:`await`, so the cancellation is not lost.
313+
314+
An added benefit of these changes is that task groups now preserve the
315+
cancellation count (:meth:`asyncio.Task.cancelling`).
316+
317+
In order to handle some corner cases, :meth:`asyncio.Task.uncancel` may now
318+
reset the undocumented ``_must_cancel`` flag when the cancellation count
319+
reaches zero.
320+
321+
(Inspired by an issue reported by Arthur Tacca in :gh:`116720`.)
322+
299323
* Add :meth:`asyncio.Queue.shutdown` (along with
300324
:exc:`asyncio.QueueShutDown`) for queue termination.
301325
(Contributed by Laurie Opperman and Yves Duprat in :gh:`104228`.)
@@ -2006,6 +2030,11 @@ Removed
20062030

20072031
(Contributed by Victor Stinner in :gh:`105182`.)
20082032

2033+
* Remove private ``_PyObject_FastCall()`` function:
2034+
use ``PyObject_Vectorcall()`` which is available since Python 3.8
2035+
(:pep:`590`).
2036+
(Contributed by Victor Stinner in :gh:`106023`.)
2037+
20092038
* Remove ``cpython/pytime.h`` header file: it only contained private functions.
20102039
(Contributed by Victor Stinner in :gh:`106316`.)
20112040

Lib/asyncio/taskgroups.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,6 @@ async def __aexit__(self, et, exc, tb):
7777
propagate_cancellation_error = exc
7878
else:
7979
propagate_cancellation_error = None
80-
if self._parent_cancel_requested:
81-
# If this flag is set we *must* call uncancel().
82-
if self._parent_task.uncancel() == 0:
83-
# If there are no pending cancellations left,
84-
# don't propagate CancelledError.
85-
propagate_cancellation_error = None
8680

8781
if et is not None:
8882
if not self._aborting:
@@ -130,6 +124,13 @@ async def __aexit__(self, et, exc, tb):
130124
if self._base_error is not None:
131125
raise self._base_error
132126

127+
if self._parent_cancel_requested:
128+
# If this flag is set we *must* call uncancel().
129+
if self._parent_task.uncancel() == 0:
130+
# If there are no pending cancellations left,
131+
# don't propagate CancelledError.
132+
propagate_cancellation_error = None
133+
133134
# Propagate CancelledError if there is one, except if there
134135
# are other errors -- those have priority.
135136
if propagate_cancellation_error is not None and not self._errors:
@@ -139,6 +140,12 @@ async def __aexit__(self, et, exc, tb):
139140
self._errors.append(exc)
140141

141142
if self._errors:
143+
# If the parent task is being cancelled from the outside
144+
# of the taskgroup, un-cancel and re-cancel the parent task,
145+
# which will keep the cancel count stable.
146+
if self._parent_task.cancelling():
147+
self._parent_task.uncancel()
148+
self._parent_task.cancel()
142149
# Exceptions are heavy objects that can have object
143150
# cycles (bad for GC); let's not keep a reference to
144151
# a bunch of them.

Lib/asyncio/tasks.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,8 @@ def uncancel(self):
255255
"""
256256
if self._num_cancels_requested > 0:
257257
self._num_cancels_requested -= 1
258+
if self._num_cancels_requested == 0:
259+
self._must_cancel = False
258260
return self._num_cancels_requested
259261

260262
def __eager_start(self):

0 commit comments

Comments
 (0)