generator is borrowed

This commit is contained in:
Jeong, YunWon
2026-01-31 17:24:29 +09:00
parent 80929f44d4
commit 7258a4ae92
17 changed files with 43 additions and 43 deletions

View File

@@ -1026,7 +1026,6 @@ class AsyncGenAsyncioTest(unittest.TestCase):
fut.cancel()
self.loop.run_until_complete(asyncio.sleep(0.01))
@unittest.expectedFailure # TODO: RUSTPYTHON; gc_collect doesn't finalize async generators
def test_async_gen_asyncio_gc_aclose_09(self):
DONE = 0
@@ -1513,7 +1512,6 @@ class AsyncGenAsyncioTest(unittest.TestCase):
self.assertIn('an error occurred during closing of asynchronous generator',
message['message'])
@unittest.expectedFailure # TODO: RUSTPYTHON; gc_collect doesn't finalize async generators, different cleanup path
def test_async_gen_asyncio_shutdown_exception_02(self):
messages = []

View File

@@ -189,10 +189,6 @@ class TestPyFreeThreading(TestFreeThreading, TestCase):
def factory(self, loop, coro, **kwargs):
return asyncio.tasks._PyTask(coro, loop=loop, **kwargs)
@unittest.expectedFailure # TODO: RUSTPYTHON; GC weak reference timing issue
def test_task_different_thread_finalized(self):
return super().test_task_different_thread_finalized()
@unittest.skip("TODO: RUSTPYTHON; hangs - Python _current_tasks dict not thread-safe")
def test_all_tasks_race(self):
return super().test_all_tasks_race()
@@ -228,10 +224,6 @@ class TestEagerPyFreeThreading(TestPyFreeThreading):
def factory(self, loop, coro, eager_start=True, **kwargs):
return asyncio.tasks._PyTask(coro, loop=loop, **kwargs, eager_start=eager_start)
@unittest.expectedFailure # TODO: RUSTPYTHON; GC weak reference timing issue
def test_task_different_thread_finalized(self):
return super().test_task_different_thread_finalized()
@unittest.skip("TODO: RUSTPYTHON; hangs - Python _current_tasks dict not thread-safe")
def test_all_tasks_race(self):
return super().test_all_tasks_race()

View File

@@ -1078,7 +1078,6 @@ class StreamTests(test_utils.TestCase):
self.assertEqual(messages, [])
@unittest.expectedFailure # TODO: RUSTPYTHON; GC finalization timing issue
def test_unclosed_resource_warnings(self):
async def inner(httpd):
rd, wr = await asyncio.open_connection(*httpd.address)

View File

@@ -72,7 +72,6 @@ class AsCompletedTests:
]
self.assertEqual(len(completed), 1)
@unittest.expectedFailure # TODO: RUSTPYTHON; GC weak reference not collected
def test_free_reference_yielded_future(self):
# Issue #14406: Generator should not keep references
# to finished futures.

View File

@@ -136,11 +136,9 @@ class FailingInitializerResourcesTest(unittest.TestCase):
self.assertEqual(_resource_tracker._exitcode, 0)
@unittest.expectedFailure # TODO: RUSTPYTHON; resource tracker exit code mismatch
def test_spawn(self):
self._test(ProcessPoolSpawnFailingInitializerTest)
@unittest.expectedFailure # TODO: RUSTPYTHON; resource tracker exit code mismatch
@support.skip_if_sanitizer("TSAN doesn't support threads after fork", thread=True)
def test_forkserver(self):
self._test(ProcessPoolForkserverFailingInitializerTest)

View File

@@ -837,15 +837,15 @@ class TestCopy(unittest.TestCase):
v[x] = y
self.assertNotIn(x, u)
@unittest.expectedFailure # TODO: RUSTPYTHON; weakref callback not fired immediately by gc_collect
def test_copy_weakkeydict(self):
self._check_copy_weakdict(weakref.WeakKeyDictionary)
@unittest.expectedFailure # TODO: RUSTPYTHON; weakref callback not fired immediately by gc_collect
def test_copy_weakvaluedict(self):
self._check_copy_weakdict(weakref.WeakValueDictionary)
@unittest.expectedFailure # TODO: RUSTPYTHON; weakref callback not fired immediately by gc_collect
def test_deepcopy_weakkeydict(self):
class C(object):
def __init__(self, i):
@@ -866,7 +866,7 @@ class TestCopy(unittest.TestCase):
support.gc_collect() # For PyPy or other GCs.
self.assertEqual(len(v), 1)
@unittest.expectedFailure # TODO: RUSTPYTHON; weakref callback not fired immediately by gc_collect
def test_deepcopy_weakvaluedict(self):
class C(object):
def __init__(self, i):

