kopia lustrzana https://github.com/cirospaciari/socketify.py
add example of selector with libuv for study and rewrite to CFFI
rodzic
5dc0ffc6eb
commit
dee8a9910d
|
@ -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
|
||||
|
|
|
@ -0,0 +1,401 @@
|
|||
/* Implements the Python event loop */
|
||||
|
||||
#include <uv.h>
|
||||
#include <map>
|
||||
|
||||
#undef NDEBUG
|
||||
#include <cassert>
|
||||
|
||||
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<int, uv_poll_t *> 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,
|
||||
};
|
Ładowanie…
Reference in New Issue