8000 bpo-39877: take_gil() now exits the thread if finalizing by vstinner · Pull Request #18854 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-39877: take_gil() now exits the thread if finalizing #18854

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
bpo-39877: take_gil() now exits the thread if finalizing
take_gil() is now responsible to exit the thread if Python is
finalizing. Not only exit before trying to acquire the GIL, but exit
also once the GIL is succesfully acquired.
  • Loading branch information
vstinner committed Mar 8, 2020
commit 9ac0b252ab77694a216c27a5622a2dd1359aa3f5
66 changes: 7 additions & 59 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -197,16 +197,6 @@ ensure_tstate_not_null(const char *func, PyThreadState *tstate)
}


#ifndef NDEBUG
static int is_tstate_valid(PyThreadState *tstate)
{
assert(!_PyMem_IsPtrFreed(tstate));
assert(!_PyMem_IsPtrFreed(tstate->interp));
return 1;
}
#endif


int
PyEval_ThreadsInitialized(void)
{
Expand All @@ -228,7 +218,8 @@ PyEval_InitThreads(void)
create_gil(gil);
PyThreadState *tstate = _PyRuntimeState_GetThreadState(runtime);
ensure_tstate_not_null(__func__, tstate);
take_gil(ceval, tstate);

take_gil(tstate);

struct _pending_calls *pending = &ceval->pending;
pending->lock = PyThread_allocate_lock();
Expand All @@ -255,29 +246,6 @@ _PyEval_FiniThreads(struct _ceval_runtime_state *ceval)
}
}

/* This function is designed to exit daemon threads immediately rather than
taking the GIL if Py_Finalize() has been called.

The caller must *not* hold the GIL, since this function does not release
the GIL before exiting the thread.

When this function is called by a daemon thread after Py_Finalize() has been
called, the GIL does no longer exist.

tstate must be non-NULL. */
static inline void
exit_thread_if_finalizing(PyThreadState *tstate)
{
/* bpo-39877: Access _PyRuntime directly rather than using
tstate->interp->runtime to support calls from Python daemon threads.
After Py_Finalize() has been called, tstate can be a dangling pointer:
point to PyThreadState freed memory. */
_PyRuntimeState *runtime = &_PyRuntime;
PyThreadState *finalizing = _PyRuntimeState_GetFinalizing(runtime);
if (finalizing != NULL && finalizing != tstate) {
PyThread_exit_thread();
}
}

void
_PyEval_Fini(void)
Expand Down Expand Up @@ -315,11 +283,8 @@ PyEval_AcquireLock(void)
PyThreadState *tstate = _PyRuntimeState_GetThreadState(runtime);
ensure_tstate_not_null(__func__, tstate);

exit_thread_if_finalizing(tstate);
assert(is_tstate_valid(tstate));
take_gil(tstate);

struct _ceval_runtime_state *ceval = &runtime->ceval;
take_gil(ceval, tstate);
}

void
Expand All @@ -339,16 +304,9 @@ PyEval_AcquireThread(PyThreadState *tstate)
{
ensure_tstate_not_null(__func__, tstate);

exit_thread_if_finalizing(tstate);
assert(is_tstate_valid(tstate));
take_gil(tstate);

_PyRuntimeState *runtime = tstate->interp->runtime;
struct _ceval_runtime_state *ceval = &runtime->ceval;

/* Check someone has called PyEval_InitThreads() to create the lock */
assert(gil_created(&ceval->gil));

take_gil(ceval, tstate);
if (_PyThreadState_Swap(&runtime->gilstate, tstate) != NULL) {
Py_FatalError("non-NULL old thread state");
}
Expand Down Expand Up @@ -382,7 +340,7 @@ _PyEval_ReInitThreads(_PyRuntimeState *runtime)
recreate_gil(&ceval->gil);
PyThreadState *tstate = _PyRuntimeState_GetThreadState(runtime);
ensure_tstate_not_null(__func__, tstate);
take_gil(ceval, tstate);
take_gil(tstate);

struct _pending_calls *pending = &ceval->pending;
pending->lock = PyThread_allocate_lock();
Expand Down Expand Up @@ -422,15 +380,9 @@ PyEval_RestoreThread(PyThreadState *tstate)
{
ensure_tstate_not_null(__func__, tstate);

exit_thread_if_finalizing(tstate);
assert(is_tstate_valid(tstate));
take_gil(tstate);

_PyRuntimeState *runtime = tstate->interp->runtime;
struct _ceval_runtime_state *ceval = &runtime->ceval;
assert(gil_created(&ceval->gil));

take_gil(ceval, tstate);

_PyThreadState_Swap(&runtime->gilstate, tstate);
}

Expand Down Expand Up @@ -793,7 +745,6 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)

