python / python/cpython

Python 3.13: PyThreadState_SetAsyncExc might deliver the exception outside of the thread

Ouverte
#139,622 6 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

3.13 interpreter-core topic-C-API type-bug
Langage dominant
Python
Étoiles
77.2k
Forks
35.9k
Métriques de merge des PR
Métriques de PR en attente

Description

Bug report

Bug description:

I have the following naive demo that uses PyThreadState_SetAsyncExc to implement a time-limited interruptible context manager:

import contextlib
import ctypes
import sys
import threading
import time


class Interruptible:
    def __init__(self, seconds: float):
        self.seconds = seconds
        self.finished = False

    def __enter__(self):
        self._thread = threading.Thread(
            target=self._interrupt_in,
            args=(threading.get_ident(), self.seconds),
            daemon=True,
        )
        self._thread.start()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.finished = True

    def _interrupt_in(self, tid: int, seconds: float) -> None:
        time.sleep(self.seconds)
        print(f"Slept for {seconds}")
        if self.finished:
            print("Finished, not interrupting")
            return
        else:
            print("Not finished, interrupting")
            ctypes.pythonapi.PyThreadState_SetAsyncExc(
                ctypes.c_long(tid), ctypes.py_object(TimeoutError)
            )


def busy_loop():
    s = time.monotonic()
    while time.monotonic() - s < 0.5:
        pass


print(f"Python version: {sys.version}")

for i in range(100):
    try:
        with Interruptible(seconds=0.1):
            s = time.monotonic()
            while time.monotonic() - s < 0.5:
                pass
            # NOTE: If we replace above block with the equivalent busy_loop(), it works.
            # busy_loop()
    except TimeoutError:
        print(f"Iteration {i + 1}: ✓ TimeoutError caught")
    else:
        print(f"Iteration {i + 1}: ✗ TimeoutError NOT raised")
    time.sleep(0.01)  # Small delay between iterations

In Python 3.13, this might fail with something like:

❯ python /tmp/demo.py
Python version: 3.13.7 (main, Sep 29 2025, 18:42:10) [GCC 11.4.0]
Slept for 0.1
Not finished, interrupting
Iteration 1: ✓ TimeoutError caught
Slept for 0.1
Not finished, interrupting
Iteration 2: ✓ TimeoutError caught
Slept for 0.1
Not finished, interrupting
Iteration 3: ✓ TimeoutError caught
Slept for 0.1
Not finished, interrupting
Iteration 4: ✓ TimeoutError caught
Slept for 0.1
Not finished, interrupting
Traceback (most recent call last):
  File "/tmp/demo.py", line 50, in <module>
    while time.monotonic() - s < 0.5:
          ^^^^^^^^^^^^^^^^^^^^^^^^^^
TimeoutError

Meaning the TimeoutError is raised but not caught by the try/except block. A few other observations:

  1. If I wrap the "work" inside the context manager to a wrapper function busy_loop(), it's NOT reproducible.
  2. I tested Python 3.11.13, 3.12.11, 3.13.7, and 3.14.0rc3, it is only reproducible in Python 3.13.7.
CPython versions tested on:

3.13, 3.12, 3.14, 3.11

Operating systems tested on:

Linux

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez par /tmp/demo.py et le point d’entrée PyThreadState_SetAsyncExc, reproduisez le problème avec Python 3.13.7 et comparez Python 3.11, 3.12 et 3.14. La tâche est terminée lorsque vous expliquez pourquoi TimeoutError échappe au gestionnaire environnant et que vous ajoutez un test de régression pour le comportement attendu de livraison de l’exception.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
python
Domaine
backend
Type d'issue
Bug
Difficulté
4/5
Temps estimé
3-5 jours
Activité
À l'abandon
Clarté
Plutôt claire
Accessibilité débutants
42/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.