Skip to content

Commit

Permalink
Merge branch 'main' into pythongh-124478
Browse files Browse the repository at this point in the history
  • Loading branch information
savannahostrowski authored Oct 3, 2024
2 parents f1e5e71 + c066bf5 commit 743bfb5
Show file tree
Hide file tree
Showing 45 changed files with 1,332 additions and 641 deletions.
76 changes: 60 additions & 16 deletions Doc/c-api/init.rst
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,11 @@ Initializing and finalizing the interpreter
Some memory allocated by extension modules may not be freed. Some extensions may not
work properly if their initialization routine is called more than once; this can
happen if an application calls :c:func:`Py_Initialize` and :c:func:`Py_FinalizeEx`
more than once.
more than once. :c:func:`Py_FinalizeEx` must not be called recursively from
within itself. Therefore, it must not be called by any code that may be run
as part of the interpreter shutdown process, such as :py:mod:`atexit`
handlers, object finalizers, or any code that may be run while flushing the
stdout and stderr files.

.. audit-event:: cpython._PySys_ClearAuditHooks "" c.Py_FinalizeEx

Expand Down Expand Up @@ -960,6 +964,37 @@ thread, where the CPython global runtime was originally initialized.
The only exception is if :c:func:`exec` will be called immediately
after.
.. _cautions-regarding-runtime-finalization:
Cautions regarding runtime finalization
---------------------------------------
In the late stage of :term:`interpreter shutdown`, after attempting to wait for
non-daemon threads to exit (though this can be interrupted by
:class:`KeyboardInterrupt`) and running the :mod:`atexit` functions, the runtime
is marked as *finalizing*: :c:func:`_Py_IsFinalizing` and
:func:`sys.is_finalizing` return true. At this point, only the *finalization
thread* that initiated finalization (typically the main thread) is allowed to
acquire the :term:`GIL`.
If any thread, other than the finalization thread, attempts to acquire the GIL
during finalization, either explicitly via :c:func:`PyGILState_Ensure`,
:c:macro:`Py_END_ALLOW_THREADS`, :c:func:`PyEval_AcquireThread`, or
:c:func:`PyEval_AcquireLock`, or implicitly when the interpreter attempts to
reacquire it after having yielded it, the thread enters **a permanently blocked
state** where it remains until the program exits. In most cases this is
harmless, but this can result in deadlock if a later stage of finalization
attempts to acquire a lock owned by the blocked thread, or otherwise waits on
the blocked thread.
Gross? Yes. This prevents random crashes and/or unexpectedly skipped C++
finalizations further up the call stack when such threads were forcibly exited
here in CPython 3.13 and earlier. The CPython runtime GIL acquiring C APIs
have never had any error reporting or handling expectations at GIL acquisition
time that would've allowed for graceful exit from this situation. Changing that
would require new stable C APIs and rewriting the majority of C code in the
CPython ecosystem to use those with error handling.
High-level API
--------------
Expand Down Expand Up @@ -1033,11 +1068,14 @@ code, or when embedding the Python interpreter:
ensues.
.. note::
Calling this function from a thread when the runtime is finalizing
will terminate the thread, even if the thread was not created by Python.
You can use :c:func:`Py_IsFinalizing` or :func:`sys.is_finalizing` to
check if the interpreter is in process of being finalized before calling
this function to avoid unwanted termination.
Calling this function from a thread when the runtime is finalizing will
hang the thread until the program exits, even if the thread was not
created by Python. Refer to
:ref:`cautions-regarding-runtime-finalization` for more details.
.. versionchanged:: next
Hangs the current thread, rather than terminating it, if called while the
interpreter is finalizing.
.. c:function:: PyThreadState* PyThreadState_Get()
Expand Down Expand Up @@ -1092,11 +1130,14 @@ with sub-interpreters:
to call arbitrary Python code. Failure is a fatal error.
.. note::
Calling this function from a thread when the runtime is finalizing
will terminate the thread, even if the thread was not created by Python.
You can use :c:func:`Py_IsFinalizing` or :func:`sys.is_finalizing` to
check if the interpreter is in process of being finalized before calling
this function to avoid unwanted termination.
Calling this function from a thread when the runtime is finalizing will
hang the thread until the program exits, even if the thread was not
created by Python. Refer to
:ref:`cautions-regarding-runtime-finalization` for more details.
.. versionchanged:: next
Hangs the current thread, rather than terminating it, if called while the
interpreter is finalizing.
.. c:function:: void PyGILState_Release(PyGILState_STATE)
Expand Down Expand Up @@ -1374,17 +1415,20 @@ All of the following functions must be called after :c:func:`Py_Initialize`.
If this thread already has the lock, deadlock ensues.
.. note::
Calling this function from a thread when the runtime is finalizing
will terminate the thread, even if the thread was not created by Python.
You can use :c:func:`Py_IsFinalizing` or :func:`sys.is_finalizing` to
check if the interpreter is in process of being finalized before calling
this function to avoid unwanted termination.
Calling this function from a thread when the runtime is finalizing will
hang the thread until the program exits, even if the thread was not
created by Python. Refer to
:ref:`cautions-regarding-runtime-finalization` for more details.
.. versionchanged:: 3.8
Updated to be consistent with :c:func:`PyEval_RestoreThread`,
:c:func:`Py_END_ALLOW_THREADS`, and :c:func:`PyGILState_Ensure`,
and terminate the current thread if called while the interpreter is finalizing.
.. versionchanged:: next
Hangs the current thread, rather than terminating it, if called while the
interpreter is finalizing.
:c:func:`PyEval_RestoreThread` is a higher-level function which is always
available (even when threads have not been initialized).
Expand Down
3 changes: 3 additions & 0 deletions Include/cpython/longobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
# error "this header file must not be included directly"
#endif

