python / python/cpython

Optionally prevent child tasks from being cancelled in `asyncio.TaskGroup`

Offen
#101,581 11 Kommentare 3 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

stdlib topic-asyncio type-feature
Vorherrschende Sprache
Python
Sterne
77.2k
Forks
36k
Ø Merge
1 T. 9 Std.
Gemergte PRs (30 T.)
558

Beschreibung

Feature or enhancement

Add a flag to asyncio.TaskGroup to control whether, if a child task crashes, other should be cancelled or not.

Pitch

Currently asyncio.TaskGroup always cancels all child tasks if one fails. Even if that's the most common use case, I'd like to be able to switch from the current behaviour to prevent child tasks cancellation when one failure occurs and raise an ExceptionGroup with all exceptions raised (only after other tasks completed).

  • Excpetions raised in the async with block still have to cause child tasks to be canceled.
  • SystemExit and KeyboardInterrupt raised in a child task still cancel other tasks.

Example usage:

async def zero_div():
    1 / 0

async def wait_some(d):
    await asyncio.sleep(d)
    print(f'finished after {d}')

async def main():
    async with asyncio.TaskGroup(abort_on_first_exception=False) as tg:
        tg.create_task(wait_some(2))
        tg.create_task(wait_some(3))
        tg.create_task(zero_div())

asyncio.run(main())
# Should print out:
# finished after 2
# finished after 3
# and then raise an `ExceptionGroup` with a `ZeroDivisionError`

Looking at asyncio.TaskGroup source code, it seems that it could be achieved by adding the abort_on_first_exception (or whatever it should be named) flag to the __init__ method and then modify _on_task_done as follow:

class TaskGroup:

    def __init__(self, abort_on_first_exception=True):
        ...
        self._abort_on_first_exception = abort_on_first_exception

    ...

    def _on_task_done(self, task):
        ...
        self._errors.append(exc)
        is_base_error = self._is_base_error(exc)
        if is_base_error and self._base_error is None:
            self._base_error = exc

        if self._parent_task.done():
            # Not sure if this case is possible, but we want to handle
            # it anyways.
            self._loop.call_exception_handler({
                'message': f'Task {task!r} has errored out but its parent '
                           f'task {self._parent_task} is already completed',
                'exception': exc,
                'task': task,
            })
            return

        if not self._aborting and not self._parent_cancel_requested:
            #comment skipped for brevity
            if is_base_error or self._abort_on_first_exception:
                self._abort()
                self._parent_cancel_requested = True
                self._parent_task.cancel()

If it's reasonable to have it in asyncio, I'll be glad to submit a PR for it.

Previous discussion

Post on discuss

Linked PRs
  • gh-101648
  • gh-105011

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne mit asyncio.TaskGroup und seinem _on_task_done-Pfad, prüfe anschließend die verknüpften PRs gh-101648 und gh-105011 sowie die vorherige Diskussion, bevor du entscheidest, ob diese API in asyncio gehört. Das Issue definiert das erwartete Verhalten für gewöhnliche Ausnahmen, Ausnahmen im async-with-Body, SystemExit und KeyboardInterrupt; abgeschlossen ist die Aufgabe, wenn ein abgestimmtes Design und entsprechende Implementierungstests vorhanden sind.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
python
Bereich
backend
Issue-Typ
Feature
Schwierigkeit
5/5
Geschätzter Aufwand
Über eine Woche
Aktivitätsstatus
Veraltet
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
20/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.