Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
TThreadStateDestroy.cpp218 linesDownload Raw Back to greenlet
1/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
2/**
3 * Implementation of the ThreadState destructors.
4 *
5 * Format with:
6 *  clang-format -i --style=file src/greenlet/greenlet.c
7 *
8 *
9 * Fix missing braces with:
10 *   clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements"
11*/
12#ifndef T_THREADSTATE_DESTROY
13#define T_THREADSTATE_DESTROY
14
15#include "TGreenlet.hpp"
16
17#include "greenlet_thread_support.hpp"
18#include "greenlet_compiler_compat.hpp"
19#include "TGreenletGlobals.cpp"
20#include "TThreadState.hpp"
21#include "TThreadStateCreator.hpp"
22
23namespace greenlet {
24
25extern "C" {
26
27struct ThreadState_DestroyNoGIL
28{
29    /**
30       This function uses the same lock that the PendingCallback does
31     */
32    static void
33    MarkGreenletDeadAndQueueCleanup(ThreadState* const state)
34    {
35#if GREENLET_BROKEN_THREAD_LOCAL_CLEANUP_JUST_LEAK
36        // One rare platform.
37        return;
38#endif
39        // We are *NOT* holding the GIL. Our thread is in the middle
40        // of its death throes and the Python thread state is already
41        // gone so we can't use most Python APIs. One that is safe is
42        // ``Py_AddPendingCall``, unless the interpreter itself has
43        // been torn down. There is a limited number of calls that can
44        // be queued: 32 (NPENDINGCALLS) in CPython 3.10, so we
45        // coalesce these calls using our own queue.
46
47        if (!MarkGreenletDeadIfNeeded(state)) {
48            // No state, or no greenlet
49            return;
50        }
51
52        // XXX: Because we don't have the GIL, this is a race condition.
53        if (!PyInterpreterState_Head()) {
54            // We have to leak the thread state, if the
55            // interpreter has shut down when we're getting
56            // deallocated, we can't run the cleanup code that
57            // deleting it would imply.
58            return;
59        }
60
61        AddToCleanupQueue(state);
62
63    }
64
65private:
66
67    // If the state has an allocated main greenlet:
68    // - mark the greenlet as dead by disassociating it from the state;
69    // - return 1
70    // Otherwise, return 0.
71    static bool
72    MarkGreenletDeadIfNeeded(ThreadState* const state)
73    {
74        if (!state) {
75            return false;
76        }
77        LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock);
78        // mark the thread as dead ASAP.
79        // TODO: While the state variable tracking the death is
80        // atomic, and used with the strictest memory ordering, could
81        // this still be hiding race conditions? Specifically, is
82        // there a scenario where a thread is dying and thread local
83        // variables are being deconstructed, and some other thread
84        // tries to switch/throw to a greenlet owned by this thread,
85        // such that we think the switch will work but it won't?
86        return state->mark_main_greenlet_dead();
87    }
88
89    static void
90    AddToCleanupQueue(ThreadState* const state)
91    {
92        assert(state && state->has_main_greenlet());
93
94        // NOTE: Because we're not holding the GIL here, some other
95        // Python thread could run and call ``os.fork()``, which would
96        // be bad if that happened while we are holding the cleanup
97        // lock (it wouldn't function in the child process).
98        // Make a best effort to try to keep the duration we hold the
99        // lock short.
100        // TODO: On platforms that support it, use ``pthread_atfork`` to
101        // drop this lock.
102        LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock);
103
104        mod_globs->queue_to_destroy(state);
105        if (mod_globs->thread_states_to_destroy.size() == 1) {
106            // We added the first item to the queue. We need to schedule
107            // the cleanup.
108
109            // A size greater than 1 means that we have already added the pending call,
110            // and in fact, it may be executing now.
111            // If it is executing, our lock makes sure that it will see the item we just added
112            // to the queue on its next iteration (after we release the lock)
113            //
114            // A size of 1 means there is no pending call, OR the pending call is
115            // currently executing, has dropped the lock, and is deleting the last item
116            // from the queue; its next iteration will go ahead and delete the item we just added.
117            // And the pending call we schedule here will have no work to do.
118            int result = AddPendingCall(
119                           PendingCallback_DestroyQueue,
120                            nullptr);
121            if (result < 0) {
122                // Hmm, what can we do here?
123                fprintf(stderr,
124                        "greenlet: WARNING: failed in call to Py_AddPendingCall; "
125                        "expect a memory leak.\n");
126            }
127        }
128    }
129
130    static int
131    PendingCallback_DestroyQueue(void* UNUSED(arg))
132    {
133        // We're may or may not be holding the GIL here (depending on
134        // Py_GIL_DISABLED), so calls to ``os.fork()`` may or may not
135        // be possible.
136        while (1) {
137            ThreadState* to_destroy;
138            {
139                LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock);
140                if (mod_globs->thread_states_to_destroy.empty()) {
141                    break;
142                }
143                to_destroy = mod_globs->take_next_to_destroy();
144            }
145            assert(to_destroy);
146            assert(to_destroy->has_main_greenlet());
147            // Drop the lock while we do the actual deletion.
148            // This allows other calls to MarkGreenletDeadAndQueueCleanup
149            // to enter and add to our queue.
150            DestroyOne(to_destroy);
151        }
152        return 0;
153    }
154
155    static void
156    DestroyOne(const ThreadState* const state)
157    {
158        // May or may not be holding the GIL (depending on Py_GIL_DISABLED).
159        // Passed a non-shared pointer to the actual thread state.
160        // state -> main greenlet
161        //
162        // The thread_state in the main greenlet has already been
163        // cleared by the time this function runs from our pending
164        // callback, but the greenlet itself is still there.
165#ifndef NDEBUG
166        PyGreenlet* main(state->borrow_main_greenlet());
167        assert(main);
168        assert(main->pimpl->thread_state() == nullptr);
169#endif
170        delete state; // Deleting this runs the destructor, DECREFs the main greenlet.
171    }
172
173
174    static int AddPendingCall(int (*func)(void*), void* arg)
175    {
176        // If the interpreter is in the middle of finalizing, we can't
177        // add a pending call. Trying to do so will end up in a
178        // SIGSEGV, as Py_AddPendingCall will not be able to get the
179        // interpreter and will try to dereference a NULL pointer.
180        // It's possible this can still segfault if we happen to get
181        // context switched, and maybe we should just always implement
182        // our own AddPendingCall, but I'd like to see if this works
183        // first
184        if (greenlet::IsShuttingDown()) {
185#ifdef GREENLET_DEBUG
186            // No need to log in the general case. Yes, we'll leak,
187            // but we're shutting down so it should be ok.
188            fprintf(stderr,
189                    "greenlet: WARNING: Interpreter is finalizing. Ignoring "
190                    "call to Py_AddPendingCall; \n");
191#endif
192            return 0;
193        }
194        return Py_AddPendingCall(func, arg);
195    }
196
197
198
199
200
201};
202};
203
204}; // namespace greenlet
205
206// The intent when GET_THREAD_STATE() is needed multiple times in a
207// function is to take a reference to its return value in a local
208// variable, to avoid the thread-local indirection. On some platforms
209// (macOS), accessing a thread-local involves a function call (plus an
210// initial function call in each function that uses a thread local);
211// in contrast, static volatile variables are at some pre-computed
212// offset.
213typedef greenlet::ThreadStateCreator<greenlet::ThreadState_DestroyNoGIL::MarkGreenletDeadAndQueueCleanup> ThreadStateCreator;
214static thread_local ThreadStateCreator g_thread_state_global;
215#define GET_THREAD_STATE() g_thread_state_global
216
217#endif //T_THREADSTATE_DESTROY
218 
codekingpro/portable-devtools · Team Ai