Skip to content

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

Description

@yilei

Bug report

Bug description:

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

importcontextlibimportctypesimportsysimportthreadingimporttimeclassInterruptible:
def__init__(self, seconds: float):
self.seconds=secondsself.finished=Falsedef__enter__(self):
self._thread=threading.Thread(
target=self._interrupt_in,
args=(threading.get_ident(), self.seconds),
daemon=True,
)
self._thread.start()
returnselfdef__exit__(self, exc_type, exc_value, traceback):
self.finished=Truedef_interrupt_in(self, tid: int, seconds: float) ->None:
time.sleep(self.seconds)
print(f"Slept for {seconds}")
ifself.finished:
print("Finished, not interrupting")
returnelse:
print("Not finished, interrupting")
ctypes.pythonapi.PyThreadState_SetAsyncExc(
ctypes.c_long(tid), ctypes.py_object(TimeoutError)
)
defbusy_loop():
s=time.monotonic()
whiletime.monotonic() -s<0.5:
passprint(f"Python version: {sys.version}")
foriinrange(100):
try:
withInterruptible(seconds=0.1):
s=time.monotonic()
whiletime.monotonic() -s<0.5:
pass# NOTE: If we replace above block with the equivalent busy_loop(), it works.# busy_loop()exceptTimeoutError:
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

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.13bugs and security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)topic-C-APItype-bugAn unexpected behavior, bug, or error

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions