Qucs-S S-parameter Viewer & RF Synthesis Tools
Loading...
Searching...
No Matches
TThreadStateDestroy.cpp
1/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
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
28{
32 static void
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 if (state->has_main_greenlet()) {
79 // mark the thread as dead ASAP.
80 // this is racy! If we try to throw or switch to a
81 // greenlet from this thread from some other thread before
82 // we clear the state pointer, it won't realize the state
83 // is dead which can crash the process.
84 PyGreenlet* p(state->borrow_main_greenlet().borrow());
85 assert(p->pimpl->thread_state() == state || p->pimpl->thread_state() == nullptr);
86 dynamic_cast<MainGreenlet*>(p->pimpl)->thread_state(nullptr);
87 return true;
88 }
89 return false;
90 }
91
92 static void
93 AddToCleanupQueue(ThreadState* const state)
94 {
95 assert(state && state->has_main_greenlet());
96
97 // NOTE: Because we're not holding the GIL here, some other
98 // Python thread could run and call ``os.fork()``, which would
99 // be bad if that happened while we are holding the cleanup
100 // lock (it wouldn't function in the child process).
101 // Make a best effort to try to keep the duration we hold the
102 // lock short.
103 // TODO: On platforms that support it, use ``pthread_atfork`` to
104 // drop this lock.
105 LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock);
106
107 mod_globs->queue_to_destroy(state);
108 if (mod_globs->thread_states_to_destroy.size() == 1) {
109 // We added the first item to the queue. We need to schedule
110 // the cleanup.
111
112 // A size greater than 1 means that we have already added the pending call,
113 // and in fact, it may be executing now.
114 // If it is executing, our lock makes sure that it will see the item we just added
115 // to the queue on its next iteration (after we release the lock)
116 //
117 // A size of 1 means there is no pending call, OR the pending call is
118 // currently executing, has dropped the lock, and is deleting the last item
119 // from the queue; its next iteration will go ahead and delete the item we just added.
120 // And the pending call we schedule here will have no work to do.
121 int result = AddPendingCall(
122 PendingCallback_DestroyQueue,
123 nullptr);
124 if (result < 0) {
125 // Hmm, what can we do here?
126 fprintf(stderr,
127 "greenlet: WARNING: failed in call to Py_AddPendingCall; "
128 "expect a memory leak.\n");
129 }
130 }
131 }
132
133 static int
134 PendingCallback_DestroyQueue(void* UNUSED(arg))
135 {
136 // We're may or may not be holding the GIL here (depending on
137 // Py_GIL_DISABLED), so calls to ``os.fork()`` may or may not
138 // be possible.
139 while (1) {
140 ThreadState* to_destroy;
141 {
142 LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock);
143 if (mod_globs->thread_states_to_destroy.empty()) {
144 break;
145 }
146 to_destroy = mod_globs->take_next_to_destroy();
147 }
148 assert(to_destroy);
149 assert(to_destroy->has_main_greenlet());
150 // Drop the lock while we do the actual deletion.
151 // This allows other calls to MarkGreenletDeadAndQueueCleanup
152 // to enter and add to our queue.
153 DestroyOne(to_destroy);
154 }
155 return 0;
156 }
157
158 static void
159 DestroyOne(const ThreadState* const state)
160 {
161 // May or may not be holding the GIL (depending on Py_GIL_DISABLED).
162 // Passed a non-shared pointer to the actual thread state.
163 // state -> main greenlet
164 assert(state->has_main_greenlet());
165 PyGreenlet* main(state->borrow_main_greenlet());
166 // When we need to do cross-thread operations, we check this.
167 // A NULL value means the thread died some time ago.
168 // We do this here, rather than in a Python dealloc function
169 // for the greenlet, in case there's still a reference out
170 // there.
171 dynamic_cast<MainGreenlet*>(main->pimpl)->thread_state(nullptr);
172
173 delete state; // Deleting this runs the destructor, DECREFs the main greenlet.
174 }
175
176
177 static int AddPendingCall(int (*func)(void*), void* arg)
178 {
179 // If the interpreter is in the middle of finalizing, we can't add a
180 // pending call. Trying to do so will end up in a SIGSEGV, as
181 // Py_AddPendingCall will not be able to get the interpreter and will
182 // try to dereference a NULL pointer. It's possible this can still
183 // segfault if we happen to get context switched, and maybe we should
184 // just always implement our own AddPendingCall, but I'd like to see if
185 // this works first
186#if GREENLET_PY313
187 if (Py_IsFinalizing()) {
188#else
189 if (_Py_IsFinalizing()) {
190#endif
191#ifdef GREENLET_DEBUG
192 // No need to log in the general case. Yes, we'll leak,
193 // but we're shutting down so it should be ok.
194 fprintf(stderr,
195 "greenlet: WARNING: Interpreter is finalizing. Ignoring "
196 "call to Py_AddPendingCall; \n");
197#endif
198 return 0;
199 }
200 return Py_AddPendingCall(func, arg);
201 }
202
203
204
205
206
207};
208};
209
210}; // namespace greenlet
211
212// The intent when GET_THREAD_STATE() is needed multiple times in a
213// function is to take a reference to its return value in a local
214// variable, to avoid the thread-local indirection. On some platforms
215// (macOS), accessing a thread-local involves a function call (plus an
216// initial function call in each function that uses a thread local);
217// in contrast, static volatile variables are at some pre-computed
218// offset.
220static thread_local ThreadStateCreator g_thread_state_global;
221#define GET_THREAD_STATE() g_thread_state_global
222
223#endif //T_THREADSTATE_DESTROY
Definition TThreadStateCreator.hpp:22
Definition TThreadState.hpp:87
Definition __init__.py:1
Definition greenlet.h:22
Definition TThreadStateDestroy.cpp:28
static void MarkGreenletDeadAndQueueCleanup(ThreadState *const state)
Definition TThreadStateDestroy.cpp:33