#define _PyLong_CAST(op) \
(assert(PyLong_Check(op)), _Py_CAST(PyLongObject*, (op)))

PyAPI_FUNC(PyObject*) PyLong_FromUnicodeObject(PyObject *u, int base);

#define Py_ASNATIVEBYTES_DEFAULTS -1
Expand Down
10 changes: 5 additions & 5 deletions Include/cpython/weakrefobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@ struct _PyWeakReference {

PyAPI_FUNC(void) _PyWeakref_ClearRef(PyWeakReference *self);

#define _PyWeakref_CAST(op) \
(assert(PyWeakref_Check(op)), _Py_CAST(PyWeakReference*, (op)))

Py_DEPRECATED(3.13) static inline PyObject* PyWeakref_GET_OBJECT(PyObject *ref_obj)
{
PyWeakReference *ref;
PyObject *obj;
assert(PyWeakref_Check(ref_obj));
ref = _Py_CAST(PyWeakReference*, ref_obj);
obj = ref->wr_object;
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
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_moduleobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ static inline PyObject* _PyModule_GetDict(PyObject *mod) {
}

PyObject* _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress);
PyObject* _Py_module_getattro(PyModuleObject *m, PyObject *name);
PyObject* _Py_module_getattro(PyObject *m, PyObject *name);

#ifdef __cplusplus
}
Expand Down
13 changes: 13 additions & 0 deletions Include/internal/pycore_pythread.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,19 @@ PyAPI_FUNC(int) PyThread_join_thread(PyThread_handle_t);
* a non-zero value on failure.
*/
PyAPI_FUNC(int) PyThread_detach_thread(PyThread_handle_t);
/*
* Hangs the thread indefinitely without exiting it.
*
* gh-87135: There is no safe way to exit a thread other than returning
* normally from its start function. This is used during finalization in lieu
* of actually exiting the thread. Since the program is expected to terminate
* soon anyway, it does not matter if the thread stack stays around until then.
*
* This is unfortunate for embedders who may not be terminating their process
* when they're done with the interpreter, but our C API design does not allow
* for safely exiting threads attempting to re-enter Python post finalization.
*/
void _Py_NO_RETURN PyThread_hang_thread(void);

