Загрузить файлы в «venv/Lib/site-packages/greenlet»
This commit is contained in:
538
venv/Lib/site-packages/greenlet/TPythonState.cpp
Normal file
538
venv/Lib/site-packages/greenlet/TPythonState.cpp
Normal file
@@ -0,0 +1,538 @@
|
||||
#ifndef GREENLET_PYTHON_STATE_CPP
|
||||
#define GREENLET_PYTHON_STATE_CPP
|
||||
|
||||
#include <Python.h>
|
||||
#include "TGreenlet.hpp"
|
||||
|
||||
namespace greenlet {
|
||||
|
||||
PythonState::PythonState()
|
||||
: _top_frame()
|
||||
#if GREENLET_USE_CFRAME
|
||||
,cframe(nullptr)
|
||||
,use_tracing(0)
|
||||
#endif
|
||||
#if GREENLET_PY314
|
||||
,py_recursion_depth(0)
|
||||
,current_executor(nullptr)
|
||||
,stackpointer(nullptr)
|
||||
#ifdef Py_GIL_DISABLED
|
||||
,c_stack_refs(nullptr)
|
||||
#endif
|
||||
#elif GREENLET_PY312
|
||||
,py_recursion_depth(0)
|
||||
,c_recursion_depth(0)
|
||||
#else
|
||||
,recursion_depth(0)
|
||||
#endif
|
||||
#if GREENLET_PY313
|
||||
,delete_later(nullptr)
|
||||
,critical_section(0)
|
||||
#else
|
||||
,trash_delete_nesting(0)
|
||||
#endif
|
||||
#if GREENLET_PY311
|
||||
,current_frame(nullptr)
|
||||
,datastack_chunk(nullptr)
|
||||
,datastack_top(nullptr)
|
||||
,datastack_limit(nullptr)
|
||||
#endif
|
||||
{
|
||||
#if GREENLET_USE_CFRAME
|
||||
/*
|
||||
The PyThreadState->cframe pointer usually points to memory on
|
||||
the stack, alloceted in a call into PyEval_EvalFrameDefault.
|
||||
|
||||
Initially, before any evaluation begins, it points to the
|
||||
initial PyThreadState object's ``root_cframe`` object, which is
|
||||
statically allocated for the lifetime of the thread.
|
||||
|
||||
A greenlet can last for longer than a call to
|
||||
PyEval_EvalFrameDefault, so we can't set its ``cframe`` pointer
|
||||
to be the current ``PyThreadState->cframe``; nor could we use
|
||||
one from the greenlet parent for the same reason. Yet a further
|
||||
no: we can't allocate one scoped to the greenlet and then
|
||||
destroy it when the greenlet is deallocated, because inside the
|
||||
interpreter the _PyCFrame objects form a linked list, and that too
|
||||
can result in accessing memory beyond its dynamic lifetime (if
|
||||
the greenlet doesn't actually finish before it dies, its entry
|
||||
could still be in the list).
|
||||
|
||||
Using the ``root_cframe`` is problematic, though, because its
|
||||
members are never modified by the interpreter and are set to 0,
|
||||
meaning that its ``use_tracing`` flag is never updated. We don't
|
||||
want to modify that value in the ``root_cframe`` ourself: it
|
||||
*shouldn't* matter much because we should probably never get
|
||||
back to the point where that's the only cframe on the stack;
|
||||
even if it did matter, the major consequence of an incorrect
|
||||
value for ``use_tracing`` is that if its true the interpreter
|
||||
does some extra work --- however, it's just good code hygiene.
|
||||
|
||||
Our solution: before a greenlet runs, after its initial
|
||||
creation, it uses the ``root_cframe`` just to have something to
|
||||
put there. However, once the greenlet is actually switched to
|
||||
for the first time, ``g_initialstub`` (which doesn't actually
|
||||
"return" while the greenlet is running) stores a new _PyCFrame on
|
||||
its local stack, and copies the appropriate values from the
|
||||
currently running _PyCFrame; this is then made the _PyCFrame for the
|
||||
newly-minted greenlet. ``g_initialstub`` then proceeds to call
|
||||
``glet.run()``, which results in ``PyEval_...`` adding the
|
||||
_PyCFrame to the list. Switches continue as normal. Finally, when
|
||||
the greenlet finishes, the call to ``glet.run()`` returns and
|
||||
the _PyCFrame is taken out of the linked list and the stack value
|
||||
is now unused and free to expire.
|
||||
|
||||
XXX: I think we can do better. If we're deallocing in the same
|
||||
thread, can't we traverse the list and unlink our frame?
|
||||
Can we just keep a reference to the thread state in case we
|
||||
dealloc in another thread? (Is that even possible if we're still
|
||||
running and haven't returned from g_initialstub?)
|
||||
*/
|
||||
this->cframe = &PyThreadState_GET()->root_cframe;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
inline void PythonState::may_switch_away() noexcept
|
||||
{
|
||||
#if GREENLET_PY311
|
||||
// PyThreadState_GetFrame is probably going to have to allocate a
|
||||
// new frame object. That may trigger garbage collection. Because
|
||||
// we call this during the early phases of a switch (it doesn't
|
||||
// matter to which greenlet, as this has a global effect), if a GC
|
||||
// triggers a switch away, two things can happen, both bad:
|
||||
// - We might not get switched back to, halting forward progress.
|
||||
// this is pathological, but possible.
|
||||
// - We might get switched back to with a different set of
|
||||
// arguments or a throw instead of a switch. That would corrupt
|
||||
// our state (specifically, PyErr_Occurred() and this->args()
|
||||
// would no longer agree).
|
||||
//
|
||||
// Thus, when we call this API, we need to have GC disabled.
|
||||
// This method serves as a bottleneck we call when maybe beginning
|
||||
// a switch. In this way, it is always safe -- no risk of GC -- to
|
||||
// use ``_GetFrame()`` whenever we need to, just as it was in
|
||||
// <=3.10 (because subsequent calls will be cached and not
|
||||
// allocate memory).
|
||||
|
||||
GCDisabledGuard no_gc;
|
||||
Py_XDECREF(PyThreadState_GetFrame(PyThreadState_GET()));
|
||||
#endif
|
||||
}
|
||||
|
||||
void PythonState::operator<<(const PyThreadState *const tstate) noexcept
|
||||
{
|
||||
this->_context.steal(tstate->context);
|
||||
#if GREENLET_USE_CFRAME
|
||||
/*
|
||||
IMPORTANT: ``cframe`` is a pointer into the STACK. Thus, because
|
||||
the call to ``slp_switch()`` changes the contents of the stack,
|
||||
you cannot read from ``ts_current->cframe`` after that call and
|
||||
necessarily get the same values you get from reading it here.
|
||||
Anything you need to restore from now to then must be saved in a
|
||||
global/threadlocal variable (because we can't use stack
|
||||
variables here either). For things that need to persist across
|
||||
the switch, use `will_switch_from`.
|
||||
*/
|
||||
this->cframe = tstate->cframe;
|
||||
#if !GREENLET_PY312
|
||||
this->use_tracing = tstate->cframe->use_tracing;
|
||||
#endif
|
||||
#endif // GREENLET_USE_CFRAME
|
||||
#if GREENLET_PY311
|
||||
#if GREENLET_PY314
|
||||
this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
|
||||
this->current_executor = tstate->current_executor;
|
||||
#ifdef Py_GIL_DISABLED
|
||||
this->c_stack_refs = ((_PyThreadStateImpl*)tstate)->c_stack_refs;
|
||||
#endif
|
||||
#elif GREENLET_PY312
|
||||
this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
|
||||
this->c_recursion_depth = Py_C_RECURSION_LIMIT - tstate->c_recursion_remaining;
|
||||
#else // not 312
|
||||
this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
|
||||
#endif // GREENLET_PY312
|
||||
#if GREENLET_PY313
|
||||
this->current_frame = tstate->current_frame;
|
||||
#elif GREENLET_USE_CFRAME
|
||||
this->current_frame = tstate->cframe->current_frame;
|
||||
#endif
|
||||
this->datastack_chunk = tstate->datastack_chunk;
|
||||
this->datastack_top = tstate->datastack_top;
|
||||
this->datastack_limit = tstate->datastack_limit;
|
||||
|
||||
PyFrameObject *frame = PyThreadState_GetFrame((PyThreadState *)tstate);
|
||||
Py_XDECREF(frame); // PyThreadState_GetFrame gives us a new
|
||||
// reference.
|
||||
this->_top_frame.steal(frame);
|
||||
#if GREENLET_PY314
|
||||
if (this->top_frame()) {
|
||||
this->stackpointer = this->_top_frame->f_frame->stackpointer;
|
||||
}
|
||||
else {
|
||||
this->stackpointer = nullptr;
|
||||
}
|
||||
#endif
|
||||
#if GREENLET_PY313
|
||||
// By contract of _PyTrash_thread_deposit_object,
|
||||
// the ``delete_later`` object has a refcount of 0.
|
||||
// We take a strong reference to it.
|
||||
//
|
||||
// Now, ``delete_later`` is managed as a
|
||||
// linked list whose objects are unconditionally deallocated
|
||||
// WITHOUT calling DECREF on them, so it's not clear what that is
|
||||
// actually accomplishing. That is, if another object is pushed on
|
||||
// the list and then the list is deallocated, this object will
|
||||
// still be deallocated. This strong reference serves as a form of
|
||||
// resurrection, meaning that when operator>> DECREFs it, we might
|
||||
// enter its ``tp_dealloc`` function again.
|
||||
//
|
||||
// In practice, it's quite difficult to arrange for this to be
|
||||
// a non-null value during a greenlet switch.
|
||||
// ``greenlet.tests.test_greenlet_trash`` tries, but under 3.14,
|
||||
// at least, fails to do so.
|
||||
this->delete_later = Py_XNewRef(tstate->delete_later);
|
||||
this->critical_section = tstate->critical_section;
|
||||
#elif GREENLET_PY312
|
||||
this->trash_delete_nesting = tstate->trash.delete_nesting;
|
||||
#else // not 312 or 3.13+
|
||||
this->trash_delete_nesting = tstate->trash_delete_nesting;
|
||||
#endif // GREENLET_PY312
|
||||
#else // Not 311
|
||||
this->recursion_depth = tstate->recursion_depth;
|
||||
this->_top_frame.steal(tstate->frame);
|
||||
this->trash_delete_nesting = tstate->trash_delete_nesting;
|
||||
#endif // GREENLET_PY311
|
||||
}
|
||||
|
||||
#if GREENLET_PY312
|
||||
void GREENLET_NOINLINE(PythonState::unexpose_frames)()
|
||||
{
|
||||
if (!this->top_frame()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// See GreenletState::expose_frames() and the comment on frames_were_exposed
|
||||
// for more information about this logic.
|
||||
_PyInterpreterFrame *iframe = this->_top_frame->f_frame;
|
||||
while (iframe != nullptr) {
|
||||
_PyInterpreterFrame *prev_exposed = iframe->previous;
|
||||
assert(iframe->frame_obj);
|
||||
memcpy(&iframe->previous, &iframe->frame_obj->_f_frame_data[0],
|
||||
sizeof(void *));
|
||||
iframe = prev_exposed;
|
||||
}
|
||||
}
|
||||
#else
|
||||
void PythonState::unexpose_frames()
|
||||
{}
|
||||
#endif
|
||||
|
||||
void PythonState::operator>>(PyThreadState *const tstate) noexcept
|
||||
{
|
||||
tstate->context = this->_context.relinquish_ownership();
|
||||
/* Incrementing this value invalidates the contextvars cache,
|
||||
which would otherwise remain valid across switches */
|
||||
tstate->context_ver++;
|
||||
#if GREENLET_USE_CFRAME
|
||||
tstate->cframe = this->cframe;
|
||||
/*
|
||||
If we were tracing, we need to keep tracing.
|
||||
There should never be the possibility of hitting the
|
||||
root_cframe here. See note above about why we can't
|
||||
just copy this from ``origin->cframe->use_tracing``.
|
||||
*/
|
||||
#if !GREENLET_PY312
|
||||
tstate->cframe->use_tracing = this->use_tracing;
|
||||
#endif
|
||||
#endif // GREENLET_USE_CFRAME
|
||||
#if GREENLET_PY311
|
||||
#if GREENLET_PY314
|
||||
tstate->py_recursion_remaining = tstate->py_recursion_limit - this->py_recursion_depth;
|
||||
tstate->current_executor = this->current_executor;
|
||||
#ifdef Py_GIL_DISABLED
|
||||
((_PyThreadStateImpl*)tstate)->c_stack_refs = this->c_stack_refs;
|
||||
#endif
|
||||
this->unexpose_frames();
|
||||
#elif GREENLET_PY312
|
||||
tstate->py_recursion_remaining = tstate->py_recursion_limit - this->py_recursion_depth;
|
||||
tstate->c_recursion_remaining = Py_C_RECURSION_LIMIT - this->c_recursion_depth;
|
||||
this->unexpose_frames();
|
||||
#else // \/ 3.11
|
||||
tstate->recursion_remaining = tstate->recursion_limit - this->recursion_depth;
|
||||
#endif // GREENLET_PY312
|
||||
#if GREENLET_PY313
|
||||
tstate->current_frame = this->current_frame;
|
||||
#elif GREENLET_USE_CFRAME
|
||||
tstate->cframe->current_frame = this->current_frame;
|
||||
#endif
|
||||
tstate->datastack_chunk = this->datastack_chunk;
|
||||
tstate->datastack_top = this->datastack_top;
|
||||
tstate->datastack_limit = this->datastack_limit;
|
||||
#if GREENLET_PY314 && defined(Py_GIL_DISABLED)
|
||||
if (this->top_frame()) {
|
||||
this->_top_frame->f_frame->stackpointer = this->stackpointer;
|
||||
}
|
||||
#endif
|
||||
this->_top_frame.relinquish_ownership();
|
||||
#if GREENLET_PY313
|
||||
// See comments in operator<<. We own a strong reference to
|
||||
// this->delete_later, which may or may not be the same object as
|
||||
// tstate->delete_later (depending if something pushed an object
|
||||
// onto the trashcan). Again, because ``delete_later`` is managed
|
||||
// as a linked list, it's not clear that saving and restoring the
|
||||
// value, especially without ever setting it to NULL, accomplishes
|
||||
// much...but the code was added by a core dev, so assume correct.
|
||||
//
|
||||
// Recall that tstate->delete_later is supposed to have a refcount
|
||||
// of 0, because objects are added there from their ``tp_dealloc``
|
||||
// method. So we should only need to DECREF it if we're the ones
|
||||
// that INCREF'd it in operator<<. (This is different than the
|
||||
// core dev's original code which always did this.)
|
||||
if (this->delete_later == tstate->delete_later) {
|
||||
Py_XDECREF(tstate->delete_later);
|
||||
tstate->delete_later = this->delete_later;
|
||||
this->delete_later = nullptr;
|
||||
}
|
||||
else {
|
||||
// it got switched behind our back. So the reference we own
|
||||
// needs to be explicitly cleared.
|
||||
tstate->delete_later = this->delete_later;
|
||||
Py_CLEAR(this->delete_later);
|
||||
}
|
||||
tstate->critical_section = this->critical_section;
|
||||
|
||||
#elif GREENLET_PY312
|
||||
tstate->trash.delete_nesting = this->trash_delete_nesting;
|
||||
#else // not 3.12
|
||||
tstate->trash_delete_nesting = this->trash_delete_nesting;
|
||||
#endif // GREENLET_PY312
|
||||
#else // not 3.11
|
||||
tstate->frame = this->_top_frame.relinquish_ownership();
|
||||
tstate->recursion_depth = this->recursion_depth;
|
||||
tstate->trash_delete_nesting = this->trash_delete_nesting;
|
||||
#endif // GREENLET_PY311
|
||||
}
|
||||
|
||||
inline void PythonState::will_switch_from(PyThreadState *const origin_tstate) noexcept
|
||||
{
|
||||
#if GREENLET_USE_CFRAME && !GREENLET_PY312
|
||||
// The weird thing is, we don't actually save this for an
|
||||
// effect on the current greenlet, it's saved for an
|
||||
// effect on the target greenlet. That is, we want
|
||||
// continuity of this setting across the greenlet switch.
|
||||
this->use_tracing = origin_tstate->cframe->use_tracing;
|
||||
#endif
|
||||
}
|
||||
|
||||
void PythonState::set_initial_state(const PyThreadState* const tstate) noexcept
|
||||
{
|
||||
this->_top_frame = nullptr;
|
||||
#if GREENLET_PY314
|
||||
this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
|
||||
this->current_executor = tstate->current_executor;
|
||||
#ifdef Py_GIL_DISABLED
|
||||
this->c_stack_refs = ((_PyThreadStateImpl*)tstate)->c_stack_refs;
|
||||
#endif
|
||||
// this->stackpointer is left null because this->_top_frame is
|
||||
// null so there is no value to copy.
|
||||
#elif GREENLET_PY312
|
||||
this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
|
||||
#if GREENLET_314
|
||||
this->c_recursion_depth = 0; // unused on 3.14
|
||||
#else
|
||||
this->c_recursion_depth = Py_C_RECURSION_LIMIT - tstate->c_recursion_remaining;
|
||||
#endif
|
||||
#elif GREENLET_PY311
|
||||
this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
|
||||
#else
|
||||
this->recursion_depth = tstate->recursion_depth;
|
||||
#endif
|
||||
}
|
||||
// TODO: Better state management about when we own the top frame.
|
||||
int PythonState::tp_traverse(visitproc visit, void* arg, bool visit_top_frame) noexcept
|
||||
{
|
||||
Py_VISIT(this->_context.borrow());
|
||||
if (visit_top_frame) {
|
||||
Py_VISIT(this->_top_frame.borrow());
|
||||
}
|
||||
#if GREENLET_PY315
|
||||
// Visit the references held by our suspended frames.
|
||||
// This is important specially on free-threading where the
|
||||
// the suspended frames may contain deferred references to
|
||||
// objects, and if they are not traversed then the interpreter
|
||||
// can free objects early causing a use-after-free crash
|
||||
// at runtime exit.
|
||||
if (this->_top_frame) {
|
||||
for (_PyInterpreterFrame* iframe = this->_top_frame->f_frame;
|
||||
iframe != nullptr; iframe = iframe->previous) {
|
||||
// Skip generator/coroutine frames; their object's traverse
|
||||
// already visits them (gen_traverse), so we'd double-count.
|
||||
// expose_frames leaves them in the ->previous chain.
|
||||
if (iframe->owner != FRAME_OWNED_BY_THREAD) {
|
||||
continue;
|
||||
}
|
||||
Py_VISIT(iframe->frame_obj);
|
||||
Py_VISIT(iframe->f_locals);
|
||||
_Py_VISIT_STACKREF(iframe->f_funcobj);
|
||||
_Py_VISIT_STACKREF(iframe->f_executable);
|
||||
int frame_result = _PyGC_VisitFrameStack(iframe, visit, arg);
|
||||
if (frame_result) {
|
||||
return frame_result;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// Note that we DO NOT visit ``delete_later``. Even if it's
|
||||
// non-null and we technically own a reference to it, its
|
||||
// reference count already went to 0 once and it was in the
|
||||
// process of being deallocated. The trash can mechanism linked it
|
||||
// into a list that will be cleaned at some later time, and it has
|
||||
// become untracked by the GC.
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PythonState::tp_clear(bool own_top_frame) noexcept
|
||||
{
|
||||
PythonStateContext::tp_clear();
|
||||
// If we get here owning a frame,
|
||||
// we got dealloc'd without being finished. We may or may not be
|
||||
// in the same thread.
|
||||
if (own_top_frame) {
|
||||
#if GREENLET_PY315
|
||||
// Release the references held by our suspended frames.
|
||||
// this->top_frame gets implicitly cleared by the Py_CLEAR(iframe->frame_obj)
|
||||
// of the first complete frame, so in the end we relinquish ownership of it.
|
||||
if (this->_top_frame) {
|
||||
for (_PyInterpreterFrame* iframe = this->_top_frame->f_frame;
|
||||
iframe != nullptr; iframe = iframe->previous) {
|
||||
if (iframe->owner != FRAME_OWNED_BY_THREAD) {
|
||||
continue;
|
||||
}
|
||||
// Clear the references held by this frame's evaluation stack.
|
||||
_PyStackRef* locals = iframe->localsplus;
|
||||
_PyStackRef* sp = iframe->stackpointer;
|
||||
if (sp) {
|
||||
while (sp > locals) {
|
||||
sp--;
|
||||
PyStackRef_CLEAR(*sp);
|
||||
}
|
||||
iframe->stackpointer = locals;
|
||||
}
|
||||
Py_CLEAR(iframe->f_locals);
|
||||
Py_CLEAR(iframe->frame_obj);
|
||||
PyStackRef_CLEAR(iframe->f_funcobj);
|
||||
PyStackRef_CLEAR(iframe->f_executable);
|
||||
}
|
||||
}
|
||||
this->_top_frame.relinquish_ownership();
|
||||
#else
|
||||
this->_top_frame.CLEAR();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if GREENLET_USE_CFRAME
|
||||
void PythonState::set_new_cframe(_PyCFrame& frame) noexcept
|
||||
{
|
||||
frame = *PyThreadState_GET()->cframe;
|
||||
/* Make the target greenlet refer to the stack value. */
|
||||
this->cframe = &frame;
|
||||
/*
|
||||
And restore the link to the previous frame so this one gets
|
||||
unliked appropriately.
|
||||
*/
|
||||
this->cframe->previous = &PyThreadState_GET()->root_cframe;
|
||||
}
|
||||
#endif
|
||||
|
||||
const PythonState::OwnedFrame& PythonState::top_frame() const noexcept
|
||||
{
|
||||
return this->_top_frame;
|
||||
}
|
||||
|
||||
void PythonState::did_finish(PyThreadState* tstate) noexcept
|
||||
{
|
||||
#if GREENLET_PY311
|
||||
// See https://github.com/gevent/gevent/issues/1924 and
|
||||
// https://github.com/python-greenlet/greenlet/issues/328. In
|
||||
// short, Python 3.11 allocates memory for frames as a sort of
|
||||
// linked list that's kept as part of PyThreadState in the
|
||||
// ``datastack_chunk`` member and friends. These are saved and
|
||||
// restored as part of switching greenlets.
|
||||
//
|
||||
// When we initially switch to a greenlet, we set those to NULL.
|
||||
// That causes the frame management code to treat this like a
|
||||
// brand new thread and start a fresh list of chunks, beginning
|
||||
// with a new "root" chunk. As we make calls in this greenlet,
|
||||
// those chunks get added, and as calls return, they get popped.
|
||||
// But the frame code (pystate.c) is careful to make sure that the
|
||||
// root chunk never gets popped.
|
||||
//
|
||||
// Thus, when a greenlet exits for the last time, there will be at
|
||||
// least a single root chunk that we must be responsible for
|
||||
// deallocating.
|
||||
//
|
||||
// The complex part is that these chunks are allocated and freed
|
||||
// using ``_PyObject_VirtualAlloc``/``Free``. Those aren't public
|
||||
// functions, and they aren't exported for linking. It so happens
|
||||
// that we know they are just thin wrappers around the Arena
|
||||
// allocator, so we can use that directly to deallocate in a
|
||||
// compatible way.
|
||||
//
|
||||
// CAUTION: Check this implementation detail on every major version.
|
||||
//
|
||||
// It might be nice to be able to do this in our destructor, but
|
||||
// can we be sure that no one else is using that memory? Plus, as
|
||||
// described below, our pointers may not even be valid anymore. As
|
||||
// a special case, there is one time that we know we can do this,
|
||||
// and that's from the destructor of the associated UserGreenlet
|
||||
// (NOT main greenlet)
|
||||
PyObjectArenaAllocator alloc;
|
||||
_PyStackChunk* chunk = nullptr;
|
||||
if (tstate) {
|
||||
// We really did finish, we can never be switched to again.
|
||||
chunk = tstate->datastack_chunk;
|
||||
// Unfortunately, we can't do much sanity checking. Our
|
||||
// this->datastack_chunk pointer is out of date (evaluation may
|
||||
// have popped down through it already) so we can't verify that
|
||||
// we deallocate it. I don't think we can even check datastack_top
|
||||
// for the same reason.
|
||||
|
||||
PyObject_GetArenaAllocator(&alloc);
|
||||
tstate->datastack_chunk = nullptr;
|
||||
tstate->datastack_limit = nullptr;
|
||||
tstate->datastack_top = nullptr;
|
||||
|
||||
}
|
||||
else if (this->datastack_chunk) {
|
||||
// The UserGreenlet (NOT the main greenlet!) is being deallocated. If we're
|
||||
// still holding a stack chunk, it's garbage because we know
|
||||
// we can never switch back to let cPython clean it up.
|
||||
// Because the last time we got switched away from, and we
|
||||
// haven't run since then, we know our chain is valid and can
|
||||
// be dealloced.
|
||||
chunk = this->datastack_chunk;
|
||||
PyObject_GetArenaAllocator(&alloc);
|
||||
}
|
||||
|
||||
if (alloc.free && chunk) {
|
||||
// In case the arena mechanism has been torn down already.
|
||||
while (chunk) {
|
||||
_PyStackChunk *prev = chunk->previous;
|
||||
chunk->previous = nullptr;
|
||||
alloc.free(alloc.ctx, chunk, chunk->size);
|
||||
chunk = prev;
|
||||
}
|
||||
}
|
||||
|
||||
this->datastack_chunk = nullptr;
|
||||
this->datastack_limit = nullptr;
|
||||
this->datastack_top = nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
}; // namespace greenlet
|
||||
|
||||
#endif // GREENLET_PYTHON_STATE_CPP
|
||||
265
venv/Lib/site-packages/greenlet/TStackState.cpp
Normal file
265
venv/Lib/site-packages/greenlet/TStackState.cpp
Normal file
@@ -0,0 +1,265 @@
|
||||
#ifndef GREENLET_STACK_STATE_CPP
|
||||
#define GREENLET_STACK_STATE_CPP
|
||||
|
||||
#include "TGreenlet.hpp"
|
||||
|
||||
namespace greenlet {
|
||||
|
||||
#ifdef GREENLET_USE_STDIO
|
||||
#include <iostream>
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const StackState& s)
|
||||
{
|
||||
os << "StackState(stack_start=" << (void*)s._stack_start
|
||||
<< ", stack_stop=" << (void*)s.stack_stop
|
||||
<< ", stack_copy=" << (void*)s.stack_copy
|
||||
<< ", stack_saved=" << s._stack_saved
|
||||
<< ", stack_prev=" << s.stack_prev
|
||||
<< ", addr=" << &s
|
||||
<< ")";
|
||||
return os;
|
||||
}
|
||||
#endif
|
||||
|
||||
StackState::StackState(void* mark, StackState& current)
|
||||
: _stack_start(nullptr),
|
||||
stack_stop((char*)mark),
|
||||
stack_copy(nullptr),
|
||||
_stack_saved(0),
|
||||
/* Skip a dying greenlet */
|
||||
stack_prev(current._stack_start
|
||||
? ¤t
|
||||
: current.stack_prev)
|
||||
{
|
||||
}
|
||||
|
||||
StackState::StackState()
|
||||
: _stack_start(nullptr),
|
||||
stack_stop(nullptr),
|
||||
stack_copy(nullptr),
|
||||
_stack_saved(0),
|
||||
stack_prev(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
StackState::StackState(const StackState& other)
|
||||
// can't use a delegating constructor because of
|
||||
// MSVC for Python 2.7
|
||||
: _stack_start(nullptr),
|
||||
stack_stop(nullptr),
|
||||
stack_copy(nullptr),
|
||||
_stack_saved(0),
|
||||
stack_prev(nullptr)
|
||||
{
|
||||
this->operator=(other);
|
||||
}
|
||||
|
||||
StackState& StackState::operator=(const StackState& other)
|
||||
{
|
||||
if (&other == this) {
|
||||
return *this;
|
||||
}
|
||||
if (other._stack_saved) {
|
||||
throw std::runtime_error("Refusing to steal memory.");
|
||||
}
|
||||
|
||||
//If we have memory allocated, dispose of it
|
||||
this->free_stack_copy();
|
||||
|
||||
this->_stack_start = other._stack_start;
|
||||
this->stack_stop = other.stack_stop;
|
||||
this->stack_copy = other.stack_copy;
|
||||
this->_stack_saved = other._stack_saved;
|
||||
this->stack_prev = other.stack_prev;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline void StackState::free_stack_copy() noexcept
|
||||
{
|
||||
PyMem_Free(this->stack_copy);
|
||||
this->stack_copy = nullptr;
|
||||
this->_stack_saved = 0;
|
||||
}
|
||||
|
||||
inline void StackState::copy_heap_to_stack(const StackState& current) noexcept
|
||||
{
|
||||
|
||||
/* Restore the heap copy back into the C stack */
|
||||
if (this->_stack_saved != 0) {
|
||||
memcpy(this->_stack_start, this->stack_copy, this->_stack_saved);
|
||||
this->free_stack_copy();
|
||||
}
|
||||
StackState* owner = const_cast<StackState*>(¤t);
|
||||
if (!owner->_stack_start) {
|
||||
owner = owner->stack_prev; /* greenlet is dying, skip it */
|
||||
}
|
||||
while (owner && owner->stack_stop <= this->stack_stop) {
|
||||
// cerr << "\tOwner: " << owner << endl;
|
||||
owner = owner->stack_prev; /* find greenlet with more stack */
|
||||
}
|
||||
this->stack_prev = owner;
|
||||
// cerr << "\tFinished with: " << *this << endl;
|
||||
}
|
||||
|
||||
inline int StackState::copy_stack_to_heap_up_to(const char* const stop) noexcept
|
||||
{
|
||||
/* Save more of g's stack into the heap -- at least up to 'stop'
|
||||
g->stack_stop |________|
|
||||
| |
|
||||
| __ stop . . . . .
|
||||
| | ==> . .
|
||||
|________| _______
|
||||
| | | |
|
||||
| | | |
|
||||
g->stack_start | | |_______| g->stack_copy
|
||||
*/
|
||||
intptr_t sz1 = this->_stack_saved;
|
||||
intptr_t sz2 = stop - this->_stack_start;
|
||||
assert(this->_stack_start);
|
||||
if (sz2 > sz1) {
|
||||
char* c = (char*)PyMem_Realloc(this->stack_copy, sz2);
|
||||
if (!c) {
|
||||
PyErr_NoMemory();
|
||||
return -1;
|
||||
}
|
||||
memcpy(c + sz1, this->_stack_start + sz1, sz2 - sz1);
|
||||
this->stack_copy = c;
|
||||
this->_stack_saved = sz2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline int StackState::copy_stack_to_heap(char* const stackref,
|
||||
const StackState& current) noexcept
|
||||
{
|
||||
/* must free all the C stack up to target_stop */
|
||||
const char* const target_stop = this->stack_stop;
|
||||
|
||||
StackState* owner = const_cast<StackState*>(¤t);
|
||||
assert(owner->_stack_saved == 0); // everything is present on the stack
|
||||
if (!owner->_stack_start) {
|
||||
owner = owner->stack_prev; /* not saved if dying */
|
||||
}
|
||||
else {
|
||||
owner->_stack_start = stackref;
|
||||
}
|
||||
|
||||
while (owner->stack_stop < target_stop) {
|
||||
/* ts_current is entierely within the area to free */
|
||||
if (owner->copy_stack_to_heap_up_to(owner->stack_stop)) {
|
||||
return -1; /* XXX */
|
||||
}
|
||||
owner = owner->stack_prev;
|
||||
}
|
||||
if (owner != this) {
|
||||
if (owner->copy_stack_to_heap_up_to(target_stop)) {
|
||||
return -1; /* XXX */
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline bool StackState::started() const noexcept
|
||||
{
|
||||
return this->stack_stop != nullptr;
|
||||
}
|
||||
|
||||
inline bool StackState::main() const noexcept
|
||||
{
|
||||
return this->stack_stop == (char*)-1;
|
||||
}
|
||||
|
||||
inline bool StackState::active() const noexcept
|
||||
{
|
||||
return this->_stack_start != nullptr;
|
||||
}
|
||||
|
||||
inline void StackState::set_active() noexcept
|
||||
{
|
||||
assert(this->_stack_start == nullptr);
|
||||
this->_stack_start = (char*)1;
|
||||
}
|
||||
|
||||
inline void StackState::set_inactive() noexcept
|
||||
{
|
||||
this->_stack_start = nullptr;
|
||||
// XXX: What if we still have memory out there?
|
||||
// That case is actually triggered by
|
||||
// test_issue251_issue252_explicit_reference_not_collectable (greenlet.tests.test_leaks.TestLeaks)
|
||||
// and
|
||||
// test_issue251_issue252_need_to_collect_in_background
|
||||
// (greenlet.tests.test_leaks.TestLeaks)
|
||||
//
|
||||
// Those objects never get deallocated, so the destructor never
|
||||
// runs.
|
||||
// It *seems* safe to clean up the memory here?
|
||||
if (this->_stack_saved) {
|
||||
this->free_stack_copy();
|
||||
}
|
||||
}
|
||||
|
||||
inline intptr_t StackState::stack_saved() const noexcept
|
||||
{
|
||||
return this->_stack_saved;
|
||||
}
|
||||
|
||||
inline char* StackState::stack_start() const noexcept
|
||||
{
|
||||
return this->_stack_start;
|
||||
}
|
||||
|
||||
|
||||
inline StackState StackState::make_main() noexcept
|
||||
{
|
||||
StackState s;
|
||||
s._stack_start = (char*)1;
|
||||
s.stack_stop = (char*)-1;
|
||||
return s;
|
||||
}
|
||||
|
||||
StackState::~StackState()
|
||||
{
|
||||
if (this->_stack_saved != 0) {
|
||||
this->free_stack_copy();
|
||||
}
|
||||
}
|
||||
|
||||
void StackState::copy_from_stack(void* vdest, const void* vsrc, size_t n) const
|
||||
{
|
||||
char* dest = static_cast<char*>(vdest);
|
||||
const char* src = static_cast<const char*>(vsrc);
|
||||
if (src + n <= this->_stack_start
|
||||
|| src >= this->_stack_start + this->_stack_saved
|
||||
|| this->_stack_saved == 0) {
|
||||
// Nothing we're copying was spilled from the stack
|
||||
memcpy(dest, src, n);
|
||||
return;
|
||||
}
|
||||
|
||||
if (src < this->_stack_start) {
|
||||
// Copy the part before the saved stack.
|
||||
// We know src + n > _stack_start due to the test above.
|
||||
const size_t nbefore = this->_stack_start - src;
|
||||
memcpy(dest, src, nbefore);
|
||||
dest += nbefore;
|
||||
src += nbefore;
|
||||
n -= nbefore;
|
||||
}
|
||||
// We know src >= _stack_start after the before-copy, and
|
||||
// src < _stack_start + _stack_saved due to the first if condition
|
||||
size_t nspilled = std::min<size_t>(n, this->_stack_start + this->_stack_saved - src);
|
||||
memcpy(dest, this->stack_copy + (src - this->_stack_start), nspilled);
|
||||
dest += nspilled;
|
||||
src += nspilled;
|
||||
n -= nspilled;
|
||||
if (n > 0) {
|
||||
// Copy the part after the saved stack
|
||||
memcpy(dest, src, n);
|
||||
}
|
||||
}
|
||||
|
||||
}; // namespace greenlet
|
||||
|
||||
#endif // GREENLET_STACK_STATE_CPP
|
||||
627
venv/Lib/site-packages/greenlet/TThreadState.hpp
Normal file
627
venv/Lib/site-packages/greenlet/TThreadState.hpp
Normal file
@@ -0,0 +1,627 @@
|
||||
#ifndef GREENLET_THREAD_STATE_HPP
|
||||
#define GREENLET_THREAD_STATE_HPP
|
||||
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <stdexcept>
|
||||
#include <atomic>
|
||||
|
||||
#include "greenlet_internal.hpp"
|
||||
#include "greenlet_refs.hpp"
|
||||
#include "greenlet_thread_support.hpp"
|
||||
|
||||
using greenlet::LockGuard;
|
||||
using greenlet::refs::BorrowedObject;
|
||||
using greenlet::refs::BorrowedGreenlet;
|
||||
using greenlet::refs::BorrowedMainGreenlet;
|
||||
using greenlet::refs::OwnedMainGreenlet;
|
||||
using greenlet::refs::OwnedObject;
|
||||
using greenlet::refs::OwnedGreenlet;
|
||||
using greenlet::refs::OwnedList;
|
||||
using greenlet::refs::PyErrFetchParam;
|
||||
using greenlet::refs::PyArgParseParam;
|
||||
using greenlet::refs::ImmortalString;
|
||||
using greenlet::refs::CreatedModule;
|
||||
using greenlet::refs::PyErrPieces;
|
||||
using greenlet::refs::NewReference;
|
||||
|
||||
|
||||
namespace greenlet {
|
||||
/**
|
||||
* Thread-local state of greenlets.
|
||||
*
|
||||
* Each native thread will get exactly one of these objects,
|
||||
* automatically accessed through the best available thread-local
|
||||
* mechanism the compiler supports (``thread_local`` for C++11
|
||||
* compilers or ``__thread``/``declspec(thread)`` for older GCC/clang
|
||||
* or MSVC, respectively.)
|
||||
*
|
||||
* Previously, we kept thread-local state mostly in a bunch of
|
||||
* ``static volatile`` variables in the main greenlet file.. This had
|
||||
* the problem of requiring extra checks, loops, and great care
|
||||
* accessing these variables if we potentially invoked any Python code
|
||||
* that could release the GIL, because the state could change out from
|
||||
* under us. Making the variables thread-local solves this problem.
|
||||
*
|
||||
* When we detected that a greenlet API accessing the current greenlet
|
||||
* was invoked from a different thread than the greenlet belonged to,
|
||||
* we stored a reference to the greenlet in the Python thread
|
||||
* dictionary for the thread the greenlet belonged to. This could lead
|
||||
* to memory leaks if the thread then exited (because of a reference
|
||||
* cycle, as greenlets referred to the thread dictionary, and deleting
|
||||
* non-current greenlets leaked their frame plus perhaps arguments on
|
||||
* the C stack). If a thread exited while still having running
|
||||
* greenlet objects (perhaps that had just switched back to the main
|
||||
* greenlet), and did not invoke one of the greenlet APIs *in that
|
||||
* thread, immediately before it exited, without some other thread
|
||||
* then being invoked*, such a leak was guaranteed.
|
||||
*
|
||||
* This can be partly solved by using compiler thread-local variables
|
||||
* instead of the Python thread dictionary, thus avoiding a cycle.
|
||||
*
|
||||
* To fully solve this problem, we need a reliable way to know that a
|
||||
* thread is done and we should clean up the main greenlet. On POSIX,
|
||||
* we can use the destructor function of ``pthread_key_create``, but
|
||||
* there's nothing similar on Windows; a C++11 thread local object
|
||||
* reliably invokes its destructor when the thread it belongs to exits
|
||||
* (non-C++11 compilers offer ``__thread`` or ``declspec(thread)`` to
|
||||
* create thread-local variables, but they can't hold C++ objects that
|
||||
* invoke destructors; the C++11 version is the most portable solution
|
||||
* I found). When the thread exits, we can drop references and
|
||||
* otherwise manipulate greenlets and frames that we know can no
|
||||
* longer be switched to.
|
||||
*
|
||||
* There are two small wrinkles. The first is that when the thread
|
||||
* exits, it is too late to actually invoke Python APIs: the Python
|
||||
* thread state is gone, and the GIL is released. To solve *this*
|
||||
* problem, our destructor uses ``Py_AddPendingCall`` to transfer the
|
||||
* destruction work to the main thread.
|
||||
*
|
||||
* The second is that once the thread exits, the thread local object
|
||||
* is invalid and we can't even access a pointer to it, so we can't
|
||||
* pass it to ``Py_AddPendingCall``. This is handled by actually using
|
||||
* a second object that's thread local (ThreadStateCreator) and having
|
||||
* it dynamically allocate this object so it can live until the
|
||||
* pending call runs.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
class ThreadState {
|
||||
private:
|
||||
// As of commit 08ad1dd7012b101db953f492e0021fb08634afad
|
||||
// this class needed 56 bytes in o Py_DEBUG build
|
||||
// on 64-bit macOS 11.
|
||||
// Adding the vector takes us up to 80 bytes ()
|
||||
|
||||
/* Strong reference to the main greenlet */
|
||||
OwnedMainGreenlet main_greenlet;
|
||||
|
||||
/* Strong reference to the current greenlet. */
|
||||
OwnedGreenlet current_greenlet;
|
||||
|
||||
/* Strong reference to the trace function, if any. */
|
||||
OwnedObject tracefunc;
|
||||
|
||||
// Use std::allocator (malloc/free) instead of PythonAllocator
|
||||
// (PyMem_Malloc) for the deleteme list. During Py_FinalizeEx on
|
||||
// Python < 3.11, the PyObject_Malloc pool that holds ThreadState
|
||||
// can be disrupted, corrupting any PythonAllocator-backed
|
||||
// containers. Using std::allocator makes this vector independent
|
||||
// of Python's allocator lifecycle.
|
||||
typedef std::vector<PyGreenlet*> deleteme_t;
|
||||
/* A vector of raw PyGreenlet pointers representing things that need
|
||||
deleted when this thread is running. The vector owns the
|
||||
references, but you need to manually INCREF/DECREF as you use
|
||||
them. We don't use a vector<refs::OwnedGreenlet> because we
|
||||
make copy of this vector, and that would become O(n) as all the
|
||||
refcounts are incremented in the copy.
|
||||
*/
|
||||
deleteme_t deleteme;
|
||||
#ifdef Py_GIL_DISABLED
|
||||
// On free-threaded builds, we need to protect shared access to
|
||||
// the deleteme list by a mutex. It can be written from one thread
|
||||
// while being read in another
|
||||
Mutex deleteme_lock;
|
||||
#endif
|
||||
|
||||
#ifdef GREENLET_NEEDS_EXCEPTION_STATE_SAVED
|
||||
void* exception_state;
|
||||
#endif
|
||||
|
||||
#ifdef Py_GIL_DISABLED
|
||||
static std::atomic<std::clock_t> _clocks_used_doing_gc;
|
||||
#else
|
||||
static std::clock_t _clocks_used_doing_gc;
|
||||
#endif
|
||||
static ImmortalString get_referrers_name;
|
||||
|
||||
G_NO_COPIES_OF_CLS(ThreadState);
|
||||
|
||||
|
||||
// Allocates a main greenlet for the thread state. If this fails,
|
||||
// exits the process. Called only during constructing a ThreadState.
|
||||
MainGreenlet* alloc_main()
|
||||
{
|
||||
PyGreenlet* gmain;
|
||||
|
||||
/* create the main greenlet for this thread */
|
||||
gmain = reinterpret_cast<PyGreenlet*>(PyType_GenericAlloc(&PyGreenlet_Type, 0));
|
||||
if (gmain == NULL) {
|
||||
throw PyFatalError("alloc_main failed to alloc"); //exits the process
|
||||
}
|
||||
|
||||
MainGreenlet* const main = new MainGreenlet(gmain, this);
|
||||
|
||||
assert(Py_REFCNT(gmain) == 1);
|
||||
assert(gmain->pimpl == main);
|
||||
return main;
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
// Allocate ThreadState with malloc/free rather than Python's
|
||||
// object allocator. ThreadState outlives many Python objects and
|
||||
// must remain valid throughout Py_FinalizeEx. On Python < 3.11,
|
||||
// PyObject_Malloc pools can be disrupted during early
|
||||
// finalization, corrupting any C++ objects stored in them.
|
||||
static void* operator new(size_t count)
|
||||
{
|
||||
void* p = std::malloc(count);
|
||||
if (!p) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static void operator delete(void* ptr)
|
||||
{
|
||||
std::free(ptr);
|
||||
}
|
||||
|
||||
static void init()
|
||||
{
|
||||
ThreadState::get_referrers_name = "get_referrers";
|
||||
ThreadState::set_clocks_used_doing_gc(0);
|
||||
}
|
||||
|
||||
ThreadState()
|
||||
{
|
||||
|
||||
#ifdef GREENLET_NEEDS_EXCEPTION_STATE_SAVED
|
||||
this->exception_state = slp_get_exception_state();
|
||||
#endif
|
||||
|
||||
// XXX: Potentially dangerous, exposing a not fully
|
||||
// constructed object.
|
||||
MainGreenlet* const main = this->alloc_main();
|
||||
this->main_greenlet = OwnedMainGreenlet::consuming(
|
||||
main->self()
|
||||
);
|
||||
assert(this->main_greenlet);
|
||||
this->current_greenlet = main->self();
|
||||
// The main greenlet starts with 1 refs: The returned one. We
|
||||
// then copied it to the current greenlet.
|
||||
assert(this->main_greenlet.REFCNT() == 2);
|
||||
}
|
||||
|
||||
inline void restore_exception_state()
|
||||
{
|
||||
#ifdef GREENLET_NEEDS_EXCEPTION_STATE_SAVED
|
||||
// It's probably important this be inlined and only call C
|
||||
// functions to avoid adding an SEH frame.
|
||||
slp_set_exception_state(this->exception_state);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool has_main_greenlet() const noexcept
|
||||
{
|
||||
return bool(this->main_greenlet);
|
||||
}
|
||||
|
||||
// Called from the ThreadStateCreator when we're in non-standard
|
||||
// threading mode. In that case, there is an object in the Python
|
||||
// thread state dictionary that points to us. The main greenlet
|
||||
// also traverses into us, in which case it's crucial not to
|
||||
// traverse back into the main greenlet.
|
||||
int tp_traverse(visitproc visit, void* arg, bool traverse_main=true)
|
||||
{
|
||||
if (traverse_main) {
|
||||
Py_VISIT(main_greenlet.borrow_o());
|
||||
}
|
||||
if (traverse_main || current_greenlet != main_greenlet) {
|
||||
Py_VISIT(current_greenlet.borrow_o());
|
||||
}
|
||||
Py_VISIT(tracefunc.borrow());
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline BorrowedMainGreenlet borrow_main_greenlet() const noexcept
|
||||
{
|
||||
assert(this->main_greenlet);
|
||||
assert(this->main_greenlet.REFCNT() >= 2);
|
||||
return this->main_greenlet;
|
||||
};
|
||||
|
||||
inline OwnedMainGreenlet get_main_greenlet() const noexcept
|
||||
{
|
||||
return this->main_greenlet;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we have a main greenlet, mark it as dead by setting its
|
||||
* thread_state to null (this part is atomic with respect to other
|
||||
* threads looking at the main greenlet's thread_state).
|
||||
*/
|
||||
inline bool mark_main_greenlet_dead() noexcept
|
||||
{
|
||||
PyGreenlet* main_greenlet = this->main_greenlet.borrow();
|
||||
if (!main_greenlet) {
|
||||
return false;
|
||||
}
|
||||
assert(main_greenlet->pimpl->thread_state() == this
|
||||
|| main_greenlet->pimpl->thread_state() == nullptr);
|
||||
dynamic_cast<MainGreenlet*>(main_greenlet->pimpl)->thread_state(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* In addition to returning a new reference to the currunt
|
||||
* greenlet, this performs any maintenance needed.
|
||||
*/
|
||||
inline OwnedGreenlet get_current()
|
||||
{
|
||||
/* green_dealloc() cannot delete greenlets from other threads, so
|
||||
it stores them in the thread dict; delete them now. */
|
||||
this->clear_deleteme_list();
|
||||
//assert(this->current_greenlet->main_greenlet == this->main_greenlet);
|
||||
//assert(this->main_greenlet->main_greenlet == this->main_greenlet);
|
||||
return this->current_greenlet;
|
||||
}
|
||||
|
||||
/**
|
||||
* As for non-const get_current();
|
||||
*/
|
||||
inline BorrowedGreenlet borrow_current()
|
||||
{
|
||||
this->clear_deleteme_list();
|
||||
return this->current_greenlet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does no maintenance.
|
||||
*/
|
||||
inline OwnedGreenlet get_current() const
|
||||
{
|
||||
return this->current_greenlet;
|
||||
}
|
||||
|
||||
template<typename T, refs::TypeChecker TC>
|
||||
inline bool is_current(const refs::PyObjectPointer<T, TC>& obj) const
|
||||
{
|
||||
return this->current_greenlet.borrow_o() == obj.borrow_o();
|
||||
}
|
||||
|
||||
inline void set_current(const OwnedGreenlet& target)
|
||||
{
|
||||
this->current_greenlet = target;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Deref and remove the greenlets from the deleteme list. Must be
|
||||
* holding the GIL.
|
||||
*
|
||||
* If *murder* is true, then we must be called from a different
|
||||
* thread than the one that these greenlets were running in.
|
||||
* In that case, if the greenlet was actually running, we destroy
|
||||
* the frame reference and otherwise make it appear dead before
|
||||
* proceeding; otherwise, we would try (and fail) to raise an
|
||||
* exception in it and wind up right back in this list.
|
||||
*/
|
||||
inline void clear_deleteme_list(const bool murder=false)
|
||||
{
|
||||
#ifdef Py_GIL_DISABLED
|
||||
LockGuard deleteme_guard(this->deleteme_lock);
|
||||
#endif
|
||||
if (this->deleteme.empty()) {
|
||||
return;
|
||||
}
|
||||
// Move the list contents out with swap — a constant-time
|
||||
// pointer exchange that never allocates. The previous
|
||||
// code used a copy (deleteme_t copy = this->deleteme)
|
||||
// which allocated through PythonAllocator / PyMem_Malloc;
|
||||
// that could SIGSEGV during early Py_FinalizeEx on Python
|
||||
// < 3.11 when the allocator is partially torn down.
|
||||
deleteme_t copy;
|
||||
std::swap(copy, this->deleteme);
|
||||
|
||||
// During Py_FinalizeEx cleanup, the GC or atexit handlers
|
||||
// may have already collected objects in this list,
|
||||
// leaving dangling pointers. Attempting Py_DECREF on
|
||||
// freed memory causes a SIGSEGV. g_greenlet_shutting_down
|
||||
// covers the early atexit phase; Py_IsFinalizing() covers
|
||||
// later phases. Thus, we deliberately leak.
|
||||
if (greenlet::IsShuttingDown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Preserve any pending exception so that cleanup-triggered
|
||||
// errors don't accidentally swallow an unrelated exception
|
||||
// (e.g. one set by throw() before a switch).
|
||||
PyErrPieces incoming_err;
|
||||
|
||||
for(deleteme_t::iterator it = copy.begin(), end = copy.end();
|
||||
it != end;
|
||||
++it ) {
|
||||
PyGreenlet* to_del = *it;
|
||||
if (murder) {
|
||||
// Force each greenlet to appear dead; we can't raise an
|
||||
// exception into it anymore anyway.
|
||||
to_del->pimpl->murder_in_place();
|
||||
}
|
||||
|
||||
// The only reference to these greenlets should be in
|
||||
// this list, decreffing them should let them be
|
||||
// deleted again, triggering calls to green_dealloc()
|
||||
// in the correct thread (if we're not murdering).
|
||||
// This may run arbitrary Python code and switch
|
||||
// threads or greenlets!
|
||||
Py_DECREF(to_del);
|
||||
if (PyErr_Occurred()) {
|
||||
PyErr_WriteUnraisable(nullptr);
|
||||
PyErr_Clear();
|
||||
}
|
||||
}
|
||||
// Not worried about C++ exception safety here in terms of
|
||||
// making sure we restore the error. Either we'll catch it
|
||||
// above and establish the error from that exception
|
||||
// (which, yes, might overwrite something from before we
|
||||
// entered, but we're in an undefined situation at that
|
||||
// point) or we won't catch it at all and will crash the
|
||||
// process.
|
||||
//
|
||||
// As for Python exception safety, there's no chance we're
|
||||
// overwriting an exception (from the loop) with no
|
||||
// exception (captured NULLs before we entered the loop),
|
||||
// because there CAN'T BE any exception from the loop ---
|
||||
// we clear them. So we're either restoring a pre-existing
|
||||
// exception, or leaving the exception unset (by restoring
|
||||
// NULL).
|
||||
incoming_err.PyErrRestore();
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Returns a new reference, or a false object.
|
||||
*/
|
||||
inline OwnedObject get_tracefunc() const
|
||||
{
|
||||
return tracefunc;
|
||||
};
|
||||
|
||||
|
||||
inline void set_tracefunc(BorrowedObject tracefunc)
|
||||
{
|
||||
assert(tracefunc);
|
||||
if (tracefunc == BorrowedObject(Py_None)) {
|
||||
this->tracefunc.CLEAR();
|
||||
}
|
||||
else {
|
||||
this->tracefunc = tracefunc;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a reference to a greenlet that some other thread
|
||||
* attempted to delete (has a refcount of 0) store it for later
|
||||
* deletion when the thread this state belongs to is current.
|
||||
*/
|
||||
inline void delete_when_thread_running(PyGreenlet* to_del)
|
||||
{
|
||||
Py_INCREF(to_del);
|
||||
#ifdef Py_GIL_DISABLED
|
||||
LockGuard deleteme_guard(this->deleteme_lock);
|
||||
#endif
|
||||
this->deleteme.push_back(to_del);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to std::clock_t(-1) to disable.
|
||||
*/
|
||||
inline static std::clock_t clocks_used_doing_gc()
|
||||
{
|
||||
#ifdef Py_GIL_DISABLED
|
||||
return ThreadState::_clocks_used_doing_gc.load(std::memory_order_relaxed);
|
||||
#else
|
||||
return ThreadState::_clocks_used_doing_gc;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline static void set_clocks_used_doing_gc(std::clock_t value)
|
||||
{
|
||||
#ifdef Py_GIL_DISABLED
|
||||
ThreadState::_clocks_used_doing_gc.store(value, std::memory_order_relaxed);
|
||||
#else
|
||||
ThreadState::_clocks_used_doing_gc = value;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline static void add_clocks_used_doing_gc(std::clock_t value)
|
||||
{
|
||||
#ifdef Py_GIL_DISABLED
|
||||
ThreadState::_clocks_used_doing_gc.fetch_add(value, std::memory_order_relaxed);
|
||||
#else
|
||||
ThreadState::_clocks_used_doing_gc += value;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Runs in some arbitrary thread that Python is using to invoke
|
||||
// pending callbacks. This may not be the thread that was
|
||||
// running the greenlets.
|
||||
~ThreadState()
|
||||
{
|
||||
if (!PyInterpreterState_Head()) {
|
||||
// We shouldn't get here (our callers protect us)
|
||||
// but if we do, all we can do is bail early.
|
||||
return;
|
||||
}
|
||||
|
||||
// During interpreter finalization, Python APIs like
|
||||
// PyImport_ImportModule are unsafe (the import machinery may
|
||||
// be partially torn down). On Python < 3.11, perform only the
|
||||
// minimal cleanup that is safe: clear our strong references
|
||||
// so we don't leak, but skip the GC-based leak detection.
|
||||
//
|
||||
// Python 3.11+ restructured interpreter finalization so that
|
||||
// these APIs remain safe during shutdown.
|
||||
if (greenlet::IsShuttingDown()) {
|
||||
this->tracefunc.CLEAR();
|
||||
if (this->current_greenlet) {
|
||||
this->current_greenlet->murder_in_place();
|
||||
this->current_greenlet.CLEAR();
|
||||
}
|
||||
this->main_greenlet.CLEAR();
|
||||
return;
|
||||
}
|
||||
|
||||
// We should not have an "origin" greenlet; that only exists
|
||||
// for the temporary time during a switch, which should not
|
||||
// be in progress as the thread dies.
|
||||
//assert(!this->switching_state.origin);
|
||||
|
||||
this->tracefunc.CLEAR();
|
||||
|
||||
// Forcibly GC as much as we can.
|
||||
this->clear_deleteme_list(true);
|
||||
|
||||
// The pending call did this.
|
||||
assert(this->main_greenlet->thread_state() == nullptr);
|
||||
|
||||
// If the main greenlet is the current greenlet,
|
||||
// then we "fell off the end" and the thread died.
|
||||
// It's possible that there is some other greenlet that
|
||||
// switched to us, leaving a reference to the main greenlet
|
||||
// on the stack, somewhere uncollectible. Try to detect that.
|
||||
if (this->current_greenlet == this->main_greenlet && this->current_greenlet) {
|
||||
assert(
|
||||
this->current_greenlet->is_currently_running_in_some_thread()
|
||||
|| this->current_greenlet->was_running_in_dead_thread()
|
||||
);
|
||||
// Drop one reference we hold.
|
||||
this->current_greenlet.CLEAR();
|
||||
assert(!this->current_greenlet);
|
||||
// Only our reference to the main greenlet should be left,
|
||||
// But hold onto the pointer in case we need to do extra cleanup.
|
||||
PyGreenlet* old_main_greenlet = this->main_greenlet.borrow();
|
||||
Py_ssize_t cnt = this->main_greenlet.REFCNT();
|
||||
this->main_greenlet.CLEAR();
|
||||
if (ThreadState::clocks_used_doing_gc() != std::clock_t(-1)
|
||||
&& cnt == 2 && Py_REFCNT(old_main_greenlet) == 1) {
|
||||
// Highly likely that the reference is somewhere on
|
||||
// the stack, not reachable by GC. Verify.
|
||||
// XXX: This is O(n) in the total number of objects.
|
||||
// TODO: Add a way to disable this at runtime, and
|
||||
// another way to report on it.
|
||||
std::clock_t begin = std::clock();
|
||||
NewReference gc(PyImport_ImportModule("gc"));
|
||||
if (gc) {
|
||||
OwnedObject get_referrers = gc.PyRequireAttr(ThreadState::get_referrers_name);
|
||||
OwnedList refs(get_referrers.PyCall(old_main_greenlet));
|
||||
if (refs && refs.empty()) {
|
||||
assert(refs.REFCNT() == 1);
|
||||
// We found nothing! So we left a dangling
|
||||
// reference: Probably the last thing some
|
||||
// other greenlet did was call
|
||||
// 'getcurrent().parent.switch()' to switch
|
||||
// back to us. Clean it up. This will be the
|
||||
// case on CPython 3.7 and newer, as they use
|
||||
// an internal calling conversion that avoids
|
||||
// creating method objects and storing them on
|
||||
// the stack.
|
||||
Py_DECREF(old_main_greenlet);
|
||||
}
|
||||
else if (refs
|
||||
&& refs.size() == 1
|
||||
&& PyCFunction_Check(refs.at(0))
|
||||
&& Py_REFCNT(refs.at(0)) == 2) {
|
||||
assert(refs.REFCNT() == 1);
|
||||
// Ok, we found a C method that refers to the
|
||||
// main greenlet, and its only referenced
|
||||
// twice, once in the list we just created,
|
||||
// once from...somewhere else. If we can't
|
||||
// find where else, then this is a leak.
|
||||
// This happens in older versions of CPython
|
||||
// that create a bound method object somewhere
|
||||
// on the stack that we'll never get back to.
|
||||
if (PyCFunction_GetFunction(refs.at(0).borrow()) == (PyCFunction)green_switch) {
|
||||
BorrowedObject function_w = refs.at(0);
|
||||
refs.clear(); // destroy the reference
|
||||
// from the list.
|
||||
// back to one reference. Can *it* be
|
||||
// found?
|
||||
assert(function_w.REFCNT() == 1);
|
||||
refs = get_referrers.PyCall(function_w);
|
||||
if (refs && refs.empty()) {
|
||||
// Nope, it can't be found so it won't
|
||||
// ever be GC'd. Drop it.
|
||||
Py_CLEAR(function_w);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::clock_t end = std::clock();
|
||||
ThreadState::add_clocks_used_doing_gc(end - begin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We need to make sure this greenlet appears to be dead,
|
||||
// because otherwise deallocing it would fail to raise an
|
||||
// exception in it (the thread is dead) and put it back in our
|
||||
// deleteme list.
|
||||
if (this->current_greenlet) {
|
||||
this->current_greenlet->murder_in_place();
|
||||
this->current_greenlet.CLEAR();
|
||||
}
|
||||
|
||||
if (this->main_greenlet) {
|
||||
// Couldn't have been the main greenlet that was running
|
||||
// when the thread exited (because we already cleared this
|
||||
// pointer if it was). This shouldn't be possible?
|
||||
|
||||
// If the main greenlet was current when the thread died (it
|
||||
// should be, right?) then we cleared its self pointer above
|
||||
// when we cleared the current greenlet's main greenlet pointer.
|
||||
// assert(this->main_greenlet->main_greenlet == this->main_greenlet
|
||||
// || !this->main_greenlet->main_greenlet);
|
||||
// // self reference, probably gone
|
||||
// this->main_greenlet->main_greenlet.CLEAR();
|
||||
|
||||
// This will actually go away when the ivar is destructed.
|
||||
this->main_greenlet.CLEAR();
|
||||
}
|
||||
|
||||
if (PyErr_Occurred()) {
|
||||
PyErr_WriteUnraisable(NULL);
|
||||
PyErr_Clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
ImmortalString ThreadState::get_referrers_name(nullptr);
|
||||
#ifdef Py_GIL_DISABLED
|
||||
std::atomic<std::clock_t> ThreadState::_clocks_used_doing_gc(0);
|
||||
#else
|
||||
std::clock_t ThreadState::_clocks_used_doing_gc(0);
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}; // namespace greenlet
|
||||
|
||||
#endif
|
||||
105
venv/Lib/site-packages/greenlet/TThreadStateCreator.hpp
Normal file
105
venv/Lib/site-packages/greenlet/TThreadStateCreator.hpp
Normal file
@@ -0,0 +1,105 @@
|
||||
#ifndef GREENLET_THREAD_STATE_CREATOR_HPP
|
||||
#define GREENLET_THREAD_STATE_CREATOR_HPP
|
||||
|
||||
#include <ctime>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "greenlet_internal.hpp"
|
||||
#include "greenlet_refs.hpp"
|
||||
#include "greenlet_thread_support.hpp"
|
||||
|
||||
#include "TThreadState.hpp"
|
||||
|
||||
namespace greenlet {
|
||||
|
||||
|
||||
typedef void (*ThreadStateDestructor)(ThreadState* const);
|
||||
|
||||
// Only one of these, auto created per thread as a thread_local.
|
||||
// This means we don't have to worry about atomic access to the
|
||||
// internals, because by definition all access is happening on a
|
||||
// single thread.
|
||||
// Constructing the state constructs the MainGreenlet.
|
||||
template<ThreadStateDestructor Destructor>
|
||||
class ThreadStateCreator
|
||||
{
|
||||
private:
|
||||
// Initialized to 1, and, if still 1, created on access.
|
||||
// Set to 0 on destruction.
|
||||
ThreadState* _state;
|
||||
G_NO_COPIES_OF_CLS(ThreadStateCreator);
|
||||
|
||||
inline bool has_initialized_state() const noexcept
|
||||
{
|
||||
return this->_state != (ThreadState*)1;
|
||||
}
|
||||
|
||||
inline bool has_state() const noexcept
|
||||
{
|
||||
return this->has_initialized_state() && this->_state != nullptr;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
ThreadStateCreator() :
|
||||
_state((ThreadState*)1)
|
||||
{
|
||||
}
|
||||
|
||||
~ThreadStateCreator()
|
||||
{
|
||||
if (this->has_state()) {
|
||||
Destructor(this->_state);
|
||||
}
|
||||
|
||||
this->_state = nullptr;
|
||||
}
|
||||
|
||||
inline ThreadState& state()
|
||||
{
|
||||
// The main greenlet will own this pointer when it is created,
|
||||
// which will be right after this. The plan is to give every
|
||||
// greenlet a pointer to the main greenlet for the thread it
|
||||
// runs in; if we are doing something cross-thread, we need to
|
||||
// access the pointer from the main greenlet. Deleting the
|
||||
// thread, and hence the thread-local storage, will delete the
|
||||
// state pointer in the main greenlet.
|
||||
if (!this->has_initialized_state()) {
|
||||
// XXX: Assuming allocation never fails
|
||||
this->_state = new ThreadState;
|
||||
// For non-standard threading, we need to store an object
|
||||
// in the Python thread state dictionary so that it can be
|
||||
// DECREF'd when the thread ends (ideally; the dict could
|
||||
// last longer) and clean this object up.
|
||||
}
|
||||
if (!this->_state) {
|
||||
throw std::runtime_error("Accessing state after destruction.");
|
||||
}
|
||||
return *this->_state;
|
||||
}
|
||||
|
||||
operator ThreadState&()
|
||||
{
|
||||
return this->state();
|
||||
}
|
||||
|
||||
operator ThreadState*()
|
||||
{
|
||||
return &this->state();
|
||||
}
|
||||
|
||||
inline int tp_traverse(visitproc visit, void* arg)
|
||||
{
|
||||
if (this->has_state()) {
|
||||
return this->_state->tp_traverse(visit, arg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
}; // namespace greenlet
|
||||
|
||||
#endif
|
||||
217
venv/Lib/site-packages/greenlet/TThreadStateDestroy.cpp
Normal file
217
venv/Lib/site-packages/greenlet/TThreadStateDestroy.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
/* -*- 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_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
|
||||
// One rare platform.
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock);
|
||||
// mark the thread as dead ASAP.
|
||||
// TODO: While the state variable tracking the death is
|
||||
// atomic, and used with the strictest memory ordering, could
|
||||
// this still be hiding race conditions? Specifically, is
|
||||
// there a scenario where a thread is dying and thread local
|
||||
// variables are being deconstructed, and some other thread
|
||||
// tries to switch/throw to a greenlet owned by this thread,
|
||||
// such that we think the switch will work but it won't?
|
||||
return state->mark_main_greenlet_dead();
|
||||
}
|
||||
|
||||
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_DestroyQueue,
|
||||
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_DestroyQueue(void* UNUSED(arg))
|
||||
{
|
||||
// We're may or may not be holding the GIL here (depending on
|
||||
// Py_GIL_DISABLED), so calls to ``os.fork()`` may or may not
|
||||
// be possible.
|
||||
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.
|
||||
DestroyOne(to_destroy);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
DestroyOne(const ThreadState* const state)
|
||||
{
|
||||
// May or may not be holding the GIL (depending on Py_GIL_DISABLED).
|
||||
// Passed a non-shared pointer to the actual thread state.
|
||||
// state -> main greenlet
|
||||
//
|
||||
// The thread_state in the main greenlet has already been
|
||||
// cleared by the time this function runs from our pending
|
||||
// callback, but the greenlet itself is still there.
|
||||
#ifndef NDEBUG
|
||||
PyGreenlet* main(state->borrow_main_greenlet());
|
||||
assert(main);
|
||||
assert(main->pimpl->thread_state() == nullptr);
|
||||
#endif
|
||||
delete state; // Deleting this runs the destructor, DECREFs the main greenlet.
|
||||
}
|
||||
|
||||
|
||||
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::IsShuttingDown()) {
|
||||
#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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
}; // 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
|
||||
Reference in New Issue
Block a user