diff -r fbba3ecc92dc -r 528fdd816160 Doc/library/select.rst --- a/Doc/library/select.rst Wed Oct 26 01:42:30 2011 +0200 +++ b/Doc/library/select.rst Mon Nov 07 15:46:02 2011 +0100 @@ -6,7 +6,8 @@ This module provides access to the :c:func:`select` and :c:func:`poll` functions -available in most operating systems, :c:func:`epoll` available on Linux 2.5+ and +available in most operating systems, :c:func:`devpoll` available on +Solaris and derivatives, :c:func:`epoll` available on Linux 2.5+ and :c:func:`kqueue` available on most BSD. Note that on Windows, it only works for sockets; on other operating systems, it also works for other file types (in particular, on Unix, it works on pipes). @@ -24,6 +25,19 @@ Following :pep:`3151`, this class was made an alias of :exc:`OSError`. +.. function:: devpoll() + (Only supported on Solaris and derivatives.) Returns a ``/dev/poll`` + polling object; see section :ref:`devpoll-objects` below for the + methods supported by devpoll objects. + + :c:func:`devpoll` objects are linked to the number of file + descriptors allowed at the time of instantiation. If your program + reduces this value, :c:func:`devpoll` will fail. If your program + increases this value, c:func:`devpoll` will return a possible + incomplete list of active file descriptors. + + .. versionadded:: 3.3 + .. function:: epoll(sizehint=-1) (Only supported on Linux 2.5.44 and newer.) Returns an edge polling object, @@ -107,6 +121,74 @@ .. versionadded:: 3.2 +.. _devpoll-objects: + +``/dev/poll`` Polling Objects +---------------------------------------------- + + http://developers.sun.com/solaris/articles/using_devpoll.html + http://developers.sun.com/solaris/articles/polling_efficient.html + +Solaris and derivatives have ``/dev/poll``. While :c:func:`select` is +O(highest file descriptor) and :c:func:`poll` is O(number of file +descriptors), ``/dev/poll`` is O(active file descriptors). + +``/dev/poll`` behaviour is very close to the standard :c:func:`poll` +object. + + +.. method:: devpoll.register(fd[, eventmask]) + + Register a file descriptor with the polling object. Future calls to the + :meth:`poll` method will then check whether the file descriptor has any pending + I/O events. *fd* can be either an integer, or an object with a :meth:`fileno` + method that returns an integer. File objects implement :meth:`fileno`, so they + can also be used as the argument. + + *eventmask* is an optional bitmask describing the type of events you want to + check for. The constants are the same that with :c:func:`poll` + object. The default value is a combination of the constants :const:`POLLIN`, + :const:`POLLPRI`, and :const:`POLLOUT`. + + .. warning:: + + Registering a file descriptor that's already registered is not an + error, but the result is undefined. The appropiate action is to + unregister or modify it first. This is an important difference + compared with :c:func:`poll`. + + +.. method:: devpoll.modify(fd[, eventmask]) + + This method does an :meth:`unregister` followed by a + :meth:`register`. It is (a bit) more efficient that doing the same + explicitly. + + +.. method:: devpoll.unregister(fd) + + Remove a file descriptor being tracked by a polling object. Just like the + :meth:`register` method, *fd* can be an integer or an object with a + :meth:`fileno` method that returns an integer. + + Attempting to remove a file descriptor that was never registered is + safely ignored. + + +.. method:: devpoll.poll([timeout]) + + Polls the set of registered file descriptors, and returns a possibly-empty list + containing ``(fd, event)`` 2-tuples for the descriptors that have events or + errors to report. *fd* is the file descriptor, and *event* is a bitmask with + bits set for the reported events for that descriptor --- :const:`POLLIN` for + waiting input, :const:`POLLOUT` to indicate that the descriptor can be written + to, and so forth. An empty list indicates that the call timed out and no file + descriptors had any events to report. If *timeout* is given, it specifies the + length of time in milliseconds which the system will wait for events before + returning. If *timeout* is omitted, negative, or :const:`None`, the call will + block until there is an event for this poll object. + + .. _epoll-objects: Edge and Level Trigger Polling (epoll) Objects diff -r fbba3ecc92dc -r 528fdd816160 Lib/test/test_devpoll.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/test/test_devpoll.py Mon Nov 07 15:46:02 2011 +0100 @@ -0,0 +1,159 @@ +# Test case for the select.devpoll() function + +# Initial tests are copied as is from "test_poll.py" + +import os, select, random, unittest +from test.support import TESTFN, run_unittest + +try: + select.devpoll +except AttributeError: + raise unittest.SkipTest("select.devpoll not defined -- skipping test_devpoll") + + +def find_ready_matching(ready, flag): + match = [] + for fd, mode in ready: + if mode & flag: + match.append(fd) + return match + +class DevPollTests(unittest.TestCase): + + def test_devpoll1(self): + # Basic functional test of poll object + # Create a bunch of pipe and test that poll works with them. + + p = select.devpoll() + + NUM_PIPES = 12 + MSG = b" This is a test." + MSG_LEN = len(MSG) + readers = [] + writers = [] + r2w = {} + w2r = {} + + for i in range(NUM_PIPES): + rd, wr = os.pipe() + p.register(rd) + p.modify(rd, select.POLLIN) + p.register(wr, select.POLLOUT) + readers.append(rd) + writers.append(wr) + r2w[rd] = wr + w2r[wr] = rd + + bufs = [] + + while writers: + ready = p.poll() + ready_writers = find_ready_matching(ready, select.POLLOUT) + if not ready_writers: + raise RuntimeError("no pipes ready for writing") + wr = random.choice(ready_writers) + os.write(wr, MSG) + + ready = p.poll() + ready_readers = find_ready_matching(ready, select.POLLIN) + if not ready_readers: + raise RuntimeError("no pipes ready for reading") + rd = random.choice(ready_readers) + buf = os.read(rd, MSG_LEN) + self.assertEqual(len(buf), MSG_LEN) + bufs.append(buf) + os.close(r2w[rd]) ; os.close( rd ) + p.unregister( r2w[rd] ) + p.unregister( rd ) + writers.remove(r2w[rd]) + + self.assertEqual(bufs, [MSG] * NUM_PIPES) + + def poll_unit_tests(self): + # returns NVAL for invalid file descriptor + FD = 42 + try: + os.close(FD) + except OSError: + pass + p = select.devpoll() + p.register(FD) + r = p.poll() + self.assertEqual(r[0], (FD, select.POLLNVAL)) + + f = open(TESTFN, 'w') + fd = f.fileno() + p = select.devpoll() + p.register(f) + r = p.poll() + self.assertEqual(r[0][0], fd) + f.close() + r = p.poll() + self.assertEqual(r[0], (fd, select.POLLNVAL)) + os.unlink(TESTFN) + + # type error for invalid arguments + p = select.devpoll() + self.assertRaises(TypeError, p.register, p) + self.assertRaises(TypeError, p.unregister, p) + + # can't unregister non-existent object + p = select.devpoll() + self.assertRaises(KeyError, p.unregister, 3) + + # Test error cases + pollster = select.devpoll() + class Nope: + pass + + class Almost: + def fileno(self): + return 'fileno' + + self.assertRaises(TypeError, pollster.register, Nope(), 0) + self.assertRaises(TypeError, pollster.register, Almost(), 0) + + # Another test case for poll(). This is copied from the test case for + # select(), modified to use poll() instead. + + def test_devpoll2(self): + cmd = 'for i in 0 1 2 3 4 5 6 7 8 9; do echo testing...; sleep 1; done' + p = os.popen(cmd, 'r') + pollster = select.devpoll() + pollster.register( p, select.POLLIN ) + for tout in (0, 1000, 2000, 4000, 8000, 16000) + (-1,)*10: + fdlist = pollster.poll(tout) + if (fdlist == []): + continue + fd, flags = fdlist[0] + if flags & select.POLLHUP: + line = p.readline() + if line != "": + self.fail('error: pipe seems to be closed, but still returns data') + continue + + elif flags & select.POLLIN: + line = p.readline() + if not line: + break + continue + else: + self.fail('Unexpected return value from select.devpoll: %s' % fdlist) + p.close() + + def test_devpoll3(self): + # test int overflow + pollster = select.devpoll() + pollster.register(1) + + self.assertRaises(OverflowError, pollster.poll, 1 << 64) + + x = 2 + 3 + if x != 5: + self.fail('Overflow must have occurred') + +def test_main(): + run_unittest(DevPollTests) + +if __name__ == '__main__': + test_main() diff -r fbba3ecc92dc -r 528fdd816160 Modules/selectmodule.c --- a/Modules/selectmodule.c Wed Oct 26 01:42:30 2011 +0200 +++ b/Modules/selectmodule.c Mon Nov 07 15:46:02 2011 +0100 @@ -7,6 +7,14 @@ #include "Python.h" #include +#ifdef HAVE_SYS_DEVPOLL_H +#include +#include +#include +#include +#include +#endif + #ifdef __APPLE__ /* Perform runtime testing for a broken poll on OSX to make it easier * to use the same binary on multiple releases of the OS. @@ -648,6 +656,322 @@ poll_methods, /*tp_methods*/ }; +#ifdef HAVE_SYS_DEVPOLL_H +typedef struct { + PyObject_HEAD + int fd_devpoll; + int max_n_fds; + int n_fds; + struct pollfd *fds; +} devpollObject; + +static PyTypeObject devpoll_Type; + +static int devpoll_flush(devpollObject *self) +{ + int size, n; + + if (!self->n_fds) return 0; + + size = sizeof(struct pollfd)*self->n_fds; + self->n_fds = 0; + + Py_BEGIN_ALLOW_THREADS + n = write(self->fd_devpoll, self->fds, size); + Py_END_ALLOW_THREADS + + if (n == -1 ) { + PyErr_SetFromErrno(PyExc_IOError); + return !0; + } + if (n < size) { + PyErr_SetString(PyExc_IOError, "failed to write all pollfds"); + return !0; + } + return 0; +} + +static PyObject * +internal_devpoll_register(devpollObject *self, PyObject *args, int remove) +{ + PyObject *o; + int fd, events = POLLIN | POLLPRI | POLLOUT; + + if (!PyArg_ParseTuple(args, "O|i:register", &o, &events)) { + return NULL; + } + + fd = PyObject_AsFileDescriptor(o); + if (fd == -1) return NULL; + + if (remove) { + self->fds[self->n_fds].fd = fd; + self->fds[self->n_fds].events = POLLREMOVE; + + if (++self->n_fds == self->max_n_fds) { + if (devpoll_flush(self)) + return NULL; + } + } + + self->fds[self->n_fds].fd = fd; + self->fds[self->n_fds].events = events; + + if (++self->n_fds == self->max_n_fds) { + if (devpoll_flush(self)) + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyDoc_STRVAR(devpoll_register_doc, +"register(fd [, eventmask] ) -> None\n\n\ +Register a file descriptor with the polling object.\n\ +fd -- either an integer, or an object with a fileno() method returning an\n\ + int.\n\ +events -- an optional bitmask describing the type of events to check for"); + +static PyObject * +devpoll_register(devpollObject *self, PyObject *args) +{ + return internal_devpoll_register(self, args, 0); +} + +PyDoc_STRVAR(devpoll_modify_doc, +"modify(fd[, eventmask]) -> None\n\n\ +Modify a possible already registered file descriptor.\n\ +fd -- either an integer, or an object with a fileno() method returning an\n\ + int.\n\ +events -- an optional bitmask describing the type of events to check for"); + +static PyObject * +devpoll_modify(devpollObject *self, PyObject *args) +{ + return internal_devpoll_register(self, args, 1); +} + + +PyDoc_STRVAR(devpoll_unregister_doc, +"unregister(fd) -> None\n\n\ +Remove a file descriptor being tracked by the polling object."); + +static PyObject * +devpoll_unregister(devpollObject *self, PyObject *o) +{ + int fd; + + fd = PyObject_AsFileDescriptor( o ); + if (fd == -1) + return NULL; + + self->fds[self->n_fds].fd = fd; + self->fds[self->n_fds].events = POLLREMOVE; + + if (++self->n_fds == self->max_n_fds) { + if (devpoll_flush(self)) + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyDoc_STRVAR(devpoll_poll_doc, +"poll( [timeout] ) -> list of (fd, event) 2-tuples\n\n\ +Polls the set of registered file descriptors, returning a list containing \n\ +any descriptors that have events or errors to report."); + +static PyObject * +devpoll_poll(devpollObject *self, PyObject *args) +{ + struct dvpoll dvp; + PyObject *result_list = NULL, *tout = NULL; + int timeout = 0, poll_result, i; + PyObject *value = NULL, *num = NULL; + + if (devpoll_flush(self)) + return NULL; + + if (!PyArg_UnpackTuple(args, "poll", 0, 1, &tout)) { + return NULL; + } + + /* Check values for timeout */ + if (tout == NULL || tout == Py_None) + timeout = -1; + else if (!PyNumber_Check(tout)) { + PyErr_SetString(PyExc_TypeError, + "timeout must be an integer or None"); + return NULL; + } + else { + tout = PyNumber_Long(tout); + if (!tout) + return NULL; + timeout = PyLong_AsLong(tout); + Py_DECREF(tout); + if (timeout == -1 && PyErr_Occurred()) + return NULL; + } + + dvp.dp_fds = self->fds; + dvp.dp_nfds = self->max_n_fds; + dvp.dp_timeout = timeout; + + /* call devpoll() */ + Py_BEGIN_ALLOW_THREADS + poll_result = ioctl(self->fd_devpoll, DP_POLL, &dvp); + Py_END_ALLOW_THREADS + + if (poll_result < 0) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + /* build the result list */ + + result_list = PyList_New(poll_result); + if (!result_list) + return NULL; + else { + for (i = 0; i < poll_result; i++) { + value = PyTuple_New(2); + if (value == NULL) + goto error; + num = PyLong_FromLong(self->fds[i].fd); + if (num == NULL) { + Py_DECREF(value); + goto error; + } + PyTuple_SET_ITEM(value, 0, num); + num = PyLong_FromLong(self->fds[i].revents); + if (num == NULL) { + Py_DECREF(value); + goto error; + } + PyTuple_SET_ITEM(value, 1, num); + if ((PyList_SetItem(result_list, i, value)) == -1) { + Py_DECREF(value); + goto error; + } + } + } + return result_list; + + error: + Py_DECREF(result_list); + return NULL; +} + +static PyMethodDef devpoll_methods[] = { + {"register", (PyCFunction)devpoll_register, + METH_VARARGS, devpoll_register_doc}, + {"modify", (PyCFunction)devpoll_modify, + METH_VARARGS, devpoll_modify_doc}, + {"unregister", (PyCFunction)devpoll_unregister, + METH_O, devpoll_unregister_doc}, + {"poll", (PyCFunction)devpoll_poll, + METH_VARARGS, devpoll_poll_doc}, + {NULL, NULL} /* sentinel */ +}; + +static devpollObject * +newDevPollObject(void) +{ + devpollObject *self; + int fd_devpoll, limit_result; + struct pollfd *fds; + struct rlimit limit; + + Py_BEGIN_ALLOW_THREADS + /* + ** If we try to process more that getrlimit() + ** fds, the kernel will give an error, so + ** we set the limit here. It is a dynamic + ** value, because we can change rlimit() anytime. + */ + limit_result = getrlimit(RLIMIT_NOFILE, &limit); + if (limit_result != -1) + fd_devpoll = open("/dev/poll", O_RDWR); + Py_END_ALLOW_THREADS + + if (limit_result == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + if (fd_devpoll == -1) { + PyErr_SetFromErrnoWithFilename(PyExc_IOError, "/dev/poll"); + return NULL; + } + + fds = PyMem_NEW(struct pollfd, limit.rlim_cur); + if (fds == NULL) { + PyErr_NoMemory(); + return NULL; + } + + self = PyObject_New(devpollObject, &devpoll_Type); + if (self == NULL) + return NULL; + self->fd_devpoll = fd_devpoll; + self->max_n_fds = limit.rlim_cur; + self->n_fds = 0; + self->fds = fds; + + return self; +} + +static void +devpoll_dealloc(devpollObject *self) +{ + Py_BEGIN_ALLOW_THREADS + close(self->fd_devpoll); + Py_END_ALLOW_THREADS + + PyMem_DEL(self->fds); + + PyObject_Del(self); +} + +static PyTypeObject devpoll_Type = { + /* The ob_type field must be initialized in the module init function + * to be portable to Windows without using C++. */ + PyVarObject_HEAD_INIT(NULL, 0) + "select.devpoll", /*tp_name*/ + sizeof(devpollObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)devpoll_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_reserved*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + devpoll_methods, /*tp_methods*/ +}; +#endif /* HAVE_SYS_DEVPOLL_H */ + + + PyDoc_STRVAR(poll_doc, "Returns a polling object, which supports registering and\n\ unregistering file descriptors, and then polling them for I/O events."); @@ -658,6 +982,19 @@ return (PyObject *)newPollObject(); } +#ifdef HAVE_SYS_DEVPOLL_H +PyDoc_STRVAR(devpoll_doc, +"Returns a polling object, which supports registering and\n\ +unregistering file descriptors, and then polling them for I/O events."); + +static PyObject * +select_devpoll(PyObject *self, PyObject *unused) +{ + return (PyObject *)newDevPollObject(); +} +#endif + + #ifdef __APPLE__ /* * On some systems poll() sets errno on invalid file descriptors. We test @@ -1715,6 +2052,11 @@ }; #endif /* HAVE_KQUEUE */ + + + + + /* ************************************************************************ */ PyDoc_STRVAR(select_doc, @@ -1746,6 +2088,9 @@ #ifdef HAVE_POLL {"poll", select_poll, METH_NOARGS, poll_doc}, #endif /* HAVE_POLL */ +#ifdef HAVE_SYS_DEVPOLL_H + {"devpoll", select_devpoll, METH_NOARGS, devpoll_doc}, +#endif {0, 0}, /* sentinel */ }; @@ -1768,6 +2113,9 @@ NULL }; + + + PyMODINIT_FUNC PyInit_select(void) { @@ -1824,6 +2172,11 @@ } #endif /* HAVE_POLL */ +#ifdef HAVE_SYS_DEVPOLL_H + if (PyType_Ready(&devpoll_Type) < 0) + return NULL; +#endif + #ifdef HAVE_EPOLL Py_TYPE(&pyEpoll_Type) = &PyType_Type; if (PyType_Ready(&pyEpoll_Type) < 0) diff -r fbba3ecc92dc -r 528fdd816160 configure --- a/configure Wed Oct 26 01:42:30 2011 +0200 +++ b/configure Mon Nov 07 15:46:02 2011 +0100 @@ -6139,12 +6139,13 @@ for ac_header in asm/types.h conio.h curses.h direct.h dlfcn.h errno.h \ fcntl.h grp.h \ -ieeefp.h io.h langinfo.h libintl.h ncurses.h poll.h process.h pthread.h \ +ieeefp.h io.h langinfo.h libintl.h ncurses.h process.h pthread.h \ sched.h shadow.h signal.h stdint.h stropts.h termios.h \ unistd.h utime.h \ -sys/audioio.h sys/xattr.h sys/bsdtty.h sys/epoll.h sys/event.h sys/file.h sys/loadavg.h \ +poll.h sys/devpoll.h sys/epoll.h sys/poll.h \ +sys/audioio.h sys/xattr.h sys/bsdtty.h sys/event.h sys/file.h sys/loadavg.h \ sys/lock.h sys/mkdev.h sys/modem.h \ -sys/param.h sys/poll.h sys/select.h sys/sendfile.h sys/socket.h sys/statvfs.h \ +sys/param.h sys/select.h sys/sendfile.h sys/socket.h sys/statvfs.h \ sys/stat.h sys/termio.h sys/time.h \ sys/times.h sys/types.h sys/uio.h sys/un.h sys/utsname.h sys/wait.h pty.h \ libutil.h sys/resource.h netpacket/packet.h sysexits.h bluetooth.h \ diff -r fbba3ecc92dc -r 528fdd816160 configure.in --- a/configure.in Wed Oct 26 01:42:30 2011 +0200 +++ b/configure.in Mon Nov 07 15:46:02 2011 +0100 @@ -1329,12 +1329,13 @@ AC_HEADER_STDC AC_CHECK_HEADERS(asm/types.h conio.h curses.h direct.h dlfcn.h errno.h \ fcntl.h grp.h \ -ieeefp.h io.h langinfo.h libintl.h ncurses.h poll.h process.h pthread.h \ +ieeefp.h io.h langinfo.h libintl.h ncurses.h process.h pthread.h \ sched.h shadow.h signal.h stdint.h stropts.h termios.h \ unistd.h utime.h \ -sys/audioio.h sys/xattr.h sys/bsdtty.h sys/epoll.h sys/event.h sys/file.h sys/loadavg.h \ +poll.h sys/devpoll.h sys/epoll.h sys/poll.h \ +sys/audioio.h sys/xattr.h sys/bsdtty.h sys/event.h sys/file.h sys/loadavg.h \ sys/lock.h sys/mkdev.h sys/modem.h \ -sys/param.h sys/poll.h sys/select.h sys/sendfile.h sys/socket.h sys/statvfs.h \ +sys/param.h sys/select.h sys/sendfile.h sys/socket.h sys/statvfs.h \ sys/stat.h sys/termio.h sys/time.h \ sys/times.h sys/types.h sys/uio.h sys/un.h sys/utsname.h sys/wait.h pty.h \ libutil.h sys/resource.h netpacket/packet.h sysexits.h bluetooth.h \ diff -r fbba3ecc92dc -r 528fdd816160 pyconfig.h.in --- a/pyconfig.h.in Wed Oct 26 01:42:30 2011 +0200 +++ b/pyconfig.h.in Mon Nov 07 15:46:02 2011 +0100 @@ -587,6 +587,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_POLL_H +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_DEVPOLL_H + /* Define to 1 if you have the `posix_fadvise' function. */ #undef HAVE_POSIX_FADVISE