#ifdef __cplusplus
}
Expand Down
21 changes: 20 additions & 1 deletion Include/pythread.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,26 @@ typedef enum PyLockStatus {

PyAPI_FUNC(void) PyThread_init_thread(void);
PyAPI_FUNC(unsigned long) PyThread_start_new_thread(void (*)(void *), void *);
PyAPI_FUNC(void) _Py_NO_RETURN PyThread_exit_thread(void);
/* Terminates the current thread. Considered unsafe.
*
* WARNING: This function is only safe to call if all functions in the full call
* stack are written to safely allow it. Additionally, the behavior is
* platform-dependent. This function should be avoided, and is no longer called
* by Python itself. It is retained only for compatibility with existing C
* extension code.
*
* With pthreads, calls `pthread_exit` causes some libcs (glibc?) to attempt to
* unwind the stack and call C++ destructors; if a `noexcept` function is
* reached, they may terminate the process. Others (macOS) do unwinding.
*
* On Windows, calls `_endthreadex` which kills the thread without calling C++
* destructors.
*
* In either case there is a risk of invalid references remaining to data on the
* thread stack.
*/
Py_DEPRECATED(3.14) PyAPI_FUNC(void) _Py_NO_RETURN PyThread_exit_thread(void);

PyAPI_FUNC(unsigned long) PyThread_get_thread_ident(void);

#if (defined(__APPLE__) || defined(__linux__) || defined(_WIN32) \
Expand Down
13 changes: 10 additions & 3 deletions Lib/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,8 +527,7 @@ def _format_action(self, action):
def _format_action_invocation(self, action):
if not action.option_strings:
default = self._get_default_metavar_for_positional(action)
metavar, = self._metavar_formatter(action, default)(1)
return metavar
return ' '.join(self._metavar_formatter(action, default)(1))

else:

Expand Down Expand Up @@ -703,7 +702,15 @@ def _get_action_name(argument):
elif argument.option_strings:
return '/'.join(argument.option_strings)
elif argument.metavar not in (None, SUPPRESS):
return argument.metavar
metavar = argument.metavar
if not isinstance(metavar, tuple):
return metavar
if argument.nargs == ZERO_OR_MORE and len(metavar) == 2:
return '%s[, %s]' % metavar
elif argument.nargs == ONE_OR_MORE:
return '%s[, %s]' % metavar
else:
return ', '.join(metavar)
elif argument.dest not in (None, SUPPRESS):
return argument.dest
elif argument.choices:
Expand Down
18 changes: 12 additions & 6 deletions Lib/asyncio/base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import collections.abc
import concurrent.futures
import errno
import functools
import heapq
import itertools
import os
Expand Down Expand Up @@ -1140,11 +1139,18 @@ async def create_connection(
except OSError:
continue
else: # using happy eyeballs
sock, _, _ = await staggered.staggered_race(
(functools.partial(self._connect_sock,
exceptions, addrinfo, laddr_infos)
for addrinfo in infos),
happy_eyeballs_delay, loop=self)
sock = (await staggered.staggered_race(
(
# can't use functools.partial as it keeps a reference
# to exceptions
lambda addrinfo=addrinfo: self._connect_sock(
exceptions, addrinfo, laddr_infos
)
for addrinfo in infos
),
happy_eyeballs_delay,
loop=self,
))[0] # can't use sock, _, _ as it keeks a reference to exceptions

if sock is None:
exceptions = [exc for sub in exceptions for exc in sub]
Expand Down
1 change: 1 addition & 0 deletions Lib/asyncio/staggered.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ async def run_one_coro(previous_failed) -> None:
raise d.exception()
return winner_result, winner_index, exceptions
finally:
del exceptions
# Make sure no tasks are left running if we leave this function
for t in running_tasks:
t.cancel()
5 changes: 3 additions & 2 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2907,10 +2907,11 @@ def in_systemd_nspawn_sync_suppressed() -> bool:
# trigger EINVAL. Otherwise, ENOENT will be given instead.
import errno
try:
with os.open(__file__, os.O_RDONLY | os.O_SYNC):
pass
fd = os.open(__file__, os.O_RDONLY | os.O_SYNC)
except OSError as err:
if err.errno == errno.EINVAL:
return True
else:
os.close(fd)

return False
Loading

0 comments on commit 743bfb5

Please sign in to comment.