MagicStack / MagicStack/uvloop
uvloop debug mode crashes on Python 3.14 during async generator finalization (traceback.walk_stack sees non-frame object)
Nessuno ha ancora preso questa issue.
- Lingua principale
- Cython
- Stelle
- 11.9k
- Fork
- 616
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Descrizione
## Overview
When an async generator is finalized under uvloop debug mode, uvloop’s extract_stack() calls traceback.walk_stack() on Python 3.14. The stack walker receives a non-frame object (e.g. _asyncio.TaskStepMethWrapper) and crashes (traceback.walk_stack_generator) or raises AttributeError. It's worth mentioning this does NOT happen with `asyncio` debug.
## Environment
- uvloop 0.22.1
- Python 3.14.0
- macOS (Darwin 24.6.0, arm64)
- Debug mode enabled (PYTHONASYNCIODEBUG=1 or uvloop.run(debug=True))
## Reproduction
1. Set `PYTHONFAULTHANDLER=1 PYTHONASYNCIODEBUG=1` as environment variables.
2. Run the following script with `python3 uvloop_python314_bug_repro.py`
```python
"""uvloop_python314_bug_repro.py
Minimal repro: uvloop 0.22.1 + Python 3.14 debug async-gen finalization crash.
This creates a nested async-generator chain and breaks on a "[DONE]" sentinel
without closing the inner iterator. Under uvloop debug, async generator
finalization schedules aclose() via call_soon_threadsafe, which calls
extract_stack(). On Python 3.14, traceback.walk_stack sees a non-frame object
(for example, _asyncio.TaskStepMethWrapper) and raises AttributeError. The
unpatched build can segfault in traceback.walk_stack_generator.
Run:
uv run --active --no-sync python uvloop_python314_bug_repro.py
"""
from __future__ import annotations
import asyncio
import gc
import os
import sys
from typing import AsyncIterator
import uvloop
os.environ["PYTHONASYNCIODEBUG"] = "1"
REPEATS = 1
class FakeResponse:
async def aiter_raw(self) -> AsyncIterator[bytes]:
payloads = [
b"data: one\n\n",
b"data: [DONE]\n\n",
b"data: trailing\n\n",
]
for payload in payloads:
yield payload
await asyncio.sleep(0)
async def aclose(self) -> None:
await asyncio.sleep(0)
class Decoder:
async def _aiter_chunks(self, source: AsyncIterator[bytes]) -> AsyncIterator[bytes]:
async for chunk in source:
yield chunk
async def aiter_bytes(self, source: AsyncIterator[bytes]) -> AsyncIterator[str]:
async for chunk in self._aiter_chunks(source):
for line in chunk.splitlines():
yield line.decode("utf-8")
class AsyncStream:
def __init__(self) -> None:
self.response = FakeResponse()
self.decoder = Decoder()
self._iterator: AsyncIterator[str] = self.__stream__()
def __aiter__(self) -> AsyncIterator[str]:
return self._iterator
async def __stream__(self) -> AsyncIterator[str]:
iterator = self._iter_events()
try:
async for event in iterator:
if event.endswith("[DONE]"):
break
yield event
finally:
await self.response.aclose()
async def _iter_events(self) -> AsyncIterator[str]:
async for event in self.decoder.aiter_bytes(self.response.aiter_raw()):
yield event
async def exercise_once() -> None:
stream = AsyncStream()
async for _ in stream:
pass
del stream
gc.collect()
await asyncio.sleep(0)
async def main() -> None:
loop = asyncio.get_running_loop()
print(f"Python: {sys.version}")
print(f"Loop: {type(loop).__name__}")
print(f"Debug: {loop.get_debug()}")
for _ in range(REPEATS):
await exercise_once()
await asyncio.sleep(0.1)
if __name__ == "__main__":
print(f"uvloop: {uvloop.__version__}")
uvloop.run(main(), debug=True)
```
## Expected
Clean exit (debug stack capture should never crash the process).
## Actual
```console
Fatal Python error: Segmentation fault
File ".../traceback.py", line 393 in walk_stack_generator
...
```
## Ideas
Python 3.14 changed frame introspection behavior. During async generator finalization, uvloop's debug stack capture (`cbhandles.pyx:extract_stack`) calls `traceback.walk_stack()`. The stack can contain non-frame objects like `_asyncio.TaskStepMethWrapper` which lack the `f_back` attribute that `walk_stack_generator` expects.
The crash path:
1. Async generator breaks early (e.g., on `[DONE]` sentinel)
2. GC finalizes the generator
3. Finalizer schedules `aclose()` via `call_soon_threadsafe`
4. uvloop debug captures stack → `traceback.walk_stack()` → crash
This happens during async generator finalization; the finalizer schedules aclose() via call_soon_threadsafe, which triggers uvloop’s debug stack capture. A defensive guard before calling traceback.walk_stack (e.g. require a real frame, or swallow AttributeError/TypeError) avoids the crash.
## Workaround
Explicitly close/break async generator chains before they go out of scope, or disable uvloop debug mode on Python 3.14.
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Direzione di ricerca
Inizia da cbhandles.pyx:extract_stack e dal comportamento di traceback.walk_stack in Python 3.14, quindi riproduci il problema usando uvloop_python314_bug_repro.py con la modalità di debug abilitata. Traccia la finalizzazione dei generatori asincroni attraverso call_soon_threadsafe e verifica che la riproduzione termini correttamente senza un AttributeError o un segmentation fault.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Stack tecnologico
- python
- Ambito
- backend
- Tipo di issue
- Bug
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Stato di attività
- Tranquilla
- Chiarezza
- Abbastanza chiara
- Idoneità per principianti
- 48/100