Загрузить файлы в «venv/Lib/site-packages/greenlet»

This commit is contained in:
2026-07-02 18:28:09 +00:00
parent 70071e58bc
commit 4e6637daf9
5 changed files with 2317 additions and 0 deletions

View File

@@ -0,0 +1,841 @@
/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
#ifndef PYGREENLET_CPP
#define PYGREENLET_CPP
/*****************
The Python slot functions for TGreenlet.
*/
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "structmember.h" // PyMemberDef
#include "greenlet_internal.hpp"
#include "TThreadStateDestroy.cpp"
#include "TGreenlet.hpp"
// #include "TUserGreenlet.cpp"
// #include "TMainGreenlet.cpp"
// #include "TBrokenGreenlet.cpp"
#include "greenlet_refs.hpp"
#include "greenlet_slp_switch.hpp"
#include "greenlet_thread_support.hpp"
#include "TGreenlet.hpp"
#include "TGreenletGlobals.cpp"
#include "TThreadStateDestroy.cpp"
#include "PyGreenlet.hpp"
// #include "TGreenlet.cpp"
// #include "TExceptionState.cpp"
// #include "TPythonState.cpp"
// #include "TStackState.cpp"
using greenlet::LockGuard;
using greenlet::LockInitError;
using greenlet::PyErrOccurred;
using greenlet::Require;
using greenlet::g_handle_exit;
using greenlet::single_result;
using greenlet::Greenlet;
using greenlet::UserGreenlet;
using greenlet::MainGreenlet;
using greenlet::BrokenGreenlet;
using greenlet::ThreadState;
using greenlet::PythonState;
using greenlet::refs::PyCriticalObjectSection;
static PyGreenlet*
green_new(PyTypeObject* type, PyObject* UNUSED(args), PyObject* UNUSED(kwds))
{
PyGreenlet* o =
(PyGreenlet*)PyBaseObject_Type.tp_new(type, mod_globs->empty_tuple, mod_globs->empty_dict);
if (o) {
// Recall: borrowing or getting the current greenlet
// causes the "deleteme list" to get cleared. So constructing a greenlet
// can do things like cause other greenlets to get finalized.
UserGreenlet* c = new UserGreenlet(o, GET_THREAD_STATE().state().borrow_current());
assert(Py_REFCNT(o) == 1);
// Also: This looks like a memory leak, but isn't.
// Constructing the C++ object assigns it to the pimpl pointer
// of the Python object (o); we'll need that later.
assert(c == o->pimpl);
}
return o;
}
// green_init is used in the tp_init slot. So it's important that
// it can be called directly from CPython. Thus, we don't use
// BorrowedGreenlet and BorrowedObject --- although in theory
// these should be binary layout compatible, that may not be
// guaranteed to be the case (32-bit linux ppc possibly).
static int
green_init(PyGreenlet* self, PyObject* args, PyObject* kwargs)
{
PyArgParseParam run;
PyArgParseParam nparent;
static const char* kwlist[] = {
"run",
"parent",
NULL
};
// recall: The O specifier does NOT increase the reference count.
if (!PyArg_ParseTupleAndKeywords(
args, kwargs, "|OO:green", (char**)kwlist, &run, &nparent)) {
return -1;
}
if (run) {
if (green_setrun(self, run, NULL)) {
return -1;
}
}
if (nparent && !nparent.is_None()) {
return green_setparent(self, nparent, NULL);
}
return 0;
}
static int
green_traverse(PyGreenlet* self, visitproc visit, void* arg)
{
// We must only visit referenced objects, i.e. only objects
// Py_INCREF'ed by this greenlet (directly or indirectly):
//
// - stack_prev is not visited: holds previous stack pointer, but it's not
// referenced
// - frames are not visited as we don't strongly reference them;
// alive greenlets are not garbage collected
// anyway. This can be a problem, however, if this greenlet is
// never allowed to finish, and is referenced from the frame: we
// have an uncollectible cycle in that case. Note that the
// frame object itself is also frequently not even tracked by the GC
// starting with Python 3.7 (frames are allocated by the
// interpreter untracked, and only become tracked when their
// evaluation is finished if they have a refcount > 1). All of
// this is to say that we should probably strongly reference
// the frame object. Doing so, while always allowing GC on a
// greenlet, solves several leaks for us.
Py_VISIT(self->dict);
if (!self->pimpl) {
// Hmm. I have seen this at interpreter shutdown time,
// I think. That's very odd because this doesn't go away until
// we're ``green_dealloc()``, at which point we shouldn't be
// traversed anymore.
return 0;
}
return self->pimpl->tp_traverse(visit, arg);
}
static int
green_is_gc(PyObject* _self)
{
BorrowedGreenlet self(_self);
int result = 0;
/* Main greenlet can be garbage collected since it can only
become unreachable if the underlying thread exited.
Active greenlets --- including those that are suspended ---
cannot be garbage collected, however.
*/
if (self->main() || !self->active()) {
result = 1;
}
// The main greenlet pointer will eventually go away after the thread dies.
if (self->was_running_in_dead_thread()) {
// Our thread is dead! We can never run again. Might as well
// GC us. Note that if a tuple containing only us and other
// immutable objects had been scanned before this, when we
// would have returned 0, the tuple will take itself out of GC
// tracking and never be investigated again. So that could
// result in both us and the tuple leaking due to an
// unreachable/uncollectible reference. The same goes for
// dictionaries.
//
// It's not a great idea to be changing our GC state on the
// fly.
result = 1;
}
return result;
}
static int
green_clear(PyGreenlet* self)
{
/* Greenlet is only cleared if it is about to be collected.
Since active greenlets are not garbage collectable, we can
be sure that, even if they are deallocated during clear,
nothing they reference is in unreachable or finalizers,
so even if it switches we are relatively safe. */
// XXX: Are we responsible for clearing weakrefs here?
Py_CLEAR(self->dict);
return self->pimpl->tp_clear();
}
/**
* Returns 0 on failure (the object was resurrected) or 1 on success.
**/
static int
_green_dealloc_kill_started_non_main_greenlet(BorrowedGreenlet self)
{
// During interpreter finalization, we cannot safely throw GreenletExit
// into the greenlet. Doing so calls g_switch(), which performs a stack
// switch and runs Python code via _PyEval_EvalFrameDefault. On Python
// < 3.11, executing Python code in a partially-torn-down interpreter
// leads to SIGSEGV (greenlet 3.x) or SIGABRT (greenlet 2.x).
//
// Python 3.11+ restructured interpreter finalization internals (frame
// representation, data stack management, recursion tracking) so that
// g_switch() during finalization is safe. On older Pythons, we simply
// mark the greenlet dead without throwing, which avoids the crash at
// the cost of not running any cleanup code inside the greenlet.
//
// See: https://github.com/python-greenlet/greenlet/issues/411
// https://github.com/python-greenlet/greenlet/issues/351
if (greenlet::IsShuttingDown()) {
self->murder_in_place();
return 1;
}
/* Hacks hacks hacks copied from instance_dealloc() */
/* Temporarily resurrect the greenlet. */
assert(self.REFCNT() == 0);
Py_SET_REFCNT(self.borrow(), 1);
/* Save the current exception, if any. */
PyErrPieces saved_err;
try {
// BY THE TIME WE GET HERE, the state may actually be going
// away
// if we're shutting down the interpreter and freeing thread
// entries,
// this could result in freeing greenlets that were leaked. So
// we can't try to read the state.
self->deallocing_greenlet_in_thread(
self->thread_state()
? static_cast<ThreadState*>(GET_THREAD_STATE())
: nullptr);
}
catch (const PyErrOccurred&) {
PyErr_WriteUnraisable(self.borrow_o());
/* XXX what else should we do? */
}
/* Check for no resurrection must be done while we keep
* our internal reference, otherwise PyFile_WriteObject
* causes recursion if using Py_INCREF/Py_DECREF
*/
if (self.REFCNT() == 1 && self->active()) {
/* Not resurrected, but still not dead!
XXX what else should we do? we complain. */
PyObject* f = PySys_GetObject("stderr");
Py_INCREF(self.borrow_o()); /* leak! */
if (f != NULL) {
// PySys_GetObject returns a borrowed ref which could go
// away when we run arbitrary code, as we do for any of
// the ``PyFile_Write`` APIs.
Py_INCREF(f);
// Note that we're not handling errors here. They either
// work or they don't, and any exception they raised will
// be replaced by PyErrRestore.
PyFile_WriteString("GreenletExit did not kill ", f);
PyFile_WriteObject(self.borrow_o(), f, 0);
PyFile_WriteString("\n", f);
Py_DECREF(f);
}
}
/* Restore the saved exception. */
saved_err.PyErrRestore();
/* Undo the temporary resurrection; can't use DECREF here,
* it would cause a recursive call.
*/
assert(self.REFCNT() > 0);
Py_ssize_t refcnt = self.REFCNT() - 1;
Py_SET_REFCNT(self.borrow_o(), refcnt);
if (refcnt != 0) {
/* Resurrected! */
_Py_NewReference(self.borrow_o());
Py_SET_REFCNT(self.borrow_o(), refcnt);
/* Better to use tp_finalizer slot (PEP 442)
* and call ``PyObject_CallFinalizerFromDealloc``,
* but that's only supported in Python 3.4+; see
* Modules/_io/iobase.c for an example.
* TODO: We no longer run on anything that old, switch to finalizers.
*
* The following approach is copied from iobase.c in CPython 2.7.
* (along with much of this function in general). Here's their
* comment:
*
* When called from a heap type's dealloc, the type will be
* decref'ed on return (see e.g. subtype_dealloc in typeobject.c).
*
* On free-threaded builds of CPython, the type is meant to be immortal
* so we probably shouldn't mess with this? See
* test_issue_245_reference_counting_subclass_no_threads
*/
if (PyType_HasFeature(self.TYPE(), Py_TPFLAGS_HEAPTYPE)) {
Py_INCREF(self.TYPE());
}
PyObject_GC_Track((PyObject*)self);
GREENLET_Py_DEC_REFTOTAL;
#ifdef COUNT_ALLOCS
--Py_TYPE(self)->tp_frees;
--Py_TYPE(self)->tp_allocs;
#endif /* COUNT_ALLOCS */
return 0;
}
return 1;
}
static void
green_dealloc(PyGreenlet* self)
{
PyObject_GC_UnTrack(self);
BorrowedGreenlet me(self);
if (me->active()
&& me->started()
&& !me->main()) {
if (!_green_dealloc_kill_started_non_main_greenlet(me)) {
return;
}
}
if (self->weakreflist != NULL) {
PyObject_ClearWeakRefs((PyObject*)self);
}
Py_CLEAR(self->dict);
if (self->pimpl) {
// In case deleting this, which frees some memory,
// somehow winds up calling back into us. That's usually a
//bug in our code.
Greenlet* p = self->pimpl;
self->pimpl = nullptr;
delete p;
}
// and finally we're done. self is now invalid.
Py_TYPE(self)->tp_free((PyObject*)self);
}
static OwnedObject
internal_green_throw(BorrowedGreenlet self, PyErrPieces& err_pieces)
{
PyObject* result = nullptr;
err_pieces.PyErrRestore();
assert(PyErr_Occurred());
if (self->started() && !self->active()) {
/* dead greenlet: turn GreenletExit into a regular return */
result = g_handle_exit(OwnedObject()).relinquish_ownership();
}
self->args() <<= result;
return single_result(self->g_switch());
}
PyDoc_STRVAR(
green_switch_doc,
"switch(*args, **kwargs)\n"
"\n"
"Switch execution to this greenlet.\n"
"\n"
"If this greenlet has never been run, then this greenlet\n"
"will be switched to using the body of ``self.run(*args, **kwargs)``.\n"
"\n"
"If the greenlet is active (has been run, but was switch()'ed\n"
"out before leaving its run function), then this greenlet will\n"
"be resumed and the return value to its switch call will be\n"
"None if no arguments are given, the given argument if one\n"
"argument is given, or the args tuple and keyword args dict if\n"
"multiple arguments are given.\n"
"\n"
"If the greenlet is dead, or is the current greenlet then this\n"
"function will simply return the arguments using the same rules as\n"
"above.\n");
static PyObject*
green_switch(PyGreenlet* self, PyObject* args, PyObject* kwargs)
{
// Our use of Greenlet::args() makes this method non-reentrant.
// Therefore, check to be sure the switch will be allowed ---
// we're calling from the same thread that ``self`` belongs to ---
// BEFORE doing anything with args(). If we don't do this, we can
// find args() getting clobbered by switches that will never
// succeed.
//
// TODO: We're only doing this for free-threaded builds because
// those are the only ones that have demonstrated an issue,
// trusting our later checks in g_switch to perform the same
// function and the GIL to keep us from being reentered in regular
// builds. BUT should we always do this as an extra measure of
// safety in case we run code at unexpected times (e.g., a GC?)
#ifdef Py_GIL_DISABLED
try {
self->pimpl->check_switch_allowed();
}
catch (const PyErrOccurred&) {
return nullptr;
}
#endif
using greenlet::SwitchingArgs;
SwitchingArgs switch_args(OwnedObject::owning(args), OwnedObject::owning(kwargs));
self->pimpl->may_switch_away();
self->pimpl->args() <<= switch_args;
// If we're switching out of a greenlet, and that switch is the
// last thing the greenlet does, the greenlet ought to be able to
// go ahead and die at that point. Currently, someone else must
// manually switch back to the greenlet so that we "fall off the
// end" and can perform cleanup. You'd think we'd be able to
// figure out that this is happening using the frame's ``f_lasti``
// member, which is supposed to be an index into
// ``frame->f_code->co_code``, the bytecode string. However, in
// recent interpreters, ``f_lasti`` tends not to be updated thanks
// to things like the PREDICT() macros in ceval.c. So it doesn't
// really work to do that in many cases. For example, the Python
// code:
// def run():
// greenlet.getcurrent().parent.switch()
// produces bytecode of len 16, with the actual call to switch()
// being at index 10 (in Python 3.10). However, the reported
// ``f_lasti`` we actually see is...5! (Which happens to be the
// second byte of the CALL_METHOD op for ``getcurrent()``).
try {
OwnedObject result(single_result(self->pimpl->g_switch()));
#ifndef NDEBUG
// Note that the current greenlet isn't necessarily self. If self
// finished, we went to one of its parents.
assert(!self->pimpl->args());
const BorrowedGreenlet& current = GET_THREAD_STATE().state().borrow_current();
// It's possible it's never been switched to.
assert(!current->args());
#endif
PyObject* p = result.relinquish_ownership();
if (!p && !PyErr_Occurred()) {
// This shouldn't be happening anymore, so the asserts
// are there for debug builds. Non-debug builds
// crash "gracefully" in this case, although there is an
// argument to be made for killing the process in all
// cases --- for this to be the case, our switches
// probably nested in an incorrect way, so the state is
// suspicious. Nothing should be corrupt though, just
// confused at the Python level. Letting this propagate is
// probably good enough.
assert(p || PyErr_Occurred());
throw PyErrOccurred(
mod_globs->PyExc_GreenletError,
"Greenlet.switch() returned NULL without an exception set."
);
}
return p;
}
catch(const PyErrOccurred&) {
return nullptr;
}
}
PyDoc_STRVAR(
green_throw_doc,
"Switches execution to this greenlet, but immediately raises the\n"
"given exception in this greenlet. If no argument is provided, the "
"exception\n"
"defaults to `greenlet.GreenletExit`. The normal exception\n"
"propagation rules apply, as described for `switch`. Note that calling "
"this\n"
"method is almost equivalent to the following::\n"
"\n"
" def raiser():\n"
" raise typ, val, tb\n"
" g_raiser = greenlet(raiser, parent=g)\n"
" g_raiser.switch()\n"
"\n"
"except that this trick does not work for the\n"
"`greenlet.GreenletExit` exception, which would not propagate\n"
"from ``g_raiser`` to ``g``.\n");
static PyObject*
green_throw(PyGreenlet* self, PyObject* args)
{
// See green_switch for why we call this early.
#ifdef Py_GIL_DISABLED
try {
self->pimpl->check_switch_allowed();
}
catch (const PyErrOccurred&) {
return nullptr;
}
#endif
PyArgParseParam typ(mod_globs->PyExc_GreenletExit);
PyArgParseParam val;
PyArgParseParam tb;
if (!PyArg_ParseTuple(args, "|OOO:throw", &typ, &val, &tb)) {
return nullptr;
}
assert(typ.borrow() || val.borrow());
self->pimpl->may_switch_away();
try {
// Both normalizing the error and the actual throw_greenlet
// could throw PyErrOccurred.
PyErrPieces err_pieces(typ.borrow(), val.borrow(), tb.borrow());
return internal_green_throw(self, err_pieces).relinquish_ownership();
}
catch (const PyErrOccurred&) {
return nullptr;
}
}
static int
green_bool(PyGreenlet* self)
{
return self->pimpl->active();
}
/**
* CAUTION: Allocates memory, may run GC and arbitrary Python code.
*/
static PyObject*
green_getdict(PyGreenlet* self, void* UNUSED(context))
{
PyCriticalObjectSection cs(self);
if (self->dict == NULL) {
self->dict = PyDict_New();
if (self->dict == NULL) {
return NULL;
}
}
Py_INCREF(self->dict);
return self->dict;
}
static int
green_setdict(PyGreenlet* self, PyObject* val, void* UNUSED(context))
{
if (val == NULL) {
PyErr_SetString(PyExc_TypeError, "__dict__ may not be deleted");
return -1;
}
if (!PyDict_Check(val)) {
PyErr_SetString(PyExc_TypeError, "__dict__ must be a dictionary");
return -1;
}
PyCriticalObjectSection cs(self);
PyObject* tmp = self->dict;
Py_INCREF(val);
self->dict = val;
Py_XDECREF(tmp);
return 0;
}
static bool
_green_not_dead(BorrowedGreenlet self)
{
// XXX: Where else should we do this?
// Probably on entry to most Python-facing functions?
if (self->was_running_in_dead_thread()) {
self->deactivate_and_free();
return false;
}
return self->active() || !self->started();
}
static PyObject*
green_getdead(PyGreenlet* self, void* UNUSED(context))
{
PyCriticalObjectSection cs(self);
if (_green_not_dead(self)) {
Py_RETURN_FALSE;
}
else {
Py_RETURN_TRUE;
}
}
static PyObject*
green_get_stack_saved(PyGreenlet* self, void* UNUSED(context))
{
return PyLong_FromSsize_t(self->pimpl->stack_saved());
}
static PyObject*
green_getrun(PyGreenlet* self, void* UNUSED(context))
{
PyCriticalObjectSection cs(self);
try {
OwnedObject result(BorrowedGreenlet(self)->run());
return result.relinquish_ownership();
}
catch(const PyErrOccurred&) {
return nullptr;
}
}
static int
green_setrun(PyGreenlet* self, PyObject* nrun, void* UNUSED(context))
{
PyCriticalObjectSection cs(self);
try {
BorrowedGreenlet(self)->run(nrun);
return 0;
}
catch(const PyErrOccurred&) {
return -1;
}
}
static PyObject*
green_getparent(PyGreenlet* self, void* UNUSED(context))
{
PyCriticalObjectSection cs(self);
return BorrowedGreenlet(self)->parent().acquire_or_None();
}
static int
green_setparent(PyGreenlet* self, PyObject* nparent, void* UNUSED(context))
{
PyCriticalObjectSection cs(self);
try {
BorrowedGreenlet(self)->parent(nparent);
}
catch(const PyErrOccurred&) {
return -1;
}
return 0;
}
static PyObject*
green_getcontext(const PyGreenlet* self, void* UNUSED(context))
{
PyCriticalObjectSection cs(self);
const Greenlet *const g = self->pimpl;
try {
OwnedObject result(g->context());
return result.relinquish_ownership();
}
catch(const PyErrOccurred&) {
return nullptr;
}
}
static int
green_setcontext(PyGreenlet* self, PyObject* nctx, void* UNUSED(context))
{
PyCriticalObjectSection cs(self);
try {
BorrowedGreenlet(self)->context(nctx);
return 0;
}
catch(const PyErrOccurred&) {
return -1;
}
}
static PyObject*
green_getframe(PyGreenlet* self, void* UNUSED(context))
{
PyCriticalObjectSection cs(self);
const PythonState::OwnedFrame& top_frame = BorrowedGreenlet(self)->top_frame();
return top_frame.acquire_or_None();
}
static PyObject*
green_getstate(PyGreenlet* self)
{
PyErr_Format(PyExc_TypeError,
"cannot serialize '%s' object",
Py_TYPE(self)->tp_name);
return nullptr;
}
static PyObject*
green_repr(PyGreenlet* _self)
{
BorrowedGreenlet self(_self);
/*
Return a string like
<greenlet.greenlet at 0xdeadbeef [current][active started]|dead main>
The handling of greenlets across threads is not super good.
We mostly use the internal definitions of these terms, but they
generally should make sense to users as well.
*/
PyObject* result;
int never_started = !self->started() && !self->active();
const char* const tp_name = Py_TYPE(self)->tp_name;
if (_green_not_dead(self)) {
/* XXX: The otid= is almost useless because you can't correlate it to
any thread identifier exposed to Python. We could use
PyThreadState_GET()->thread_id, but we'd need to save that in the
greenlet, or save the whole PyThreadState object itself.
As it stands, its only useful for identifying greenlets from the same thread.
*/
const char* state_in_thread;
if (self->was_running_in_dead_thread()) {
// The thread it was running in is dead!
// This can happen, especially at interpreter shut down.
// It complicates debugging output because it may be
// impossible to access the current thread state at that
// time. Thus, don't access the current thread state.
state_in_thread = " (thread exited)";
}
else {
state_in_thread = GET_THREAD_STATE().state().is_current(self)
? " current"
: (self->started() ? " suspended" : "");
}
result = PyUnicode_FromFormat(
"<%s object at %p (otid=%p)%s%s%s%s>",
tp_name,
self.borrow_o(),
self->thread_state(),
state_in_thread,
self->active() ? " active" : "",
never_started ? " pending" : " started",
self->main() ? " main" : ""
);
}
else {
result = PyUnicode_FromFormat(
"<%s object at %p (otid=%p) %sdead>",
tp_name,
self.borrow_o(),
self->thread_state(),
self->was_running_in_dead_thread()
? "(thread exited) "
: ""
);
}
return result;
}
static PyMethodDef green_methods[] = {
{
.ml_name="switch",
.ml_meth=reinterpret_cast<PyCFunction>(green_switch),
.ml_flags=METH_VARARGS | METH_KEYWORDS,
.ml_doc=green_switch_doc
},
{.ml_name="throw", .ml_meth=(PyCFunction)green_throw, .ml_flags=METH_VARARGS, .ml_doc=green_throw_doc},
{.ml_name="__getstate__", .ml_meth=(PyCFunction)green_getstate, .ml_flags=METH_NOARGS, .ml_doc=NULL},
{.ml_name=NULL, .ml_meth=NULL} /* sentinel */
};
static PyGetSetDef green_getsets[] = {
/* name, getter, setter, doc, context pointer */
{.name="__dict__", .get=(getter)green_getdict, .set=(setter)green_setdict},
{.name="run", .get=(getter)green_getrun, .set=(setter)green_setrun},
{.name="parent", .get=(getter)green_getparent, .set=(setter)green_setparent},
{.name="gr_frame", .get=(getter)green_getframe },
{
.name="gr_context",
.get=(getter)green_getcontext,
.set=(setter)green_setcontext
},
{.name="dead", .get=(getter)green_getdead},
{.name="_stack_saved", .get=(getter)green_get_stack_saved},
{.name=NULL}
};
static PyMemberDef green_members[] = {
{.name=NULL}
};
static PyNumberMethods green_as_number = {
.nb_bool=(inquiry)green_bool,
};
PyTypeObject PyGreenlet_Type = {
.ob_base=PyVarObject_HEAD_INIT(NULL, 0)
.tp_name="greenlet.greenlet", /* tp_name */
.tp_basicsize=sizeof(PyGreenlet), /* tp_basicsize */
/* methods */
.tp_dealloc=(destructor)green_dealloc, /* tp_dealloc */
.tp_repr=(reprfunc)green_repr, /* tp_repr */
.tp_as_number=&green_as_number, /* tp_as _number*/
.tp_flags=G_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
.tp_doc="greenlet(run=None, parent=None) -> greenlet\n\n"
"Creates a new greenlet object (without running it).\n\n"
" - *run* -- The callable to invoke.\n"
" - *parent* -- The parent greenlet. The default is the current "
"greenlet.", /* tp_doc */
.tp_traverse=(traverseproc)green_traverse, /* tp_traverse */
.tp_clear=(inquiry)green_clear, /* tp_clear */
.tp_weaklistoffset=offsetof(PyGreenlet, weakreflist), /* tp_weaklistoffset */
.tp_methods=green_methods, /* tp_methods */
.tp_members=green_members, /* tp_members */
.tp_getset=green_getsets, /* tp_getset */
.tp_dictoffset=offsetof(PyGreenlet, dict), /* tp_dictoffset */
.tp_init=(initproc)green_init, /* tp_init */
.tp_alloc=PyType_GenericAlloc, /* tp_alloc */
.tp_new=(newfunc)green_new, /* tp_new */
.tp_free=PyObject_GC_Del, /* tp_free */
#if !GREENLET_PY315 && !(GREENLET_PY314 && defined(Py_GIL_DISABLED))
/*
We may have been handling this wrong all along.
It shows as a problem with the GIL disabled. In builds of 3.14 with
assertions enabled, we break the garbage collector if we *ever*
return false from this function. The docs say this is to distinguish
some objects that are collectable vs some that are not, specifically
giving the example of PyTypeObject as the only place this is done,
where it distinguishes between static types like this one (allocated
by the C runtime at load time) and dynamic heap types (created at
runtime as objects). With the GIL disabled, all allocations that are
potentially collectable go in the mimalloc heap, and the collector
asserts that tp_is_gc() is true for them as it walks through the
heap object by object. Since we set the Py_TPFLAGS_HAS_GC bit, we
are always allocated in that mimalloc heap, so we must always be
collectable.
XXX: TODO: Could this be responsible for some apparent leaks, even
on GIL builds, at least in 3.14? See if we can catch an assertion
failure in the GC on regular 3.14 as well.
*/
.tp_is_gc=(inquiry)green_is_gc, /* tp_is_gc */
#endif
};
#endif
// Local Variables:
// flycheck-clang-include-path: ("/opt/local/Library/Frameworks/Python.framework/Versions/3.8/include/python3.8")
// End:

