From dee8a9910d042ced05a5cfdb1570d18e34663cb3 Mon Sep 17 00:00:00 2001 From: Ciro Date: Mon, 14 Nov 2022 16:15:48 -0300 Subject: [PATCH] add example of selector with libuv for study and rewrite to CFFI --- src/socketify/loop.py | 2 +- src/socketify/native/uv_selector.txt | 401 +++++++++++++++++++++++++++ 2 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 src/socketify/native/uv_selector.txt diff --git a/src/socketify/loop.py b/src/socketify/loop.py index 1af87b3..2bd28b0 100644 --- a/src/socketify/loop.py +++ b/src/socketify/loop.py @@ -114,7 +114,7 @@ class Loop: # asyncio.run(main()) -#https://github.com/SpaceBlocks/uWebSockets.py/blob/master/src/Selector.h +#see ./native/uv_selector.txt # class UVSelector(asyncio.SelectorEventLoop): # def tick(self): # pass diff --git a/src/socketify/native/uv_selector.txt b/src/socketify/native/uv_selector.txt new file mode 100644 index 0000000..f2bff26 --- /dev/null +++ b/src/socketify/native/uv_selector.txt @@ -0,0 +1,401 @@ +/* Implements the Python event loop */ + +#include +#include + +#undef NDEBUG +#include + +typedef struct { + PyObject_HEAD + uv_loop_t *loop; + PyTypeObject *namedTuple; + /* We build the list of (SelectorKey, events) tuples in iterations */ + PyObject *list; + int list_length; + PyObject *lenFunc; + + PyObject *sked; + + bool tick, thrw; + uv_timer_t *timer; + + uv_signal_t *sig; +} SelectorObject; + +/* These map perfectly to uv */ +const int read_mask = 1; +const int write_mask = 2; + +/* This is the (native) map from fd to uv_poll_t, which points to SelectorKey in userdata */ +std::map polls; + +/* Takes an integer, or object with method fileno. Returns -1 on invalid input. */ +int getFd(PyObject *arg) { + int fd = -1; + if (PyLong_Check(arg)) { + fd = PyLong_AsLong(arg); + } else { + PyObject *fdObj = PyObject_CallMethod(arg, "fileno", NULL); + if (PyLong_Check(fdObj)) { + fd = PyLong_AsLong(fdObj); + } + Py_DECREF(fdObj); + } + return fd; +} + +/* Takes fileobj, bitmask, userdata. Returns SelectorKey. */ +static PyObject *Selector_register(SelectorObject *self, PyObject **args, int nargs) { + /* First two args are required */ + if (nargs < 2) { + PyErr_SetString(PyExc_ValueError, "yo!"); + return NULL; + } + + /* fd */ + int fd = getFd(args[0]); + if (fd == -1) { + PyErr_SetString(PyExc_ValueError, "yo!"); + return NULL; + } + + /* mask */ + int mask = 0; + if (PyLong_Check(args[1])) { + mask = PyLong_AsLong(args[1]); + } else { + PyErr_SetString(PyExc_ValueError, "yo!"); + return NULL; + } + + PyObject *selectorKey = PyStructSequence_New(self->namedTuple); + /* Fileobj */ + Py_INCREF(args[0]); + PyStructSequence_SetItem(selectorKey, 0, args[0]); + /* fd */ + PyStructSequence_SetItem(selectorKey, 1, PyLong_FromLong(fd)); + /* mask */ + PyStructSequence_SetItem(selectorKey, 2, PyLong_FromLong(mask)); + + /* Third arg is optional */ + if (nargs == 3) { + /* userdata */ + Py_INCREF(args[2]); + PyStructSequence_SetItem(selectorKey, 3, args[2]); + } + + /* Do we already have this fd? */ + if (polls.find(fd) != polls.end()) { + PyErr_SetString(PyExc_KeyError, "yo!"); + return NULL; + } + + /* Create the actual poll */ + uv_poll_t *p = new uv_poll_t; + uv_poll_init_socket(self->loop, p, fd); + + /* The poll points to its SelectorKey */ + p->data = selectorKey; + + /* Start polling according to mask */ + uv_poll_start(p, mask, [](uv_poll_t *handle, int status, int events) { + /* Get loop from poll, and from that SelectorObject */ + uv_loop_t *loop = uv_handle_get_loop((uv_handle_t *) handle); + SelectorObject *self = (SelectorObject *) loop->data; + + /* Grab our SelectorKey */ + PyObject *selectorKey = (PyObject *) handle->data; + + PyObject *eventsLong = PyLong_FromLong(events); + // this increases ref count of all passed objects + PyObject *tuple = PyTuple_Pack(2, selectorKey, eventsLong); + Py_DECREF(eventsLong); + + // this does not increase, it steals the existing reference + PyList_SetItem(self->list, self->list_length++, tuple); + }); + + /* Finally add it to the map */ + polls.insert({fd, p}); + + /* Return the SelectorKey */ + Py_INCREF(selectorKey); + return selectorKey; +} + +/* Takes fileobj or integer */ +static PyObject *Selector_unregister(SelectorObject *self, PyObject **args, int nargs) { + + /* We require exactly one argument */ + if (nargs != 1) { + PyErr_SetString(PyExc_ValueError, "yo!"); + return NULL; + } + + /* We also require that to hold a valid fd */ + int fd = getFd(args[0]); + if (fd == -1) { + PyErr_SetString(PyExc_ValueError, "yo!"); + return NULL; + } + + /* Decrease the SelectorKey refcount */ + uv_poll_t *p = polls[fd]; + PyObject *selectorKey = (PyObject *) p->data; + Py_DECREF(selectorKey); + + /* Stop and delete the poll */ + uv_poll_stop(p); + uv_close((uv_handle_t *) p, [](uv_handle_t *p) { + delete (uv_poll_t *) p; + }); + + /* Remove it from our map */ + polls.erase(fd); + + Py_INCREF(Py_None); + return Py_None; +} + +/* Takes fileobj, events and optionally data. Returns a new SelectorKey */ +static PyObject *Selector_modify(SelectorObject *self, PyObject **args, int nargs) { + + /* We require at least 2 args */ + if (nargs < 2) { + PyErr_SetString(PyExc_ValueError, "yo!"); + return NULL; + } + + /* We also require first one to be a valid fd */ + int fd = getFd(args[0]); + if (fd == -1) { + PyErr_SetString(PyExc_ValueError, "yo!"); + return NULL; + } + + uv_poll_t *p = polls[fd]; + + /* Delete the old selectorKey */ + PyObject *selectorKey = (PyObject *) p->data; + Py_DECREF(selectorKey); + + /* Create a new one instead */ + + Py_INCREF(Py_None); + return Py_None; +} + +/* This only returns to Python when there are Python events, or when the loop has ready handles. */ + +/* Optionally takes timeout */ +static PyObject *Selector_select(SelectorObject *self, PyObject **args, int nargs) { + + self->thrw = false; + + // milliseconds + int timeout = -1; + bool hasTimeout = false; + + if (nargs == 1) { + + if (PyLong_Check(args[0])) { + timeout = PyLong_AsLong(args[0]) * 1000; + hasTimeout = true; + } else if (PyFloat_Check(args[0])) { + timeout = PyFloat_AsDouble(args[0]) * 1000.0; + hasTimeout = true; + } else { + // assume we are given the None object here + } + + + } else { + //printf("Select called\n"); + } + + if (hasTimeout) { + // we have timeout set + uv_timer_stop(self->timer); + uv_timer_start(self->timer, [](uv_timer_t *t) { + // timer points towards self + ((SelectorObject *) t->data)->tick = true; + }, timeout, 0); + } + + /* We want to stay as long as we can in this loop, not yielding to asyncio until we have to */ + while (true) { + if (hasTimeout && timeout <= 0) { + uv_run(self->loop, UV_RUN_NOWAIT); + break; + } else { + int keepGoing = uv_run(self->loop, UV_RUN_ONCE); + /* We don't need to keep going if we don't have to */ + if (!keepGoing) { + break; + } + } + + if (self->thrw) { + // raise keyboardinterrupt + PyErr_SetString(PyExc_KeyboardInterrupt, "yo! klciked!"); + return NULL; + } + + if (self->tick) { + self->tick = false; + break; + } + + /* If we have any ready polls for Python we have to return them now */ + if (self->list_length) { + break; + } + } + + /* Return the ready list of (SelectorKey, events) tuples, make a new one */ + PyObject *list = self->list; + + // make it a function + int high = self->list_length; + self->list_length = 0; + return PyList_GetSlice(self->list, 0, high); +} + +/* How is this even a thing? We would need to create a whole map here every time */ +static PyObject *Selector_get_map(SelectorObject *self, PyObject *args) { + + printf("Mappen innehÄller %d polls\n", polls.size()); + + //PyErr_SetString(PyExc_ValueError, "uWS.Selector.get_map not implemented!"); + //return NULL; + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *Selector_get_key(SelectorObject *self, PyObject **args, int nargs) { + + int fd = getFd(args[0]); + if (fd == -1) { + PyErr_SetString(PyExc_ValueError, "yo!"); + return NULL; + } + + auto it = polls.find(fd); + if (it == polls.end()) { + PyErr_SetString(PyExc_KeyError, "yo!"); + return NULL; + } + + PyObject *selectorKey = (PyObject *) it->second->data; + + /* Return the selectorKey */ + Py_INCREF(selectorKey); + return selectorKey; +} + +// essentially like closing the event loop altogether +static PyObject *Selector_close(SelectorObject *self, PyObject *args) { + + printf("Close called\n"); + + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *Selector_tick(SelectorObject *self, PyObject **args, int nargs) { + + + self->tick = true; + + + Py_INCREF(Py_None); + return Py_None; +} + +static PyMethodDef Selector_methods[] = { + + {"register", (PyCFunction) Selector_register, METH_FASTCALL, "no doc"}, + {"unregister", (PyCFunction) Selector_unregister, METH_FASTCALL, "no doc"}, + {"modify", (PyCFunction) Selector_modify, METH_FASTCALL, "no doc"}, + {"select", (PyCFunction) Selector_select, METH_FASTCALL, "no doc"}, + {"get_key", (PyCFunction) Selector_get_key, METH_FASTCALL, "no doc"}, + {"get_map", (PyCFunction) Selector_get_map, METH_VARARGS, "no doc"}, + {"close", (PyCFunction) Selector_get_map, METH_FASTCALL, "no doc"}, + + {"tick", (PyCFunction) Selector_tick, METH_FASTCALL, "no doc"}, + + {NULL} +}; + +static PyObject *Selector_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { + SelectorObject *self = (SelectorObject *) type->tp_alloc(type, 0); + if (self != NULL) { + self->loop = uv_default_loop(); + + self->timer = new uv_timer_t; + uv_timer_init(self->loop, self->timer); + self->timer->data = self; + + self->sig = new uv_signal_t; + uv_signal_init(self->loop, self->sig); + self->sig->data = self; + + uv_signal_start(self->sig, [](uv_signal_t *sig, int signum) { + //printf("We got sigint\n"); + + SelectorObject *self = (SelectorObject *) sig->data; + + self->thrw = true; + }, SIGINT); + + PyStructSequence_Field fields[] = { + {"fileobj", "doc"}, + {"fd", "doc"}, + {"events", "doc"}, + {"data", "doc"}, + {"__module__", "doc"}, + NULL + }; + + PyStructSequence_Desc desc = { + "uWS.SelectorKey", + "doc", + fields, + 4 + }; + + /* We start with an empty list */ + + /* Let's make a list with a hard limit */ + self->list = PyList_New(1024); + self->list_length = 0; + + /* Create selectorKey type object */ + self->namedTuple = PyStructSequence_NewType(&desc); + + /* Fix for Python 3.7- that probably should do it */ + self->namedTuple->tp_flags |= Py_TPFLAGS_HEAPTYPE; + PyType_Modified(self->namedTuple); + + self->tick = false; + + /* The uv_loop_t points to self in userdata */ + self->loop->data = self; + } + Py_INCREF(self); + return (PyObject *) self; +} + +static PyTypeObject SelectorType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "uWS.Selector", + .tp_basicsize = sizeof(SelectorObject), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = "no doc", + .tp_methods = Selector_methods, + .tp_new = Selector_new, +}; \ No newline at end of file