Keep escaped generator frames and wrap asyncgen throw (#8697)

* Keep escaped generator frames and wrap asyncgen throw

Mark gi_frame/cr_frame/ag_frame as escaped so locals survive
deallocation, and let frame.clear() drop those locals after
owner finalize. Route throw() through finalize_send_result so
StopAsyncIteration is wrapped like send(). Add ag_suspended
and drop never-started frame locals on close().

Assisted-by: Grok:grok-4.6

* Store escaped on iframe; skip unused cold alloc

Keep the escaped flag on InterpreterFrame so mark_escaped
and has_escaped do not allocate FrameColdData. Clear optional
cold fields only when they exist. Transfer frame owner in
close() the same way send/throw already do.

Assisted-by: Grok:grok-4.6

* Replace escaped flag with take_ownership

gi_frame returns the frame object. Close uses a uniquely-referenced
check to clear locals or take_ownership onto a husk. frame.clear()
finalizes a generator-owned frame and only clears FRAME_OBJECT
frames. close() leaves the generator suspended when it yields on
GeneratorExit.

Assisted-by: Grok:grok-4.6

* Drop frame mutex; steal frame_obj atomically

take_ownership stores None in an atomic slot instead of locking
and swapping a husk. gi_code reads the kept executable. Resume
exclusivity stays on the running claim.

Assisted-by: Grok:grok-4.6
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2026-09-13 21:18:22 +09:00
1 parent f18edb72f1
commit e18facff3e
10 files changed
+121 -147

No files matched your search

-1
View File
@@ -130,7 +130,6 @@ class AsyncContextManagerTestCase(unittest.TestCase):
raise ZeroDivisionError()
self.assertEqual(state, [1, 42, 999])
@unittest.expectedFailure # TODO: RUSTPYTHON
@_async_test
async def test_contextmanager_traceback(self):
@asynccontextmanager
-2
View File
@@ -302,7 +302,6 @@ class GeneratorTest(unittest.TestCase):
self.assertEqual([1, 2], list(i for i in C()))
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: False is not true
def test_close_clears_frame(self):
# gh-142766: Test that closing a generator clears its frame
class DetectDelete:
@@ -721,7 +720,6 @@ class GeneratorCloseTest(unittest.TestCase):
# See https://github.com/python/cpython/issues/125723
class GeneratorDeallocTest(unittest.TestCase):
@unittest.expectedFailure # TODO: RUSTPYTHON; frame uses shared Arc, no ownership transfer
def test_frame_outlives_generator(self):
def g1():
a = 42
-5
View File
@@ -2930,17 +2930,14 @@ class TestGetAsyncGenState(unittest.IsolatedAsyncioTestCase):
def _asyncgenstate(self):
return inspect.getasyncgenstate(self.asyncgen)
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'async_generator' object has no attribute 'ag_suspended'
def test_created(self):
self.assertEqual(self._asyncgenstate(), inspect.AGEN_CREATED)
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'async_generator' object has no attribute 'ag_suspended'
async def test_suspended(self):
value = await anext(self.asyncgen)
self.assertEqual(self._asyncgenstate(), inspect.AGEN_SUSPENDED)
self.assertEqual(value, 0)
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'async_generator' object has no attribute 'ag_suspended'
async def test_closed_after_exhaustion(self):
countdown = 7
with self.assertRaises(StopAsyncIteration):
@@ -2949,13 +2946,11 @@ class TestGetAsyncGenState(unittest.IsolatedAsyncioTestCase):
self.assertEqual(countdown, 1)
self.assertEqual(self._asyncgenstate(), inspect.AGEN_CLOSED)
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'async_generator' object has no attribute 'ag_suspended'
async def test_closed_after_immediate_exception(self):
with self.assertRaises(RuntimeError):
await self.asyncgen.athrow(RuntimeError)
self.assertEqual(self._asyncgenstate(), inspect.AGEN_CLOSED)
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'async_generator' object has no attribute 'ag_suspended'
async def test_running(self):
async def running_check_asyncgen():
for number in range(5):
+10 -6
View File
@@ -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()
}
#[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();
}
}
}
+7 -5
View File
@@ -89,14 +89,14 @@ impl PyCoroutine {
#[pygetset]
fn cr_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 cr_frame(&self, _vm: &VirtualMachine) -> Option<FrameObjectRef> {
if self.inner.closed() {
None
} else {
Some(self.inner.frame())
self.inner.frame_opt()
}
}
#[pygetset]
@@ -105,7 +105,7 @@ impl PyCoroutine {
}
#[pygetset]
fn cr_code(&self, _vm: &VirtualMachine) -> PyRef<PyCode> {
self.inner.frame().iframe().code().to_owned()
self.inner.code()
}
#[pygetset]
fn cr_origin(&self, _vm: &VirtualMachine) -> Option<PyTupleRef> {
@@ -179,7 +179,7 @@ impl Destructor for PyCoroutine {
if zelf.inner.closed() || zelf.inner.running() {
return Ok(());
}
if zelf.inner.frame().lasti() == 0 {
if zelf.inner.frame_opt().is_none_or(|f| f.lasti() == 0) {
crate::warn::warn_unawaited_coroutine(zelf.as_object(), &zelf.inner.qualname(), vm);
zelf.inner.closed.store(true);
return Ok(());
@@ -266,7 +266,9 @@ impl IterNext for PyCoroutineWrapper {
impl Drop for PyCoroutine {
fn drop(&mut self) {
self.inner.frame().clear_generator();
if let Some(frame) = self.inner.frame_opt() {
frame.clear_generator();
}
}
}
+16 -35
View File
@@ -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);
}
}
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);
}
+6 -10
View File
@@ -74,7 +74,7 @@ impl PyGenerator {
if self.inner.closed() {
None
} else {
Some(self.inner.frame())
self.inner.frame_opt()
}
}
@@ -85,12 +85,12 @@ impl PyGenerator {
#[pygetset]
fn gi_code(&self, _vm: &VirtualMachine) -> PyRef<PyCode> {
self.inner.frame().iframe().code().to_owned()
self.inner.code()
}
#[pygetset]
fn gi_yieldfrom(&self, _vm: &VirtualMachine) -> Option<PyObjectRef> {
self.inner.frame().yield_from_target()
self.inner.frame_opt().and_then(|f| f.yield_from_target())
}
#[pygetset]
@@ -159,12 +159,6 @@ impl Destructor for PyGenerator {
if zelf.inner.closed() || zelf.inner.running() {
return Ok(());
}
// Generator was never started, just mark as closed
if zelf.inner.frame().lasti() == 0 {
zelf.inner.closed.store(true);
return Ok(());
}
// Throw GeneratorExit to run finally blocks
if let Err(e) = zelf.inner.close(zelf.as_object(), vm) {
vm.run_unraisable(e, None, zelf.as_object().to_owned());
}
@@ -174,7 +168,9 @@ impl Destructor for PyGenerator {
impl Drop for PyGenerator {
fn drop(&mut self) {
self.inner.frame().clear_generator();
if let Some(frame) = self.inner.frame_opt() {
frame.clear_generator();
}
}
}
+69 -46
View File
@@ -1,6 +1,6 @@
use crate::{
AsObject, Py, PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine,
builtins::{PyStrRef, PyTupleRef},
AsObject, Py, PyObject, PyObjectRef, PyRef, PyResult, TryFromObject, VirtualMachine,
builtins::{PyCode, PyStrRef, PyTupleRef},
common::lock::PyMutex,
exceptions::types::PyBaseException,
frame::{ExecutionResult, FrameObject, FrameObjectRef, FrameOwner, InterpreterFrame},
@@ -30,10 +30,13 @@ impl ExecutionResult {
#[derive(Debug)]
pub struct Coro {
frame: FrameObjectRef,
/// Generator iframe, like `gi_iframe`. Cleared to `None` by take_ownership
/// the same way `frame_obj` is stolen from the iframe.
frame: PyAtomicRef<Option<FrameObject>>,
/// `f_executable`; survives `_PyFrame_ClearExceptCode`.
code: PyRef<PyCode>,
pub closed: AtomicCell<bool>, // TODO: https://github.com/RustPython/RustPython/pull/3183#discussion_r720560652
running: AtomicCell<bool>,
// code
// _weakreflist
name: PyMutex<PyStrRef>,
qualname: PyMutex<PyStrRef>,
@@ -42,7 +45,10 @@ pub struct Coro {
unsafe impl Traverse for Coro {
fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
self.frame.traverse(tracer_fn);
if let Some(frame) = self.frame.deref() {
tracer_fn(frame.as_object());
}
self.code.traverse(tracer_fn);
self.name.traverse(tracer_fn);
self.qualname.traverse(tracer_fn);
if let Some(exc) = self.exception.deref() {
@@ -79,8 +85,10 @@ fn gen_name(jen: &PyObject, vm: &VirtualMachine) -> &'static str {
impl Coro {
pub fn new(frame: FrameObjectRef, name: PyStrRef, qualname: PyStrRef) -> Self {
let code = frame.iframe().code().to_owned();
Self {
frame,
frame: Some(frame).into(),
code,
closed: AtomicCell::new(false),
running: AtomicCell::new(false),
exception: PyAtomicRef::from(None),
@@ -89,20 +97,32 @@ impl Coro {
}
}
/// Free the finished frame's locals and stack, unless a frame object has
/// escaped (e.g. through an `f_locals` proxy or `sys._getframe`). An
/// escaped frame husk owns its heap-resident locals and must keep them
/// readable after the generator closes.
fn clear_frame_locals_on_close(&self) {
// Keep locals alive if a durable frame reference escaped (e.g. through
// an `f_locals` proxy or `sys._getframe`): that reference now owns the
// heap-resident locals and must keep them readable after close,
// matching `take_ownership`.
if !self.frame.has_escaped() {
self.frame.clear_locals_and_stack();
/// `_PyFrame_ClearExceptCode`. Clear locals unless another reference
/// still holds the frame object; then take_ownership instead.
fn clear_except_code(&self) {
let Some(frame) = self.frame.deref() else {
return;
};
if frame.as_object().strong_count() == 1 {
frame.clear_locals_and_stack();
} else {
self.take_ownership();
}
}
/// Steal the iframe's `frame_obj` pointer (`take_ownership`). The live
/// locals stay on that frame object; this generator no longer roots it.
fn take_ownership(&self) {
let Some(frame) = (unsafe { self.frame.swap(None) }) else {
return;
};
frame.iframe().owner.store(
FrameOwner::FrameObject as i8,
core::sync::atomic::Ordering::Release,
);
frame.clear_generator();
}
/// Retire the generator if the frame it just ran came to an end. The claim
/// is still held, so a thread waiting for it cannot resume a frame that has
/// already finished.
@@ -110,14 +130,7 @@ impl Coro {
match res {
Ok(ExecutionResult::Return(_)) | Err(_) => {
self.closed.store(true);
// FrameObject is no longer suspended; allow frame.clear() to succeed.
self.frame.iframe().owner.store(
FrameOwner::FrameObject as i8,
core::sync::atomic::Ordering::Release,
);
// Completed generators/coroutines should not keep their locals
// alive while the wrapper object itself remains referenced.
self.clear_frame_locals_on_close();
self.clear_except_code();
}
Ok(ExecutionResult::Yield(_)) => {}
Ok(ExecutionResult::TailCall) => unreachable!("TailCall in generator/coroutine"),
@@ -148,7 +161,8 @@ impl Coro {
let gen_exc = unsafe { self.exception.swap(None) };
let exception_ptr = &self.exception as *const PyAtomicRef<Option<PyBaseException>>;
vm.resume_gen_frame(&self.frame, gen_exc, |f| {
let frame = self.frame();
vm.resume_gen_frame(&frame, gen_exc, |f| {
let result = func(f);
// SAFETY: exclusive access guaranteed by the claim
let _old = unsafe { (*exception_ptr).swap(vm.current_exception()) };
@@ -168,8 +182,8 @@ impl Coro {
if e.fast_isinstance(vm.ctx.exceptions.stop_iteration) {
let err =
vm.new_runtime_error(format!("{} raised StopIteration", gen_name(jen, vm)));
// PEP 479: chain __context__ as well as __cause__ to match
// CPython, which sets both to the original StopIteration.
// PEP 479: chain __context__ as well as __cause__ to the
// original StopIteration.
err.set___context__(Some(e.clone()));
err.set___cause__(Some(e));
Err(err)
@@ -196,7 +210,7 @@ impl Coro {
if self.closed.load() {
return Self::send_when_closed(jen, vm);
}
let value = if self.frame.lasti() > 0 {
let value = if self.frame_opt().is_some_and(|f| f.lasti() > 0) {
Some(vm.ctx.none())
} else {
None
@@ -243,7 +257,7 @@ impl Coro {
if self.closed.load() {
return Self::send_when_closed(jen, vm);
}
let value = if self.frame.lasti() > 0 {
let value = if self.frame_opt().is_some_and(|f| f.lasti() > 0) {
Some(value)
} else if !vm.is_none(&value) {
return Err(vm.new_type_error(format!(
@@ -291,7 +305,7 @@ impl Coro {
let result = self.run_claimed(&claim, vm, |f| f.gen_throw(vm, exc_type, exc_val, exc_tb));
self.maybe_close(&result, &claim);
drop(claim);
Ok(result?.into_iter_return(vm))
self.finalize_send_result(result, jen, vm)
}
pub fn close(&self, jen: &PyObject, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
@@ -303,9 +317,10 @@ impl Coro {
if self.closed.load() {
return Ok(vm.ctx.none());
}
// If generator hasn't started (FRAME_CREATED), just mark as closed
if self.frame.lasti() == 0 {
// FRAME_CREATED: mark finished and clear the iframe.
if self.frame_opt().is_none_or(|f| f.lasti() == 0) {
self.closed.store(true);
self.clear_except_code();
return Ok(vm.ctx.none());
}
let result = self.run_claimed(&claim, vm, |f| {
@@ -316,23 +331,27 @@ impl Coro {
vm.ctx.none(),
)
});
self.closed.store(true);
// Release frame locals and stack to free references held by the
// closed generator, matching gen_send_ex2 with close_on_completion.
self.clear_frame_locals_on_close();
drop(claim);
match result {
Ok(ExecutionResult::Yield(_)) => {
Err(vm.new_runtime_error(format!("{} ignored GeneratorExit", gen_name(jen, vm))))
}
Err(e) if !is_gen_exit(&e, vm) => Err(e),
Ok(ExecutionResult::Return(value)) => Ok(value),
_ => Ok(vm.ctx.none()),
other => {
self.closed.store(true);
self.clear_except_code();
match other {
Err(e) if !is_gen_exit(&e, vm) => Err(e),
Ok(ExecutionResult::Return(value)) => Ok(value),
_ => Ok(vm.ctx.none()),
}
}
}
}
pub fn suspended(&self) -> bool {
!self.closed.load() && !self.running.load() && self.frame.lasti() > 0
!self.closed.load()
&& !self.running.load()
&& self.frame_opt().is_some_and(|f| f.lasti() > 0)
}
pub fn running(&self) -> bool {
@@ -344,7 +363,15 @@ impl Coro {
}
pub fn frame(&self) -> FrameObjectRef {
self.frame.clone()
self.frame_opt().expect("generator frame")
}
pub fn frame_opt(&self) -> Option<FrameObjectRef> {
self.frame.to_owned()
}
pub fn code(&self) -> PyRef<PyCode> {
self.code.clone()
}
pub fn name(&self) -> PyStrRef {
@@ -434,8 +461,6 @@ pub(crate) fn get_awaitable_iter(obj: PyObjectRef, vm: &VirtualMachine) -> PyRes
if obj.downcastable::<PyCoroutine>()
|| obj.downcast_ref::<PyGenerator>().is_some_and(|g| {
g.as_coro()
.frame()
.iframe()
.code()
.flags
.contains(crate::bytecode::CodeFlags::ITERABLE_COROUTINE)
@@ -450,8 +475,6 @@ pub(crate) fn get_awaitable_iter(obj: PyObjectRef, vm: &VirtualMachine) -> PyRes
if result.downcastable::<PyCoroutine>()
|| result.downcast_ref::<PyGenerator>().is_some_and(|g| {
g.as_coro()
.frame()
.iframe()
.code()
.flags
.contains(crate::bytecode::CodeFlags::ITERABLE_COROUTINE)
+13 -35
View File
@@ -820,7 +820,6 @@ pub(crate) struct FrameColdData {
/// `PyEval_GetLocals()` C API. It is populated only by that adapter.
pub f_locals_cache: PyMutex<Option<PyDictRef>>,
pub f_overwritten_fast_locals: PyMutex<Vec<PyObjectRef>>,
pub escaped: atomic::AtomicBool,
pub retained_back: PyMutex<Option<FrameObjectRef>>,
pub pending_stack_pops: PyAtomic<u32>,
pub pending_unwind_from_stack: PyAtomic<i64>,
@@ -842,7 +841,6 @@ impl Default for FrameColdData {
f_extra_locals: PyMutex::new(None),
f_locals_cache: PyMutex::new(None),
f_overwritten_fast_locals: PyMutex::new(Vec::new()),
escaped: atomic::AtomicBool::new(false),
retained_back: PyMutex::new(None),
pending_stack_pops: Default::default(),
pending_unwind_from_stack: Default::default(),
@@ -1243,7 +1241,6 @@ impl InterpreterFrame {
datastack_base: core::ptr::null_mut(),
materialized: Radium::new(0),
cold: OnceCell::from(Box::new(FrameColdData {
escaped: atomic::AtomicBool::new(true),
attached_tid: atomic::AtomicU64::new(current_thread_ident()),
..FrameColdData::default()
})),
@@ -1323,10 +1320,7 @@ impl InterpreterFrame {
owner: atomic::AtomicI8::new(FrameOwner::FrameObject as i8),
datastack_base: core::ptr::null_mut(),
materialized: Radium::new(0),
cold: OnceCell::from(Box::new(FrameColdData {
escaped: atomic::AtomicBool::new(true),
..FrameColdData::default()
})),
cold: OnceCell::new(),
};
let frame_obj = FrameObject {
@@ -1770,18 +1764,13 @@ impl FrameObject {
.filter_map(Option::take)
.collect::<Vec<_>>()
};
let cold = self.iframe().cold();
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 (extra_locals, locals_cache, overwritten) = match self.iframe().cold_opt() {
Some(cold) => (
cold.f_extra_locals.lock().take(),
cold.f_locals_cache.lock().take(),
core::mem::take(&mut *cold.f_overwritten_fast_locals.lock()),
),
None => (None, None, Vec::new()),
};
drop((fastlocals, extra_locals, locals_cache, overwritten));
}
@@ -1853,21 +1842,6 @@ impl FrameObject {
core::ptr::null()
}
/// Record that a durable Python-level reference to this frame escaped.
pub(crate) fn mark_escaped(&self) {
self.iframe()
.cold()
.escaped
.store(true, atomic::Ordering::Release);
}
/// Whether a durable reference to this frame has escaped.
pub(crate) fn has_escaped(&self) -> bool {
self.iframe()
.cold_opt()
.is_some_and(|c| c.escaped.load(atomic::Ordering::Acquire))
}
pub fn lasti(&self) -> u32 {
self.iframe().lasti.load(Relaxed)
}
@@ -4152,7 +4126,11 @@ impl ExecutingFrame<'_> {
// Check if coroutine is already being awaited
if let Some(coro) = iter.downcast_ref::<PyCoroutine>()
&& coro.as_coro().frame().yield_from_target().is_some()
&& coro
.as_coro()
.frame_opt()
.and_then(|f| f.yield_from_target())
.is_some()
{
return Err(vm.new_runtime_error("coroutine is being awaited already"));
}
-2
View File
@@ -990,8 +990,6 @@ pub mod sys {
let offset = offset.into_option().unwrap_or(0);
let frame_ref = crate::frame::frame_at_offset(offset, vm)
.ok_or_else(|| vm.new_value_error("call stack is not deep enough"))?;
frame_ref.mark_escaped();
if let Ok(audit) = vm.sys_module.get_attr("audit", vm) {
audit.call((vm.ctx.new_str("sys._getframe"), frame_ref.to_owned()), vm)?;
}