Index: setup.py =================================================================== --- setup.py (révision 54147) +++ setup.py (copie de travail) @@ -1134,6 +1134,10 @@ extra_link_args=['-framework', 'QuickTime', '-framework', 'Carbon']) ) + if find_file("sys/epoll.h", inc_dirs, []) is not None: + exts.append( Extension("epoll", + ["epoll.c"], + define_macros=define_macros)) self.extensions.extend(exts) Index: Lib/test/test_epoll.py =================================================================== --- Lib/test/test_epoll.py (révision 0) +++ Lib/test/test_epoll.py (révision 0) @@ -0,0 +1,168 @@ +""" +Tests for epoll wrapper. +""" + +import socket, errno, time + +import unittest + +try: + import epoll +except ImportError: + epoll = None + + +class EPoll(unittest.TestCase): + """ + Tests for the low-level epoll bindings. + """ + def setUp(self): + """ + Create a listening server port and a list with which to keep track + of created sockets. + """ + self.serverSocket = socket.socket() + self.serverSocket.bind(('127.0.0.1', 0)) + self.serverSocket.listen(1) + self.connections = [self.serverSocket] + + + def tearDown(self): + """ + Close any sockets which were opened by the test. + """ + for skt in self.connections: + skt.close() + + + def _connected_pair(self): + """ + Return the two sockets which make up a new TCP connection. + """ + client = socket.socket() + client.setblocking(False) + try: + client.connect(('127.0.0.1', self.serverSocket.getsockname()[1])) + except socket.error, e: + self.assertEquals(e.args[0], errno.EINPROGRESS) + else: + raise AssertionError("Connect should have raised EINPROGRESS") + server, addr = self.serverSocket.accept() + + self.connections.extend((client, server)) + return client, server + + + def test_create(self): + """ + Test the creation of an epoll object. + """ + try: + p = epoll.create(16) + except OSError, e: + raise AssertionError(str(e)) + else: + epoll.close(p) + + + def test_badcreate(self): + """ + Test that attempting to create an epoll object with some random + objects raises a TypeError. + """ + self.assertRaises(TypeError, epoll.create, 1, 2, 3) + self.assertRaises(TypeError, epoll.create, 'foo') + self.assertRaises(TypeError, epoll.create, None) + self.assertRaises(TypeError, epoll.create, ()) + self.assertRaises(TypeError, epoll.create, ['foo']) + self.assertRaises(TypeError, epoll.create, {}) + self.assertRaises(TypeError, epoll.create) + + + def test_add(self): + """ + Test adding a socket to an epoll object. + """ + server, client = self._connected_pair() + + p = epoll.create(2) + try: + epoll.control(p, epoll.CTL_ADD, server.fileno(), epoll.IN | epoll.OUT) + epoll.control(p, epoll.CTL_ADD, client.fileno(), epoll.IN | epoll.OUT) + finally: + epoll.close(p) + + + def test_control_and_wait(self): + """ + Test waiting on an epoll object which has had some sockets added to + it. + """ + client, server = self._connected_pair() + + p = epoll.create(16) + epoll.control(p, epoll.CTL_ADD, client.fileno(), epoll.IN | epoll.OUT | + epoll.ET) + epoll.control(p, epoll.CTL_ADD, server.fileno(), epoll.IN | epoll.OUT | + epoll.ET) + + now = time.time() + events = epoll.wait(p, 4, 1000) + then = time.time() + self.failIf(then - now > 0.01) + + events.sort() + expected = [(client.fileno(), epoll.OUT), + (server.fileno(), epoll.OUT)] + expected.sort() + + self.assertEquals(events, expected) + + now = time.time() + events = epoll.wait(p, 4, 200) + then = time.time() + self.failUnless(then - now > 0.1) + self.failIf(events) + + client.send("Hello!") + server.send("world!!!") + + now = time.time() + events = epoll.wait(p, 4, 1000) + then = time.time() + self.failIf(then - now > 0.01) + + events.sort() + expected = [(client.fileno(), epoll.IN | epoll.OUT), + (server.fileno(), epoll.IN | epoll.OUT)] + expected.sort() + + self.assertEquals(events, expected) + + def test_errors(self): + """ + Test some error conditions of epoll methods. + """ + self.assertRaises(IOError, epoll.create, -1) + self.assertRaises(IOError, epoll.control, -1, epoll.CTL_ADD, -1, epoll.IN) + self.assertRaises(IOError, epoll.wait, -1, 4, 1000) + self.assertRaises(IOError, epoll.close, -1) + +if epoll is None: + EPoll.skip = "epoll module unavailable" +else: + try: + e = epoll.create(16) + except IOError, exc: + if exc.errno == errno.ENOSYS: + del exc + EPoll.skip = "epoll support missing from platform" + else: + raise + else: + epoll.close(e) + del e + +if __name__ == '__main__': + unittest.main() + Index: Modules/epoll.c =================================================================== --- Modules/epoll.c (révision 0) +++ Modules/epoll.c (révision 0) @@ -0,0 +1,174 @@ +/* + * Epoll wrapper. + * + */ +#include "Python.h" +#include + +static char epoll__doc__[] = +"Interface to epoll I/O event notification facility.\n"; + +/* + * Wrapper around epoll_create + * + */ +static PyObject * _epoll_create(PyObject *self, PyObject *args) { + int size; + int kdpfd; + + if (!PyArg_ParseTuple(args, "i", &size)) { + return NULL; + } + + // Real call to epoll_create, I should get the epoll fd + kdpfd = epoll_create(size); + + if (kdpfd == -1) { + return PyErr_SetFromErrno(PyExc_IOError); + } else { + return Py_BuildValue("i", kdpfd); + } +} + +/* + * Wrapper around epoll_ctl + * + */ +static PyObject * _epoll_control(PyObject *self, PyObject *args) { + int kdpfd; + int epop; + int fd; + int epevents; + int ret; + struct epoll_event ev; + + if (!PyArg_ParseTuple(args, "iiik", &kdpfd, &epop, &fd, &epevents)) { + return NULL; + } + + ev.events = epevents; + ev.data.fd = fd; + + // Reall call to epoll_ctl + ret = epoll_ctl(kdpfd, epop, fd, &ev); + + if (ret == -1) { + return PyErr_SetFromErrno(PyExc_IOError); + } else { + return Py_BuildValue("i", ret); + } +} + +/* + * Wrapper around epoll_wait. Warning: it uses threads. + * + */ +static PyObject * _epoll_wait(PyObject *self, PyObject *args) { + struct epoll_event *events; + int kdpfd; + unsigned int maxevents; + int timeout; + int nfds; + int i; + int nbytes; + PyObject *eplist; + + if (!PyArg_ParseTuple(args, "iIi", &kdpfd, &maxevents, &timeout)) { + return NULL; + } + + nbytes = sizeof(struct epoll_event) * maxevents; + events = (struct epoll_event*) malloc(nbytes); + if (!events) { + return NULL; + } + memset(events, 0, nbytes); + + // Here the job is done: allow threading, and then call to epoll_wait + Py_BEGIN_ALLOW_THREADS; + nfds = epoll_wait(kdpfd, events, maxevents, timeout); + Py_END_ALLOW_THREADS; + + if (nfds == -1) { + free(events); + return PyErr_SetFromErrno(PyExc_IOError); + } + + eplist = PyList_New(nfds); + if (!eplist) { + free(events); + return NULL; + } + + for (i = 0; i < nfds; i++) { + int evevents; + int evdatafd; + PyObject *eptuple; + + evevents = events[i].events; + evdatafd = events[i].data.fd; + eptuple = Py_BuildValue("ik", evdatafd, evevents); + + PyList_SET_ITEM(eplist, i, eptuple); + } + free(events); + return eplist; +} + +/* + * Close the epoll fd returned by create. + * + * For now it's a simple close(), but the function is here to do more job + * if necessary. + * + */ +static PyObject * _epoll_close(PyObject *self, PyObject *args) { + int kdpfd; + int ret; + + if (!PyArg_ParseTuple(args, "i", &kdpfd)) { + return NULL; + } + + // Call close on the given fd. + ret = close(kdpfd); + + if (ret == -1) { + return PyErr_SetFromErrno(PyExc_IOError); + } else { + return Py_BuildValue("i", ret); + } +} + +static PyMethodDef epoll__methods__[] = { + {"wait", _epoll_wait, METH_VARARGS, + "Wait for events on the epoll fd for a maximum time of timeout.\n\n It " + "returns a list of (fd, events)."}, + {"close", _epoll_close, METH_VARARGS, + "Close the epoll fd."}, + {"control", _epoll_control, METH_VARARGS, + "Control an epoll fd with given operation.\n\n The following operations " + "can be used: CTL_ADD, CTL_DEL, CTL_MOD."}, + {"create", _epoll_create, METH_VARARGS, + "Create a new epoll file descriptor.\n\nThe fd returned should be used " + "with the control and wait functions."}, + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + +PyMODINIT_FUNC initepoll(void) { + PyObject *epoll_module; + + epoll_module = Py_InitModule3("epoll", epoll__methods__, epoll__doc__); + + PyModule_AddIntConstant(epoll_module, "IN", EPOLLIN); + PyModule_AddIntConstant(epoll_module, "OUT", EPOLLOUT); + PyModule_AddIntConstant(epoll_module, "PRI", EPOLLPRI); + PyModule_AddIntConstant(epoll_module, "ERR", EPOLLERR); + PyModule_AddIntConstant(epoll_module, "HUP", EPOLLHUP); + PyModule_AddIntConstant(epoll_module, "ET", EPOLLET); + PyModule_AddIntConstant(epoll_module, "ONESHOT", EPOLLONESHOT); + PyModule_AddIntConstant(epoll_module, "CTL_ADD", EPOLL_CTL_ADD); + PyModule_AddIntConstant(epoll_module, "CTL_DEL", EPOLL_CTL_DEL); + PyModule_AddIntConstant(epoll_module, "CTL_MOD", EPOLL_CTL_MOD); +} +