Skip to content

Commit 6eb348c

Browse files
committed
sys.set_asyncgen_hook
1 parent 3fbf54f commit 6eb348c

File tree

2 files changed

+64
-13
lines changed

2 files changed

+64
-13
lines changed

Lib/test/test_asyncgen.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1485,8 +1485,6 @@ async def main():
14851485

14861486
self.assertEqual(messages, [])
14871487

1488-
# TODO: RUSTPYTHON, ValueError: not enough values to unpack (expected 1, got 0)
1489-
@unittest.expectedFailure
14901488
def test_async_gen_asyncio_shutdown_exception_01(self):
14911489
messages = []
14921490

crates/vm/src/builtins/asyncgenerator.rs

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::{
33
AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
44
builtins::PyBaseExceptionRef,
55
class::PyClassImpl,
6+
common::lock::PyMutex,
67
coroutine::Coro,
78
frame::FrameRef,
89
function::OptionalArg,
@@ -17,6 +18,10 @@ use crossbeam_utils::atomic::AtomicCell;
1718
pub struct PyAsyncGen {
1819
inner: Coro,
1920
running_async: AtomicCell<bool>,
21+
// CPython: ag_hooks_inited - whether hooks have been initialized
22+
ag_hooks_inited: AtomicCell<bool>,
23+
// CPython: ag_origin_or_finalizer - stores the finalizer callback
24+
ag_finalizer: PyMutex<Option<PyObjectRef>>,
2025
}
2126
type PyAsyncGenRef = PyRef<PyAsyncGen>;
2227

@@ -37,6 +42,46 @@ impl PyAsyncGen {
3742
Self {
3843
inner: Coro::new(frame, name, qualname),
3944
running_async: AtomicCell::new(false),
45+
ag_hooks_inited: AtomicCell::new(false),
46+
ag_finalizer: PyMutex::new(None),
47+
}
48+
}
49+
50+
/// Initialize async generator hooks (CPython: async_gen_init_hooks).
51+
/// Returns Ok(()) if successful, Err if firstiter hook raised an exception.
52+
fn init_hooks(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<()> {
53+
if zelf.ag_hooks_inited.load() {
54+
return Ok(());
55+
}
56+
57+
zelf.ag_hooks_inited.store(true);
58+
59+
// Get and store finalizer from thread-local storage
60+
let finalizer = crate::vm::thread::ASYNC_GEN_FINALIZER.with_borrow(|f| f.as_ref().cloned());
61+
if let Some(finalizer) = finalizer {
62+
*zelf.ag_finalizer.lock() = Some(finalizer);
63+
}
64+
65+
// Call firstiter hook
66+
let firstiter = crate::vm::thread::ASYNC_GEN_FIRSTITER.with_borrow(|f| f.as_ref().cloned());
67+
if let Some(firstiter) = firstiter {
68+
let obj: PyObjectRef = zelf.to_owned().into();
69+
firstiter.call((obj,), vm)?;
70+
}
71+
72+
Ok(())
73+
}
74+
75+
/// Call finalizer hook if set (CPython: gen_dealloc for async generators)
76+
#[allow(dead_code)]
77+
fn call_finalizer(zelf: &Py<Self>, vm: &VirtualMachine) {
78+
let finalizer = zelf.ag_finalizer.lock().clone();
79+
if let Some(finalizer) = finalizer {
80+
if !zelf.inner.closed.load() {
81+
// Call finalizer, ignore any errors (CPython: PyErr_WriteUnraisable)
82+
let obj: PyObjectRef = zelf.to_owned().into();
83+
let _ = finalizer.call((obj,), vm);
84+
}
4085
}
4186
}
4287

@@ -91,17 +136,23 @@ impl PyRef<PyAsyncGen> {
91136
}
92137

93138
#[pymethod]
94-
fn __anext__(self, vm: &VirtualMachine) -> PyAsyncGenASend {
95-
Self::asend(self, vm.ctx.none(), vm)
139+
fn __anext__(self, vm: &VirtualMachine) -> PyResult<PyAsyncGenASend> {
140+
PyAsyncGen::init_hooks(&self, vm)?;
141+
Ok(PyAsyncGenASend {
142+
ag: self,
143+
state: AtomicCell::new(AwaitableState::Init),
144+
value: vm.ctx.none(),
145+
})
96146
}
97147

98148
#[pymethod]
99-
const fn asend(self, value: PyObjectRef, _vm: &VirtualMachine) -> PyAsyncGenASend {
100-
PyAsyncGenASend {
149+
fn asend(self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyAsyncGenASend> {
150+
PyAsyncGen::init_hooks(&self, vm)?;
151+
Ok(PyAsyncGenASend {
101152
ag: self,
102153
state: AtomicCell::new(AwaitableState::Init),
103154
value,
104-
}
155+
})
105156
}
106157

107158
#[pymethod]
@@ -111,8 +162,9 @@ impl PyRef<PyAsyncGen> {
111162
exc_val: OptionalArg,
112163
exc_tb: OptionalArg,
113164
vm: &VirtualMachine,
114-
) -> PyAsyncGenAThrow {
115-
PyAsyncGenAThrow {
165+
) -> PyResult<PyAsyncGenAThrow> {
166+
PyAsyncGen::init_hooks(&self, vm)?;
167+
Ok(PyAsyncGenAThrow {
116168
ag: self,
117169
aclose: false,
118170
state: AtomicCell::new(AwaitableState::Init),
@@ -121,12 +173,13 @@ impl PyRef<PyAsyncGen> {
121173
exc_val.unwrap_or_none(vm),
122174
exc_tb.unwrap_or_none(vm),
123175
),
124-
}
176+
})
125177
}
126178

127179
#[pymethod]
128-
fn aclose(self, vm: &VirtualMachine) -> PyAsyncGenAThrow {
129-
PyAsyncGenAThrow {
180+
fn aclose(self, vm: &VirtualMachine) -> PyResult<PyAsyncGenAThrow> {
181+
PyAsyncGen::init_hooks(&self, vm)?;
182+
Ok(PyAsyncGenAThrow {
130183
ag: self,
131184
aclose: true,
132185
state: AtomicCell::new(AwaitableState::Init),
@@ -135,7 +188,7 @@ impl PyRef<PyAsyncGen> {
135188
vm.ctx.none(),
136189
vm.ctx.none(),
137190
),
138-
}
191+
})
139192
}
140193
}
141194

0 commit comments

Comments
 (0)