From c8624cd36746b17d8f991cde63705e9419e940de Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Wed, 24 Sep 2025 05:46:05 +0200 Subject: [PATCH 1/4] gh-138860: Lazy import rlcompleter in pdb to avoid deadlock in subprocess (#139185) --- Lib/pdb.py | 15 +++++++++---- Lib/test/test_pdb.py | 22 +++++++++++++++++++ ...-09-20-17-50-31.gh-issue-138860.Y9JXap.rst | 1 + 3 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-09-20-17-50-31.gh-issue-138860.Y9JXap.rst diff --git a/Lib/pdb.py b/Lib/pdb.py index a783583a2b1c38..fd48882e28fe7c 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -100,7 +100,6 @@ import _pyrepl.utils from contextlib import ExitStack, closing, contextmanager -from rlcompleter import Completer from types import CodeType from warnings import deprecated @@ -364,6 +363,15 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None, readline.set_completer_delims(' \t\n`@#%^&*()=+[{]}\\|;:\'",<>?') except ImportError: pass + + # GH-138860 + # We need to lazy-import rlcompleter to avoid deadlock + # We cannot import it during self.complete* methods because importing + # rlcompleter for the first time will overwrite readline's completer + # So we import it here and save the Completer class + from rlcompleter import Completer + self.RlCompleter = Completer + self.allow_kbdint = False self.nosigint = nosigint # Consider these characters as part of the command so when the users type @@ -1186,10 +1194,9 @@ def completedefault(self, text, line, begidx, endidx): conv_vars = self.curframe.f_globals.get('__pdb_convenience_variables', {}) return [f"${name}" for name in conv_vars if name.startswith(text[1:])] - # Use rlcompleter to do the completion state = 0 matches = [] - completer = Completer(self.curframe.f_globals | self.curframe.f_locals) + completer = self.RlCompleter(self.curframe.f_globals | self.curframe.f_locals) while (match := completer.complete(text, state)) is not None: matches.append(match) state += 1 @@ -1204,8 +1211,8 @@ def _enable_rlcompleter(self, ns): return try: + completer = self.RlCompleter(ns) old_completer = readline.get_completer() - completer = Completer(ns) readline.set_completer(completer.complete) yield finally: diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 6b74e21ad73d1a..9a7d855003551a 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -4688,6 +4688,28 @@ def foo(): stdout, _ = self._run_script(script, commands) self.assertIn("42", stdout) + def test_readline_not_imported(self): + """GH-138860 + Directly or indirectly importing readline might deadlock a subprocess + if it's launched with process_group=0 or preexec_fn=setpgrp + + It's also a pattern that readline is never imported with just import pdb. + + This test is to ensure that readline is not imported for import pdb. + It's possible that we have a good reason to do that in the future. + """ + + script = textwrap.dedent(""" + import sys + import pdb + if "readline" in sys.modules: + print("readline imported") + """) + commands = "" + stdout, stderr = self._run_script(script, commands) + self.assertNotIn("readline imported", stdout) + self.assertEqual(stderr, "") + @support.force_colorized_test_class class PdbTestColorize(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2025-09-20-17-50-31.gh-issue-138860.Y9JXap.rst b/Misc/NEWS.d/next/Library/2025-09-20-17-50-31.gh-issue-138860.Y9JXap.rst new file mode 100644 index 00000000000000..0903eb71ae4346 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-20-17-50-31.gh-issue-138860.Y9JXap.rst @@ -0,0 +1 @@ +Lazy import :mod:`rlcompleter` in :mod:`pdb` to avoid deadlock in subprocess. From c4f21d7c7c415a85a975fb878a1e578c12969d82 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Wed, 24 Sep 2025 14:19:17 +0900 Subject: [PATCH 2/4] gh-133171: Re-enable JUMP_BACKWARD to free-threading build (gh-137800) --- .github/workflows/jit.yml | 56 +++++++++++++++--------------- Include/internal/pycore_stackref.h | 6 ++++ Python/bytecodes.c | 5 +-- Python/generated_cases.c.h | 5 +-- Python/optimizer.c | 4 +++ configure | 3 +- configure.ac | 2 +- 7 files changed, 47 insertions(+), 34 deletions(-) diff --git a/.github/workflows/jit.yml b/.github/workflows/jit.yml index 52f7d0d2b3df95..80e4ae603a2614 100644 --- a/.github/workflows/jit.yml +++ b/.github/workflows/jit.yml @@ -134,6 +134,34 @@ jobs: make all --jobs 4 ./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3 + jit-with-disabled-gil: + name: Free-Threaded (Debug) + needs: interpreter + runs-on: ubuntu-24.04 + timeout-minutes: 90 + strategy: + fail-fast: false + matrix: + llvm: + - 19 + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Build with JIT enabled and GIL disabled + run: | + sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }} + export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH" + ./configure --enable-experimental-jit --with-pydebug --disable-gil + make all --jobs 4 + - name: Run tests + run: | + ./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3 + continue-on-error: true + no-opt-jit: name: JIT without optimizations (Debug) needs: interpreter @@ -160,31 +188,3 @@ jobs: - name: Run tests without optimizations run: | PYTHON_UOPS_OPTIMIZE=0 ./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3 - - # XXX: GH-133171 - # jit-with-disabled-gil: - # name: Free-Threaded (Debug) - # needs: interpreter - # runs-on: ubuntu-24.04 - # timeout-minutes: 90 - # strategy: - # fail-fast: false - # matrix: - # llvm: - # - 19 - # steps: - # - uses: actions/checkout@v4 - # with: - # persist-credentials: false - # - uses: actions/setup-python@v5 - # with: - # python-version: '3.11' - # - name: Build with JIT enabled and GIL disabled - # run: | - # sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }} - # export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH" - # ./configure --enable-experimental-jit --with-pydebug --disable-gil - # make all --jobs 4 - # - name: Run tests - # run: | - # ./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3 diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h index c4e8f10fe05276..062834368bcd29 100644 --- a/Include/internal/pycore_stackref.h +++ b/Include/internal/pycore_stackref.h @@ -464,6 +464,12 @@ PyStackRef_CLOSE_SPECIALIZED(_PyStackRef ref, destructor destruct) PyStackRef_CLOSE(ref); } +static inline int +PyStackRef_RefcountOnObject(_PyStackRef ref) +{ + return (ref.bits & Py_TAG_REFCNT) == 0; +} + static inline _PyStackRef PyStackRef_DUP(_PyStackRef stackref) { diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 9b993188fb73c7..f9f14322df0a5e 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2940,9 +2940,10 @@ dummy_func( }; tier1 op(_SPECIALIZE_JUMP_BACKWARD, (--)) { - #if ENABLE_SPECIALIZATION + #if ENABLE_SPECIALIZATION_FT if (this_instr->op.code == JUMP_BACKWARD) { - this_instr->op.code = tstate->interp->jit ? JUMP_BACKWARD_JIT : JUMP_BACKWARD_NO_JIT; + uint8_t desired = tstate->interp->jit ? JUMP_BACKWARD_JIT : JUMP_BACKWARD_NO_JIT; + FT_ATOMIC_STORE_UINT8_RELAXED(this_instr->op.code, desired); // Need to re-dispatch so the warmup counter isn't off by one: next_instr = this_instr; DISPATCH_SAME_OPARG(); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index e33d15f2e51e16..79328a7b725613 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -7589,9 +7589,10 @@ /* Skip 1 cache entry */ // _SPECIALIZE_JUMP_BACKWARD { - #if ENABLE_SPECIALIZATION + #if ENABLE_SPECIALIZATION_FT if (this_instr->op.code == JUMP_BACKWARD) { - this_instr->op.code = tstate->interp->jit ? JUMP_BACKWARD_JIT : JUMP_BACKWARD_NO_JIT; + uint8_t desired = tstate->interp->jit ? JUMP_BACKWARD_JIT : JUMP_BACKWARD_NO_JIT; + FT_ATOMIC_STORE_UINT8_RELAXED(this_instr->op.code, desired); next_instr = this_instr; DISPATCH_SAME_OPARG(); } diff --git a/Python/optimizer.c b/Python/optimizer.c index 53f1500f3989a4..7b76cddeabff44 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -119,6 +119,7 @@ _PyOptimizer_Optimize( PyInterpreterState *interp = _PyInterpreterState_GET(); assert(interp->jit); assert(!interp->compiling); +#ifndef Py_GIL_DISABLED interp->compiling = true; // The first executor in a chain and the MAX_CHAIN_DEPTH'th executor *must* // make progress in order to avoid infinite loops or excessively-long @@ -160,6 +161,9 @@ _PyOptimizer_Optimize( assert((*executor_ptr)->vm_data.valid); interp->compiling = false; return 1; +#else + return 0; +#endif } static _PyExecutorObject * diff --git a/configure b/configure index cd8f2f19c0b92c..7624cbf0d2ae3d 100755 --- a/configure +++ b/configure @@ -10891,7 +10891,8 @@ printf "%s\n" "$tier2_flags $jit_flags" >&6; } if test "$disable_gil" = "yes" -a "$enable_experimental_jit" != "no"; then # GH-133171: This configuration builds the JIT but never actually uses it, # which is surprising (and strictly worse than not building it at all): - as_fn_error $? "--enable-experimental-jit cannot be used with --disable-gil." "$LINENO" 5 + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: --enable-experimental-jit does not work correctly with --disable-gil." >&5 +printf "%s\n" "$as_me: WARNING: --enable-experimental-jit does not work correctly with --disable-gil." >&2;} fi case "$ac_cv_cc_name" in diff --git a/configure.ac b/configure.ac index 8312dc55084333..7a7e32d42945b9 100644 --- a/configure.ac +++ b/configure.ac @@ -2799,7 +2799,7 @@ AC_MSG_RESULT([$tier2_flags $jit_flags]) if test "$disable_gil" = "yes" -a "$enable_experimental_jit" != "no"; then # GH-133171: This configuration builds the JIT but never actually uses it, # which is surprising (and strictly worse than not building it at all): - AC_MSG_ERROR([--enable-experimental-jit cannot be used with --disable-gil.]) + AC_MSG_WARN([--enable-experimental-jit does not work correctly with --disable-gil.]) fi case "$ac_cv_cc_name" in From 30f849250b7bac871bb9b7361833ea921585d659 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> Date: Wed, 24 Sep 2025 09:38:57 +0100 Subject: [PATCH 3/4] gh-83336: Add alias for consistency to `utf-8-sig` (#136530) Closes #83336 --- Doc/library/codecs.rst | 2 +- Lib/encodings/aliases.py | 5 ++++- .../Library/2025-07-11-08-15-17.gh-issue-83336.ptpmq7.rst | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-07-11-08-15-17.gh-issue-83336.ptpmq7.rst diff --git a/Doc/library/codecs.rst b/Doc/library/codecs.rst index 90a695ef937f75..8c5c87a7ef16e4 100644 --- a/Doc/library/codecs.rst +++ b/Doc/library/codecs.rst @@ -1339,7 +1339,7 @@ particular, the following variants typically exist: +-----------------+--------------------------------+--------------------------------+ | utf_8 | U8, UTF, utf8, cp65001 | all languages | +-----------------+--------------------------------+--------------------------------+ -| utf_8_sig | | all languages | +| utf_8_sig | utf8-sig | all languages | +-----------------+--------------------------------+--------------------------------+ .. versionchanged:: 3.4 diff --git a/Lib/encodings/aliases.py b/Lib/encodings/aliases.py index 474d74ea3dc191..f4b1b8dd43f920 100644 --- a/Lib/encodings/aliases.py +++ b/Lib/encodings/aliases.py @@ -17,7 +17,7 @@ """ aliases = { - # Please keep this list sorted alphabetically by value ! + # Please keep this list sorted alphabetically by value! # ascii codec '646' : 'ascii', @@ -554,6 +554,9 @@ 'utf8_ucs4' : 'utf_8', 'cp65001' : 'utf_8', + # utf_8_sig codec + 'utf8_sig' : 'utf_8_sig', + # uu_codec codec 'uu' : 'uu_codec', diff --git a/Misc/NEWS.d/next/Library/2025-07-11-08-15-17.gh-issue-83336.ptpmq7.rst b/Misc/NEWS.d/next/Library/2025-07-11-08-15-17.gh-issue-83336.ptpmq7.rst new file mode 100644 index 00000000000000..022ff664ed94df --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-11-08-15-17.gh-issue-83336.ptpmq7.rst @@ -0,0 +1 @@ +``utf8_sig`` is now aliased to :mod:`encodings.utf_8_sig` From a5e056235075ebd3e7eef1c9fef264bca3461a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Wed, 24 Sep 2025 11:25:56 +0200 Subject: [PATCH 4/4] gh-133644: remove `PyWeakref_GetObject` and `PyWeakref_GET_OBJECT` (GH-133657) --- Doc/c-api/weakref.rst | 24 ------------------- Doc/data/refcounts.dat | 6 ----- Doc/data/stable_abi.dat | 1 - .../c-api-pending-removal-in-3.15.rst | 2 +- Doc/howto/free-threading-extensions.rst | 4 ++-- Doc/whatsnew/3.11.rst | 2 +- Doc/whatsnew/3.13.rst | 6 ++--- Doc/whatsnew/3.15.rst | 5 ++++ Include/cpython/weakrefobject.h | 17 ------------- Include/weakrefobject.h | 1 - Misc/NEWS.d/3.11.0a2.rst | 2 +- Misc/NEWS.d/3.13.0a1.rst | 6 ++--- ...-05-08-12-40-59.gh-issue-133644.FNexLJ.rst | 3 +++ Misc/stable_abi.toml | 1 + Modules/_testcapimodule.c | 14 ++--------- Objects/weakrefobject.c | 3 ++- 16 files changed, 24 insertions(+), 73 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2025-05-08-12-40-59.gh-issue-133644.FNexLJ.rst diff --git a/Doc/c-api/weakref.rst b/Doc/c-api/weakref.rst index c3c6cf413dcef5..14ec9d951c4a5f 100644 --- a/Doc/c-api/weakref.rst +++ b/Doc/c-api/weakref.rst @@ -64,30 +64,6 @@ as much as it can. .. versionadded:: 3.13 -.. c:function:: PyObject* PyWeakref_GetObject(PyObject *ref) - - Return a :term:`borrowed reference` to the referenced object from a weak - reference, *ref*. If the referent is no longer live, returns ``Py_None``. - - .. note:: - - This function returns a :term:`borrowed reference` to the referenced object. - This means that you should always call :c:func:`Py_INCREF` on the object - except when it cannot be destroyed before the last usage of the borrowed - reference. - - .. deprecated-removed:: 3.13 3.15 - Use :c:func:`PyWeakref_GetRef` instead. - - -.. c:function:: PyObject* PyWeakref_GET_OBJECT(PyObject *ref) - - Similar to :c:func:`PyWeakref_GetObject`, but does no error checking. - - .. deprecated-removed:: 3.13 3.15 - Use :c:func:`PyWeakref_GetRef` instead. - - .. c:function:: int PyWeakref_IsDead(PyObject *ref) Test if the weak reference *ref* is dead. Returns 1 if the reference is diff --git a/Doc/data/refcounts.dat b/Doc/data/refcounts.dat index 144c5608e07426..44ee2586301259 100644 --- a/Doc/data/refcounts.dat +++ b/Doc/data/refcounts.dat @@ -2947,12 +2947,6 @@ PyWeakref_CheckProxy:PyObject*:ob:0: PyWeakref_CheckRef:int::: PyWeakref_CheckRef:PyObject*:ob:0: -PyWeakref_GET_OBJECT:PyObject*::0: -PyWeakref_GET_OBJECT:PyObject*:ref:0: - -PyWeakref_GetObject:PyObject*::0: -PyWeakref_GetObject:PyObject*:ref:0: - PyWeakref_GetRef:int::: PyWeakref_GetRef:PyObject*:ref:0: PyWeakref_GetRef:PyObject**:pobj:+1: diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 3f51254494c654..7ad5f3ecfab5b4 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -830,7 +830,6 @@ member,PyVarObject.ob_size,3.2,, func,PyVectorcall_Call,3.12,, func,PyVectorcall_NARGS,3.12,, type,PyWeakReference,3.2,,opaque -func,PyWeakref_GetObject,3.2,, func,PyWeakref_GetRef,3.13,, func,PyWeakref_NewProxy,3.2,, func,PyWeakref_NewRef,3.2,, diff --git a/Doc/deprecations/c-api-pending-removal-in-3.15.rst b/Doc/deprecations/c-api-pending-removal-in-3.15.rst index 8db3631cab31f1..9927b876760d34 100644 --- a/Doc/deprecations/c-api-pending-removal-in-3.15.rst +++ b/Doc/deprecations/c-api-pending-removal-in-3.15.rst @@ -3,7 +3,7 @@ Pending removal in Python 3.15 * The :c:func:`!PyImport_ImportModuleNoBlock`: Use :c:func:`PyImport_ImportModule` instead. -* :c:func:`PyWeakref_GetObject` and :c:func:`PyWeakref_GET_OBJECT`: +* :c:func:`!PyWeakref_GetObject` and :c:func:`!PyWeakref_GET_OBJECT`: Use :c:func:`PyWeakref_GetRef` instead. The `pythoncapi-compat project `__ can be used to get :c:func:`PyWeakref_GetRef` on Python 3.12 and older. diff --git a/Doc/howto/free-threading-extensions.rst b/Doc/howto/free-threading-extensions.rst index 577e283bb9cb4c..3776132c685414 100644 --- a/Doc/howto/free-threading-extensions.rst +++ b/Doc/howto/free-threading-extensions.rst @@ -173,9 +173,9 @@ that return :term:`strong references `. +-----------------------------------+-----------------------------------+ | :c:func:`PyDict_Next` | none (see :ref:`PyDict_Next`) | +-----------------------------------+-----------------------------------+ -| :c:func:`PyWeakref_GetObject` | :c:func:`PyWeakref_GetRef` | +| :c:func:`!PyWeakref_GetObject` | :c:func:`PyWeakref_GetRef` | +-----------------------------------+-----------------------------------+ -| :c:func:`PyWeakref_GET_OBJECT` | :c:func:`PyWeakref_GetRef` | +| :c:func:`!PyWeakref_GET_OBJECT` | :c:func:`PyWeakref_GetRef` | +-----------------------------------+-----------------------------------+ | :c:func:`PyImport_AddModule` | :c:func:`PyImport_AddModuleRef` | +-----------------------------------+-----------------------------------+ diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 2dd205dd2b8831..a095d887352127 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -2673,7 +2673,7 @@ Removed (Contributed by Victor Stinner in :issue:`45474`.) -* Exclude :c:func:`PyWeakref_GET_OBJECT` from the limited C API. It never +* Exclude :c:func:`!PyWeakref_GET_OBJECT` from the limited C API. It never worked since the :c:type:`!PyWeakReference` structure is opaque in the limited C API. (Contributed by Victor Stinner in :issue:`35134`.) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 67fec4ebc4a234..fbb27adbf9969c 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -2246,7 +2246,7 @@ New Features (Contributed by Serhiy Storchaka in :gh:`110289`.) * Add the :c:func:`PyWeakref_GetRef` function - as an alternative to :c:func:`PyWeakref_GetObject` + as an alternative to :c:func:`!PyWeakref_GetObject` that returns a :term:`strong reference` or ``NULL`` if the referent is no longer live. (Contributed by Victor Stinner in :gh:`105927`.) @@ -2531,8 +2531,8 @@ Deprecated C APIs are just aliases to :c:type:`!wchar_t`. (Contributed by Victor Stinner in :gh:`105156`.) -* Deprecate the :c:func:`PyWeakref_GetObject` and - :c:func:`PyWeakref_GET_OBJECT` functions, +* Deprecate the :c:func:`!PyWeakref_GetObject` and + :c:func:`!PyWeakref_GET_OBJECT` functions, which return a :term:`borrowed reference`. Replace them with the new :c:func:`PyWeakref_GetRef` function, which returns a :term:`strong reference`. diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 7b146621dddcfa..d5d387d9a0aaa7 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -869,6 +869,11 @@ Removed C APIs of :c:func:`PyImport_ImportModule`. (Contributed by Bénédikt Tran in :gh:`133644`.) +* :c:func:`!PyWeakref_GetObject` and :c:macro:`!PyWeakref_GET_OBJECT`: + use :c:func:`PyWeakref_GetRef` instead. The |pythoncapi_compat_project| + can be used to get :c:func:`!PyWeakref_GetRef` on Python 3.12 and older. + (Contributed by Bénédikt Tran in :gh:`133644`.) + * Remove deprecated :c:func:`!PySys_ResetWarnOptions`. Clear :data:`sys.warnoptions` and :data:`!warnings.filters` instead. diff --git a/Include/cpython/weakrefobject.h b/Include/cpython/weakrefobject.h index da8e77cddaca63..e0711407cee470 100644 --- a/Include/cpython/weakrefobject.h +++ b/Include/cpython/weakrefobject.h @@ -47,20 +47,3 @@ PyAPI_FUNC(void) _PyWeakref_ClearRef(PyWeakReference *self); // Test if a weak reference is dead. PyAPI_FUNC(int) PyWeakref_IsDead(PyObject *ref); - -Py_DEPRECATED(3.13) static inline PyObject* PyWeakref_GET_OBJECT(PyObject *ref_obj) -{ - PyWeakReference *ref = _PyWeakref_CAST(ref_obj); - PyObject *obj = ref->wr_object; - // Explanation for the Py_REFCNT() check: when a weakref's target is part - // of a long chain of deallocations which triggers the trashcan mechanism, - // clearing the weakrefs can be delayed long after the target's refcount - // has dropped to zero. In the meantime, code accessing the weakref will - // be able to "see" the target object even though it is supposed to be - // unreachable. See issue gh-60806. - if (Py_REFCNT(obj) > 0) { - return obj; - } - return Py_None; -} -#define PyWeakref_GET_OBJECT(ref) PyWeakref_GET_OBJECT(_PyObject_CAST(ref)) diff --git a/Include/weakrefobject.h b/Include/weakrefobject.h index a6e71eb178b124..17fac62961c0fb 100644 --- a/Include/weakrefobject.h +++ b/Include/weakrefobject.h @@ -27,7 +27,6 @@ PyAPI_FUNC(PyObject *) PyWeakref_NewRef(PyObject *ob, PyObject *callback); PyAPI_FUNC(PyObject *) PyWeakref_NewProxy(PyObject *ob, PyObject *callback); -Py_DEPRECATED(3.13) PyAPI_FUNC(PyObject *) PyWeakref_GetObject(PyObject *ref); #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030D0000 PyAPI_FUNC(int) PyWeakref_GetRef(PyObject *ref, PyObject **pobj); diff --git a/Misc/NEWS.d/3.11.0a2.rst b/Misc/NEWS.d/3.11.0a2.rst index 48cf2c1e428d87..12e03b46db0b3f 100644 --- a/Misc/NEWS.d/3.11.0a2.rst +++ b/Misc/NEWS.d/3.11.0a2.rst @@ -1188,7 +1188,7 @@ context objects can now be disabled. .. nonce: Z0Zk_m .. section: C API -Exclude :c:func:`PyWeakref_GET_OBJECT` from the limited C API. It never +Exclude :c:func:`!PyWeakref_GET_OBJECT` from the limited C API. It never worked since the :c:type:`!PyWeakReference` structure is opaque in the limited C API. diff --git a/Misc/NEWS.d/3.13.0a1.rst b/Misc/NEWS.d/3.13.0a1.rst index a3aa7353a1bba1..a5f0161e8f591e 100644 --- a/Misc/NEWS.d/3.13.0a1.rst +++ b/Misc/NEWS.d/3.13.0a1.rst @@ -6458,8 +6458,8 @@ Victor Stinner. .. nonce: GRxZtI .. section: C API -Deprecate the :c:func:`PyWeakref_GetObject` and -:c:func:`PyWeakref_GET_OBJECT` functions: use the new +Deprecate the :c:func:`!PyWeakref_GetObject` and +:c:func:`!PyWeakref_GET_OBJECT` functions: use the new :c:func:`PyWeakref_GetRef` function instead. Patch by Victor Stinner. .. @@ -6470,7 +6470,7 @@ Deprecate the :c:func:`PyWeakref_GetObject` and .. section: C API Add :c:func:`PyWeakref_GetRef` function: similar to -:c:func:`PyWeakref_GetObject` but returns a :term:`strong reference`, or +:c:func:`!PyWeakref_GetObject` but returns a :term:`strong reference`, or ``NULL`` if the referent is no longer live. Patch by Victor Stinner. .. diff --git a/Misc/NEWS.d/next/C_API/2025-05-08-12-40-59.gh-issue-133644.FNexLJ.rst b/Misc/NEWS.d/next/C_API/2025-05-08-12-40-59.gh-issue-133644.FNexLJ.rst new file mode 100644 index 00000000000000..71f1eaa5290d46 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2025-05-08-12-40-59.gh-issue-133644.FNexLJ.rst @@ -0,0 +1,3 @@ +Remove deprecated function :c:func:`!PyWeakref_GetObject` and macro +:c:macro:`!PyWeakref_GET_OBJECT`. Use :c:func:`PyWeakref_GetRef` instead. +Patch by Bénédikt Tran. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index d651e0fac111b1..4a03cc76f5e1e9 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -1600,6 +1600,7 @@ added = '3.2' [function.PyWeakref_GetObject] added = '3.2' + abi_only = true [function.PyWeakref_NewProxy] added = '3.2' [function.PyWeakref_NewRef] diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index a5c4604056ab4e..508ef55511e49d 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2206,9 +2206,8 @@ test_macros(PyObject *self, PyObject *Py_UNUSED(args)) static PyObject * test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) { - // Ignore PyWeakref_GetObject() deprecation, we test it on purpose - _Py_COMP_DIAG_PUSH - _Py_COMP_DIAG_IGNORE_DEPR_DECLS + // Get the function (removed in 3.15) from the stable ABI. + PyAPI_FUNC(PyObject *) PyWeakref_GetObject(PyObject *); // Create a new heap type, create an instance of this type, and delete the // type. This object supports weak references. @@ -2249,19 +2248,12 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) ref = PyWeakref_GetObject(weakref); // borrowed ref assert(ref == obj); - // test PyWeakref_GET_OBJECT(), reference is alive - ref = PyWeakref_GET_OBJECT(weakref); // borrowed ref - assert(ref == obj); - // delete the referenced object: clear the weakref assert(Py_REFCNT(obj) == 1); Py_DECREF(obj); assert(PyWeakref_IsDead(weakref)); - // test PyWeakref_GET_OBJECT(), reference is dead - assert(PyWeakref_GET_OBJECT(weakref) == Py_None); - // test PyWeakref_GetRef(), reference is dead ref = UNINITIALIZED_PTR; assert(PyWeakref_GetRef(weakref, &ref) == 0); @@ -2312,8 +2304,6 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) Py_DECREF(weakref); Py_RETURN_NONE; - - _Py_COMP_DIAG_POP } struct simpletracer_data { diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c index bd4c4ac9b3475a..61fa3ddad0bfd8 100644 --- a/Objects/weakrefobject.c +++ b/Objects/weakrefobject.c @@ -964,7 +964,8 @@ PyWeakref_GetRef(PyObject *ref, PyObject **pobj) } -PyObject * +/* removed in 3.15, but kept for stable ABI compatibility */ +PyAPI_FUNC(PyObject *) PyWeakref_GetObject(PyObject *ref) { if (ref == NULL || !PyWeakref_Check(ref)) {