From a2a846678e54b1b5defdccd451c573679094ff02 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 22 Jul 2026 21:25:37 +0300 Subject: [PATCH 1/3] gh-154272: Skip more forkserver tests if the start method is unavailable (GH-154499) Two tests were still not skipped: they were only guarded against Cygwin and Windows. Co-authored-by: Claude Opus 4.8 --- Lib/test/test_concurrent_futures/test_init.py | 5 +++-- Lib/test/test_profiling/test_tracing_profiler.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_concurrent_futures/test_init.py b/Lib/test/test_concurrent_futures/test_init.py index ca612db17ce8021..c9fedc7213256c7 100644 --- a/Lib/test/test_concurrent_futures/test_init.py +++ b/Lib/test/test_concurrent_futures/test_init.py @@ -1,5 +1,6 @@ import contextlib import logging +import multiprocessing import queue import time import unittest @@ -147,8 +148,8 @@ def test_spawn(self): self._test(ProcessPoolSpawnFailingInitializerTest) @support.skip_if_sanitizer("TSAN doesn't support threads after fork", thread=True) - @unittest.skipIf(sys.platform == "cygwin", - "Forkserver is not available on Cygwin") + @unittest.skipUnless("forkserver" in multiprocessing.get_all_start_methods(), + "forkserver start method is not available") def test_forkserver(self): self._test(ProcessPoolForkserverFailingInitializerTest) diff --git a/Lib/test/test_profiling/test_tracing_profiler.py b/Lib/test/test_profiling/test_tracing_profiler.py index 8a7070142b60eec..6a4d968f12ef156 100644 --- a/Lib/test/test_profiling/test_tracing_profiler.py +++ b/Lib/test/test_profiling/test_tracing_profiler.py @@ -1,4 +1,5 @@ """Test suite for the cProfile module.""" +import multiprocessing import sys import unittest @@ -220,8 +221,8 @@ def test_process_spawn_pickle(self): # gh-140729: test use Process in cProfile. self._test_process_run_pickle('spawn') - @unittest.skipIf(sys.platform == 'win32', - "No 'forkserver' start method on Windows") + @unittest.skipUnless("forkserver" in multiprocessing.get_all_start_methods(), + "forkserver start method is not available") def test_process_forkserver_pickle(self): # gh-140729: test use Process in cProfile. self._test_process_run_pickle('forkserver') From 6fb51f23f7973560c009552c61e86955584a2fb2 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 22 Jul 2026 23:43:18 +0100 Subject: [PATCH 2/3] gh-154401: Skip fetching the thread state for non-GC types in `_Py_Dealloc` (GH-154430) _Py_Dealloc() fetched the current thread state and computed the C recursion margin on every deallocation, but both are only used by the trashcan mechanism, which applies only to GC-tracked types. Gate them behind the Py_TPFLAGS_HAVE_GC check so the common non-GC case (int, float, str and similar atomic types) skips them. On platforms where _PyThreadState_GET() reads a _Thread_local via a function call (e.g. macOS TLV), this removes a call from every non-GC deallocation. Co-authored-by: Claude Opus 4.8 (1M context) --- ...-07-22-14-30-00.gh-issue-154429.Qk9mZ2.rst | 2 ++ Objects/object.c | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-07-22-14-30-00.gh-issue-154429.Qk9mZ2.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-22-14-30-00.gh-issue-154429.Qk9mZ2.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-22-14-30-00.gh-issue-154429.Qk9mZ2.rst new file mode 100644 index 000000000000000..04781b03356b9fa --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-22-14-30-00.gh-issue-154429.Qk9mZ2.rst @@ -0,0 +1,2 @@ +Slightly speed up deallocation of objects that are not tracked by the garbage +collector, such as :class:`int`, :class:`float` and :class:`str`. diff --git a/Objects/object.c b/Objects/object.c index bd23c2e23881949..fadd9273a36607c 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -3292,13 +3292,20 @@ _Py_Dealloc(PyObject *op) PyTypeObject *type = Py_TYPE(op); unsigned long gc_flag = type->tp_flags & Py_TPFLAGS_HAVE_GC; destructor dealloc = type->tp_dealloc; - PyThreadState *tstate = _PyThreadState_GET(); - intptr_t margin = _Py_RecursionLimit_GetMargin(tstate); - if (margin < 2 && gc_flag) { - _PyTrash_thread_deposit_object(tstate, (PyObject *)op); - return; + PyThreadState *tstate = NULL; + intptr_t margin = 0; + if (gc_flag) { + tstate = _PyThreadState_GET(); + margin = _Py_RecursionLimit_GetMargin(tstate); + if (margin < 2) { + _PyTrash_thread_deposit_object(tstate, (PyObject *)op); + return; + } } #ifdef Py_DEBUG + if (tstate == NULL) { + tstate = _PyThreadState_GET(); + } #if !defined(Py_GIL_DISABLED) && !defined(Py_STACKREF_DEBUG) /* This assertion doesn't hold for the free-threading build, as * PyStackRef_CLOSE_SPECIALIZED is not implemented */ @@ -3340,7 +3347,7 @@ _Py_Dealloc(PyObject *op) Py_XDECREF(old_exc); Py_DECREF(type); #endif - if (tstate->delete_later && margin >= 4 && gc_flag) { + if (gc_flag && tstate->delete_later && margin >= 4) { _PyTrash_thread_destroy_chain(tstate); } } From 206788a6dd23b79fc38fde9a7100293d7efcc37c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 23 Jul 2026 01:58:39 +0300 Subject: [PATCH 3/3] gh-154502: Disable junk filling in the OpenBSD allocator in test_capi (GH-154503) Co-authored-by: Claude Opus 4.8 --- Lib/test/test_capi/test_mem.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_capi/test_mem.py b/Lib/test/test_capi/test_mem.py index 66ab072733e17bd..e6389b4e0c1e924 100644 --- a/Lib/test/test_capi/test_mem.py +++ b/Lib/test/test_capi/test_mem.py @@ -24,9 +24,12 @@ def check(self, code): out = assert_python_failure( '-c', code, PYTHONMALLOC=self.PYTHONMALLOC, - # FreeBSD: instruct jemalloc to not fill freed() memory - # with junk byte 0x5a, see JEMALLOC(3) + # Instruct the system allocator to not fill freed() memory + # with junk bytes: + # FreeBSD: jemalloc, see JEMALLOC(3). MALLOC_CONF="junk:false", + # OpenBSD: see MALLOC.CONF(5). + MALLOC_OPTIONS="j", ) stderr = out.err return stderr.decode('ascii', 'replace') @@ -102,7 +105,9 @@ def check_pyobject_is_freed(self, func_name): assert_python_ok( '-c', code, PYTHONMALLOC=self.PYTHONMALLOC, + # See the comment in check() above. MALLOC_CONF="junk:false", + MALLOC_OPTIONS="j", ) def test_pyobject_null_is_freed(self):