123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738 |
- /* -*- 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;
- 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) {
- new UserGreenlet(o, GET_THREAD_STATE().state().borrow_current());
- assert(Py_REFCNT(o) == 1);
- }
- 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)
- {
- /* 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) {
- PyFile_WriteString("GreenletExit did not kill ", f);
- PyFile_WriteObject(self.borrow_o(), f, 0);
- PyFile_WriteString("\n", 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.
- *
- * 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). */
- if (PyType_HasFeature(self.TYPE(), Py_TPFLAGS_HEAPTYPE)) {
- Py_INCREF(self.TYPE());
- }
- PyObject_GC_Track((PyObject*)self);
- _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)
- {
- 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());
- 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)
- {
- 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))
- {
- 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))
- {
- PyObject* tmp;
- 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;
- }
- 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))
- {
- 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))
- {
- 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))
- {
- try {
- BorrowedGreenlet(self)->run(nrun);
- return 0;
- }
- catch(const PyErrOccurred&) {
- return -1;
- }
- }
- static PyObject*
- green_getparent(PyGreenlet* self, void* UNUSED(context))
- {
- return BorrowedGreenlet(self)->parent().acquire_or_None();
- }
- static int
- green_setparent(PyGreenlet* self, PyObject* nparent, void* UNUSED(context))
- {
- try {
- BorrowedGreenlet(self)->parent(nparent);
- }
- catch(const PyErrOccurred&) {
- return -1;
- }
- return 0;
- }
- static PyObject*
- green_getcontext(const PyGreenlet* self, void* UNUSED(context))
- {
- 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))
- {
- try {
- BorrowedGreenlet(self)->context(nctx);
- return 0;
- }
- catch(const PyErrOccurred&) {
- return -1;
- }
- }
- static PyObject*
- green_getframe(PyGreenlet* self, void* UNUSED(context))
- {
- 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 */
- .tp_is_gc=(inquiry)green_is_gc, /* tp_is_gc */
- };
- #endif
- // Local Variables:
- // flycheck-clang-include-path: ("/opt/local/Library/Frameworks/Python.framework/Versions/3.8/include/python3.8")
- // End:
|