codekingpro/portable-devtools
115k
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 