diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 74db871139c..1bf216a0e0c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -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" diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 0855e384f24..0041d536862 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -6592,7 +6592,8 @@ def tearDownClass(cls): # 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 tearDownModule(): 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 diff --git a/Lib/test/test_multiprocessing_fork/test_processes.py b/Lib/test/test_multiprocessing_fork/test_processes.py index 8c7e6b56896..02b7256e41b 100644 --- a/Lib/test/test_multiprocessing_fork/test_processes.py +++ b/Lib/test/test_multiprocessing_fork/test_processes.py @@ -22,23 +22,6 @@ def test_args_argument(self): super().test_args_argument() # 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 diff --git a/Lib/test/test_multiprocessing_forkserver/test_processes.py b/Lib/test/test_multiprocessing_forkserver/test_processes.py index a4a2aa21a3c..6f6b8f56837 100644 --- a/Lib/test/test_multiprocessing_forkserver/test_processes.py +++ b/Lib/test/test_multiprocessing_forkserver/test_processes.py @@ -11,33 +11,11 @@ def test_notify(self): super().test_notify() 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() diff --git a/Lib/test/test_multiprocessing_forkserver/test_threads.py b/Lib/test/test_multiprocessing_forkserver/test_threads.py index 1ca30b0bb83..719c752aa05 100644 --- a/Lib/test/test_multiprocessing_forkserver/test_threads.py +++ b/Lib/test/test_multiprocessing_forkserver/test_threads.py @@ -3,13 +3,5 @@ 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() diff --git a/Lib/test/test_multiprocessing_spawn/test_processes.py b/Lib/test/test_multiprocessing_spawn/test_processes.py index f3468a86a06..21fd6abd655 100644 --- a/Lib/test/test_multiprocessing_spawn/test_processes.py +++ b/Lib/test/test_multiprocessing_spawn/test_processes.py @@ -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() diff --git a/crates/common/src/refcount.rs b/crates/common/src/refcount.rs index a5fbfa8fc36..c7038667099 100644 --- a/crates/common/src/refcount.rs +++ b/crates/common/src/refcount.rs @@ -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 { diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index 43b2f7dc61a..3b744e9007b 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -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(()),