PyThreadState * const tstate = _PyRuntimeState_GetThreadState(runtime);
ensure_tstate_not_null(__func__, tstate);
assert(is_tstate_valid(tstate));

/* when tracing we set things up so that

Expand Down Expand Up @@ -1282,10 +1233,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)

/* Other threads may run now */

/* Check if we should make a quick exit. */
exit_thread_if_finalizing(tstate);

take_gil(ceval, tstate);
take_gil(tstate);

if (_PyThreadState_Swap(&runtime->gilstate, tstate) != NULL) {
Py_FatalError("orphan tstate");
Expand Down
54 changes: 53 additions & 1 deletion Python/ceval_gil.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,17 +180,57 @@ drop_gil(struct _ceval_runtime_state *ceval, PyThreadState *tstate)
#endif
}


static inline int
thread_must_exit(PyThreadState *tstate)
{
/* bpo-39877: Access _PyRuntime directly rather than using
tstate->interp->runtime to support calls from Python daemon threads.
After Py_Finalize() has been called, tstate can be a dangling pointer:
point to PyThreadState freed memory. */
_PyRuntimeState *runtime = &_PyRuntime;
PyThreadState *finalizing = _PyRuntimeState_GetFinalizing(runtime);
return (finalizing != NULL && finalizing != tstate);
}


/* Take the GIL.

The function saves errno at entry and restores its value at exit.

Exit immediately the thread if Py_Finalize() has been called and tstate is
not the thread which called Py_Finalize(). For example, exit daemon threads
which survive after Py_Finalize().

tstate must be non-NULL. */
static void
take_gil(struct _ceval_runtime_state *ceval, PyThreadState *tstate)
take_gil(PyThreadState *tstate)
{
int err = errno;

if (thread_must_exit(tstate)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, but shouldn't you do this after COND_TIMED_WAIT below as well?

/* bpo-39877: If Py_Finalize() has been called and tstate is not the
thread which called Py_Finalize(), exit immediately the thread.

This code path can be reached by a daemon thread after Py_Finalize()
completes. In this case, tstate is a dangling pointer: points to
PyThreadState freed memory.

When this function is called after Py_Finalize() completed, the GIL
does no longer exist. */
PyThread_exit_thread();
}

/* ensure that tstate is valid */
assert(!_PyMem_IsPtrFreed(tstate));
assert(!_PyMem_IsPtrFreed(tstate->interp));

struct _ceval_runtime_state *ceval = &tstate->interp->runtime->ceval;
struct _gil_runtime_state *gil = &ceval->gil;

/* Check someone has called PyEval_InitThreads() to create the lock */
assert(gil_created(gil));

MUTEX_LOCK(gil->mutex);

if (!_Py_atomic_load_relaxed(&gil->locked)) {
Expand Down Expand Up @@ -243,6 +283,18 @@ take_gil(struct _ceval_runtime_state *ceval, PyThreadState *tstate)

MUTEX_UNLOCK(gil->mutex);

if (thread_must_exit(tstate)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why you're doing this here. It doesn't seem necessary and, furthermore, it's unlikely to trigger.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code path is to prevent https://bugs.python.org/issue39877#msg363667 crash. Are you suggesting to move this code at line 249, after COND_TIMED_WAIT()?

thread_must_exit() at entry is to prevent https://bugs.python.org/issue39877#msg363512 crash.

/* bpo-36475: If Py_Finalize() has been called and tstate is not the
thread which called Py_Finalize(), exit immediately the thread.

This code path can be reached by a daemon thread which continues to
run after wait_for_thread_shutdown() and before Py_Finalize()
completes. For example, when _PyImport_Cleanup() executes Python
code. */
drop_gil(ceval, tstate);
PyThread_exit_thread();
}

errno = err;
}

Expand Down
0