View File

@@ -1076,7 +1076,6 @@ class ExceptionTests(unittest.TestCase):
g.close()
self._check_generator_cleanup_exc_state(do_close)
@unittest.expectedFailure # TODO: RUSTPYTHON; GC generator cleanup timing
def test_generator_del_cleanup_exc_state(self):
def do_del(g):
g = None

View File

@@ -3282,7 +3282,6 @@ class POSIXProcessTestCase(BaseTestCase):
finally:
p.wait()
@unittest.expectedFailure # TODO: RUSTPYTHON; GC Popen.__del__ timing
def test_zombie_fast_process_del(self):
# Issue #12650: on Unix, if Popen.__del__() was called before the
# process exited, it wouldn't be added to subprocess._active, and would
@@ -3307,7 +3306,6 @@ class POSIXProcessTestCase(BaseTestCase):
# check that p is in the active processes list
self.assertIn(ident, [id(o) for o in subprocess._active])
@unittest.expectedFailure # TODO: RUSTPYTHON; GC Popen.__del__ timing
def test_leak_fast_process_del_killed(self):
# Issue #12650: on Unix, if Popen.__del__() was called before the
# process exited, and the process got killed by a signal, it would never

View File

@@ -374,11 +374,9 @@ class Test_TestSuite(unittest.TestCase, TestEquality):
self.assertEqual(suite._tests, [None])
self.assertIsNone(wref())
@unittest.expectedFailure # TODO: RUSTPYTHON; GC test not collected after run
def test_garbage_collect_test_after_run_BaseTestSuite(self):
self.assert_garbage_collect_test_after_run(unittest.BaseTestSuite)
@unittest.expectedFailure # TODO: RUSTPYTHON; GC test not collected after run
def test_garbage_collect_test_after_run_TestSuite(self):
self.assert_garbage_collect_test_after_run(unittest.TestSuite)

View File

@@ -1368,7 +1368,6 @@ class MappingTestCase(TestBase):
def test_weak_valued_len_race(self):
self.check_len_race(weakref.WeakValueDictionary, lambda k: (1, k))
@unittest.expectedFailure # TODO: RUSTPYTHON; weakref callback not fired immediately by gc_collect
def test_weak_values(self):
#
# This exercises d.copy(), d.items(), d[], del d[], len(d).
@@ -1401,7 +1400,6 @@ class MappingTestCase(TestBase):
gc_collect() # For PyPy or other GCs.
self.assertRaises(KeyError, dict.__getitem__, 2)
@unittest.expectedFailure # TODO: RUSTPYTHON; weakref callback not fired immediately by gc_collect
def test_weak_keys(self):
#
# This exercises d.copy(), d.items(), d[] = v, d[], del d[],
@@ -1767,7 +1765,6 @@ class MappingTestCase(TestBase):
self.assertEqual(list(d.keys()), [kw])
self.assertEqual(d[kw], o)
@unittest.expectedFailure # TODO: RUSTPYTHON; weakref callback not fired immediately by gc_collect
def test_weak_valued_union_operators(self):
a = C()
b = C()
@@ -1820,7 +1817,6 @@ class MappingTestCase(TestBase):
self.assertEqual(len(d), 1)
self.assertEqual(list(d.keys()), [o2])
@unittest.expectedFailure # TODO: RUSTPYTHON; weakref callback not fired immediately by gc_collect
def test_weak_keyed_union_operators(self):
o1 = C()
o2 = C()

View File

@@ -69,7 +69,6 @@ class TestWeakSet(unittest.TestCase):
support.gc_collect() # For PyPy or other GCs.
self.assertNotIn(ustr('F'), self.fs)
@unittest.expectedFailure # TODO: RUSTPYTHON; GC weak reference not collected
def test_union(self):
u = self.s.union(self.items2)
for c in self.letters:
@@ -92,7 +91,6 @@ class TestWeakSet(unittest.TestCase):
self.assertEqual(self.s | set(self.items2), i)
self.assertEqual(self.s | frozenset(self.items2), i)
@unittest.expectedFailure # TODO: RUSTPYTHON; GC weak reference not collected
def test_intersection(self):
s = WeakSet(self.letters)
i = s.intersection(self.items2)
@@ -130,7 +128,6 @@ class TestWeakSet(unittest.TestCase):
self.assertEqual(self.s - set(self.items2), i)
self.assertEqual(self.s - frozenset(self.items2), i)
@unittest.expectedFailure # TODO: RUSTPYTHON; GC weak reference not collected
def test_symmetric_difference(self):
i = self.s.symmetric_difference(self.items2)
for c in self.letters:

View File

