Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Doc/c-api/arg.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
4 changes: 4 additions & 0 deletions Doc/c-api/module.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion Doc/library/xmlrpc.client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
62 changes: 38 additions & 24 deletions Modules/_asynciomodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
44 changes: 25 additions & 19 deletions Python/bltinmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1501,66 +1501,72 @@ 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();
}
if (i) {
// 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);
PyObject *val = (*Py_TYPE(it)->tp_iternext)(it);
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 *
Expand Down
24 changes: 3 additions & 21 deletions Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down Expand Up @@ -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

Expand Down
Loading