diff -r e63a9695a0d4 Doc/library/selectors.rst --- a/Doc/library/selectors.rst Fri Dec 06 17:46:22 2013 -0800 +++ b/Doc/library/selectors.rst Fri Dec 06 19:36:21 2013 -0800 @@ -102,7 +102,8 @@ Register a file object for selection, monitoring it for I/O events. - *fileobj* is the file object to monitor. + *fileobj* is the file object to monitor. It may either be an integer + file descriptor or an object with a ``fileno()`` method. *events* is a bitwise mask of events to monitor. *data* is an opaque object. @@ -118,7 +119,9 @@ *fileobj* must be a file object previously registered. This returns the associated :class:`SelectorKey` instance, or raises a - :exc:`KeyError` if the file object is not registered. + :exc:`KeyError` if *fileobj* is not registered. It will raise + :exc:`ValueError` if *fileobj* is invalid (e.g. it has no ``fileno()`` + method or its ``fileno()`` method has an invalid return value). .. method:: modify(fileobj, events, data=None) diff -r e63a9695a0d4 Lib/selectors.py --- a/Lib/selectors.py Fri Dec 06 17:46:22 2013 -0800 +++ b/Lib/selectors.py Fri Dec 06 19:36:21 2013 -0800 @@ -17,28 +17,6 @@ EVENT_WRITE = (1 << 1) -def _fileobj_to_fd(fileobj): - """Return a file descriptor from a file object. - - Parameters: - fileobj -- file object or file descriptor - - Returns: - corresponding file descriptor - """ - if isinstance(fileobj, int): - fd = fileobj - else: - try: - fd = int(fileobj.fileno()) - except (AttributeError, TypeError, ValueError): - raise ValueError("Invalid file object: " - "{!r}".format(fileobj)) from None - if fd < 0: - raise ValueError("Invalid file descriptor: {}".format(fd)) - return fd - - SelectorKey = namedtuple('SelectorKey', ['fileobj', 'fd', 'events', 'data']) """Object used to associate a file object to its backing file descriptor, selected event mask and attached data.""" @@ -55,7 +33,8 @@ def __getitem__(self, fileobj): try: - return self._selector._fd_to_key[_fileobj_to_fd(fileobj)] + fd = self._selector._fileobj_to_fd(fileobj) + return self._selector._fd_to_key[fd] except KeyError: raise KeyError("{!r} is not registered".format(fileobj)) from None @@ -89,6 +68,15 @@ Returns: SelectorKey instance + + Raises: + ValueError if events is invalid + KeyError if fileobj is already registered + OSError if fileobj is closed or otherwise is unacceptable to + the underlying system call (if a system call is made) + + Note: + OSError may or may not be raised """ raise NotImplementedError @@ -101,6 +89,13 @@ Returns: SelectorKey instance + + Raises: + KeyError if fileobj is not registered + + Note: + If fileobj is registered but has since been closed this does + *not* raise OSError (even if the wrapped syscall does) """ raise NotImplementedError @@ -114,6 +109,9 @@ Returns: SelectorKey instance + + Raises: + Anything that unregister() or register() raises """ self.unregister(fileobj) return self.register(fileobj, events, data) @@ -177,22 +175,65 @@ # read-only mapping returned by get_map() self._map = _SelectorMapping(self) + def _fileobj_to_fd(self, fileobj): + """Return a file descriptor from a file object. + + Parameters: + fileobj -- file object or file descriptor + + Returns: + corresponding file descriptor + """ + if isinstance(fileobj, int): + fd = fileobj + else: + try: + fd = fileobj.fileno() + except (TypeError, AttributeError): + # No fileno() method or it badly failed. + # In this case we won't look further. + raise ValueError("Not a file object: {!r}" + .format(fileobj)) from None + except ValueError: + # Closed IOStreams raise this. + fd = None + else: + if fd is not None: + try: + fd = int(fd) + except (TypeError, AttributeError): + # Strange: fileno() returned something bad. + # Swallow it. + fd = None + if fd is None or fd < 0: + # Looks like the object is closed. + # Do an exhaustive search. + for key in self._fd_to_key.values(): + if key.fileobj is fileobj: + fd = key.fd + break + if fd is None: + raise ValueError("Invalid file object: {!r}".format(fileobj)) + if fd < 0: + raise ValueError("Invalid file descriptor: {}".format(fd)) + return fd + def register(self, fileobj, events, data=None): if (not events) or (events & ~(EVENT_READ | EVENT_WRITE)): raise ValueError("Invalid events: {!r}".format(events)) - key = SelectorKey(fileobj, _fileobj_to_fd(fileobj), events, data) + key = SelectorKey(fileobj, self._fileobj_to_fd(fileobj), events, data) if key.fd in self._fd_to_key: - raise KeyError("{!r} (FD {}) is already " - "registered".format(fileobj, key.fd)) + raise KeyError("{!r} (FD {}) is already registered" + .format(fileobj, key.fd)) self._fd_to_key[key.fd] = key return key def unregister(self, fileobj): try: - key = self._fd_to_key.pop(_fileobj_to_fd(fileobj)) + key = self._fd_to_key.pop(self._fileobj_to_fd(fileobj)) except KeyError: raise KeyError("{!r} is not registered".format(fileobj)) from None return key @@ -200,7 +241,7 @@ def modify(self, fileobj, events, data=None): # TODO: Subclasses can probably optimize this even further. try: - key = self._fd_to_key[_fileobj_to_fd(fileobj)] + key = self._fd_to_key[self._fileobj_to_fd(fileobj)] except KeyError: raise KeyError("{!r} is not registered".format(fileobj)) from None if events != key.events: @@ -352,7 +393,12 @@ def unregister(self, fileobj): key = super().unregister(fileobj) - self._epoll.unregister(key.fd) + try: + self._epoll.unregister(key.fd) + except OSError: + # This can happen if the FD was closed since it + # was registered. + pass return key def select(self, timeout=None): @@ -409,11 +455,20 @@ if key.events & EVENT_READ: kev = select.kevent(key.fd, select.KQ_FILTER_READ, select.KQ_EV_DELETE) - self._kqueue.control([kev], 0, 0) + try: + self._kqueue.control([kev], 0, 0) + except OSError: + # This can happen if the FD was closed since it + # was registered. + pass if key.events & EVENT_WRITE: kev = select.kevent(key.fd, select.KQ_FILTER_WRITE, select.KQ_EV_DELETE) - self._kqueue.control([kev], 0, 0) + try: + self._kqueue.control([kev], 0, 0) + except OSError: + # See comment above. + pass return key def select(self, timeout=None): diff -r e63a9695a0d4 Lib/test/test_selectors.py --- a/Lib/test/test_selectors.py Fri Dec 06 17:46:22 2013 -0800 +++ b/Lib/test/test_selectors.py Fri Dec 06 19:36:21 2013 -0800 @@ -1,4 +1,5 @@ import errno +import os import random import selectors import signal @@ -94,6 +95,50 @@ # unregister twice self.assertRaises(KeyError, s.unregister, rd) + def test_unregister_after_fd_close(self): + s = self.SELECTOR() + self.addCleanup(s.close) + rd, wr = socketpair() + r, w = rd.fileno(), wr.fileno() + self.addCleanup(rd.close) + self.addCleanup(wr.close) + s.register(r, selectors.EVENT_READ) + s.register(w, selectors.EVENT_WRITE) + rd.close() + wr.close() + s.unregister(r) + s.unregister(w) + + def test_unregister_after_fd_close_and_reuse(self): + s = self.SELECTOR() + self.addCleanup(s.close) + rd, wr = socketpair() + r, w = rd.fileno(), wr.fileno() + self.addCleanup(rd.close) + self.addCleanup(wr.close) + s.register(r, selectors.EVENT_READ) + s.register(w, selectors.EVENT_WRITE) + rd.close() + wr.close() + rd2, wr2 = socketpair() + os.dup2(rd2.fileno(), r) + os.dup2(wr2.fileno(), w) + s.unregister(r) + s.unregister(w) + + def test_unregister_after_socket_close(self): + s = self.SELECTOR() + self.addCleanup(s.close) + rd, wr = socketpair() + self.addCleanup(rd.close) + self.addCleanup(wr.close) + s.register(rd, selectors.EVENT_READ) + s.register(wr, selectors.EVENT_WRITE) + rd.close() + wr.close() + s.unregister(rd) + s.unregister(wr) + def test_modify(self): s = self.SELECTOR() self.addCleanup(s.close)