-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Keep escaped generator frames and wrap asyncgen throw #8697
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
684fc2f
7363111
4982c2f
454b050
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -132,14 +132,14 @@ impl PyAsyncGen { | |
|
|
||
| #[pygetset] | ||
| fn ag_await(&self, _vm: &VirtualMachine) -> Option<PyObjectRef> { | ||
| self.inner.frame().yield_from_target() | ||
| self.inner.frame_opt().and_then(|f| f.yield_from_target()) | ||
| } | ||
| #[pygetset] | ||
| fn ag_frame(&self, _vm: &VirtualMachine) -> Option<FrameObjectRef> { | ||
| if self.inner.closed() { | ||
| None | ||
| } else { | ||
| Some(self.inner.frame()) | ||
| self.inner.frame_opt() | ||
| } | ||
| } | ||
| #[pygetset] | ||
|
|
@@ -148,7 +148,11 @@ impl PyAsyncGen { | |
| } | ||
| #[pygetset] | ||
| fn ag_code(&self, _vm: &VirtualMachine) -> PyRef<PyCode> { | ||
| self.inner.frame().iframe().code().to_owned() | ||
| self.inner.code() | ||
| } | ||
| #[pygetset] | ||
| fn ag_suspended(&self, _vm: &VirtualMachine) -> bool { | ||
| self.inner.suspended() | ||
|
Comment on lines
+154
to
+155
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an async generator catches Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| #[pyclassmethod] | ||
|
|
@@ -718,8 +722,6 @@ impl PyAnextAwaitable { | |
| if let Some(generator) = wrapped.downcast_ref::<PyGenerator>() | ||
| && generator | ||
| .as_coro() | ||
| .frame() | ||
| .iframe() | ||
| .code() | ||
| .flags | ||
| .contains(crate::bytecode::CodeFlags::ITERABLE_COROUTINE) | ||
|
|
@@ -841,7 +843,9 @@ impl Destructor for PyAsyncGen { | |
|
|
||
| impl Drop for PyAsyncGen { | ||
| fn drop(&mut self) { | ||
| self.inner.frame().clear_generator(); | ||
| if let Some(frame) = self.inner.frame_opt() { | ||
| frame.clear_generator(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,7 @@ | |
|
|
||
| */ | ||
|
|
||
| use super::{PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyIntRef, PyStrRef}; | ||
| use super::{PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyIntRef, PyStrRef}; | ||
| use crate::{ | ||
| Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, | ||
| class::PyClassImpl, | ||
|
|
@@ -778,10 +778,8 @@ impl Py<FrameObject> { | |
| ); | ||
| match owner { | ||
| FrameOwner::Generator => { | ||
| // Generator frame: check if suspended (lasti > 0 means | ||
| // FRAME_SUSPENDED). lasti == 0 means FRAME_CREATED and | ||
| // can be cleared. Finalize the owner so a never-started | ||
| // coroutine emits its never-awaited warning. | ||
| // FRAME_SUSPENDED (lasti > 0) cannot be cleared. FRAME_CREATED | ||
| // and finished frames go through the owner finalizer. | ||
| if self.lasti() != 0 { | ||
| return Err(vm.new_runtime_error("cannot clear a suspended frame")); | ||
| } | ||
|
|
@@ -790,17 +788,16 @@ impl Py<FrameObject> { | |
| let _ = PyCoroutine::del(coro, vm); | ||
| } else if let Some(async_gen) = owner.downcast_ref::<PyAsyncGen>() { | ||
| let _ = PyAsyncGen::del(async_gen, vm); | ||
| } else if let Some(generator) = owner.downcast_ref::<PyGenerator>() { | ||
| let _ = PyGenerator::del(generator, vm); | ||
| } | ||
|
Comment on lines
789
to
793
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
| } | ||
| return Ok(()); | ||
| } | ||
| FrameOwner::Thread => { | ||
| // Thread-owned frame: always executing, cannot clear. | ||
| return Err(vm.new_runtime_error("cannot clear an executing frame")); | ||
| } | ||
| FrameOwner::FrameObject => { | ||
| // Check if this materialized frame is backed by a live | ||
| // stack-allocated iframe — if so, the frame is executing. | ||
| if !self.find_live_source_iframe().is_null() { | ||
| return Err(vm.new_runtime_error("cannot clear an executing frame")); | ||
| } | ||
|
|
@@ -822,27 +819,17 @@ impl Py<FrameObject> { | |
| // Clear the evaluation stack and cell references | ||
| self.clear_stack_and_cells(); | ||
|
|
||
| let cold = self.iframe().cold(); | ||
| let temporary_refs = { | ||
| let mut guard = cold.temporary_refs.lock(); | ||
| core::mem::take(&mut *guard) | ||
| }; | ||
| let extra_locals = { | ||
| let mut guard = cold.f_extra_locals.lock(); | ||
| guard.take() | ||
| }; | ||
| let locals_cache = { | ||
| let mut guard = cold.f_locals_cache.lock(); | ||
| guard.take() | ||
| }; | ||
| let overwritten = { | ||
| let mut guard = cold.f_overwritten_fast_locals.lock(); | ||
| core::mem::take(&mut *guard) | ||
| }; | ||
| let retained_back = { | ||
| let mut guard = cold.retained_back.lock(); | ||
| guard.take() | ||
| }; | ||
| let (temporary_refs, extra_locals, locals_cache, overwritten, retained_back) = | ||
| match self.iframe().cold_opt() { | ||
| Some(cold) => ( | ||
| core::mem::take(&mut *cold.temporary_refs.lock()), | ||
| cold.f_extra_locals.lock().take(), | ||
| cold.f_locals_cache.lock().take(), | ||
| core::mem::take(&mut *cold.f_overwritten_fast_locals.lock()), | ||
| cold.retained_back.lock().take(), | ||
| ), | ||
| None => (Vec::new(), None, None, Vec::new(), None), | ||
| }; | ||
| drop(( | ||
| fastlocals, | ||
| temporary_refs, | ||
|
|
@@ -858,7 +845,6 @@ impl Py<FrameObject> { | |
| #[pygetset] | ||
| fn f_locals(&self, vm: &VirtualMachine) -> PyResult { | ||
| if self.uses_locals_proxy(vm)? { | ||
| self.mark_escaped(); | ||
| let proxy = crate::builtins::FrameLocalsProxy::new(self.to_owned()); | ||
| Ok(proxy.into_ref(&vm.ctx).into()) | ||
| } else { | ||
|
|
@@ -895,7 +881,6 @@ impl Py<FrameObject> { | |
| // Check retained_back for frames whose callers have returned | ||
| let retained = self.iframe().cold().retained_back.lock().clone(); | ||
| if let Some(frame) = retained { | ||
| frame.mark_escaped(); | ||
| return Some(frame); | ||
| } | ||
| return None; | ||
|
|
@@ -911,7 +896,6 @@ impl Py<FrameObject> { | |
| if core::ptr::eq(cur, prev) { | ||
| let iframe_ref = unsafe { &*cur }; | ||
| let fo = iframe_ref.materialize(vm); | ||
| fo.mark_escaped(); | ||
| return Some(fo.to_owned()); | ||
| } | ||
| cur = unsafe { (*cur).previous() }; | ||
|
|
@@ -921,7 +905,6 @@ impl Py<FrameObject> { | |
| // The caller already returned — check retained_back | ||
| let retained = self.iframe().cold().retained_back.lock().clone(); | ||
| if let Some(frame) = retained { | ||
| frame.mark_escaped(); | ||
| return Some(frame); | ||
| } | ||
|
|
||
|
|
@@ -936,13 +919,11 @@ impl Py<FrameObject> { | |
| let prev_ref = unsafe { &*prev }; | ||
| // Fast path: already materialized. | ||
| if let Some(fo) = prev_ref.frame_obj() { | ||
| fo.mark_escaped(); | ||
| return Some(fo.to_owned()); | ||
| } | ||
| // Slow path: copy the whole chain, linked through retained_back. | ||
| // SAFETY: the world is stopped, so the owning thread is parked. | ||
| let fo = unsafe { prev_ref.materialize_detached_chain(vm) }; | ||
| fo.mark_escaped(); | ||
| return Some(fo); | ||
| } | ||
|
|
||
|
|
||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When an async generator is paused inside an inner
await,running_asyncremains true for the outstandinganext()/asend(), butRunningGuardhas already resetinner.running()to false. With this newly exposed property,inspect.getasyncgenstate()consequently seesag_running == Falseandag_suspended == Trueand reportsAGEN_SUSPENDED; CPython reportsAGEN_RUNNINGuntil that outstanding asynchronous iteration completes. The public running state needs to account forrunning_asyncbefore enabling this state API.Useful? React with 👍 / 👎.