diff options
author | Jason Madden <jamadden@gmail.com> | 2021-10-05 17:50:09 -0500 |
---|---|---|
committer | Jason Madden <jamadden@gmail.com> | 2021-10-12 08:53:58 -0500 |
commit | 4a35c59c8a1f090b87cb194d410b0e59920d7f71 (patch) | |
tree | dd3311b7e10c3fea25bde02809f9151fbed7612d | |
parent | 6c350ef1d4584be67e6dab74725717f275da9b54 (diff) | |
download | greenlet-4a35c59c8a1f090b87cb194d410b0e59920d7f71.tar.gz |
Introduce PyMainGreenlet as a subtype of greenlet.
This way we don't have to grow the greenlet class by a pointer that is almost always empty. Also brings more type safety to the code.
-rw-r--r-- | src/greenlet/greenlet.cpp | 36 | ||||
-rw-r--r-- | src/greenlet/greenlet.h | 5 | ||||
-rw-r--r-- | src/greenlet/greenlet_compiler_compat.hpp | 4 | ||||
-rw-r--r-- | src/greenlet/greenlet_internal.hpp | 25 | ||||
-rw-r--r-- | src/greenlet/greenlet_thread_state.hpp | 28 |
5 files changed, 64 insertions, 34 deletions
diff --git a/src/greenlet/greenlet.cpp b/src/greenlet/greenlet.cpp index 480a6fe..c3552c6 100644 --- a/src/greenlet/greenlet.cpp +++ b/src/greenlet/greenlet.cpp @@ -178,7 +178,7 @@ _ThreadStateCreator_Destroy(void* arg) // Passed a borrowed reference to the main greenlet. // main greenlet -> state -> main greenlet // main greenlet -> main greenlet - PyGreenlet* main = (PyGreenlet*)arg; + PyMainGreenlet* main = (PyMainGreenlet*)arg; ThreadState* s = main->thread_state; _GDPrint("PendingCall: Destroying main greenlet %p (refcount %ld)\n", main, Py_REFCNT(main)); // When we need to do cross-thread operations, we check this. @@ -240,7 +240,7 @@ ThreadStateCreator_Destroy(ThreadState* state) return; } - g_cleanup_queue.push_back(state->borrow_main_greenlet()); + g_cleanup_queue.push_back((PyGreenlet*)state->borrow_main_greenlet()); if (g_cleanup_queue.size() == 1) { // We added the first item to the queue. We need to schedule // the cleanup. @@ -322,7 +322,7 @@ public: { return this->state().borrow_origin(); }; - inline PyGreenlet* borrow_main_greenlet() + inline PyMainGreenlet* borrow_main_greenlet() { return this->state().borrow_main_greenlet(); }; @@ -372,20 +372,20 @@ green_clear_exc(PyGreenlet* g) #endif } -static PyGreenlet* +static PyMainGreenlet* green_create_main(void) { - PyGreenlet* gmain; + PyMainGreenlet* gmain; /* create the main greenlet for this thread */ - gmain = (PyGreenlet*)PyType_GenericAlloc(&PyGreenlet_Type, 0); + gmain = (PyMainGreenlet*)PyType_GenericAlloc(&PyMainGreenlet_Type, 0); if (gmain == NULL) { return NULL; } - gmain->stack_start = (char*)1; - gmain->stack_stop = (char*)-1; + gmain->super.stack_start = (char*)1; + gmain->super.stack_stop = (char*)-1; // circular reference; the pending call will clean this up. - gmain->main_greenlet_s = gmain; + gmain->super.main_greenlet_s = gmain; Py_INCREF(gmain); assert(Py_REFCNT(gmain) == 2); return gmain; @@ -488,7 +488,7 @@ green_create_main(void) // return g->run_info; // } -static PyGreenlet* +static PyMainGreenlet* find_and_borrow_main_greenlet_in_lineage(PyGreenlet* g) { while (!PyGreenlet_STARTED(g)) { @@ -1566,11 +1566,17 @@ green_dealloc(PyGreenlet* self) #endif Py_CLEAR(self->dict); assert(!PyErr_Occurred()); + // XXX: We should never get here with a main greenlet that still + // has a thread state attached. If we do, that's a problem, right? + // (If it's not a problem, we can customize the dealloc function + // for the main greenlet class). +#if 0 if (self->thread_state) { // HMM. We shouldn't get here. _GDPrint("DEALLOC MAIN GREENLET (%p) WITH THREAD STATE (%p)\n", self, self->thread_state); assert(!self->thread_state); } +#endif Py_TYPE(self)->tp_free((PyObject*)self); assert(!PyErr_Occurred()); @@ -1838,14 +1844,14 @@ green_setparent(PyGreenlet* self, PyObject* nparent, void* c) _GDPrint("Examining parent "); _GDPoPrint((PyObject*)p); _GDPrint("\n\tActive? %d Run info: %p\n", PyGreenlet_ACTIVE(p), p->main_greenlet_s); - run_info = PyGreenlet_ACTIVE(p) ? p->main_greenlet_s : NULL; + run_info = PyGreenlet_ACTIVE(p) ? (PyGreenlet*)p->main_greenlet_s : NULL; } if (run_info == NULL) { PyErr_SetString(PyExc_ValueError, "parent must not be garbage collected"); return -1; } - if (PyGreenlet_STARTED(self) && self->main_greenlet_s != run_info) { + if (PyGreenlet_STARTED(self) && self->main_greenlet_s != (void*)run_info) { PyErr_SetString(PyExc_ValueError, "parent cannot be on a different thread"); return -1; @@ -2244,6 +2250,7 @@ PyTypeObject PyGreenlet_Type = { }; + PyDoc_STRVAR(mod_getcurrent_doc, "getcurrent() -> greenlet\n" "\n" @@ -2403,6 +2410,11 @@ init_greenlet(void) if (PyType_Ready(&PyGreenlet_Type) < 0) { INITERROR; } + PyMainGreenlet_Type.tp_base = &PyGreenlet_Type; + if (PyType_Ready(&PyMainGreenlet_Type) < 0) { + INITERROR; + } + PyExc_GreenletError = PyErr_NewException("greenlet.error", NULL, NULL); if (PyExc_GreenletError == NULL) { INITERROR; diff --git a/src/greenlet/greenlet.h b/src/greenlet/greenlet.h index 8344809..1375bd9 100644 --- a/src/greenlet/greenlet.h +++ b/src/greenlet/greenlet.h @@ -15,7 +15,7 @@ extern "C" { #define GREENLET_VERSION "1.0.0" #ifndef GREENLET_MODULE -#define state_ptr_t void* +#define main_greenlet_ptr_t void* #endif typedef struct _greenlet { @@ -28,7 +28,7 @@ typedef struct _greenlet { struct _greenlet* parent; /* strong reference, set when the greenlet begins to run. */ /* Used to be called run_info */ - struct _greenlet* main_greenlet_s; + main_greenlet_ptr_t main_greenlet_s; struct _frame* top_frame; /* weak reference (suspended) or NULL (running) */ int recursion_depth; PyObject* weakreflist; @@ -52,7 +52,6 @@ typedef struct _greenlet { // If we can get it down to just one pointer, we could // re-purpose an existing field. PyObject* run_callable; - state_ptr_t thread_state; } PyGreenlet; #define PyGreenlet_Check(op) PyObject_TypeCheck(op, &PyGreenlet_Type) diff --git a/src/greenlet/greenlet_compiler_compat.hpp b/src/greenlet/greenlet_compiler_compat.hpp index b22092c..b57b1d8 100644 --- a/src/greenlet/greenlet_compiler_compat.hpp +++ b/src/greenlet/greenlet_compiler_compat.hpp @@ -15,10 +15,10 @@ typedef unsigned long long uint64_t; typedef signed long long int64_t; typedef unsigned int uint32_t; #define nullptr NULL -#define G_DELETED_METHOD 0 +#define G_HAS_METHOD_DELETE 0 #else #include <cstdint> -#define G_DELETED_METHOD delete +#define G_HAS_METHOD_DELETE 1 #endif // CAUTION: MSVC is stupidly picky: diff --git a/src/greenlet/greenlet_internal.hpp b/src/greenlet/greenlet_internal.hpp index 746db75..4318f6a 100644 --- a/src/greenlet/greenlet_internal.hpp +++ b/src/greenlet/greenlet_internal.hpp @@ -13,16 +13,35 @@ namespace greenlet { class ThreadState; }; -#define state_ptr_t greenlet::ThreadState* +struct _PyMainGreenlet; +#define main_greenlet_ptr_t struct _PyMainGreenlet* + #include "greenlet.h" #include "greenlet_compiler_compat.hpp" #include "greenlet_cpython_compat.hpp" - #include <vector> #include <memory> +// Define a special type for the main greenlets. This way it can have +// a thread state pointer without having to carry the expense of a +// NULL field around on every other greenlet. +// At the Python level, the main greenlet class is +// *almost* indistinguisable from plain greenlets. +typedef struct _PyMainGreenlet +{ + PyGreenlet super; + greenlet::ThreadState* thread_state; +} PyMainGreenlet; + +static PyTypeObject PyMainGreenlet_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "greenlet.greenlet", + .tp_basicsize = sizeof(PyMainGreenlet) +}; + + #define UNUSED(expr) do { (void)(expr); } while (0) namespace greenlet { @@ -90,7 +109,7 @@ typedef std::vector<PyGreenlet*, PythonAllocator<PyGreenlet*> > g_deleteme_t; /** * Forward declarations needed in multiple files. */ -static PyGreenlet* green_create_main(); +static PyMainGreenlet* green_create_main(); static PyObject* green_switch(PyGreenlet* self, PyObject* args, PyObject* kwargs); diff --git a/src/greenlet/greenlet_thread_state.hpp b/src/greenlet/greenlet_thread_state.hpp index af36f8c..9b7952d 100644 --- a/src/greenlet/greenlet_thread_state.hpp +++ b/src/greenlet/greenlet_thread_state.hpp @@ -53,7 +53,7 @@ private: // Adding the vector takes us up to 80 bytes () /* Strong reference to the main greenlet */ - PyGreenlet* main_greenlet_s; + PyMainGreenlet* main_greenlet_s; /* Strong reference to the current greenlet. */ PyGreenlet* current_greenlet_s; @@ -111,7 +111,7 @@ public: return this->main_greenlet_s != nullptr; } - inline PyGreenlet* borrow_main_greenlet() + inline PyMainGreenlet* borrow_main_greenlet() { if (!this->main_greenlet_s) { assert(!this->current_greenlet_s); @@ -119,16 +119,16 @@ public: this->main_greenlet_s = green_create_main(); if (this->main_greenlet_s) { this->main_greenlet_s->thread_state = this; - this->set_current(this->main_greenlet_s); + this->set_current((PyGreenlet*)this->main_greenlet_s); assert(Py_REFCNT(this->main_greenlet_s) == 3); } } return this->main_greenlet_s; }; - inline PyGreenlet* get_main_greenlet() + inline PyMainGreenlet* get_main_greenlet() { - PyGreenlet* g = this->borrow_main_greenlet(); + PyMainGreenlet* g = this->borrow_main_greenlet(); Py_XINCREF(g); return g; } @@ -277,7 +277,7 @@ public: */ assert(this->current_greenlet_s->main_greenlet_s == this->main_greenlet_s); - assert(this->main_greenlet_s->main_greenlet_s == this->main_greenlet_s); + assert(this->main_greenlet_s->super.main_greenlet_s == this->main_greenlet_s); this->clear_deleteme_list(); return true; }; @@ -401,18 +401,18 @@ public: // It's possible that there is some other greenlet that // switched to us, leaving a reference to the main greenlet // on the stack, somewhere uncollectable. Try to detect that. - if (this->current_greenlet_s == this->main_greenlet_s && this->current_greenlet_s) { + if (this->current_greenlet_s == (void*)this->main_greenlet_s && this->current_greenlet_s) { assert(PyGreenlet_MAIN(this->main_greenlet_s)); assert(!this->current_greenlet_s->top_frame); - assert(this->main_greenlet_s->main_greenlet_s == this->main_greenlet_s); + assert(this->main_greenlet_s->super.main_greenlet_s == this->main_greenlet_s); // Break a cycle we know about, the self reference // the main greenlet keeps. - Py_CLEAR(this->main_greenlet_s->main_greenlet_s); + Py_CLEAR(this->main_greenlet_s->super.main_greenlet_s); // Drop one reference we hold. Py_CLEAR(this->current_greenlet_s); // 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_s; + PyMainGreenlet* old_main_greenlet = this->main_greenlet_s; Py_ssize_t cnt = Py_REFCNT(this->main_greenlet_s); Py_CLEAR(this->main_greenlet_s); _GDPrint("Destructor: Init refcount: %ld Current: %ld\n", @@ -519,7 +519,7 @@ public: // when the thread exited (because we already cleared this // pointer if it was). This shouldn't be possible? - if (this->main_greenlet_s->top_frame) { + if (this->main_greenlet_s->super.top_frame) { _GDPrint("The main greenlet had a frame: %p (refcount %ld)", this->main_greenlet_s, Py_REFCNT(this->main_greenlet_s->top_frame) @@ -534,10 +534,10 @@ public: // 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_s->main_greenlet_s == this->main_greenlet_s - || !this->main_greenlet_s->main_greenlet_s); + assert(this->main_greenlet_s->super.main_greenlet_s == this->main_greenlet_s + || !this->main_greenlet_s->super.main_greenlet_s); // self reference, probably gone - Py_CLEAR(this->main_greenlet_s->main_greenlet_s); + Py_CLEAR(this->main_greenlet_s->super.main_greenlet_s); _GDPrint("\tFinishing double decref of main greenlet %p Refcount: %ld\n", this->main_greenlet_s, |