View File

@@ -0,0 +1,143 @@
#ifndef GREENLET_MSVC_COMPAT_HPP
#define GREENLET_MSVC_COMPAT_HPP
/*
* Support for MSVC on Windows.
*
* Beginning with Python 3.14, some of the internal
* include files we need are not compatible with MSVC
* in C++ mode:
*
* internal\pycore_stackref.h(253): error C4576: a parenthesized type
* followed by an initializer list is a non-standard explicit type conversion syntax
*
* This file is included from ``internal/pycore_interpframe.h``, which
* we need for the ``_PyFrame_IsIncomplete`` API.
*
* Unfortunately, that API is a ``static inline`` function, as are a
* bunch of the functions it calls. The only solution seems to be to
* copy those definitions and the supporting inline functions here.
*
* Now, this makes us VERY fragile to changes in those functions. Because
* they're internal and static, the CPython devs might feel free to change
* them in even minor versions, meaning that we could runtime link and load,
* but still crash. We have that problem on all platforms though. It's just worse
* here because we have to keep copying the updated definitions.
*/
#include <Python.h>
#include "greenlet_cpython_compat.hpp"
// This file is only included on 3.14+
extern "C" {
// pycore_code.h ----------------
#define _PyCode_CODE(CO) _Py_RVALUE((_Py_CODEUNIT *)(CO)->co_code_adaptive)
#ifdef Py_GIL_DISABLED
static inline _PyCodeArray *
_PyCode_GetTLBCArray(PyCodeObject *co)
{
return _Py_STATIC_CAST(_PyCodeArray *,
_Py_atomic_load_ptr_acquire(&co->co_tlbc));
}
#endif
// End pycore_code.h ----------
// pycore_interpframe.h ----------
#if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG)
#define Py_TAG_BITS 0
#else
#define Py_TAG_BITS ((uintptr_t)1)
#define Py_TAG_DEFERRED (1)
#define Py_INT_TAG 3
#endif
static const _PyStackRef PyStackRef_NULL = { .bits = Py_TAG_DEFERRED};
#define PyStackRef_IsNull(stackref) ((stackref).bits == PyStackRef_NULL.bits)
static inline bool
PyStackRef_IsTaggedInt(_PyStackRef i)
{
return (i.bits & Py_INT_TAG) == Py_INT_TAG;
}
static inline bool
PyStackRef_IsNullOrInt(_PyStackRef stackref)
{
return PyStackRef_IsNull(stackref) || PyStackRef_IsTaggedInt(stackref);
}
#define _Py_VISIT_STACKREF(ref) \
do { \
if (!PyStackRef_IsNullOrInt(ref)) { \
int vret = _PyGC_VisitStackRef(&(ref), visit, arg); \
if (vret) \
return vret; \
} \
} while (0)
static inline PyObject *
PyStackRef_AsPyObjectBorrow(_PyStackRef stackref)
{
PyObject *cleared = ((PyObject *)((stackref).bits & (~Py_TAG_BITS)));
return cleared;
}
#define Py_TAG_REFCNT 1
#define BITS_TO_PTR(ref) ((PyObject *)((ref).bits))
#define PyStackRef_RefcountOnObject(ref) (((ref).bits & Py_TAG_REFCNT) == 0)
#define PyStackRef_CLOSE(REF) \
do { \
_PyStackRef _close_tmp = (REF); \
if (PyStackRef_RefcountOnObject(_close_tmp)) { \
Py_DECREF(BITS_TO_PTR(_close_tmp)); \
} \
} while (0)
#define PyStackRef_CLEAR(REF) \
do { \
_PyStackRef* _clear_ptr = &(REF); \
_PyStackRef _clear_old = (*_clear_ptr); \
*_clear_ptr = PyStackRef_NULL; \
PyStackRef_CLOSE(_clear_old); \
} while (0)
static inline PyCodeObject *_PyFrame_GetCode(_PyInterpreterFrame *f) {
assert(!PyStackRef_IsNullOrInt(f->f_executable));
PyObject *executable = PyStackRef_AsPyObjectBorrow(f->f_executable);
assert(PyCode_Check(executable));
return (PyCodeObject *)executable;
}
static inline _Py_CODEUNIT *
_PyFrame_GetBytecode(_PyInterpreterFrame *f)
{
#ifdef Py_GIL_DISABLED
PyCodeObject *co = _PyFrame_GetCode(f);
_PyCodeArray *tlbc = _PyCode_GetTLBCArray(co);
assert(f->tlbc_index >= 0 && f->tlbc_index < tlbc->size);
return (_Py_CODEUNIT *)tlbc->entries[f->tlbc_index];
#else
return _PyCode_CODE(_PyFrame_GetCode(f));
#endif
}
static inline bool //_Py_NO_SANITIZE_THREAD
_PyFrame_IsIncomplete(_PyInterpreterFrame *frame)
{
if (frame->owner >= FRAME_OWNED_BY_INTERPRETER) {
return true;
}
return frame->owner != FRAME_OWNED_BY_GENERATOR &&
frame->instr_ptr < _PyFrame_GetBytecode(frame) +
_PyFrame_GetCode(frame)->_co_firsttraceable;
}
// pycore_interpframe.h ----------
}
#endif // GREENLET_MSVC_COMPAT_HPP

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,103 @@
#ifndef GREENLET_SLP_SWITCH_HPP
#define GREENLET_SLP_SWITCH_HPP
#include "greenlet_compiler_compat.hpp"
#include "greenlet_refs.hpp"
/*
* the following macros are spliced into the OS/compiler
* specific code, in order to simplify maintenance.
*/
// We can save about 10% of the time it takes to switch greenlets if
// we thread the thread state through the slp_save_state() and the
// following slp_restore_state() calls from
// slp_switch()->g_switchstack() (which already needs to access it).
//
// However:
//
// that requires changing the prototypes and implementations of the
// switching functions. If we just change the prototype of
// slp_switch() to accept the argument and update the macros, without
// changing the implementation of slp_switch(), we get crashes on
// 64-bit Linux and 32-bit x86 (for reasons that aren't 100% clear);
// on the other hand, 64-bit macOS seems to be fine. Also, 64-bit
// windows is an issue because slp_switch is written fully in assembly
// and currently ignores its argument so some code would have to be
// adjusted there to pass the argument on to the
// ``slp_save_state_asm()`` function (but interestingly, because of
// the calling convention, the extra argument is just ignored and
// things function fine, albeit slower, if we just modify
// ``slp_save_state_asm`()` to fetch the pointer to pass to the
// macro.)
//
// Our compromise is to use a *glabal*, untracked, weak, pointer
// to the necessary thread state during the process of switching only.
// This is safe because we're protected by the GIL, and if we're
// running this code, the thread isn't exiting. This also nets us a
// 10-12% speed improvement.
#if Py_GIL_DISABLED
thread_local greenlet::Greenlet* switching_thread_state = nullptr;
#else
static greenlet::Greenlet* volatile switching_thread_state = nullptr;
#endif
extern "C" {
static int GREENLET_NOINLINE(slp_save_state_trampoline)(char* stackref);
static void GREENLET_NOINLINE(slp_restore_state_trampoline)();
}
#define SLP_SAVE_STATE(stackref, stsizediff) \
do { \
assert(switching_thread_state); \
stackref += STACK_MAGIC; \
if (slp_save_state_trampoline((char*)stackref)) \
return -1; \
if (!switching_thread_state->active()) \
return 1; \
stsizediff = switching_thread_state->stack_start() - (char*)stackref; \
} while (0)
#define SLP_RESTORE_STATE() slp_restore_state_trampoline()
#define SLP_EVAL
extern "C" {
#define slp_switch GREENLET_NOINLINE(slp_switch)
#include "slp_platformselect.h"
}
#undef slp_switch
#ifndef STACK_MAGIC
# error \
"greenlet needs to be ported to this platform, or taught how to detect your compiler properly."
#endif /* !STACK_MAGIC */
#ifdef EXTERNAL_ASM
/* CCP addition: Make these functions, to be called from assembler.
* The token include file for the given platform should enable the
* EXTERNAL_ASM define so that this is included.
*/
extern "C" {
intptr_t
slp_save_state_asm(intptr_t* ref)
{
intptr_t diff;
SLP_SAVE_STATE(ref, diff);
return diff;
}
void
slp_restore_state_asm(void)
{
SLP_RESTORE_STATE();
}
extern int slp_switch(void);
};
#endif
#endif

View File

@@ -0,0 +1,31 @@
#ifndef GREENLET_THREAD_SUPPORT_HPP
#define GREENLET_THREAD_SUPPORT_HPP
/**
* Defines various utility functions to help greenlet integrate well
* with threads. This used to be needed when we supported Python
* 2.7 on Windows, which used a very old compiler. We wrote an
* alternative implementation using Python APIs and POSIX or Windows
* APIs, but that's no longer needed. So this file is a shadow of its
* former self --- but may be needed in the future.
*/
#include <stdexcept>
#include <thread>
#include <mutex>
#include "greenlet_compiler_compat.hpp"
namespace greenlet {
typedef std::mutex Mutex;
typedef std::lock_guard<Mutex> LockGuard;
class LockInitError : public std::runtime_error
{
public:
LockInitError(const char* what) : std::runtime_error(what)
{};
};
};
#endif /* GREENLET_THREAD_SUPPORT_HPP */