python / python/cpython

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

オープン
#139,622 コメント 6 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

3.13 interpreter-core topic-C-API type-bug
主要言語
Python
スター
77.2k
フォーク
35.9k
PR マージ指標
PR 指標を取得中

説明

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

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

/tmp/demo.py とエントリーポイント PyThreadState_SetAsyncExc から始め、Python 3.13.7 で再現し、Python 3.11、3.12、3.14 と比較します。TimeoutError が周囲のハンドラーをすり抜ける理由を説明し、期待される例外配信の挙動に対するリグレッションテストを追加できれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
python
領域
backend
issue の種類
バグ
難易度
4/5
見積もり時間
3〜5日
活発さ
停滞
明瞭さ
おおむね明確
初心者へのやさしさ
42/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。