@@ -513,9 +513,7 @@ impl PyAsyncGenAThrow {
) -> PyResult {
match self.state.load() {
AwaitableState::Closed => {
return Err(
vm.new_runtime_error("cannot reuse already awaited aclose()/athrow()")
);
return Err(vm.new_runtime_error("cannot reuse already awaited aclose()/athrow()"));
}
AwaitableState::Init => {
if self.ag.running_async.load() {
@@ -816,6 +814,12 @@ impl Destructor for PyAsyncGen {
}
}
impl Drop for PyAsyncGen {
fn drop(&mut self) {
self.inner.frame().clear_generator();
}
}
pub fn init(ctx: &Context) {
PyAsyncGen::extend_class(ctx, ctx.types.async_generator);
PyAsyncGenASend::extend_class(ctx, ctx.types.async_generator_asend);

View File

@@ -222,6 +222,12 @@ impl IterNext for PyCoroutineWrapper {
}
}
impl Drop for PyCoroutine {
fn drop(&mut self) {
self.inner.frame().clear_generator();
}
}
pub fn init(ctx: &Context) {
PyCoroutine::extend_class(ctx, ctx.types.coroutine_type);
PyCoroutineWrapper::extend_class(ctx, ctx.types.coroutine_wrapper_type);

View File

@@ -153,7 +153,7 @@ impl Frame {
impl Py<Frame> {
#[pygetset]
fn f_generator(&self) -> Option<PyObjectRef> {
self.generator.lock().clone()
self.generator.to_owned()
}
#[pygetset]

View File

@@ -531,19 +531,19 @@ impl Py<PyFunction> {
(true, false) => {
let obj = PyGenerator::new(frame.clone(), self.__name__(), self.__qualname__())
.into_pyobject(vm);
frame.set_generator(obj.clone());
frame.set_generator(&obj);
Ok(obj)
}
(false, true) => {
let obj = PyCoroutine::new(frame.clone(), self.__name__(), self.__qualname__())
.into_pyobject(vm);
frame.set_generator(obj.clone());
frame.set_generator(&obj);
Ok(obj)
}
(true, true) => {
let obj = PyAsyncGen::new(frame.clone(), self.__name__(), self.__qualname__())
.into_pyobject(vm);
frame.set_generator(obj.clone());
frame.set_generator(&obj);
Ok(obj)
}
(false, false) => vm.run_frame(frame),

View File

@@ -140,6 +140,12 @@ impl IterNext for PyGenerator {
}
}
impl Drop for PyGenerator {
fn drop(&mut self) {
self.inner.frame().clear_generator();
}
}
pub fn init(ctx: &Context) {
PyGenerator::extend_class(ctx, ctx.types.generator_type);
}

View File

@@ -15,6 +15,7 @@ use crate::{
coroutine::Coro,
exceptions::ExceptionCtor,
function::{ArgMapping, Either, FuncArgs},
object::PyAtomicBorrow,
object::{Traverse, TraverseFn},
protocol::{PyIter, PyIterReturn},
scope::Scope,
@@ -85,8 +86,10 @@ pub struct Frame {
pub trace_lines: PyMutex<bool>,
pub trace_opcodes: PyMutex<bool>,
pub temporary_refs: PyMutex<Vec<PyObjectRef>>,
/// Back-reference to owning generator/coroutine/async generator
pub generator: PyMutex<Option<PyObjectRef>>,
/// Back-reference to owning generator/coroutine/async generator.
/// Borrowed reference (not ref-counted) to avoid Generator↔Frame cycle.
/// Cleared by the generator's Drop impl.
pub generator: PyAtomicBorrow,
}
impl PyPayload for Frame {
@@ -114,7 +117,7 @@ unsafe impl Traverse for Frame {
self.trace.traverse(tracer_fn);
self.state.traverse(tracer_fn);
self.temporary_refs.traverse(tracer_fn);
self.generator.traverse(tracer_fn);
// generator is a borrowed reference, not traversed
}
}
@@ -175,12 +178,19 @@ impl Frame {
trace_lines: PyMutex::new(true),
trace_opcodes: PyMutex::new(false),
temporary_refs: PyMutex::new(vec![]),
generator: PyMutex::new(None),
generator: PyAtomicBorrow::new(),
}
}
pub fn set_generator(&self, generator: PyObjectRef) {
*self.generator.lock() = Some(generator);
/// Store a borrowed back-reference to the owning generator/coroutine.
/// The caller must ensure the generator outlives the frame.
pub fn set_generator(&self, generator: &PyObject) {
self.generator.store(generator);
}
/// Clear the generator back-reference. Called when the generator is finalized.
pub fn clear_generator(&self) {
self.generator.clear();
}
pub fn current_location(&self) -> SourceLocation {