Align del behavior (#6772)

* slot_del

* refcount inc_by for atomicity

* temp patch multithreading

* apply review
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2026-01-31 23:09:10 +09:00
1 parent cdd47b6a59
commit ba8749b792
8 files changed
+30 -78

No files matched your search

-5
View File
@@ -108,12 +108,7 @@ env:
test_yield_from
ENV_POLLUTING_TESTS_COMMON: >-
ENV_POLLUTING_TESTS_LINUX: >-
test.test_multiprocessing_fork.test_processes
test.test_multiprocessing_forkserver.test_processes
test.test_multiprocessing_spawn.test_processes
ENV_POLLUTING_TESTS_MACOS: >-
test.test_multiprocessing_forkserver.test_processes
test.test_multiprocessing_spawn.test_processes
ENV_POLLUTING_TESTS_WINDOWS: >-
# Python version targeted by the CI.
PYTHON_VERSION: "3.14.2"
+4 -2
View File
@@ -6592,7 +6592,8 @@ class BaseMixin(object):
# cycles. Trigger a garbage collection to break these cycles.
test.support.gc_collect()
processes = set(multiprocessing.process._dangling) - set(cls.dangling[0])
# TODO: RUSTPYTHON: Filter out stopped processes since gc.collect() is a no-op
processes = {p for p in multiprocessing.process._dangling if p.is_alive()} - {p for p in cls.dangling[0] if p.is_alive()}
if processes:
test.support.environment_altered = True
support.print_warning(f'Dangling processes: {processes}')
@@ -6789,7 +6790,8 @@ def install_tests_in_module_dict(remote_globs, start_method,
multiprocessing.set_start_method(old_start_method[0], force=True)
# pause a bit so we don't get warning about dangling threads/processes
processes = set(multiprocessing.process._dangling) - set(dangling[0])
# TODO: RUSTPYTHON: Filter out stopped processes since gc.collect() is a no-op
processes = {p for p in multiprocessing.process._dangling if p.is_alive()} - {p for p in dangling[0] if p.is_alive()}
if processes:
need_sleep = True
test.support.environment_altered = True
-17
View File
@@ -22,23 +22,6 @@ class WithProcessesTestProcess(WithProcessesTestProcess): # TODO: RUSTPYTHON
@unittest.skipIf(sys.platform == 'linux', 'TODO: RUSTPYTHON flaky timeout')
def test_process(self): super().test_process() # TODO: RUSTPYTHON
class WithProcessesTestPool(WithProcessesTestPool): # TODO: RUSTPYTHON
@unittest.skipIf( # TODO: RUSTPYTHON
sys.platform == 'linux' and 'RUSTPYTHON_SKIP_ENV_POLLUTERS' in os.environ, # TODO: RUSTPYTHON
'TODO: RUSTPYTHON environment pollution when running rustpython -m test --fail-env-changed due to unknown reason'
) # TODO: RUSTPYTHON
def test_async_timeout(self): super().test_async_timeout() # TODO: RUSTPYTHON
@unittest.skipIf( # TODO: RUSTPYTHON
sys.platform == 'linux' and 'RUSTPYTHON_SKIP_ENV_POLLUTERS' in os.environ, # TODO: RUSTPYTHON
'TODO: RUSTPYTHON environment pollution when running rustpython -m test --fail-env-changed due to unknown reason'
) # TODO: RUSTPYTHON
def test_terminate(self): super().test_terminate() # TODO: RUSTPYTHON
@unittest.skipIf( # TODO: RUSTPYTHON
sys.platform == 'linux' and 'RUSTPYTHON_SKIP_ENV_POLLUTERS' in os.environ, # TODO: RUSTPYTHON
'TODO: RUSTPYTHON environment pollution when running rustpython -m test --fail-env-changed due to unknown reason'
) # TODO: RUSTPYTHON
def test_traceback(self): super().test_traceback() # TODO: RUSTPYTHON
class WithProcessesTestPoolWorkerLifetime(WithProcessesTestPoolWorkerLifetime): # TODO: RUSTPYTHON
@unittest.skipIf(sys.platform == 'linux', 'TODO: RUSTPYTHON flaky timeout')
def test_pool_worker_lifetime(self): super().test_pool_worker_lifetime() # TODO: RUSTPYTHON
@@ -11,33 +11,11 @@ class WithProcessesTestCondition(WithProcessesTestCondition): # TODO: RUSTPYTHO
def test_notify_n(self): super().test_notify_n()
class WithProcessesTestLock(WithProcessesTestLock): # TODO: RUSTPYTHON
@unittest.skipIf( # TODO: RUSTPYTHON
'RUSTPYTHON_SKIP_ENV_POLLUTERS' in os.environ, # TODO: RUSTPYTHON
'TODO: RUSTPYTHON environment pollution when running rustpython -m test --fail-env-changed due to unknown reason'
) # TODO: RUSTPYTHON
def test_repr_lock(self): super().test_repr_lock() # TODO: RUSTPYTHON
@unittest.skipIf( # TODO: RUSTPYTHON
sys.platform == 'linux', # TODO: RUSTPYTHON
'TODO: RUSTPYTHON flaky BrokenPipeError, flaky ConnectionRefusedError, flaky ConnectionResetError, flaky EOFError'
) # TODO: RUSTPYTHON
def test_repr_rlock(self): super().test_repr_rlock() # TODO: RUSTPYTHON
class WithProcessesTestPool(WithProcessesTestPool): # TODO: RUSTPYTHON
@unittest.skipIf( # TODO: RUSTPYTHON
'RUSTPYTHON_SKIP_ENV_POLLUTERS' in os.environ, # TODO: RUSTPYTHON
'TODO: RUSTPYTHON environment pollution when running rustpython -m test --fail-env-changed due to unknown reason'
) # TODO: RUSTPYTHON
def test_async_timeout(self): super().test_async_timeout() # TODO: RUSTPYTHON
@unittest.skipIf( # TODO: RUSTPYTHON
'RUSTPYTHON_SKIP_ENV_POLLUTERS' in os.environ, # TODO: RUSTPYTHON
'TODO: RUSTPYTHON environment pollution when running rustpython -m test --fail-env-changed due to unknown reason'
) # TODO: RUSTPYTHON
def test_terminate(self): super().test_terminate() # TODO: RUSTPYTHON
@unittest.skipIf( # TODO: RUSTPYTHON
'RUSTPYTHON_SKIP_ENV_POLLUTERS' in os.environ, # TODO: RUSTPYTHON
'TODO: RUSTPYTHON environment pollution when running rustpython -m test --fail-env-changed due to unknown reason'
) # TODO: RUSTPYTHON
def test_traceback(self): super().test_traceback() # TODO: RUSTPYTHON
if __name__ == '__main__':
unittest.main()
@@ -3,13 +3,5 @@ from test._test_multiprocessing import install_tests_in_module_dict
install_tests_in_module_dict(globals(), 'forkserver', only_type="threads")
import os # TODO: RUSTPYTHON
class WithThreadsTestPool(WithThreadsTestPool): # TODO: RUSTPYTHON
@unittest.skipIf( # TODO: RUSTPYTHON
'RUSTPYTHON_SKIP_ENV_POLLUTERS' in os.environ, # TODO: RUSTPYTHON
'TODO: RUSTPYTHON environment pollution when running rustpython -m test --fail-env-changed due to unknown reason'
) # TODO: RUSTPYTHON
def test_terminate(self): super().test_terminate() # TODO: RUSTPYTHON
if __name__ == '__main__':
unittest.main()
-22
View File
@@ -9,33 +9,11 @@ class WithProcessesTestCondition(WithProcessesTestCondition): # TODO: RUSTPYTHO
def test_notify(self): super().test_notify()
class WithProcessesTestLock(WithProcessesTestLock): # TODO: RUSTPYTHON
@unittest.skipIf( # TODO: RUSTPYTHON
sys.platform in ('darwin', 'linux') and 'RUSTPYTHON_SKIP_ENV_POLLUTERS' in os.environ, # TODO: RUSTPYTHON
'TODO: RUSTPYTHON environment pollution when running rustpython -m test --fail-env-changed due to unknown reason'
) # TODO: RUSTPYTHON
def test_repr_lock(self): super().test_repr_lock() # TODO: RUSTPYTHON
@unittest.skipIf( # TODO: RUSTPYTHON
sys.platform == 'linux', # TODO: RUSTPYTHON
'TODO: RUSTPYTHON flaky BrokenPipeError, flaky ConnectionRefusedError, flaky ConnectionResetError, flaky EOFError'
) # TODO: RUSTPYTHON
def test_repr_rlock(self): super().test_repr_rlock() # TODO: RUSTPYTHON
class WithProcessesTestPool(WithProcessesTestPool): # TODO: RUSTPYTHON
@unittest.skipIf( # TODO: RUSTPYTHON
sys.platform in ('darwin', 'linux') and 'RUSTPYTHON_SKIP_ENV_POLLUTERS' in os.environ, # TODO: RUSTPYTHON
'TODO: RUSTPYTHON environment pollution when running rustpython -m test --fail-env-changed due to unknown reason'
) # TODO: RUSTPYTHON
def test_async_timeout(self): super().test_async_timeout() # TODO: RUSTPYTHON
@unittest.skipIf( # TODO: RUSTPYTHON
sys.platform in ('darwin', 'linux') and 'RUSTPYTHON_SKIP_ENV_POLLUTERS' in os.environ, # TODO: RUSTPYTHON
'TODO: RUSTPYTHON environment pollution when running rustpython -m test --fail-env-changed due to unknown reason'
) # TODO: RUSTPYTHON
def test_terminate(self): super().test_terminate() # TODO: RUSTPYTHON
@unittest.skipIf( # TODO: RUSTPYTHON
sys.platform in ('darwin', 'linux') and 'RUSTPYTHON_SKIP_ENV_POLLUTERS' in os.environ, # TODO: RUSTPYTHON
'TODO: RUSTPYTHON environment pollution when running rustpython -m test --fail-env-changed due to unknown reason'
) # TODO: RUSTPYTHON
def test_traceback(self): super().test_traceback() # TODO: RUSTPYTHON
if __name__ == '__main__':
unittest.main()
+10
View File
@@ -40,6 +40,16 @@ impl RefCount {
}
}
#[inline]
pub fn inc_by(&self, n: usize) {
debug_assert!(n <= Self::MASK);
let old_size = self.strong.fetch_add(n, Relaxed);
if old_size & Self::MASK > Self::MASK - n {
std::process::abort();
}
}
/// Returns true if successful
#[inline]
pub fn safe_inc(&self) -> bool {
+16 -2
View File
@@ -220,6 +220,12 @@ impl WeakRefList {
}))
});
let mut inner = unsafe { inner_ptr.as_ref().lock() };
// If obj was cleared but object is still alive (e.g., new weakref
// created during __del__), restore the obj pointer
if inner.obj.is_none() {
inner.obj = Some(NonNull::from(obj));
inner.ref_count += 1;
}
if is_generic && let Some(generic_weakref) = inner.generic_weakref {
let generic_weakref = unsafe { generic_weakref.as_ref() };
if generic_weakref.0.ref_count.get() != 0 {
@@ -835,15 +841,23 @@ impl PyObject {
slot_del: fn(&PyObject, &VirtualMachine) -> PyResult<()>,
) -> Result<(), ()> {
let ret = crate::vm::thread::with_vm(zelf, |vm| {
zelf.0.ref_count.inc();
// Temporarily resurrect (0→2) so ref_count stays positive
// during __del__, preventing safe_inc from seeing 0.
zelf.0.ref_count.inc_by(2);
if let Err(e) = slot_del(zelf, vm) {
let del_method = zelf.get_class_attr(identifier!(vm, __del__)).unwrap();
vm.run_unraisable(e, None, del_method);
}
// Undo the temporary resurrection. Always remove both
// temporary refs; the second dec returns true only when
// ref_count drops to 0 (no resurrection).
zelf.0.ref_count.dec();
zelf.0.ref_count.dec()
});
match ret {
// the decref right above set ref_count back to 0
// the decref set ref_count back to 0
Some(true) => Ok(()),
// we've been resurrected by __del__
Some(false) => Err(()),