File size: 9,568 Bytes
db4a26f |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
/**
* Implementation of the ThreadState destructors.
*
* Format with:
* clang-format -i --style=file src/greenlet/greenlet.c
*
*
* Fix missing braces with:
* clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements"
*/
#ifndef T_THREADSTATE_DESTROY
#define T_THREADSTATE_DESTROY
#include "TGreenlet.hpp"
#include "greenlet_thread_support.hpp"
#include "greenlet_cpython_add_pending.hpp"
#include "greenlet_compiler_compat.hpp"
#include "TGreenletGlobals.cpp"
#include "TThreadState.hpp"
#include "TThreadStateCreator.hpp"
namespace greenlet {
extern "C" {
struct ThreadState_DestroyNoGIL
{
/**
This function uses the same lock that the PendingCallback does
*/
static void
MarkGreenletDeadAndQueueCleanup(ThreadState* const state)
{
#if GREENLET_BROKEN_THREAD_LOCAL_CLEANUP_JUST_LEAK
return;
#endif
// We are *NOT* holding the GIL. Our thread is in the middle
// of its death throes and the Python thread state is already
// gone so we can't use most Python APIs. One that is safe is
// ``Py_AddPendingCall``, unless the interpreter itself has
// been torn down. There is a limited number of calls that can
// be queued: 32 (NPENDINGCALLS) in CPython 3.10, so we
// coalesce these calls using our own queue.
if (!MarkGreenletDeadIfNeeded(state)) {
// No state, or no greenlet
return;
}
// XXX: Because we don't have the GIL, this is a race condition.
if (!PyInterpreterState_Head()) {
// We have to leak the thread state, if the
// interpreter has shut down when we're getting
// deallocated, we can't run the cleanup code that
// deleting it would imply.
return;
}
AddToCleanupQueue(state);
}
private:
// If the state has an allocated main greenlet:
// - mark the greenlet as dead by disassociating it from the state;
// - return 1
// Otherwise, return 0.
static bool
MarkGreenletDeadIfNeeded(ThreadState* const state)
{
if (state && state->has_main_greenlet()) {
// mark the thread as dead ASAP.
// this is racy! If we try to throw or switch to a
// greenlet from this thread from some other thread before
// we clear the state pointer, it won't realize the state
// is dead which can crash the process.
PyGreenlet* p(state->borrow_main_greenlet().borrow());
assert(p->pimpl->thread_state() == state || p->pimpl->thread_state() == nullptr);
dynamic_cast<MainGreenlet*>(p->pimpl)->thread_state(nullptr);
return true;
}
return false;
}
static void
AddToCleanupQueue(ThreadState* const state)
{
assert(state && state->has_main_greenlet());
// NOTE: Because we're not holding the GIL here, some other
// Python thread could run and call ``os.fork()``, which would
// be bad if that happened while we are holding the cleanup
// lock (it wouldn't function in the child process).
// Make a best effort to try to keep the duration we hold the
// lock short.
// TODO: On platforms that support it, use ``pthread_atfork`` to
// drop this lock.
LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock);
mod_globs->queue_to_destroy(state);
if (mod_globs->thread_states_to_destroy.size() == 1) {
// We added the first item to the queue. We need to schedule
// the cleanup.
// A size greater than 1 means that we have already added the pending call,
// and in fact, it may be executing now.
// If it is executing, our lock makes sure that it will see the item we just added
// to the queue on its next iteration (after we release the lock)
//
// A size of 1 means there is no pending call, OR the pending call is
// currently executing, has dropped the lock, and is deleting the last item
// from the queue; its next iteration will go ahead and delete the item we just added.
// And the pending call we schedule here will have no work to do.
int result = AddPendingCall(
PendingCallback_DestroyQueueWithGIL,
nullptr);
if (result < 0) {
// Hmm, what can we do here?
fprintf(stderr,
"greenlet: WARNING: failed in call to Py_AddPendingCall; "
"expect a memory leak.\n");
}
}
}
static int
PendingCallback_DestroyQueueWithGIL(void* UNUSED(arg))
{
// We're holding the GIL here, so no Python code should be able to
// run to call ``os.fork()``.
while (1) {
ThreadState* to_destroy;
{
LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock);
if (mod_globs->thread_states_to_destroy.empty()) {
break;
}
to_destroy = mod_globs->take_next_to_destroy();
}
assert(to_destroy);
assert(to_destroy->has_main_greenlet());
// Drop the lock while we do the actual deletion.
// This allows other calls to MarkGreenletDeadAndQueueCleanup
// to enter and add to our queue.
DestroyOneWithGIL(to_destroy);
}
return 0;
}
static void
DestroyOneWithGIL(const ThreadState* const state)
{
// Holding the GIL.
// Passed a non-shared pointer to the actual thread state.
// state -> main greenlet
assert(state->has_main_greenlet());
PyGreenlet* main(state->borrow_main_greenlet());
// When we need to do cross-thread operations, we check this.
// A NULL value means the thread died some time ago.
// We do this here, rather than in a Python dealloc function
// for the greenlet, in case there's still a reference out
// there.
dynamic_cast<MainGreenlet*>(main->pimpl)->thread_state(nullptr);
delete state; // Deleting this runs the destructor, DECREFs the main greenlet.
}
// ensure this is actually defined.
static_assert(GREENLET_BROKEN_PY_ADD_PENDING == 1 || GREENLET_BROKEN_PY_ADD_PENDING == 0,
"GREENLET_BROKEN_PY_ADD_PENDING not defined correctly.");
#if GREENLET_BROKEN_PY_ADD_PENDING
static int _push_pending_call(struct _pending_calls *pending,
int (*func)(void *), void *arg)
{
int i = pending->last;
int j = (i + 1) % NPENDINGCALLS;
if (j == pending->first) {
return -1; /* Queue full */
}
pending->calls[i].func = func;
pending->calls[i].arg = arg;
pending->last = j;
return 0;
}
static int AddPendingCall(int (*func)(void *), void *arg)
{
_PyRuntimeState *runtime = &_PyRuntime;
if (!runtime) {
// obviously impossible
return 0;
}
struct _pending_calls *pending = &runtime->ceval.pending;
if (!pending->lock) {
return 0;
}
int result = 0;
PyThread_acquire_lock(pending->lock, WAIT_LOCK);
if (!pending->finishing) {
result = _push_pending_call(pending, func, arg);
}
PyThread_release_lock(pending->lock);
SIGNAL_PENDING_CALLS(&runtime->ceval);
return result;
}
#else
// Python < 3.8 or >= 3.9
static int AddPendingCall(int (*func)(void*), void* arg)
{
// If the interpreter is in the middle of finalizing, we can't add a
// pending call. Trying to do so will end up in a SIGSEGV, as
// Py_AddPendingCall will not be able to get the interpreter and will
// try to dereference a NULL pointer. It's possible this can still
// segfault if we happen to get context switched, and maybe we should
// just always implement our own AddPendingCall, but I'd like to see if
// this works first
#if GREENLET_PY313
if (Py_IsFinalizing()) {
#else
if (_Py_IsFinalizing()) {
#endif
#ifdef GREENLET_DEBUG
// No need to log in the general case. Yes, we'll leak,
// but we're shutting down so it should be ok.
fprintf(stderr,
"greenlet: WARNING: Interpreter is finalizing. Ignoring "
"call to Py_AddPendingCall; \n");
#endif
return 0;
}
return Py_AddPendingCall(func, arg);
}
#endif
};
};
}; // namespace greenlet
// The intent when GET_THREAD_STATE() is needed multiple times in a
// function is to take a reference to its return value in a local
// variable, to avoid the thread-local indirection. On some platforms
// (macOS), accessing a thread-local involves a function call (plus an
// initial function call in each function that uses a thread local);
// in contrast, static volatile variables are at some pre-computed
// offset.
typedef greenlet::ThreadStateCreator<greenlet::ThreadState_DestroyNoGIL::MarkGreenletDeadAndQueueCleanup> ThreadStateCreator;
static thread_local ThreadStateCreator g_thread_state_global;
#define GET_THREAD_STATE() g_thread_state_global
#endif //T_THREADSTATE_DESTROY
|