From fc2e23c2ed25bb7b23cf8a870a2e21f012735507 Mon Sep 17 00:00:00 2001 From: RayXu <140802139+F18-Maverick@users.noreply.github.com> Date: Fri, 24 Oct 2025 20:17:52 +0800 Subject: [PATCH 1/6] Docs: Fix a grammatical error in `Doc/c-api/arg.rst` (GH-140525) "have" -> "has" --- Doc/c-api/arg.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/arg.rst b/Doc/c-api/arg.rst index 28bf04651bd1737..fd6be6a9b67a031 100644 --- a/Doc/c-api/arg.rst +++ b/Doc/c-api/arg.rst @@ -160,7 +160,7 @@ There are three ways strings and buffers can be converted to C: ``w*`` (read-write :term:`bytes-like object`) [Py_buffer] This format accepts any object which implements the read-write buffer interface. It fills a :c:type:`Py_buffer` structure provided by the caller. - The buffer may contain embedded null bytes. The caller have to call + The buffer may contain embedded null bytes. The caller has to call :c:func:`PyBuffer_Release` when it is done with the buffer. ``es`` (:class:`str`) [const char \*encoding, char \*\*buffer] From 289360ae63933c8956f87307ff091ec9ed19afed Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 24 Oct 2025 15:28:48 +0200 Subject: [PATCH 2/6] Document that PyModule_GetDef can return NULL with or without setting an exception (GH-140529) --- Doc/c-api/module.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/c-api/module.rst b/Doc/c-api/module.rst index 29d5cd8d5391a39..6626f628fcc47f5 100644 --- a/Doc/c-api/module.rst +++ b/Doc/c-api/module.rst @@ -102,6 +102,10 @@ Module Objects Return a pointer to the :c:type:`PyModuleDef` struct from which the module was created, or ``NULL`` if the module wasn't created from a definition. + On error, return ``NULL`` with an exception set. + Use :c:func:`PyErr_Occurred` to tell this case apart from a mising + :c:type:`!PyModuleDef`. + .. c:function:: PyObject* PyModule_GetFilenameObject(PyObject *module) From ebf99384966344df77828e69dd88d34df878a7b6 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Fri, 24 Oct 2025 19:53:06 +0530 Subject: [PATCH 3/6] gh-140544: cleanup `HAVE_THREAD_LOCAL` checks in pystate.c (#140547) --- Python/pystate.c | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index 955457cdcf5c289..2f76adf50260125 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -67,48 +67,34 @@ to avoid the expense of doing their own locking). For each of these functions, the GIL must be held by the current thread. */ +#ifndef HAVE_THREAD_LOCAL +# error "no supported thread-local variable storage classifier" +#endif -#ifdef HAVE_THREAD_LOCAL /* The attached thread state for the current thread. */ _Py_thread_local PyThreadState *_Py_tss_tstate = NULL; /* The "bound" thread state used by PyGILState_Ensure(), also known as a "gilstate." */ _Py_thread_local PyThreadState *_Py_tss_gilstate = NULL; -#endif static inline PyThreadState * current_fast_get(void) { -#ifdef HAVE_THREAD_LOCAL return _Py_tss_tstate; -#else - // XXX Fall back to the PyThread_tss_*() API. -# error "no supported thread-local variable storage classifier" -#endif } static inline void current_fast_set(_PyRuntimeState *Py_UNUSED(runtime), PyThreadState *tstate) { assert(tstate != NULL); -#ifdef HAVE_THREAD_LOCAL _Py_tss_tstate = tstate; -#else - // XXX Fall back to the PyThread_tss_*() API. -# error "no supported thread-local variable storage classifier" -#endif } static inline void current_fast_clear(_PyRuntimeState *Py_UNUSED(runtime)) { -#ifdef HAVE_THREAD_LOCAL _Py_tss_tstate = NULL; -#else - // XXX Fall back to the PyThread_tss_*() API. -# error "no supported thread-local variable storage classifier" -#endif } #define tstate_verify_not_active(tstate) \ @@ -2494,14 +2480,10 @@ _PyThreadState_Bind(PyThreadState *tstate) uintptr_t _Py_GetThreadLocal_Addr(void) { -#ifdef HAVE_THREAD_LOCAL // gh-112535: Use the address of the thread-local PyThreadState variable as // a unique identifier for the current thread. Each thread has a unique // _Py_tss_tstate variable with a unique address. return (uintptr_t)&_Py_tss_tstate; -#else -# error "no supported thread-local variable storage classifier" -#endif } #endif From 95e5d596308620acbd860ec25a40ef95c2b62eaa Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Fri, 24 Oct 2025 20:02:17 +0530 Subject: [PATCH 4/6] gh-140414: add fastpath for current running loop in `asyncio.all_tasks` (#140542) Optimize `asyncio.all_tasks()` for the common case where the event loop is running in the current thread by avoiding stop-the-world pauses and locking. This optimization is already present for `asyncio.current_task()` so we do the same for `asyncio.all_tasks()`. --- Modules/_asynciomodule.c | 62 ++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 99408e60721c602..1f58b1fb3506c6c 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -4079,30 +4079,44 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) return NULL; } - PyInterpreterState *interp = PyInterpreterState_Get(); - // Stop the world and traverse the per-thread linked list - // of asyncio tasks for every thread, as well as the - // interpreter's linked list, and add them to `tasks`. - // The interpreter linked list is used for any lingering tasks - // whose thread state has been deallocated while the task was - // still alive. This can happen if a task is referenced by - // a different thread, in which case the task is moved to - // the interpreter's linked list from the thread's linked - // list before deallocation. See PyThreadState_Clear. - // - // The stop-the-world pause is required so that no thread - // modifies its linked list while being iterated here - // in parallel. This design allows for lock-free - // register_task/unregister_task for loops running in parallel - // in different threads (the general case). - _PyEval_StopTheWorld(interp); - int ret = add_tasks_interp(interp, (PyListObject *)tasks); - _PyEval_StartTheWorld(interp); - if (ret < 0) { - // call any escaping calls after starting the world to avoid any deadlocks. - Py_DECREF(tasks); - Py_DECREF(loop); - return NULL; + _PyThreadStateImpl *ts = (_PyThreadStateImpl *)_PyThreadState_GET(); + if (ts->asyncio_running_loop == loop) { + // Fast path for the current running loop of current thread + // no locking or stop the world pause is required + struct llist_node *head = &ts->asyncio_tasks_head; + if (add_tasks_llist(head, (PyListObject *)tasks) < 0) { + Py_DECREF(tasks); + Py_DECREF(loop); + return NULL; + } + } + else { + // Slow path for loop running in different thread + PyInterpreterState *interp = ts->base.interp; + // Stop the world and traverse the per-thread linked list + // of asyncio tasks for every thread, as well as the + // interpreter's linked list, and add them to `tasks`. + // The interpreter linked list is used for any lingering tasks + // whose thread state has been deallocated while the task was + // still alive. This can happen if a task is referenced by + // a different thread, in which case the task is moved to + // the interpreter's linked list from the thread's linked + // list before deallocation. See PyThreadState_Clear. + // + // The stop-the-world pause is required so that no thread + // modifies its linked list while being iterated here + // in parallel. This design allows for lock-free + // register_task/unregister_task for loops running in parallel + // in different threads (the general case). + _PyEval_StopTheWorld(interp); + int ret = add_tasks_interp(interp, (PyListObject *)tasks); + _PyEval_StartTheWorld(interp); + if (ret < 0) { + // call any escaping calls after starting the world to avoid any deadlocks. + Py_DECREF(tasks); + Py_DECREF(loop); + return NULL; + } } // All the tasks are now in the list, now filter the tasks which are done From be5af997f3461eee638645396866d9cd7acf18fb Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Fri, 24 Oct 2025 18:59:16 +0300 Subject: [PATCH 5/6] gh-140517: fix leak in `map_next` in strict mode (#140543) --- Lib/test/test_builtin.py | 16 +++++++++++++++ Python/bltinmodule.c | 44 +++++++++++++++++++++++----------------- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 034cd5f7f9e89f3..fe3e391a7f5ba14 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -1385,6 +1385,22 @@ def test_map_strict(self): self.assertRaises(ValueError, tuple, map(pack, (1, 2), (1, 2), 'abc', strict=True)) + # gh-140517: Testing refleaks with mortal objects. + t1 = (None, object()) + t2 = (object(), object()) + t3 = (object(),) + + self.assertRaises(ValueError, tuple, + map(pack, t1, 'a', strict=True)) + self.assertRaises(ValueError, tuple, + map(pack, t1, t2, 'a', strict=True)) + self.assertRaises(ValueError, tuple, + map(pack, t1, t2, t3, strict=True)) + self.assertRaises(ValueError, tuple, + map(pack, 'a', t1, strict=True)) + self.assertRaises(ValueError, tuple, + map(pack, 'a', t2, t3, strict=True)) + def test_map_strict_iterators(self): x = iter(range(5)) y = [0] diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 64249177eec5f20..f6fadd936bb8ff4 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -1501,34 +1501,27 @@ map_next(PyObject *self) } Py_ssize_t nargs = 0; - for (i=0; i < niters; i++) { + for (i = 0; i < niters; i++) { PyObject *it = PyTuple_GET_ITEM(lz->iters, i); PyObject *val = Py_TYPE(it)->tp_iternext(it); if (val == NULL) { if (lz->strict) { goto check; } - goto exit; + goto exit_no_result; } stack[i] = val; nargs++; } result = _PyObject_VectorcallTstate(tstate, lz->func, stack, nargs, NULL); + goto exit; -exit: - for (i=0; i < nargs; i++) { - Py_DECREF(stack[i]); - } - if (stack != small_stack) { - PyMem_Free(stack); - } - return result; check: if (PyErr_Occurred()) { if (!PyErr_ExceptionMatches(PyExc_StopIteration)) { // next() on argument i raised an exception (not StopIteration) - return NULL; + goto exit_no_result; } PyErr_Clear(); } @@ -1536,9 +1529,10 @@ map_next(PyObject *self) // ValueError: map() argument 2 is shorter than argument 1 // ValueError: map() argument 3 is shorter than arguments 1-2 const char* plural = i == 1 ? " " : "s 1-"; - return PyErr_Format(PyExc_ValueError, - "map() argument %d is shorter than argument%s%d", - i + 1, plural, i); + PyErr_Format(PyExc_ValueError, + "map() argument %d is shorter than argument%s%d", + i + 1, plural, i); + goto exit_no_result; } for (i = 1; i < niters; i++) { PyObject *it = PyTuple_GET_ITEM(lz->iters, i); @@ -1546,21 +1540,33 @@ map_next(PyObject *self) if (val) { Py_DECREF(val); const char* plural = i == 1 ? " " : "s 1-"; - return PyErr_Format(PyExc_ValueError, - "map() argument %d is longer than argument%s%d", - i + 1, plural, i); + PyErr_Format(PyExc_ValueError, + "map() argument %d is longer than argument%s%d", + i + 1, plural, i); + goto exit_no_result; } if (PyErr_Occurred()) { if (!PyErr_ExceptionMatches(PyExc_StopIteration)) { // next() on argument i raised an exception (not StopIteration) - return NULL; + goto exit_no_result; } PyErr_Clear(); } // Argument i is exhausted. So far so good... } // All arguments are exhausted. Success! - goto exit; + +exit_no_result: + assert(result == NULL); + +exit: + for (i = 0; i < nargs; i++) { + Py_DECREF(stack[i]); + } + if (stack != small_stack) { + PyMem_Free(stack); + } + return result; } static PyObject * From 44b6eead21080e1abb3ca9ee2e1c7bc8e7a3c3a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=A1a=20Jakovljevi=C4=87?= Date: Fri, 24 Oct 2025 18:04:43 +0200 Subject: [PATCH 6/6] gh-140463: Fix typo in xmlrpc.client documentation (GH-140552) Fix typo in xmlrpc.client documentation --- Doc/library/xmlrpc.client.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/xmlrpc.client.rst b/Doc/library/xmlrpc.client.rst index 547cb50be78a782..a21c7d3e4e3ad52 100644 --- a/Doc/library/xmlrpc.client.rst +++ b/Doc/library/xmlrpc.client.rst @@ -524,7 +524,7 @@ Convenience Functions .. function:: dumps(params, methodname=None, methodresponse=None, encoding=None, allow_none=False) - Convert *params* into an XML-RPC request. or into a response if *methodresponse* + Convert *params* into an XML-RPC request, or into a response if *methodresponse* is true. *params* can be either a tuple of arguments or an instance of the :exc:`Fault` exception class. If *methodresponse* is true, only a single value can be returned, meaning that *params* must be of length 1. *encoding*, if