From 0e9ab5f2f0f907b57c70557e21633ce8c341d1d1 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 21 Mar 2008 23:49:44 +0000 Subject: Applied patch #1657 epoll and kqueue wrappers for the select module The patch adds wrappers for the Linux epoll syscalls and the BSD kqueue syscalls. Thanks to Thomas Herve and the Twisted people for their support and help. TODO: Finish documentation documentation --- Doc/library/select.rst | 304 +++++++++++- Doc/license.rst | 55 +++ Lib/test/regrtest.py | 30 ++ Lib/test/test_epoll.py | 189 ++++++++ Lib/test/test_kqueue.py | 166 +++++++ Lib/test/test_poll.py | 3 +- Misc/NEWS | 2 + Modules/selectmodule.c | 1175 ++++++++++++++++++++++++++++++++++++++++++++++- configure | 110 ++++- configure.in | 20 +- pyconfig.h.in | 12 + 11 files changed, 2044 insertions(+), 22 deletions(-) create mode 100644 Lib/test/test_epoll.py create mode 100644 Lib/test/test_kqueue.py diff --git a/Doc/library/select.rst b/Doc/library/select.rst index 4a97179..1368c78 100644 --- a/Doc/library/select.rst +++ b/Doc/library/select.rst @@ -7,10 +7,12 @@ This module provides access to the :cfunc:`select` and :cfunc:`poll` functions -available in most operating systems. 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). It cannot be used on regular files to -determine whether a file has grown since it was last read. +available in most operating systems, :cfunc:`epoll` available on Linux 2.5+ and +:cfunc:`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). +It cannot be used on regular files to determine whether a file has grown since +it was last read. The module defines the following: @@ -22,6 +24,16 @@ The module defines the following: string, as would be printed by the C function :cfunc:`perror`. +.. type:: epoll([sizehint=-1]) + + (Only supported on Linux 2.5.44 and newer.) Returns an edge polling + object, which can be used as Edge or Level Triggered interface for I/O + events; see section :ref:`epoll-objects` below for the methods supported + by epolling objects. + + .. versionadded:: 2.6 + + .. function:: poll() (Not supported by all operating systems.) Returns a polling object, which @@ -30,6 +42,24 @@ The module defines the following: by polling objects. +.. type:: kqueue() + + (Only supported on BSD.) Returns a kernel queue object + object; see section :ref:`kqueue-objects` below for the methods supported + by kqueue objects. + + .. versionadded:: 2.6 + + +.. type:: kqueue(ident, filter=KQ_FILTER_READ, flags=KQ_ADD, fflags=0, data=0, udata=0) + + (Only supported on BSD.) Returns a kernel event object + object; see section :ref:`kevent-objects` below for the methods supported + by kqueue objects. + + .. versionadded:: 2.6 + + .. function:: select(iwtd, owtd, ewtd[, timeout]) This is a straightforward interface to the Unix :cfunc:`select` system call. @@ -67,6 +97,81 @@ The module defines the following: not handle file descriptors that don't originate from WinSock. +.. _epoll-objects: + +Edge and Level Trigger Polling (epoll) Objects +---------------------------------------------- + + http://linux.die.net/man/4/epoll + + *eventmask* + + +-----------------------+-----------------------------------------------+ + | Constant | Meaning | + +=======================+===============================================+ + | :const:`EPOLLIN` | Available for read | + +-----------------------+-----------------------------------------------+ + | :const:`EPOLLOUT` | Available for write | + +-----------------------+-----------------------------------------------+ + | :const:`EPOLLPRI` | Urgent data for read | + +-----------------------+-----------------------------------------------+ + | :const:`EPOLLERR` | Error condition happend on the assoc. fd | + +-----------------------+-----------------------------------------------+ + | :const:`EPOLLHUP` | Hang up happend on the assoc. fd | + +-----------------------+-----------------------------------------------+ + | :const:`EPOLLET` | Set Edge Trigger behavior, the default is | + | | Level Trigger behavior | + +-----------------------+-----------------------------------------------+ + | :const:`EPOLLONESHOT` | Set one-shot behavior. After one event is | + | | pulled out, the fd is internally disabled | + +-----------------------+-----------------------------------------------+ + | :const:`EPOLLRDNORM` | ??? | + +-----------------------+-----------------------------------------------+ + | :const:`EPOLLRDBAND` | ??? | + +-----------------------+-----------------------------------------------+ + | :const:`EPOLLWRNORM` | ??? | + +-----------------------+-----------------------------------------------+ + | :const:`EPOLLWRBAND` | ??? | + +-----------------------+-----------------------------------------------+ + | :const:`EPOLLMSG` | ??? | + +-----------------------+-----------------------------------------------+ + + +.. method:: epoll.close() + + Close the control file descriptor of the epoll object. + + +.. method:: epoll.fileno() + + Return the file descriptor number of the control fd. + + +.. method:: epoll.fromfd(fd) + + Create an epoll object from a given file descriptor. + + +.. method:: epoll.register(fd[, eventmask]) + + Register a fd descriptor with the epoll object. + + +.. method:: epoll.modify(fd, eventmask) + + Modify a register file descriptor. + + +.. method:: epoll.unregister(fd) + + Remove a registered file descriptor from the epoll object. + + +.. method:: epoll.poll([timeout=-1[, maxevents=-1]]) + + Wait for events. timeout in seconds (float) + + .. _poll-objects: Polling Objects @@ -114,6 +219,16 @@ linearly scanned again. :cfunc:`select` is O(highest file descriptor), while the same effect as registering the descriptor exactly once. +.. method:: poll.modify(fd, eventmask) + + Modifies an already registered fd. This has the same effect as + :meth:`register(fd, eventmask)`. Attempting to modify a file descriptor + that was never registered causes an :exc:`IOError` exception with errno + :const:`ENOENT` to be raised. + + .. versionadded:: 2.6 + + .. method:: poll.unregister(fd) Remove a file descriptor being tracked by a polling object. Just like the @@ -137,3 +252,184 @@ linearly scanned again. :cfunc:`select` is O(highest file descriptor), while returning. If *timeout* is omitted, negative, or :const:`None`, the call will block until there is an event for this poll object. + +.. _kqueue-objects: + +Kqueue Objects +-------------- + +.. method:: kqueue.close() + + Close the control file descriptor of the kqueue object. + + +.. method:: kqueue.fileno() + + Return the file descriptor number of the control fd. + + +.. method:: epoll.fromfd(fd) + + Create a kqueue object from a given file descriptor. + + +.. method:: control(changelist, max_events=0[, timeout=None]) -> eventlist + + Low level interface to kevent + + - changelist must be an iterable of kevent object or None + - max_events must be 0 or a positive integer + - timeout in seconds (floats possible) + + +.. _kevent-objects: + +Kevent Objects +-------------- + + http://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2 + + .. attribute:: ident + + Value used to identify the event. The interpretation depends on the filter + but it's usually the file descriptor. In the constructor ident can either + be an int or an object with a fileno() function. kevent stores the integer + internally. + + .. attribute:: filter + + Name of the kernel filter + + +---------------------------+---------------------------------------------+ + | Constant | Meaning | + +===========================+=============================================+ + | :const:`KQ_FILTER_READ` | Takes a descriptor and returns whenever | + | | there is data available to read | + +---------------------------+---------------------------------------------+ + | :const:`KQ_FILTER_WRITE` | Takes a descriptor and returns whenever | + | | there is data available to read | + +---------------------------+---------------------------------------------+ + | :const:`KQ_FILTER_AIO` | AIO requests | + +---------------------------+---------------------------------------------+ + | :const:`KQ_FILTER_VNODE` | Returns when one or more of the requested | + | | events watched in *fflag* occurs | + +---------------------------+---------------------------------------------+ + | :const:`KQ_FILTER_PROC` | Watch for events on a process id | + +---------------------------+---------------------------------------------+ + | :const:`KQ_FILTER_NETDEV` | Watch for events on a network device | + | | [not available on Mac OS X] | + +---------------------------+---------------------------------------------+ + | :const:`KQ_FILTER_SIGNAL` | Returns whenever the watched signal is | + | | delivered to the process | + +---------------------------+---------------------------------------------+ + | :const:`KQ_FILTER_TIMER` | Establishes an arbitrary timer | + +---------------------------+---------------------------------------------+ + + .. attribute:: flags + + Filter action + + +---------------------------+---------------------------------------------+ + | Constant | Meaning | + +===========================+=============================================+ + | :const:`KQ_EV_ADD` | Adds or modifies an event | + +---------------------------+---------------------------------------------+ + | :const:`KQ_EV_DELETE` | Removes an event from the queue | + +---------------------------+---------------------------------------------+ + | :const:`KQ_EV_ENABLE` | Permitscontrol() to returns the event | + +---------------------------+---------------------------------------------+ + | :const:`KQ_EV_DISABLE` | Disablesevent | + +---------------------------+---------------------------------------------+ + | :const:`KQ_EV_ONESHOT` | Removes event after first occurence | + +---------------------------+---------------------------------------------+ + | :const:`KQ_EV_CLEAR` | Reset the state after an event is retrieved | + +---------------------------+---------------------------------------------+ + | :const:`KQ_EV_SYSFLAGS` | internal event | + +---------------------------+---------------------------------------------+ + | :const:`KQ_EV_FLAG1` | internal event | + +---------------------------+---------------------------------------------+ + | :const:`KQ_EV_EOF` | Filter specific EOF condition | + +---------------------------+---------------------------------------------+ + | :const:`KQ_EV_ERROR` | See return values | + +---------------------------+---------------------------------------------+ + + + .. attribute:: fflags + + Filter specific flags + + + *:const:`KQ_FILTER_READ` and :const:`KQ_FILTER_WRITE` filter flags + + +----------------------------+--------------------------------------------+ + | Constant | Meaning | + +============================+============================================+ + | :const:`KQ_NOTE_LOWAT` | low water mark of a socket buffer | + +----------------------------+--------------------------------------------+ + + + *:const:`KQ_FILTER_VNODE` filter flags* + + +----------------------------+--------------------------------------------+ + | Constant | Meaning | + +============================+============================================+ + | :const:`KQ_NOTE_DELETE` | *unlink()* was called | + +----------------------------+--------------------------------------------+ + | :const:`KQ_NOTE_WRITE` | a write occured | + +----------------------------+--------------------------------------------+ + | :const:`KQ_NOTE_EXTEND` | the file was extended | + +----------------------------+--------------------------------------------+ + | :const:`KQ_NOTE_ATTRIB` | an attribute was changed | + +----------------------------+--------------------------------------------+ + | :const:`KQ_NOTE_LINK` | the link count has changed | + +----------------------------+--------------------------------------------+ + | :const:`KQ_NOTE_RENAME` | the file was renamed | + +----------------------------+--------------------------------------------+ + | :const:`KQ_NOTE_REVOKE` | access to the file was revoked | + +----------------------------+--------------------------------------------+ + + + *:const:`KQ_FILTER_PROC` filter flags* + + +----------------------------+--------------------------------------------+ + | Constant | Meaning | + +============================+============================================+ + | :const:`KQ_NOTE_EXIT` | the process has exited | + +----------------------------+--------------------------------------------+ + | :const:`KQ_NOTE_FORK` | the process has called *fork()* | + +----------------------------+--------------------------------------------+ + | :const:`KQ_NOTE_EXEC` | the process has executed a new process | + +----------------------------+--------------------------------------------+ + | :const:`KQ_NOTE_PCTRLMASK` | internal filter flag | + +----------------------------+--------------------------------------------+ + | :const:`KQ_NOTE_PDATAMASK` | internal filter flag | + +----------------------------+--------------------------------------------+ + | :const:`KQ_NOTE_TRACK` | follow a process across *fork()* | + +----------------------------+--------------------------------------------+ + | :const:`KQ_NOTE_CHILD` | returned on the child process for | + | | *NOTE_TRACK* | + +----------------------------+--------------------------------------------+ + | :const:`KQ_NOTE_TRACKERR` | unable to attach to a child | + +----------------------------+--------------------------------------------+ + + *:const:`KQ_FILTER_NETDEV` filter flags* [not available on Mac OS X] + + +----------------------------+--------------------------------------------+ + | Constant | Meaning | + +============================+============================================+ + | :const:`KQ_NOTE_LINKUP` | link is up | + +----------------------------+--------------------------------------------+ + | :const:`KQ_NOTE_LINKDOWN` | link is down | + +----------------------------+--------------------------------------------+ + | :const:`KQ_NOTE_LINKINV` | link state is invalid | + +----------------------------+--------------------------------------------+ + + + .. attribute:: data + + Filter specific data + + + .. attribute:: udata + + User defined value diff --git a/Doc/license.rst b/Doc/license.rst index 89f379d..66a30e7 100644 --- a/Doc/license.rst +++ b/Doc/license.rst @@ -645,3 +645,58 @@ The :mod:`xmlrpclib` module contains the following notice:: ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +test_epoll +---------- + +The :mod:`test_epoll` contains the following notice:: + + Copyright (c) 2001-2006 Twisted Matrix Laboratories. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Select kqueue +------------- + +The :mod:`select` and contains the following notice for the kqueue interface:: + + Copyright (c) 2000 Doug White, 2006 James Knight, 2007 Christian Heimes + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. diff --git a/Lib/test/regrtest.py b/Lib/test/regrtest.py index 5706813..aed565b 100755 --- a/Lib/test/regrtest.py +++ b/Lib/test/regrtest.py @@ -825,10 +825,12 @@ _expectations = { test_dl test_fcntl test_fork1 + test_epoll test_gdbm test_grp test_ioctl test_largefile + test_kqueue test_mhlib test_openpty test_ossaudiodev @@ -850,6 +852,7 @@ _expectations = { test_curses test_dl test_largefile + test_kqueue test_ossaudiodev """, 'mac': @@ -866,10 +869,12 @@ _expectations = { test_dl test_fcntl test_fork1 + test_epoll test_grp test_ioctl test_largefile test_locale + test_kqueue test_mmap test_openpty test_ossaudiodev @@ -890,7 +895,9 @@ _expectations = { test_bsddb test_bsddb185 test_dl + test_epoll test_largefile + test_kqueue test_minidom test_openpty test_pyexpat @@ -902,7 +909,9 @@ _expectations = { test_bsddb test_bsddb185 test_dl + test_epoll test_largefile + test_kqueue test_minidom test_openpty test_pyexpat @@ -916,9 +925,11 @@ _expectations = { test_bsddb185 test_dl test_fork1 + test_epoll test_gettext test_largefile test_locale + test_kqueue test_minidom test_openpty test_pyexpat @@ -943,10 +954,12 @@ _expectations = { test_dl test_fcntl test_fork1 + test_epoll test_gdbm test_grp test_largefile test_locale + test_kqueue test_mmap test_openpty test_poll @@ -967,9 +980,11 @@ _expectations = { test_bsddb test_bsddb3 test_curses + test_epoll test_gdbm test_largefile test_locale + test_kqueue test_minidom test_ossaudiodev test_poll @@ -980,6 +995,8 @@ _expectations = { test_bsddb185 test_curses test_dbm + test_epoll + test_kqueue test_gdbm test_gzip test_openpty @@ -992,10 +1009,12 @@ _expectations = { test_bsddb185 test_curses test_dl + test_epoll test_gdbm test_gzip test_largefile test_locale + test_kqueue test_minidom test_openpty test_pyexpat @@ -1009,8 +1028,10 @@ _expectations = { test_curses test_dl test_gdbm + test_epoll test_largefile test_locale + test_kqueue test_mhlib test_mmap test_poll @@ -1023,7 +1044,9 @@ _expectations = { test_bsddb3 test_curses test_dbm + test_epoll test_ioctl + test_kqueue test_largefile test_locale test_ossaudiodev @@ -1037,6 +1060,8 @@ _expectations = { test_commands test_curses test_dl + test_epoll + test_kqueue test_largefile test_mhlib test_mmap @@ -1050,6 +1075,7 @@ _expectations = { """ test_bsddb test_bsddb3 + test_epoll test_gdbm test_locale test_ossaudiodev @@ -1068,8 +1094,10 @@ _expectations = { test_bsddb3 test_bz2 test_dl + test_epoll test_gdbm test_gzip + test_kqueue test_ossaudiodev test_tcl test_zipimport @@ -1081,6 +1109,7 @@ _expectations = { test_bsddb3 test_ctypes test_dl + test_epoll test_gdbm test_locale test_normalization @@ -1096,6 +1125,7 @@ _expectations = { test_ctypes test_curses test_dl + test_epoll test_gdbm test_locale test_ossaudiodev diff --git a/Lib/test/test_epoll.py b/Lib/test/test_epoll.py new file mode 100644 index 0000000..f56d74f --- /dev/null +++ b/Lib/test/test_epoll.py @@ -0,0 +1,189 @@ +# Copyright (c) 2001-2006 Twisted Matrix Laboratories. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +""" +Tests for epoll wrapper. +""" +import os +import socket +import errno +import time +import select +import tempfile +import unittest + +from test import test_support +if not hasattr(select, "epoll"): + raise test_support.TestSkipped("test works only on Linux 2.6") + +class TestEPoll(unittest.TestCase): + + def setUp(self): + self.serverSocket = socket.socket() + self.serverSocket.bind(('127.0.0.1', 0)) + self.serverSocket.listen(1) + self.connections = [self.serverSocket] + + + def tearDown(self): + for skt in self.connections: + skt.close() + + def _connected_pair(self): + 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): + try: + ep = select.epoll(16) + except OSError, e: + raise AssertionError(str(e)) + self.assert_(ep.fileno() > 0, ep.fileno()) + self.assert_(not ep.closed) + ep.close() + self.assert_(ep.closed) + self.assertRaises(ValueError, ep.fileno) + + def test_badcreate(self): + self.assertRaises(TypeError, select.epoll, 1, 2, 3) + self.assertRaises(TypeError, select.epoll, 'foo') + self.assertRaises(TypeError, select.epoll, None) + self.assertRaises(TypeError, select.epoll, ()) + self.assertRaises(TypeError, select.epoll, ['foo']) + self.assertRaises(TypeError, select.epoll, {}) + + def test_add(self): + server, client = self._connected_pair() + + ep = select.epoll(2) + try: + ep.register(server.fileno(), select.EPOLLIN | select.EPOLLOUT) + ep.register(client.fileno(), select.EPOLLIN | select.EPOLLOUT) + finally: + ep.close() + + def test_fromfd(self): + server, client = self._connected_pair() + + ep = select.epoll(2) + ep2 = select.epoll.fromfd(ep.fileno()) + + ep2.register(server.fileno(), select.EPOLLIN | select.EPOLLOUT) + ep2.register(client.fileno(), select.EPOLLIN | select.EPOLLOUT) + + events = ep.poll(1, 4) + events2 = ep2.poll(0.9, 4) + self.assertEqual(len(events), 2) + self.assertEqual(len(events2), 2) + + ep.close() + try: + ep2.poll(1, 4) + except IOError, e: + self.failUnlessEqual(e.args[0], errno.EBADF, e) + else: + self.fail("epoll on closed fd didn't raise EBADF") + + def test_control_and_wait(self): + client, server = self._connected_pair() + + ep = select.epoll(16) + ep.register(server.fileno(), + select.EPOLLIN | select.EPOLLOUT | select.EPOLLET) + ep.register(client.fileno(), + select.EPOLLIN | select.EPOLLOUT | select.EPOLLET) + + now = time.time() + events = ep.poll(1, 4) + then = time.time() + self.failIf(then - now > 0.1, then - now) + + events.sort() + expected = [(client.fileno(), select.EPOLLOUT), + (server.fileno(), select.EPOLLOUT)] + expected.sort() + + self.assertEquals(events, expected) + self.failIf(then - now > 0.01, then - now) + + now = time.time() + events = ep.poll(timeout=2.1, maxevents=4) + then = time.time() + self.failIf(events) + + client.send("Hello!") + server.send("world!!!") + + now = time.time() + events = ep.poll(1, 4) + then = time.time() + self.failIf(then - now > 0.01) + + events.sort() + expected = [(client.fileno(), select.EPOLLIN | select.EPOLLOUT), + (server.fileno(), select.EPOLLIN | select.EPOLLOUT)] + expected.sort() + + self.assertEquals(events, expected) + + ep.unregister(client.fileno()) + ep.modify(server.fileno(), select.EPOLLOUT) + now = time.time() + events = ep.poll(1, 4) + then = time.time() + self.failIf(then - now > 0.01) + + expected = [(server.fileno(), select.EPOLLOUT)] + self.assertEquals(events, expected) + + def test_errors(self): + self.assertRaises(ValueError, select.epoll, -2) + self.assertRaises(ValueError, select.epoll().register, -1, + select.EPOLLIN) + + def test_unregister_closed(self): + server, client = self._connected_pair() + fd = server.fileno() + ep = select.epoll(16) + ep.register(server) + + now = time.time() + events = ep.poll(1, 4) + then = time.time() + self.failIf(then - now > 0.01) + + server.close() + ep.unregister(fd) + +def test_main(): + test_support.run_unittest(TestEPoll) + +if __name__ == "__main__": + test_main() diff --git a/Lib/test/test_kqueue.py b/Lib/test/test_kqueue.py new file mode 100644 index 0000000..6f78ca0 --- /dev/null +++ b/Lib/test/test_kqueue.py @@ -0,0 +1,166 @@ +""" +Tests for kqueue wrapper. +""" +import socket +import errno +import time +import select +import sys +import unittest + +from test import test_support +if not hasattr(select, "kqueue"): + raise test_support.TestSkipped("test works only on BSD") + +class TestKQueue(unittest.TestCase): + def test_create_queue(self): + kq = select.kqueue() + self.assert_(kq.fileno() > 0, kq.fileno()) + self.assert_(not kq.closed) + kq.close() + self.assert_(kq.closed) + self.assertRaises(ValueError, kq.fileno) + + def test_create_event(self): + fd = sys.stderr.fileno() + ev = select.kevent(fd) + other = select.kevent(1000) + self.assertEqual(ev.ident, fd) + self.assertEqual(ev.filter, select.KQ_FILTER_READ) + self.assertEqual(ev.flags, select.KQ_EV_ADD) + self.assertEqual(ev.fflags, 0) + self.assertEqual(ev.data, 0) + self.assertEqual(ev.udata, 0) + self.assertEqual(ev, ev) + self.assertNotEqual(ev, other) + self.assertEqual(cmp(ev, other), -1) + self.assert_(ev < other) + self.assert_(other >= ev) + self.assertRaises(TypeError, cmp, ev, None) + self.assertRaises(TypeError, cmp, ev, 1) + self.assertRaises(TypeError, cmp, ev, "ev") + + ev = select.kevent(fd, select.KQ_FILTER_WRITE) + self.assertEqual(ev.ident, fd) + self.assertEqual(ev.filter, select.KQ_FILTER_WRITE) + self.assertEqual(ev.flags, select.KQ_EV_ADD) + self.assertEqual(ev.fflags, 0) + self.assertEqual(ev.data, 0) + self.assertEqual(ev.udata, 0) + self.assertEqual(ev, ev) + self.assertNotEqual(ev, other) + + ev = select.kevent(fd, select.KQ_FILTER_WRITE, select.KQ_EV_ONESHOT) + self.assertEqual(ev.ident, fd) + self.assertEqual(ev.filter, select.KQ_FILTER_WRITE) + self.assertEqual(ev.flags, select.KQ_EV_ONESHOT) + self.assertEqual(ev.fflags, 0) + self.assertEqual(ev.data, 0) + self.assertEqual(ev.udata, 0) + self.assertEqual(ev, ev) + self.assertNotEqual(ev, other) + + ev = select.kevent(1, 2, 3, 4, 5, 6) + self.assertEqual(ev.ident, 1) + self.assertEqual(ev.filter, 2) + self.assertEqual(ev.flags, 3) + self.assertEqual(ev.fflags, 4) + self.assertEqual(ev.data, 5) + self.assertEqual(ev.udata, 6) + self.assertEqual(ev, ev) + self.assertNotEqual(ev, other) + + def test_queue_event(self): + serverSocket = socket.socket() + serverSocket.bind(('127.0.0.1', 0)) + serverSocket.listen(1) + client = socket.socket() + client.setblocking(False) + try: + client.connect(('127.0.0.1', serverSocket.getsockname()[1])) + except socket.error, e: + self.assertEquals(e.args[0], errno.EINPROGRESS) + else: + #raise AssertionError("Connect should have raised EINPROGRESS") + pass # FreeBSD doesn't raise an exception here + server, addr = serverSocket.accept() + + if sys.platform.startswith("darwin"): + flags = select.KQ_EV_ADD | select.KQ_EV_ENABLE + else: + flags = 0 + + kq = select.kqueue() + kq2 = select.kqueue.fromfd(kq.fileno()) + + ev = select.kevent(server.fileno(), + select.KQ_FILTER_WRITE, + select.KQ_EV_ADD | select.KQ_EV_ENABLE) + kq.control([ev], 0) + ev = select.kevent(server.fileno(), + select.KQ_FILTER_READ, + select.KQ_EV_ADD | select.KQ_EV_ENABLE) + kq.control([ev], 0) + ev = select.kevent(client.fileno(), + select.KQ_FILTER_WRITE, + select.KQ_EV_ADD | select.KQ_EV_ENABLE) + kq2.control([ev], 0) + ev = select.kevent(client.fileno(), + select.KQ_FILTER_READ, + select.KQ_EV_ADD | select.KQ_EV_ENABLE) + kq2.control([ev], 0) + + events = kq.control(None, 4, 1) + events = [(e.ident, e.filter, e.flags) for e in events] + events.sort() + self.assertEquals(events, [ + (client.fileno(), select.KQ_FILTER_WRITE, flags), + (server.fileno(), select.KQ_FILTER_WRITE, flags)]) + + client.send("Hello!") + server.send("world!!!") + + events = kq.control(None, 4, 1) + # We may need to call it several times + for i in range(5): + if len(events) == 4: + break + events = kq.control(None, 4, 1) + events = [(e.ident, e.filter, e.flags) for e in events] + events.sort() + + self.assertEquals(events, [ + (client.fileno(), select.KQ_FILTER_WRITE, flags), + (client.fileno(), select.KQ_FILTER_READ, flags), + (server.fileno(), select.KQ_FILTER_WRITE, flags), + (server.fileno(), select.KQ_FILTER_READ, flags)]) + + # Remove completely client, and server read part + ev = select.kevent(client.fileno(), + select.KQ_FILTER_WRITE, + select.KQ_EV_DELETE) + kq.control([ev], 0) + ev = select.kevent(client.fileno(), + select.KQ_FILTER_READ, + select.KQ_EV_DELETE) + kq.control([ev], 0) + ev = select.kevent(server.fileno(), + select.KQ_FILTER_READ, + select.KQ_EV_DELETE) + kq.control([ev], 0, 0) + + events = kq.control([], 4, 0.99) + events = [(e.ident, e.filter, e.flags) for e in events] + events.sort() + self.assertEquals(events, [ + (server.fileno(), select.KQ_FILTER_WRITE, flags)]) + + client.close() + server.close() + serverSocket.close() + +def test_main(): + test_support.run_unittest(TestKQueue) + +if __name__ == "__main__": + test_main() diff --git a/Lib/test/test_poll.py b/Lib/test/test_poll.py index 5dbfc2c..fb6cd30 100644 --- a/Lib/test/test_poll.py +++ b/Lib/test/test_poll.py @@ -34,7 +34,8 @@ class PollTests(unittest.TestCase): for i in range(NUM_PIPES): rd, wr = os.pipe() - p.register(rd, select.POLLIN) + p.register(rd) + p.modify(rd, select.POLLIN) p.register(wr, select.POLLOUT) readers.append(rd) writers.append(wr) diff --git a/Misc/NEWS b/Misc/NEWS index 615effc..62803ff 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -1363,6 +1363,8 @@ Library Extension Modules ----------------- +- Patch #1657: added select.epoll and select.kqueue + - Patch #1506171: added operator.methodcaller(). - Patch #1826: operator.attrgetter() now supports dotted attribute paths. diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c index 8583eb9..9a71f47 100644 --- a/Modules/selectmodule.c +++ b/Modules/selectmodule.c @@ -7,6 +7,7 @@ */ #include "Python.h" +#include #ifdef __APPLE__ /* Perform runtime testing for a broken poll on OSX to make it easier @@ -56,7 +57,6 @@ extern void bzero(void *, int); # endif #endif - static PyObject *SelectError; /* list of Python objects and their file descriptor */ @@ -241,7 +241,7 @@ select_select(PyObject *self, PyObject *args) seconds = (long)timeout; timeout = timeout - (double)seconds; tv.tv_sec = seconds; - tv.tv_usec = (long)(timeout*1000000.0); + tv.tv_usec = (long)(timeout * 1E6); tvp = &tv; } @@ -405,11 +405,59 @@ poll_register(pollObject *self, PyObject *args) return NULL; self->ufd_uptodate = 0; - + Py_INCREF(Py_None); return Py_None; } +PyDoc_STRVAR(poll_modify_doc, +"modify(fd, eventmask) -> None\n\n\ +Modify an already register 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 * +poll_modify(pollObject *self, PyObject *args) +{ + PyObject *o, *key, *value; + int fd, events; + int err; + + if (!PyArg_ParseTuple(args, "Oi:modify", &o, &events)) { + return NULL; + } + + fd = PyObject_AsFileDescriptor(o); + if (fd == -1) return NULL; + + /* Modify registered fd */ + key = PyInt_FromLong(fd); + if (key == NULL) + return NULL; + if (PyDict_GetItem(self->dict, key) == NULL) { + errno = ENOENT; + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + value = PyInt_FromLong(events); + if (value == NULL) { + Py_DECREF(key); + return NULL; + } + err = PyDict_SetItem(self->dict, key, value); + Py_DECREF(key); + Py_DECREF(value); + if (err < 0) + return NULL; + + self->ufd_uptodate = 0; + + Py_INCREF(Py_None); + return Py_None; +} + + PyDoc_STRVAR(poll_unregister_doc, "unregister(fd) -> None\n\n\ Remove a file descriptor being tracked by the polling object."); @@ -483,9 +531,9 @@ poll_poll(pollObject *self, PyObject *args) return NULL; /* call poll() */ - Py_BEGIN_ALLOW_THREADS; + Py_BEGIN_ALLOW_THREADS poll_result = poll(self->ufds, self->ufd_len, timeout); - Py_END_ALLOW_THREADS; + Py_END_ALLOW_THREADS if (poll_result < 0) { PyErr_SetFromErrno(SelectError); @@ -543,7 +591,9 @@ poll_poll(pollObject *self, PyObject *args) static PyMethodDef poll_methods[] = { {"register", (PyCFunction)poll_register, METH_VARARGS, poll_register_doc}, - {"unregister", (PyCFunction)poll_unregister, + {"modify", (PyCFunction)poll_modify, + METH_VARARGS, poll_modify_doc}, + {"unregister", (PyCFunction)poll_unregister, METH_O, poll_unregister_doc}, {"poll", (PyCFunction)poll_poll, METH_VARARGS, poll_poll_doc}, @@ -646,6 +696,1007 @@ static int select_have_broken_poll(void) #endif /* HAVE_POLL */ +#ifdef HAVE_EPOLL +/* ************************************************************************** + * epoll interface for Linux 2.6 + * + * Written by Christian Heimes + * Inspired by Twisted's _epoll.pyx and select.poll() + */ + +#ifdef HAVE_SYS_EPOLL_H +#include +#endif + +typedef struct { + PyObject_HEAD + SOCKET epfd; /* epoll control file descriptor */ +} pyEpoll_Object; + +static PyTypeObject pyEpoll_Type; +#define pyepoll_CHECK(op) (PyObject_TypeCheck((op), &pyEpoll_Type)) + +static PyObject * +pyepoll_err_closed(void) +{ + PyErr_SetString(PyExc_ValueError, "I/O operation on closed epoll fd"); + return NULL; +} + +static int +pyepoll_internal_close(pyEpoll_Object *self) +{ + int save_errno = 0; + if (self->epfd >= 0) { + int epfd = self->epfd; + self->epfd = -1; + Py_BEGIN_ALLOW_THREADS + if (close(epfd) < 0) + save_errno = errno; + Py_END_ALLOW_THREADS + } + return save_errno; +} + +static PyObject * +newPyEpoll_Object(PyTypeObject *type, int sizehint, SOCKET fd) +{ + pyEpoll_Object *self; + + if (sizehint == -1) { + sizehint = FD_SETSIZE-1; + } + else if (sizehint < 1) { + PyErr_Format(PyExc_ValueError, + "sizehint must be greater zero, got %d", + sizehint); + return NULL; + } + + assert(type != NULL && type->tp_alloc != NULL); + self = (pyEpoll_Object *) type->tp_alloc(type, 0); + if (self == NULL) + return NULL; + + if (fd == -1) { + Py_BEGIN_ALLOW_THREADS + self->epfd = epoll_create(sizehint); + Py_END_ALLOW_THREADS + } + else { + self->epfd = fd; + } + if (self->epfd < 0) { + Py_DECREF(self); + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + return (PyObject *)self; +} + + +static PyObject * +pyepoll_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + int sizehint = -1; + static char *kwlist[] = {"sizehint", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|i:epoll", kwlist, + &sizehint)) + return NULL; + + return newPyEpoll_Object(type, sizehint, -1); +} + + +static void +pyepoll_dealloc(pyEpoll_Object *self) +{ + (void)pyepoll_internal_close(self); + Py_TYPE(self)->tp_free(self); +} + +static PyObject* +pyepoll_close(pyEpoll_Object *self) +{ + errno = pyepoll_internal_close(self); + if (errno < 0) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + Py_RETURN_NONE; +} + +PyDoc_STRVAR(pyepoll_close_doc, +"close() -> None\n\ +\n\ +Close the epoll control file descriptor. Further operations on the epoll\n\ +object will raise an exception."); + +static PyObject* +pyepoll_get_closed(pyEpoll_Object *self) +{ + if (self->epfd < 0) + Py_RETURN_TRUE; + else + Py_RETURN_FALSE; +} + +static PyObject* +pyepoll_fileno(pyEpoll_Object *self) +{ + if (self->epfd < 0) + return pyepoll_err_closed(); + return PyInt_FromLong(self->epfd); +} + +PyDoc_STRVAR(pyepoll_fileno_doc, +"fileno() -> int\n\ +\n\ +Return the epoll control file descriptor."); + +static PyObject* +pyepoll_fromfd(PyObject *cls, PyObject *args) +{ + SOCKET fd; + + if (!PyArg_ParseTuple(args, "i:fromfd", &fd)) + return NULL; + + return newPyEpoll_Object((PyTypeObject*)cls, -1, fd); +} + +PyDoc_STRVAR(pyepoll_fromfd_doc, +"fromfd(fd) -> epoll\n\ +\n\ +Create an epoll object from a given control fd."); + +static PyObject * +pyepoll_internal_ctl(int epfd, int op, PyObject *pfd, unsigned int events) +{ + struct epoll_event ev; + int result; + int fd; + + if (epfd < 0) + return pyepoll_err_closed(); + + fd = PyObject_AsFileDescriptor(pfd); + if (fd == -1) { + return NULL; + } + + switch(op) { + case EPOLL_CTL_ADD: + case EPOLL_CTL_MOD: + ev.events = events; + ev.data.fd = fd; + Py_BEGIN_ALLOW_THREADS + result = epoll_ctl(epfd, op, fd, &ev); + Py_END_ALLOW_THREADS + break; + case EPOLL_CTL_DEL: + /* In kernel versions before 2.6.9, the EPOLL_CTL_DEL + * operation required a non-NULL pointer in event, even + * though this argument is ignored. */ + Py_BEGIN_ALLOW_THREADS + result = epoll_ctl(epfd, op, fd, &ev); + if (errno == EBADF) { + /* fd already closed */ + result = 0; + errno = 0; + } + Py_END_ALLOW_THREADS + break; + default: + result = -1; + errno = EINVAL; + } + + if (result < 0) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * +pyepoll_register(pyEpoll_Object *self, PyObject *args, PyObject *kwds) +{ + PyObject *pfd; + unsigned int events = EPOLLIN | EPOLLOUT | EPOLLPRI; + static char *kwlist[] = {"fd", "eventmask", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|I:register", kwlist, + &pfd, &events)) { + return NULL; + } + + return pyepoll_internal_ctl(self->epfd, EPOLL_CTL_ADD, pfd, events); +} + +PyDoc_STRVAR(pyepoll_register_doc, +"register(fd[, eventmask]) -> bool\n\ +\n\ +Registers a new fd or modifies an already registered fd. register returns\n\ +True if a new fd was registered or False if the event mask for fd was modified.\n\ +fd is the target file descriptor of the operation\n\ +events is a bit set composed of the various EPOLL constants, the default\n\ +is EPOLL_IN | EPOLL_OUT | EPOLL_PRI.\n\ +\n\ +The epoll interface supports all file descriptors that support poll."); + +static PyObject * +pyepoll_modify(pyEpoll_Object *self, PyObject *args, PyObject *kwds) +{ + PyObject *pfd; + unsigned int events; + static char *kwlist[] = {"fd", "eventmask", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OI:modify", kwlist, + &pfd, &events)) { + return NULL; + } + + return pyepoll_internal_ctl(self->epfd, EPOLL_CTL_MOD, pfd, events); +} + +PyDoc_STRVAR(pyepoll_modify_doc, +"modify(fd, eventmask) -> None\n\ +\n\ +fd is the target file descriptor of the operation\n\ +events is a bit set composed of the various EPOLL constants"); + +static PyObject * +pyepoll_unregister(pyEpoll_Object *self, PyObject *args, PyObject *kwds) +{ + PyObject *pfd; + static char *kwlist[] = {"fd", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:unregister", kwlist, + &pfd)) { + return NULL; + } + + return pyepoll_internal_ctl(self->epfd, EPOLL_CTL_DEL, pfd, 0); +} + +PyDoc_STRVAR(pyepoll_unregister_doc, +"unregister(fd) -> None\n\ +\n\ +fd is the target file descriptor of the operation."); + +static PyObject * +pyepoll_poll(pyEpoll_Object *self, PyObject *args, PyObject *kwds) +{ + double dtimeout = -1.; + int timeout; + int maxevents = -1; + int nfds, i; + PyObject *elist = NULL, *etuple = NULL; + struct epoll_event *evs = NULL; + static char *kwlist[] = {"timeout", "maxevents", NULL}; + + if (self->epfd < 0) + return pyepoll_err_closed(); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|di:poll", kwlist, + &dtimeout, &maxevents)) { + return NULL; + } + + if (dtimeout < 0) { + timeout = -1; + } + else if (dtimeout * 1000.0 > INT_MAX) { + PyErr_SetString(PyExc_OverflowError, + "timeout is too large"); + } + else { + timeout = (int)(dtimeout * 1000.0); + } + + if (maxevents == -1) { + maxevents = FD_SETSIZE-1; + } + else if (maxevents < 1) { + PyErr_Format(PyExc_ValueError, + "maxevents must be greater than 0, got %d", + maxevents); + return NULL; + } + + evs = PyMem_New(struct epoll_event, maxevents); + if (evs == NULL) { + Py_DECREF(self); + PyErr_NoMemory(); + return NULL; + } + + Py_BEGIN_ALLOW_THREADS + nfds = epoll_wait(self->epfd, evs, maxevents, timeout); + Py_END_ALLOW_THREADS + if (nfds < 0) { + PyErr_SetFromErrno(PyExc_IOError); + goto error; + } + + elist = PyList_New(nfds); + if (elist == NULL) { + goto error; + } + + for (i = 0; i < nfds; i++) { + etuple = Py_BuildValue("iI", evs[i].data.fd, + evs[i].events); + if (etuple == NULL) { + goto error; + } + PyList_SET_ITEM(elist, i, etuple); + } + + if (0) { + error: + Py_CLEAR(elist); + Py_XDECREF(etuple); + } + PyMem_Free(evs); + return elist; +} + +PyDoc_STRVAR(pyepoll_poll_doc, +"poll([timeout=-1[, maxevents=-1]]) -> [(fd, events), (...)]\n\ +\n\ +Wait for events on the epoll file descriptor for a maximum time of timeout\n\ +in seconds (as float). -1 makes poll wait indefinitely.\n\ +Up to maxevents are returned to the caller."); + +static PyMethodDef pyepoll_methods[] = { + {"fromfd", (PyCFunction)pyepoll_fromfd, + METH_VARARGS | METH_CLASS, pyepoll_fromfd_doc}, + {"close", (PyCFunction)pyepoll_close, METH_NOARGS, + pyepoll_close_doc}, + {"fileno", (PyCFunction)pyepoll_fileno, METH_NOARGS, + pyepoll_fileno_doc}, + {"modify", (PyCFunction)pyepoll_modify, + METH_VARARGS | METH_KEYWORDS, pyepoll_modify_doc}, + {"register", (PyCFunction)pyepoll_register, + METH_VARARGS | METH_KEYWORDS, pyepoll_register_doc}, + {"unregister", (PyCFunction)pyepoll_unregister, + METH_VARARGS | METH_KEYWORDS, pyepoll_unregister_doc}, + {"poll", (PyCFunction)pyepoll_poll, + METH_VARARGS | METH_KEYWORDS, pyepoll_poll_doc}, + {NULL, NULL}, +}; + +static PyGetSetDef pyepoll_getsetlist[] = { + {"closed", (getter)pyepoll_get_closed, NULL, + "True if the epoll handler is closed"}, + {0}, +}; + +PyDoc_STRVAR(pyepoll_doc, +"select.epoll([sizehint=-1])\n\ +\n\ +Returns an epolling object\n\ +\n\ +sizehint must be a positive integer or -1 for the default size. The\n\ +sizehint is used to optimize internal data structures. It doesn't limit\n\ +the maximum number of monitored events."); + +static PyTypeObject pyEpoll_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "select.epoll", /* tp_name */ + sizeof(pyEpoll_Object), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)pyepoll_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + pyepoll_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + pyepoll_methods, /* tp_methods */ + 0, /* tp_members */ + pyepoll_getsetlist, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + pyepoll_new, /* tp_new */ + 0, /* tp_free */ +}; + +#endif /* HAVE_EPOLL */ + +#ifdef HAVE_KQUEUE +/* ************************************************************************** + * kqueue interface for BSD + * + * Copyright (c) 2000 Doug White, 2006 James Knight, 2007 Christian Heimes + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifdef HAVE_SYS_EVENT_H +#include +#endif + +PyDoc_STRVAR(kqueue_event_doc, +"kevent(ident, filter=KQ_FILTER_READ, flags=KQ_ADD, fflags=0, data=0, udata=0)\n\ +\n\ +This object is the equivalent of the struct kevent for the C API.\n\ +\n\ +See the kqueue manpage for more detailed information about the meaning\n\ +of the arguments.\n\ +\n\ +One minor note: while you might hope that udata could store a\n\ +reference to a python object, it cannot, because it is impossible to\n\ +keep a proper reference count of the object once it's passed into the\n\ +kernel. Therefore, I have restricted it to only storing an integer. I\n\ +recommend ignoring it and simply using the 'ident' field to key off\n\ +of. You could also set up a dictionary on the python side to store a\n\ +udata->object mapping."); + +typedef struct { + PyObject_HEAD + struct kevent e; +} kqueue_event_Object; + +static PyTypeObject kqueue_event_Type; + +#define kqueue_event_Check(op) (PyObject_TypeCheck((op), &kqueue_event_Type)) + +typedef struct { + PyObject_HEAD + SOCKET kqfd; /* kqueue control fd */ +} kqueue_queue_Object; + +static PyTypeObject kqueue_queue_Type; + +#define kqueue_queue_Check(op) (PyObject_TypeCheck((op), &kqueue_queue_Type)) + +/* Unfortunately, we can't store python objects in udata, because + * kevents in the kernel can be removed without warning, which would + * forever lose the refcount on the object stored with it. + */ + +#define KQ_OFF(x) offsetof(kqueue_event_Object, x) +static struct PyMemberDef kqueue_event_members[] = { + {"ident", T_UINT, KQ_OFF(e.ident)}, + {"filter", T_SHORT, KQ_OFF(e.filter)}, + {"flags", T_USHORT, KQ_OFF(e.flags)}, + {"fflags", T_UINT, KQ_OFF(e.fflags)}, + {"data", T_INT, KQ_OFF(e.data)}, + {"udata", T_INT, KQ_OFF(e.udata)}, + {NULL} /* Sentinel */ +}; +#undef KQ_OFF + +static PyObject * +kqueue_event_repr(kqueue_event_Object *s) +{ + char buf[1024]; + PyOS_snprintf( + buf, sizeof(buf), + "", + (unsigned long)(s->e.ident), s->e.filter, s->e.flags, + s->e.fflags, (long)(s->e.data), s->e.udata); + return PyString_FromString(buf); +} + +static int +kqueue_event_init(kqueue_event_Object *self, PyObject *args, PyObject *kwds) +{ + PyObject *pfd; + static char *kwlist[] = {"ident", "filter", "flags", "fflags", + "data", "udata", NULL}; + + EV_SET(&(self->e), 0, EVFILT_READ, EV_ADD, 0, 0, 0); /* defaults */ + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|hhiii:kevent", kwlist, + &pfd, &(self->e.filter), &(self->e.flags), + &(self->e.fflags), &(self->e.data), &(self->e.udata))) { + return -1; + } + + self->e.ident = PyObject_AsFileDescriptor(pfd); + if (self->e.ident == -1) { + return -1; + } + return 0; +} + +static PyObject * +kqueue_event_richcompare(kqueue_event_Object *s, kqueue_event_Object *o, + int op) +{ + int result = 0; + + if (!kqueue_event_Check(o)) { + if (op == Py_EQ || op == Py_NE) { + PyObject *res = op == Py_EQ ? Py_False : Py_True; + Py_INCREF(res); + return res; + } + PyErr_Format(PyExc_TypeError, + "can't compare %.200s to %.200s", + Py_TYPE(s)->tp_name, Py_TYPE(o)->tp_name); + return NULL; + } + if (((result = s->e.ident - o->e.ident) == 0) && + ((result = s->e.filter - o->e.filter) == 0) && + ((result = s->e.flags - o->e.flags) == 0) && + ((result = s->e.fflags - o->e.fflags) == 0) && + ((result = s->e.data - o->e.data) == 0) && + ((result = s->e.udata - o->e.udata) == 0) + ) { + result = 0; + } + + switch (op) { + case Py_EQ: + result = (result == 0); + break; + case Py_NE: + result = (result != 0); + break; + case Py_LE: + result = (result <= 0); + break; + case Py_GE: + result = (result >= 0); + break; + case Py_LT: + result = (result < 0); + break; + case Py_GT: + result = (result > 0); + break; + } + return PyBool_FromLong(result); +} + +static PyTypeObject kqueue_event_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "select.kevent", /* tp_name */ + sizeof(kqueue_event_Object), /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)kqueue_event_repr, /* 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 */ + kqueue_event_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + (richcmpfunc)kqueue_event_richcompare, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + kqueue_event_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)kqueue_event_init, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ + 0, /* tp_free */ +}; + +static PyObject * +kqueue_queue_err_closed(void) +{ + PyErr_SetString(PyExc_ValueError, "I/O operation on closed kqueue fd"); + return NULL; +} + +static int +kqueue_queue_internal_close(kqueue_queue_Object *self) +{ + int save_errno = 0; + if (self->kqfd >= 0) { + int kqfd = self->kqfd; + self->kqfd = -1; + Py_BEGIN_ALLOW_THREADS + if (close(kqfd) < 0) + save_errno = errno; + Py_END_ALLOW_THREADS + } + return save_errno; +} + +static PyObject * +newKqueue_Object(PyTypeObject *type, SOCKET fd) +{ + kqueue_queue_Object *self; + assert(type != NULL && type->tp_alloc != NULL); + self = (kqueue_queue_Object *) type->tp_alloc(type, 0); + if (self == NULL) { + return NULL; + } + + if (fd == -1) { + Py_BEGIN_ALLOW_THREADS + self->kqfd = kqueue(); + Py_END_ALLOW_THREADS + } + else { + self->kqfd = fd; + } + if (self->kqfd < 0) { + Py_DECREF(self); + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + return (PyObject *)self; +} + +static PyObject * +kqueue_queue_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + + if ((args != NULL && PyObject_Size(args)) || + (kwds != NULL && PyObject_Size(kwds))) { + PyErr_SetString(PyExc_ValueError, + "select.kqueue doesn't accept arguments"); + return NULL; + } + + return newKqueue_Object(type, -1); +} + +static void +kqueue_queue_dealloc(kqueue_queue_Object *self) +{ + kqueue_queue_internal_close(self); + Py_TYPE(self)->tp_free(self); +} + +static PyObject* +kqueue_queue_close(kqueue_queue_Object *self) +{ + errno = kqueue_queue_internal_close(self); + if (errno < 0) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + Py_RETURN_NONE; +} + +PyDoc_STRVAR(kqueue_queue_close_doc, +"close() -> None\n\ +\n\ +Close the kqueue control file descriptor. Further operations on the kqueue\n\ +object will raise an exception."); + +static PyObject* +kqueue_queue_get_closed(kqueue_queue_Object *self) +{ + if (self->kqfd < 0) + Py_RETURN_TRUE; + else + Py_RETURN_FALSE; +} + +static PyObject* +kqueue_queue_fileno(kqueue_queue_Object *self) +{ + if (self->kqfd < 0) + return kqueue_queue_err_closed(); + return PyInt_FromLong(self->kqfd); +} + +PyDoc_STRVAR(kqueue_queue_fileno_doc, +"fileno() -> int\n\ +\n\ +Return the kqueue control file descriptor."); + +static PyObject* +kqueue_queue_fromfd(PyObject *cls, PyObject *args) +{ + SOCKET fd; + + if (!PyArg_ParseTuple(args, "i:fromfd", &fd)) + return NULL; + + return newKqueue_Object((PyTypeObject*)cls, fd); +} + +PyDoc_STRVAR(kqueue_queue_fromfd_doc, +"fromfd(fd) -> kqueue\n\ +\n\ +Create a kqueue object from a given control fd."); + +static PyObject * +kqueue_queue_control(kqueue_queue_Object *self, PyObject *args) +{ + int nevents = 0; + int gotevents = 0; + int nchanges = 0; + int i = 0; + PyObject *otimeout = NULL; + PyObject *ch = NULL; + PyObject *it = NULL, *ei = NULL; + PyObject *result = NULL; + struct kevent *evl = NULL; + struct kevent *chl = NULL; + struct timespec timeoutspec; + struct timespec *ptimeoutspec; + + if (self->kqfd < 0) + return kqueue_queue_err_closed(); + + if (!PyArg_ParseTuple(args, "Oi|O:control", &ch, &nevents, &otimeout)) + return NULL; + + if (nevents < 0) { + PyErr_Format(PyExc_ValueError, + "Length of eventlist must be 0 or positive, got %d", + nchanges); + return NULL; + } + + if (ch != NULL && ch != Py_None) { + it = PyObject_GetIter(ch); + if (it == NULL) { + PyErr_SetString(PyExc_TypeError, + "changelist is not iterable"); + return NULL; + } + nchanges = PyObject_Size(ch); + if (nchanges < 0) { + return NULL; + } + } + + if (otimeout == Py_None || otimeout == NULL) { + ptimeoutspec = NULL; + } + else if (PyNumber_Check(otimeout)) { + double timeout; + long seconds; + + timeout = PyFloat_AsDouble(otimeout); + if (timeout == -1 && PyErr_Occurred()) + return NULL; + if (timeout > (double)LONG_MAX) { + PyErr_SetString(PyExc_OverflowError, + "timeout period too long"); + return NULL; + } + if (timeout < 0) { + PyErr_SetString(PyExc_ValueError, + "timeout must be positive or None"); + return NULL; + } + + seconds = (long)timeout; + timeout = timeout - (double)seconds; + timeoutspec.tv_sec = seconds; + timeoutspec.tv_nsec = (long)(timeout * 1E9); + ptimeoutspec = &timeoutspec; + } + else { + PyErr_Format(PyExc_TypeError, + "timeout argument must be an number " + "or None, got %.200s", + Py_TYPE(otimeout)->tp_name); + return NULL; + } + + if (nchanges) { + chl = PyMem_New(struct kevent, nchanges); + if (chl == NULL) { + PyErr_NoMemory(); + return NULL; + } + while ((ei = PyIter_Next(it)) != NULL) { + if (!kqueue_event_Check(ei)) { + Py_DECREF(ei); + PyErr_SetString(PyExc_TypeError, + "changelist must be an iterable of " + "select.kevent objects"); + goto error; + } else { + chl[i] = ((kqueue_event_Object *)ei)->e; + } + Py_DECREF(ei); + } + } + Py_CLEAR(it); + + /* event list */ + if (nevents) { + evl = PyMem_New(struct kevent, nevents); + if (evl == NULL) { + PyErr_NoMemory(); + return NULL; + } + } + + Py_BEGIN_ALLOW_THREADS + gotevents = kevent(self->kqfd, chl, nchanges, + evl, nevents, ptimeoutspec); + Py_END_ALLOW_THREADS + + if (gotevents == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + result = PyList_New(gotevents); + if (result == NULL) { + goto error; + } + + for (i=0; i < gotevents; i++) { + kqueue_event_Object *ch; + + ch = PyObject_New(kqueue_event_Object, &kqueue_event_Type); + if (ch == NULL) { + goto error; + } + ch->e = evl[i]; + PyList_SET_ITEM(result, i, (PyObject *)ch); + } + PyMem_Free(chl); + PyMem_Free(evl); + return result; + + error: + PyMem_Free(chl); + PyMem_Free(evl); + Py_XDECREF(result); + Py_XDECREF(it); + return NULL; +} + +PyDoc_STRVAR(kqueue_queue_control_doc, +"control(changelist, max_events=0[, timeout=None]) -> eventlist\n\ +\n\ +Calls the kernel kevent function.\n\ +- changelist must be a list of kevent objects describing the changes\n\ + to be made to the kernel's watch list or None.\n\ +- max_events lets you specify the maximum number of events that the\n\ + kernel will return.\n\ +- timeout is the maximum time to wait in seconds, or else None,\n\ + to wait forever. timeout accepts floats for smaller timeouts, too."); + + +static PyMethodDef kqueue_queue_methods[] = { + {"fromfd", (PyCFunction)kqueue_queue_fromfd, + METH_VARARGS | METH_CLASS, kqueue_queue_fromfd_doc}, + {"close", (PyCFunction)kqueue_queue_close, METH_NOARGS, + kqueue_queue_close_doc}, + {"fileno", (PyCFunction)kqueue_queue_fileno, METH_NOARGS, + kqueue_queue_fileno_doc}, + {"control", (PyCFunction)kqueue_queue_control, + METH_VARARGS , kqueue_queue_control_doc}, + {NULL, NULL}, +}; + +static PyGetSetDef kqueue_queue_getsetlist[] = { + {"closed", (getter)kqueue_queue_get_closed, NULL, + "True if the kqueue handler is closed"}, + {0}, +}; + +PyDoc_STRVAR(kqueue_queue_doc, +"Kqueue syscall wrapper.\n\ +\n\ +For example, to start watching a socket for input:\n\ +>>> kq = kqueue()\n\ +>>> sock = socket()\n\ +>>> sock.connect((host, port))\n\ +>>> kq.control([kevent(sock, KQ_FILTER_WRITE, KQ_EV_ADD)], 0)\n\ +\n\ +To wait one second for it to become writeable:\n\ +>>> kq.control(None, 1, 1000)\n\ +\n\ +To stop listening:\n\ +>>> kq.control([kevent(sock, KQ_FILTER_WRITE, KQ_EV_DELETE)], 0)"); + +static PyTypeObject kqueue_queue_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "select.kqueue", /* tp_name */ + sizeof(kqueue_queue_Object), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)kqueue_queue_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 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 */ + kqueue_queue_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + kqueue_queue_methods, /* tp_methods */ + 0, /* tp_members */ + kqueue_queue_getsetlist, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + kqueue_queue_new, /* tp_new */ + 0, /* tp_free */ +}; + +#endif /* HAVE_KQUEUE */ +/* ************************************************************************ */ + PyDoc_STRVAR(select_doc, "select(rlist, wlist, xlist[, timeout]) -> (rlist, wlist, xlist)\n\ \n\ @@ -667,14 +1718,15 @@ arguments; each contains the subset of the corresponding file descriptors\n\ that are ready.\n\ \n\ *** IMPORTANT NOTICE ***\n\ -On Windows and OpenVMS, only sockets are supported; on Unix, all file descriptors."); +On Windows and OpenVMS, only sockets are supported; on Unix, all file\n\ +descriptors."); static PyMethodDef select_methods[] = { - {"select", select_select, METH_VARARGS, select_doc}, -#if defined(HAVE_POLL) - {"poll", select_poll, METH_NOARGS, poll_doc}, + {"select", select_select, METH_VARARGS, select_doc}, +#ifdef HAVE_POLL + {"poll", select_poll, METH_NOARGS, poll_doc}, #endif /* HAVE_POLL */ - {0, 0}, /* sentinel */ + {0, 0}, /* sentinel */ }; PyDoc_STRVAR(module_doc, @@ -694,8 +1746,8 @@ initselect(void) SelectError = PyErr_NewException("select.error", NULL, NULL); Py_INCREF(SelectError); PyModule_AddObject(m, "error", SelectError); -#if defined(HAVE_POLL) +#if defined(HAVE_POLL) #ifdef __APPLE__ if (select_have_broken_poll()) { if (PyObject_DelAttrString(m, "poll") == -1) { @@ -730,4 +1782,103 @@ initselect(void) #endif } #endif /* HAVE_POLL */ + +#ifdef HAVE_EPOLL + Py_TYPE(&pyEpoll_Type) = &PyType_Type; + if (PyType_Ready(&pyEpoll_Type) < 0) + return; + + Py_INCREF(&pyEpoll_Type); + PyModule_AddObject(m, "epoll", (PyObject *) &pyEpoll_Type); + + PyModule_AddIntConstant(m, "EPOLLIN", EPOLLIN); + PyModule_AddIntConstant(m, "EPOLLOUT", EPOLLOUT); + PyModule_AddIntConstant(m, "EPOLLPRI", EPOLLPRI); + PyModule_AddIntConstant(m, "EPOLLERR", EPOLLERR); + PyModule_AddIntConstant(m, "EPOLLHUP", EPOLLHUP); + PyModule_AddIntConstant(m, "EPOLLET", EPOLLET); +#ifdef EPOLLONESHOT + /* Kernel 2.6.2+ */ + PyModule_AddIntConstant(m, "EPOLLONESHOT", EPOLLONESHOT); +#endif + /* PyModule_AddIntConstant(m, "EPOLL_RDHUP", EPOLLRDHUP); */ + PyModule_AddIntConstant(m, "EPOLLRDNORM", EPOLLRDNORM); + PyModule_AddIntConstant(m, "EPOLLRDBAND", EPOLLRDBAND); + PyModule_AddIntConstant(m, "EPOLLWRNORM", EPOLLWRNORM); + PyModule_AddIntConstant(m, "EPOLLWRBAND", EPOLLWRBAND); + PyModule_AddIntConstant(m, "EPOLLMSG", EPOLLMSG); +#endif /* HAVE_EPOLL */ + +#ifdef HAVE_KQUEUE + kqueue_event_Type.tp_new = PyType_GenericNew; + Py_TYPE(&kqueue_event_Type) = &PyType_Type; + if(PyType_Ready(&kqueue_event_Type) < 0) + return; + + Py_INCREF(&kqueue_event_Type); + PyModule_AddObject(m, "kevent", (PyObject *)&kqueue_event_Type); + + Py_TYPE(&kqueue_queue_Type) = &PyType_Type; + if(PyType_Ready(&kqueue_queue_Type) < 0) + return; + Py_INCREF(&kqueue_queue_Type); + PyModule_AddObject(m, "kqueue", (PyObject *)&kqueue_queue_Type); + + /* event filters */ + PyModule_AddIntConstant(m, "KQ_FILTER_READ", EVFILT_READ); + PyModule_AddIntConstant(m, "KQ_FILTER_WRITE", EVFILT_WRITE); + PyModule_AddIntConstant(m, "KQ_FILTER_AIO", EVFILT_AIO); + PyModule_AddIntConstant(m, "KQ_FILTER_VNODE", EVFILT_VNODE); + PyModule_AddIntConstant(m, "KQ_FILTER_PROC", EVFILT_PROC); +#ifdef EVFILT_NETDEV + PyModule_AddIntConstant(m, "KQ_FILTER_NETDEV", EVFILT_NETDEV); +#endif + PyModule_AddIntConstant(m, "KQ_FILTER_SIGNAL", EVFILT_SIGNAL); + PyModule_AddIntConstant(m, "KQ_FILTER_TIMER", EVFILT_TIMER); + + /* event flags */ + PyModule_AddIntConstant(m, "KQ_EV_ADD", EV_ADD); + PyModule_AddIntConstant(m, "KQ_EV_DELETE", EV_DELETE); + PyModule_AddIntConstant(m, "KQ_EV_ENABLE", EV_ENABLE); + PyModule_AddIntConstant(m, "KQ_EV_DISABLE", EV_DISABLE); + PyModule_AddIntConstant(m, "KQ_EV_ONESHOT", EV_ONESHOT); + PyModule_AddIntConstant(m, "KQ_EV_CLEAR", EV_CLEAR); + + PyModule_AddIntConstant(m, "KQ_EV_SYSFLAGS", EV_SYSFLAGS); + PyModule_AddIntConstant(m, "KQ_EV_FLAG1", EV_FLAG1); + + PyModule_AddIntConstant(m, "KQ_EV_EOF", EV_EOF); + PyModule_AddIntConstant(m, "KQ_EV_ERROR", EV_ERROR); + + /* READ WRITE filter flag */ + PyModule_AddIntConstant(m, "KQ_NOTE_LOWAT", NOTE_LOWAT); + + /* VNODE filter flags */ + PyModule_AddIntConstant(m, "KQ_NOTE_DELETE", NOTE_DELETE); + PyModule_AddIntConstant(m, "KQ_NOTE_WRITE", NOTE_WRITE); + PyModule_AddIntConstant(m, "KQ_NOTE_EXTEND", NOTE_EXTEND); + PyModule_AddIntConstant(m, "KQ_NOTE_ATTRIB", NOTE_ATTRIB); + PyModule_AddIntConstant(m, "KQ_NOTE_LINK", NOTE_LINK); + PyModule_AddIntConstant(m, "KQ_NOTE_RENAME", NOTE_RENAME); + PyModule_AddIntConstant(m, "KQ_NOTE_REVOKE", NOTE_REVOKE); + + /* PROC filter flags */ + PyModule_AddIntConstant(m, "KQ_NOTE_EXIT", NOTE_EXIT); + PyModule_AddIntConstant(m, "KQ_NOTE_FORK", NOTE_FORK); + PyModule_AddIntConstant(m, "KQ_NOTE_EXEC", NOTE_EXEC); + PyModule_AddIntConstant(m, "KQ_NOTE_PCTRLMASK", NOTE_PCTRLMASK); + PyModule_AddIntConstant(m, "KQ_NOTE_PDATAMASK", NOTE_PDATAMASK); + + PyModule_AddIntConstant(m, "KQ_NOTE_TRACK", NOTE_TRACK); + PyModule_AddIntConstant(m, "KQ_NOTE_CHILD", NOTE_CHILD); + PyModule_AddIntConstant(m, "KQ_NOTE_TRACKERR", NOTE_TRACKERR); + + /* NETDEV filter flags */ +#ifdef EVFILT_NETDEV + PyModule_AddIntConstant(m, "KQ_NOTE_LINKUP", NOTE_LINKUP); + PyModule_AddIntConstant(m, "KQ_NOTE_LINKDOWN", NOTE_LINKDOWN); + PyModule_AddIntConstant(m, "KQ_NOTE_LINKINV", NOTE_LINKINV); +#endif + +#endif /* HAVE_KQUEUE */ } diff --git a/configure b/configure index 99f5cb0..aa3e4d9 100755 --- a/configure +++ b/configure @@ -5419,13 +5419,15 @@ done + + for ac_header in asm/types.h conio.h curses.h direct.h dlfcn.h errno.h \ fcntl.h grp.h \ io.h langinfo.h libintl.h ncurses.h poll.h process.h pthread.h \ shadow.h signal.h stdint.h stropts.h termios.h thread.h \ unistd.h utime.h \ -sys/audioio.h sys/bsdtty.h sys/file.h sys/loadavg.h sys/lock.h sys/mkdev.h \ -sys/modem.h \ +sys/audioio.h sys/bsdtty.h sys/epoll.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/socket.h sys/statvfs.h sys/stat.h \ sys/time.h \ sys/times.h sys/types.h sys/un.h sys/utsname.h sys/wait.h pty.h libutil.h \ @@ -15917,7 +15919,111 @@ echo "${ECHO_T}no" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +{ echo "$as_me:$LINENO: checking for epoll" >&5 +echo $ECHO_N "checking for epoll... $ECHO_C" >&6; } +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include +int +main () +{ +void *x=epoll_create + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + +cat >>confdefs.h <<\_ACEOF +#define HAVE_EPOLL 1 +_ACEOF + + { echo "$as_me:$LINENO: result: yes" >&5 +echo "${ECHO_T}yes" >&6; } +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + { echo "$as_me:$LINENO: result: no" >&5 +echo "${ECHO_T}no" >&6; } + +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +{ echo "$as_me:$LINENO: checking for kqueue" >&5 +echo $ECHO_N "checking for kqueue... $ECHO_C" >&6; } +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +#include +#include + +int +main () +{ +int x=kqueue() + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + +cat >>confdefs.h <<\_ACEOF +#define HAVE_KQUEUE 1 +_ACEOF + { echo "$as_me:$LINENO: result: yes" >&5 +echo "${ECHO_T}yes" >&6; } +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + { echo "$as_me:$LINENO: result: no" >&5 +echo "${ECHO_T}no" >&6; } + +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext # On some systems (eg. FreeBSD 5), we would find a definition of the # functions ctermid_r, setgroups in the library, but no prototype # (e.g. because we use _XOPEN_SOURCE). See whether we can take their diff --git a/configure.in b/configure.in index 0d352fa..d50ba50 100644 --- a/configure.in +++ b/configure.in @@ -1102,8 +1102,8 @@ fcntl.h grp.h \ io.h langinfo.h libintl.h ncurses.h poll.h process.h pthread.h \ shadow.h signal.h stdint.h stropts.h termios.h thread.h \ unistd.h utime.h \ -sys/audioio.h sys/bsdtty.h sys/file.h sys/loadavg.h sys/lock.h sys/mkdev.h \ -sys/modem.h \ +sys/audioio.h sys/bsdtty.h sys/epoll.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/socket.h sys/statvfs.h sys/stat.h \ sys/time.h \ sys/times.h sys/types.h sys/un.h sys/utsname.h sys/wait.h pty.h libutil.h \ @@ -2354,7 +2354,21 @@ AC_TRY_COMPILE([#include ], void *x=fdatasync, AC_MSG_RESULT(yes), AC_MSG_RESULT(no) ) - +AC_MSG_CHECKING(for epoll) +AC_TRY_COMPILE([#include ], void *x=epoll_create, + AC_DEFINE(HAVE_EPOLL, 1, Define if you have the 'epoll' functions.) + AC_MSG_RESULT(yes), + AC_MSG_RESULT(no) +) +AC_MSG_CHECKING(for kqueue) +AC_TRY_COMPILE([ +#include +#include + ], int x=kqueue(), + AC_DEFINE(HAVE_KQUEUE, 1, Define if you have the 'kqueue' functions.) + AC_MSG_RESULT(yes), + AC_MSG_RESULT(no) +) # On some systems (eg. FreeBSD 5), we would find a definition of the # functions ctermid_r, setgroups in the library, but no prototype # (e.g. because we use _XOPEN_SOURCE). See whether we can take their diff --git a/pyconfig.h.in b/pyconfig.h.in index 4d43a21..f23ccfd 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -147,6 +147,9 @@ /* Defined when any dynamic module loading is enabled. */ #undef HAVE_DYNAMIC_LOADING +/* Define if you have the 'epoll' functions. */ +#undef HAVE_EPOLL + /* Define to 1 if you have the header file. */ #undef HAVE_ERRNO_H @@ -318,6 +321,9 @@ /* Define to 1 if you have the `killpg' function. */ #undef HAVE_KILLPG +/* Define if you have the 'kqueue' functions. */ +#undef HAVE_KQUEUE + /* Define to 1 if you have the header file. */ #undef HAVE_LANGINFO_H @@ -627,6 +633,12 @@ */ #undef HAVE_SYS_DIR_H +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_EPOLL_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_EVENT_H + /* Define to 1 if you have the header file. */ #undef HAVE_SYS_FILE_H -- cgit v0.12