Author: Armin Rigo <ar...@tunes.org>
Branch: py3.5
Changeset: r88885:b5eaea946a6b
Date: 2016-12-04 21:44 +0100
http://bitbucket.org/pypy/pypy/changeset/b5eaea946a6b/

Log:    hg merge py3.5-eintr-pep475

        Implement PEP475, which makes a number of os-, file-, select- and
        socket-related functions no longer raise OSError/IOError on getting
        EINTR, but instead automatically retry.

        There are a few functions mentioned in the PEP too which are not
        present in PyPy so far.

diff too long, truncating to 2000 out of 2118 lines

diff --git a/lib_pypy/_pypy_wait.py b/lib_pypy/_pypy_wait.py
--- a/lib_pypy/_pypy_wait.py
+++ b/lib_pypy/_pypy_wait.py
@@ -1,3 +1,5 @@
+import os
+from errno import EINTR
 from resource import ffi, lib, _make_struct_rusage
 
 __all__ = ["wait3", "wait4"]
@@ -6,7 +8,13 @@
 def wait3(options):
     status = ffi.new("int *")
     ru = ffi.new("struct rusage *")
-    pid = lib.wait3(status, options, ru)
+    while True:
+        pid = lib.wait3(status, options, ru)
+        if pid != -1:
+            break
+        errno = ffi.errno
+        if errno != EINTR:
+            raise OSError(errno, os.strerror(errno))
 
     rusage = _make_struct_rusage(ru)
 
@@ -15,7 +23,13 @@
 def wait4(pid, options):
     status = ffi.new("int *")
     ru = ffi.new("struct rusage *")
-    pid = lib.wait4(pid, status, options, ru)
+    while True:
+        pid = lib.wait4(pid, status, options, ru)
+        if pid != -1:
+            break
+        errno = ffi.errno
+        if errno != EINTR:
+            raise OSError(errno, os.strerror(errno))
 
     rusage = _make_struct_rusage(ru)
 
diff --git a/pypy/interpreter/error.py b/pypy/interpreter/error.py
--- a/pypy/interpreter/error.py
+++ b/pypy/interpreter/error.py
@@ -543,6 +543,7 @@
     _WINDOWS = True
 
     def wrap_windowserror(space, e, w_filename=None):
+        XXX    # WindowsError no longer exists in Py3.5
         from rpython.rlib import rwin32
 
         winerror = e.winerror
@@ -559,43 +560,72 @@
                                           space.wrap(msg))
         return OperationError(exc, w_error)
 
-@specialize.arg(3)
+@specialize.arg(3, 6)
 def wrap_oserror2(space, e, w_filename=None, exception_name='w_OSError',
-                  w_exception_class=None, w_filename2=None):
+                  w_exception_class=None, w_filename2=None, eintr_retry=False):
+    """A double API here:
+
+        * if eintr_retry is False, always return the OperationError to
+          be raised by the caller.  It can possibly be about EINTR
+          (checksignals() is still called here).
+
+        * if eintr_retry is True (PEP 475 compliant API for retrying
+          system calls failing with EINTR), then this function raises
+          the OperationError directly, or for EINTR it calls
+          checksignals() and returns None in case the original
+          operation should be retried.
+    """
     assert isinstance(e, OSError)
 
     if _WINDOWS and isinstance(e, WindowsError):
         return wrap_windowserror(space, e, w_filename)
 
+    if w_exception_class is None:
+        w_exc = getattr(space, exception_name)
+    else:
+        w_exc = w_exception_class
+    operror = _wrap_oserror2_impl(space, e, w_filename, w_filename2, w_exc,
+                                  eintr_retry)
+    if eintr_retry:
+        assert operror is None   # otherwise, _wrap_oserror2_impl() has raised
+    else:
+        assert operror is not None   # tell the annotator we don't return None
+        return operror
+
+def _wrap_oserror2_impl(space, e, w_filename, w_filename2, w_exc, eintr_retry):
+    # move the common logic in its own function, instead of having it
+    # duplicated 4 times in all 4 specialized versions of wrap_oserror2()
     errno = e.errno
 
     if errno == EINTR:
         space.getexecutioncontext().checksignals()
+        if eintr_retry:
+            return None
 
     try:
         msg = strerror(errno)
     except ValueError:
         msg = u'error %d' % errno
-    if w_exception_class is None:
-        exc = getattr(space, exception_name)
-    else:
-        exc = w_exception_class
     if w_filename is not None:
         if w_filename2 is not None:
-            w_error = space.call_function(exc, space.wrap(errno),
+            w_error = space.call_function(w_exc, space.wrap(errno),
                                           space.wrap(msg), w_filename,
                                           space.w_None, w_filename2)
         else:
-            w_error = space.call_function(exc, space.wrap(errno),
+            w_error = space.call_function(w_exc, space.wrap(errno),
                                           space.wrap(msg), w_filename)
     else:
-        w_error = space.call_function(exc, space.wrap(errno),
+        w_error = space.call_function(w_exc, space.wrap(errno),
                                       space.wrap(msg))
-    return OperationError(exc, w_error)
+    operror = OperationError(w_exc, w_error)
+    if eintr_retry:
+        raise operror
+    return operror
+_wrap_oserror2_impl._dont_inline_ = True
 
-@specialize.arg(3)
+@specialize.arg(3, 6)
 def wrap_oserror(space, e, filename=None, exception_name='w_OSError',
-                 w_exception_class=None, filename2=None):
+                 w_exception_class=None, filename2=None, eintr_retry=False):
     w_filename = None
     w_filename2 = None
     if filename is not None:
@@ -605,7 +635,9 @@
     return wrap_oserror2(space, e, w_filename,
                          exception_name=exception_name,
                          w_exception_class=w_exception_class,
-                         w_filename2=w_filename2)
+                         w_filename2=w_filename2,
+                         eintr_retry=eintr_retry)
+wrap_oserror._dont_inline_ = True
 
 def exception_from_saved_errno(space, w_type):
     from rpython.rlib.rposix import get_saved_errno
diff --git a/pypy/interpreter/timeutils.py b/pypy/interpreter/timeutils.py
new file mode 100644
--- /dev/null
+++ b/pypy/interpreter/timeutils.py
@@ -0,0 +1,11 @@
+"""
+Access to the time module's high-resolution monotonic clock
+"""
+
+def monotonic(space):
+    from pypy.module.time import interp_time
+    if interp_time.HAS_MONOTONIC:
+        w_res = interp_time.monotonic(space)
+    else:
+        w_res = interp_time.gettimeofday(space)
+    return space.float_w(w_res)   # xxx back and forth
diff --git a/pypy/module/_io/interp_fileio.py b/pypy/module/_io/interp_fileio.py
--- a/pypy/module/_io/interp_fileio.py
+++ b/pypy/module/_io/interp_fileio.py
@@ -175,46 +175,48 @@
                                 "Cannot use closefd=False with file name")
 
                 from pypy.module.posix.interp_posix import dispatch_filename
-                try:
-                    self.fd = dispatch_filename(rposix.open)(
-                        space, w_name, flags, 0666)
-                except OSError as e:
-                    raise wrap_oserror2(space, e, w_name,
-                                        exception_name='w_IOError')
-                finally:
-                    fd_is_own = True
+                while True:
+                    try:
+                        self.fd = dispatch_filename(rposix.open)(
+                            space, w_name, flags, 0666)
+                        fd_is_own = True
+                        break
+                    except OSError as e:
+                        wrap_oserror2(space, e, w_name,
+                                      exception_name='w_IOError',
+                                      eintr_retry=True)
                 if not rposix._WIN32:
                     try:
                         _open_inhcache.set_non_inheritable(self.fd)
                     except OSError as e:
-                        raise wrap_oserror2(space, e, w_name)
+                        raise wrap_oserror2(space, e, w_name, 
eintr_retry=False)
             else:
                 w_fd = space.call_function(w_opener, w_name, space.wrap(flags))
                 try:
                     self.fd = space.int_w(w_fd)
+                    fd_is_own = True
                 except OperationError as e:
                     if not e.match(space, space.w_TypeError):
                         raise
                     raise oefmt(space.w_TypeError,
                                 "expected integer from opener")
-                finally:
-                    fd_is_own = True
                 if not rposix._WIN32:
                     try:
                         rposix.set_inheritable(self.fd, False)
                     except OSError as e:
-                        raise wrap_oserror2(space, e, w_name)
+                        raise wrap_oserror2(space, e, w_name, 
eintr_retry=False)
 
             try:
                 st = os.fstat(self.fd)
             except OSError as e:
-                raise wrap_oserror(space, e)
+                raise wrap_oserror(space, e, eintr_retry=False)
             # On Unix, fopen will succeed for directories.
             # In Python, there should be no file objects referring to
             # directories, so we need a check.
             if stat.S_ISDIR(st.st_mode):
                 raise wrap_oserror2(space, OSError(errno.EISDIR, "fstat"),
-                                    w_name, exception_name='w_IOError')
+                                    w_name, exception_name='w_IOError',
+                                    eintr_retry=False)
             self.blksize = DEFAULT_BUFFER_SIZE
             if HAS_BLKSIZE and st.st_blksize > 1:
                 self.blksize = st.st_blksize
@@ -227,7 +229,8 @@
                 try:
                     os.lseek(self.fd, 0, os.SEEK_END)
                 except OSError as e:
-                    raise wrap_oserror(space, e, exception_name='w_IOError')
+                    raise wrap_oserror(space, e, exception_name='w_IOError',
+                                       eintr_retry=False)
         except:
             if not fd_is_own:
                 self.fd = -1
@@ -285,7 +288,8 @@
             os.close(fd)
         except OSError as e:
             raise wrap_oserror(space, e,
-                               exception_name='w_IOError')
+                               exception_name='w_IOError',
+                               eintr_retry=False)
 
     def close_w(self, space):
         try:
@@ -319,7 +323,8 @@
             pos = os.lseek(self.fd, pos, whence)
         except OSError as e:
             raise wrap_oserror(space, e,
-                               exception_name='w_IOError')
+                               exception_name='w_IOError',
+                               eintr_retry=False)
         return space.wrap(pos)
 
     def tell_w(self, space):
@@ -328,7 +333,8 @@
             pos = os.lseek(self.fd, 0, 1)
         except OSError as e:
             raise wrap_oserror(space, e,
-                               exception_name='w_IOError')
+                               exception_name='w_IOError',
+                               eintr_retry=False)
         return space.wrap(pos)
 
     def readable_w(self, space):
@@ -361,7 +367,8 @@
         try:
             res = os.isatty(self.fd)
         except OSError as e:
-            raise wrap_oserror(space, e, exception_name='w_IOError')
+            raise wrap_oserror(space, e, exception_name='w_IOError',
+                               eintr_retry=False)
         return space.wrap(res)
 
     def repr_w(self, space):
@@ -387,13 +394,16 @@
         self._check_writable(space)
         data = space.getarg_w('y*', w_data).as_str()
 
-        try:
-            n = os.write(self.fd, data)
-        except OSError as e:
-            if e.errno == errno.EAGAIN:
-                return space.w_None
-            raise wrap_oserror(space, e,
-                               exception_name='w_IOError')
+        while True:
+            try:
+                n = os.write(self.fd, data)
+                break
+            except OSError as e:
+                if e.errno == errno.EAGAIN:
+                    return space.w_None
+                wrap_oserror(space, e,
+                             exception_name='w_IOError',
+                             eintr_retry=True)
 
         return space.wrap(n)
 
@@ -405,13 +415,16 @@
         if size < 0:
             return self.readall_w(space)
 
-        try:
-            s = os.read(self.fd, size)
-        except OSError as e:
-            if e.errno == errno.EAGAIN:
-                return space.w_None
-            raise wrap_oserror(space, e,
-                               exception_name='w_IOError')
+        while True:
+            try:
+                s = os.read(self.fd, size)
+                break
+            except OSError as e:
+                if e.errno == errno.EAGAIN:
+                    return space.w_None
+                wrap_oserror(space, e,
+                             exception_name='w_IOError',
+                             eintr_retry=True)
 
         return space.newbytes(s)
 
@@ -420,13 +433,16 @@
         self._check_readable(space)
         rwbuffer = space.getarg_w('w*', w_buffer)
         length = rwbuffer.getlength()
-        try:
-            buf = os.read(self.fd, length)
-        except OSError as e:
-            if e.errno == errno.EAGAIN:
-                return space.w_None
-            raise wrap_oserror(space, e,
-                               exception_name='w_IOError')
+        while True:
+            try:
+                buf = os.read(self.fd, length)
+                break
+            except OSError as e:
+                if e.errno == errno.EAGAIN:
+                    return space.w_None
+                wrap_oserror(space, e,
+                             exception_name='w_IOError',
+                             eintr_retry=True)
         rwbuffer.setslice(0, buf)
         return space.wrap(len(buf))
 
@@ -442,17 +458,13 @@
             try:
                 chunk = os.read(self.fd, newsize - total)
             except OSError as e:
-                if e.errno == errno.EINTR:
-                    space.getexecutioncontext().checksignals()
-                    continue
-                if total > 0:
-                    # return what we've got so far
-                    break
                 if e.errno == errno.EAGAIN:
+                    if total > 0:
+                        break   # return what we've got so far
                     return space.w_None
-                raise wrap_oserror(space, e,
-                                   exception_name='w_IOError')
-
+                wrap_oserror(space, e, exception_name='w_IOError',
+                             eintr_retry=True)
+                continue
             if not chunk:
                 break
             builder.append(chunk)
@@ -476,7 +488,8 @@
         try:
             self._truncate(space.r_longlong_w(w_size))
         except OSError as e:
-            raise wrap_oserror(space, e, exception_name='w_IOError')
+            raise wrap_oserror(space, e, exception_name='w_IOError',
+                               eintr_retry=False)
 
         return w_size
 
diff --git a/pypy/module/_socket/interp_socket.py 
b/pypy/module/_socket/interp_socket.py
--- a/pypy/module/_socket/interp_socket.py
+++ b/pypy/module/_socket/interp_socket.py
@@ -1,5 +1,6 @@
-import sys
+import sys, errno
 from rpython.rlib import rsocket, rweaklist
+from rpython.rlib.objectmodel import specialize
 from rpython.rlib.rarithmetic import intmask
 from rpython.rlib.rsocket import (
     RSocket, AF_INET, SOCK_STREAM, SocketError, SocketErrorWithErrno,
@@ -227,12 +228,13 @@
         representing the connection, and the address of the client.
         For IP sockets, the address info is a pair (hostaddr, port).
         """
-        try:
-            fd, addr = self.sock.accept(inheritable=False)
-            return space.newtuple([space.wrap(fd),
-                                   addr_as_object(addr, fd, space)])
-        except SocketError as e:
-            raise converted_error(space, e)
+        while True:
+            try:
+                fd, addr = self.sock.accept(inheritable=False)
+                return space.newtuple([space.wrap(fd),
+                                       addr_as_object(addr, fd, space)])
+            except SocketError as e:
+                converted_error(space, e, eintr_retry=True)
 
     # convert an Address into an app-level object
     def addr_as_object(self, space, address):
@@ -274,10 +276,12 @@
         Connect the socket to a remote address.  For IP sockets, the address
         is a pair (host, port).
         """
-        try:
-            self.sock.connect(self.addr_from_object(space, w_addr))
-        except SocketError as e:
-            raise converted_error(space, e)
+        while True:
+            try:
+                self.sock.connect(self.addr_from_object(space, w_addr))
+                break
+            except SocketError as e:
+                converted_error(space, e, eintr_retry=True)
 
     def connect_ex_w(self, space, w_addr):
         """connect_ex(address) -> errno
@@ -289,7 +293,11 @@
             addr = self.addr_from_object(space, w_addr)
         except SocketError as e:
             raise converted_error(space, e)
-        error = self.sock.connect_ex(addr)
+        while True:
+            error = self.sock.connect_ex(addr)
+            if error != errno.EINTR:
+                break
+            space.getexecutioncontext().checksignals()
         return space.wrap(error)
 
     def fileno_w(self, space):
@@ -384,10 +392,12 @@
         at least one byte is available or until the remote end is closed.  When
         the remote end is closed and all data is read, return the empty string.
         """
-        try:
-            data = self.sock.recv(buffersize, flags)
-        except SocketError as e:
-            raise converted_error(space, e)
+        while True:
+            try:
+                data = self.sock.recv(buffersize, flags)
+                break
+            except SocketError as e:
+                converted_error(space, e, eintr_retry=True)
         return space.newbytes(data)
 
     @unwrap_spec(buffersize='nonnegint', flags=int)
@@ -396,15 +406,17 @@
 
         Like recv(buffersize, flags) but also return the sender's address info.
         """
-        try:
-            data, addr = self.sock.recvfrom(buffersize, flags)
-            if addr:
-                w_addr = addr_as_object(addr, self.sock.fd, space)
-            else:
-                w_addr = space.w_None
-            return space.newtuple([space.newbytes(data), w_addr])
-        except SocketError as e:
-            raise converted_error(space, e)
+        while True:
+            try:
+                data, addr = self.sock.recvfrom(buffersize, flags)
+                if addr:
+                    w_addr = addr_as_object(addr, self.sock.fd, space)
+                else:
+                    w_addr = space.w_None
+                break
+            except SocketError as e:
+                converted_error(space, e, eintr_retry=True)
+        return space.newtuple([space.newbytes(data), w_addr])
 
     @unwrap_spec(data='bufferstr', flags=int)
     def send_w(self, space, data, flags=0):
@@ -414,10 +426,12 @@
         argument, see the Unix manual.  Return the number of bytes
         sent; this may be less than len(data) if the network is busy.
         """
-        try:
-            count = self.sock.send(data, flags)
-        except SocketError as e:
-            raise converted_error(space, e)
+        while True:
+            try:
+                count = self.sock.send(data, flags)
+                break
+            except SocketError as e:
+                converted_error(space, e, eintr_retry=True)
         return space.wrap(count)
 
     @unwrap_spec(data='bufferstr', flags=int)
@@ -450,11 +464,13 @@
             # 3 args version
             flags = space.int_w(w_param2)
             w_addr = w_param3
-        try:
-            addr = self.addr_from_object(space, w_addr)
-            count = self.sock.sendto(data, flags, addr)
-        except SocketError as e:
-            raise converted_error(space, e)
+        while True:
+            try:
+                addr = self.addr_from_object(space, w_addr)
+                count = self.sock.sendto(data, flags, addr)
+                break
+            except SocketError as e:
+                converted_error(space, e, eintr_retry=True)
         return space.wrap(count)
 
     @unwrap_spec(flag=int)
@@ -520,10 +536,13 @@
         lgt = rwbuffer.getlength()
         if nbytes == 0 or nbytes > lgt:
             nbytes = lgt
-        try:
-            return space.wrap(self.sock.recvinto(rwbuffer, nbytes, flags))
-        except SocketError as e:
-            raise converted_error(space, e)
+        while True:
+            try:
+                nbytes_read = self.sock.recvinto(rwbuffer, nbytes, flags)
+                break
+            except SocketError as e:
+                converted_error(space, e, eintr_retry=True)
+        return space.wrap(nbytes_read)
 
     @unwrap_spec(nbytes=int, flags=int)
     def recvfrom_into_w(self, space, w_buffer, nbytes=0, flags=0):
@@ -538,15 +557,20 @@
         elif nbytes > lgt:
             raise oefmt(space.w_ValueError,
                         "nbytes is greater than the length of the buffer")
-        try:
-            readlgt, addr = self.sock.recvfrom_into(rwbuffer, nbytes, flags)
-            if addr:
+        while True:
+            try:
+                readlgt, addr = self.sock.recvfrom_into(rwbuffer, nbytes, 
flags)
+                break
+            except SocketError as e:
+                converted_error(space, e, eintr_retry=True)
+        if addr:
+            try:
                 w_addr = addr_as_object(addr, self.sock.fd, space)
-            else:
-                w_addr = space.w_None
-            return space.newtuple([space.wrap(readlgt), w_addr])
-        except SocketError as e:
-            raise converted_error(space, e)
+            except SocketError as e:
+                raise converted_error(space, e)
+        else:
+            w_addr = space.w_None
+        return space.newtuple([space.wrap(readlgt), w_addr])
 
     @unwrap_spec(cmd=int)
     def ioctl_w(self, space, cmd, w_option):
@@ -690,15 +714,20 @@
 def get_error(space, name):
     return space.fromcache(SocketAPI).get_exception(name)
 
-def converted_error(space, e):
+@specialize.arg(2)
+def converted_error(space, e, eintr_retry=False):
     message = e.get_msg()
     w_exception_class = get_error(space, e.applevelerrcls)
     if isinstance(e, SocketErrorWithErrno):
+        if e.errno == errno.EINTR:
+            space.getexecutioncontext().checksignals()
+            if eintr_retry:
+                return       # only return None if eintr_retry==True
         w_exception = space.call_function(w_exception_class, 
space.wrap(e.errno),
                                       space.wrap(message))
     else:
         w_exception = space.call_function(w_exception_class, 
space.wrap(message))
-    return OperationError(w_exception_class, w_exception)
+    raise OperationError(w_exception_class, w_exception)
 
 # ____________________________________________________________
 
diff --git a/pypy/module/posix/interp_posix.py 
b/pypy/module/posix/interp_posix.py
--- a/pypy/module/posix/interp_posix.py
+++ b/pypy/module/posix/interp_posix.py
@@ -226,15 +226,21 @@
   If it is unavailable, using it will raise a NotImplementedError."""
     if rposix.O_CLOEXEC is not None:
         flags |= rposix.O_CLOEXEC
+    while True:
+        try:
+            if rposix.HAVE_OPENAT and dir_fd != DEFAULT_DIR_FD:
+                path = space.fsencode_w(w_path)
+                fd = rposix.openat(path, flags, mode, dir_fd)
+            else:
+                fd = dispatch_filename(rposix.open)(space, w_path, flags, mode)
+            break
+        except OSError as e:
+            wrap_oserror2(space, e, w_path, eintr_retry=True)
     try:
-        if rposix.HAVE_OPENAT and dir_fd != DEFAULT_DIR_FD:
-            path = space.fsencode_w(w_path)
-            fd = rposix.openat(path, flags, mode, dir_fd)
-        else:
-            fd = dispatch_filename(rposix.open)(space, w_path, flags, mode)
         _open_inhcache.set_non_inheritable(fd)
     except OSError as e:
-        raise wrap_oserror2(space, e, w_path)
+        rposix.c_close(fd)
+        raise wrap_oserror2(space, e, w_path, eintr_retry=False)
     return space.wrap(fd)
 
 @unwrap_spec(fd=c_int, position=r_longlong, how=c_int)
@@ -245,7 +251,7 @@
     try:
         pos = os.lseek(fd, position, how)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     else:
         return space.wrap(pos)
 
@@ -256,39 +262,45 @@
     try:
         res = os.isatty(fd)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     else:
         return space.wrap(res)
 
 @unwrap_spec(fd=c_int, length=int)
 def read(space, fd, length):
     """Read data from a file descriptor."""
-    try:
-        s = os.read(fd, length)
-    except OSError as e:
-        raise wrap_oserror(space, e)
-    else:
-        return space.newbytes(s)
+    while True:
+        try:
+            s = os.read(fd, length)
+        except OSError as e:
+            wrap_oserror(space, e, eintr_retry=True)
+        else:
+            return space.newbytes(s)
 
 @unwrap_spec(fd=c_int)
 def write(space, fd, w_data):
     """Write a string to a file descriptor.  Return the number of bytes
 actually written, which may be smaller than len(data)."""
     data = space.getarg_w('y*', w_data)
-    try:
-        res = os.write(fd, data.as_str())
-    except OSError as e:
-        raise wrap_oserror(space, e)
-    else:
-        return space.wrap(res)
+    while True:
+        try:
+            res = os.write(fd, data.as_str())
+        except OSError as e:
+            wrap_oserror(space, e, eintr_retry=True)
+        else:
+            return space.wrap(res)
 
 @unwrap_spec(fd=c_int)
 def close(space, fd):
     """Close a file descriptor (for low level IO)."""
+    # PEP 475 note: os.close() must not retry upon EINTR.  Like in
+    # previous versions of Python it raises OSError in this case.
+    # The text of PEP 475 seems to suggest that EINTR is eaten and
+    # hidden from app-level, but it is not the case in CPython 3.5.2.
     try:
         os.close(fd)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 @unwrap_spec(fd_low=c_int, fd_high=c_int)
 def closerange(fd_low, fd_high):
@@ -298,10 +310,12 @@
 @unwrap_spec(fd=c_int, length=r_longlong)
 def ftruncate(space, fd, length):
     """Truncate a file (by file descriptor) to a specified length."""
-    try:
-        os.ftruncate(fd, length)
-    except OSError as e:
-        raise wrap_oserror(space, e)
+    while True:
+        try:
+            os.ftruncate(fd, length)
+            break
+        except OSError as e:
+            wrap_oserror(space, e, eintr_retry=True)
 
 def truncate(space, w_path, w_length):
     """Truncate a file to a specified length."""
@@ -325,19 +339,23 @@
 def fsync(space, w_fd):
     """Force write of file with filedescriptor to disk."""
     fd = space.c_filedescriptor_w(w_fd)
-    try:
-        os.fsync(fd)
-    except OSError as e:
-        raise wrap_oserror(space, e)
+    while True:
+        try:
+            os.fsync(fd)
+            break
+        except OSError as e:
+            wrap_oserror(space, e, eintr_retry=True)
 
 def fdatasync(space, w_fd):
     """Force write of file with filedescriptor to disk.
 Does not force update of metadata."""
     fd = space.c_filedescriptor_w(w_fd)
-    try:
-        os.fdatasync(fd)
-    except OSError as e:
-        raise wrap_oserror(space, e)
+    while True:
+        try:
+            os.fdatasync(fd)
+            break
+        except OSError as e:
+            wrap_oserror(space, e, eintr_retry=True)
 
 def sync(space):
     """Force write of everything to disk."""
@@ -347,10 +365,12 @@
     """Change to the directory of the given file descriptor.  fildes must be
 opened on a directory, not a file."""
     fd = space.c_filedescriptor_w(w_fd)
-    try:
-        os.fchdir(fd)
-    except OSError as e:
-        raise wrap_oserror(space, e)
+    while True:
+        try:
+            os.fchdir(fd)
+            break
+        except OSError as e:
+            wrap_oserror(space, e, eintr_retry=True)
 
 # ____________________________________________________________
 
@@ -415,12 +435,13 @@
 def fstat(space, fd):
     """Perform a stat system call on the file referenced to by an open
 file descriptor."""
-    try:
-        st = rposix_stat.fstat(fd)
-    except OSError as e:
-        raise wrap_oserror(space, e)
-    else:
-        return build_stat_result(space, st)
+    while True:
+        try:
+            st = rposix_stat.fstat(fd)
+        except OSError as e:
+            wrap_oserror(space, e, eintr_retry=True)
+        else:
+            return build_stat_result(space, st)
 
 @unwrap_spec(
     path=path_or_fd(allow_fd=True),
@@ -466,7 +487,7 @@
             raise oefmt(space.w_NotImplementedError,
                 "%s: unsupported argument combination", funcname)
     except OSError as e:
-        raise wrap_oserror2(space, e, path.w_path)
+        raise wrap_oserror2(space, e, path.w_path, eintr_retry=False)
     else:
         return build_stat_result(space, st)
 
@@ -503,12 +524,13 @@
 
 @unwrap_spec(fd=c_int)
 def fstatvfs(space, fd):
-    try:
-        st = rposix_stat.fstatvfs(fd)
-    except OSError as e:
-        raise wrap_oserror(space, e)
-    else:
-        return build_statvfs_result(space, st)
+    while True:
+        try:
+            st = rposix_stat.fstatvfs(fd)
+        except OSError as e:
+            wrap_oserror(space, e, eintr_retry=True)
+        else:
+            return build_statvfs_result(space, st)
 
 
 def statvfs(space, w_path):
@@ -524,7 +546,7 @@
             rposix_stat.statvfs,
             allow_fd_fn=rposix_stat.fstatvfs)(space, w_path)
     except OSError as e:
-        raise wrap_oserror2(space, e, w_path)
+        raise wrap_oserror2(space, e, w_path, eintr_retry=False)
     else:
         return build_statvfs_result(space, st)
 
@@ -536,17 +558,19 @@
     try:
         newfd = rposix.dup(fd, inheritable=False)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     else:
         return space.wrap(newfd)
 
 @unwrap_spec(fd=c_int, fd2=c_int, inheritable=bool)
 def dup2(space, fd, fd2, inheritable=1):
     """Duplicate a file descriptor."""
+    # like os.close(), this can still raise EINTR to app-level in
+    # CPython 3.5.2
     try:
         rposix.dup2(fd, fd2, inheritable)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 @unwrap_spec(mode=c_int,
     dir_fd=DirFD(rposix.HAVE_FACCESSAT), effective_ids=bool,
@@ -591,7 +615,7 @@
         else:
             ok = dispatch_filename(rposix.access)(space, w_path, mode)
     except OSError as e:
-        raise wrap_oserror2(space, e, w_path)
+        raise wrap_oserror2(space, e, w_path, eintr_retry=False)
     else:
         return space.wrap(ok)
 
@@ -605,7 +629,7 @@
     try:
         times = os.times()
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     else:
         return space.newtuple([space.wrap(times[0]),
                                space.wrap(times[1]),
@@ -619,7 +643,7 @@
     try:
         rc = os.system(command)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     else:
         return space.wrap(rc)
 
@@ -640,7 +664,7 @@
         else:
             dispatch_filename(rposix.unlink)(space, w_path)
     except OSError as e:
-        raise wrap_oserror2(space, e, w_path)
+        raise wrap_oserror2(space, e, w_path, eintr_retry=False)
 
 @unwrap_spec(dir_fd=DirFD(rposix.HAVE_UNLINKAT))
 def remove(space, w_path, __kwonly__, dir_fd=DEFAULT_DIR_FD):
@@ -659,7 +683,7 @@
         else:
             dispatch_filename(rposix.unlink)(space, w_path)
     except OSError as e:
-        raise wrap_oserror2(space, e, w_path)
+        raise wrap_oserror2(space, e, w_path, eintr_retry=False)
 
 def _getfullpathname(space, w_path):
     """helper for ntpath.abspath """
@@ -673,7 +697,7 @@
             fullpath = rposix.getfullpathname(path)
             w_fullpath = space.newbytes(fullpath)
     except OSError as e:
-        raise wrap_oserror2(space, e, w_path)
+        raise wrap_oserror2(space, e, w_path, eintr_retry=False)
     else:
         return w_fullpath
 
@@ -682,7 +706,7 @@
     try:
         cur = os.getcwd()
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     else:
         return space.newbytes(cur)
 
@@ -692,7 +716,7 @@
         try:
             cur = os.getcwdu()
         except OSError as e:
-            raise wrap_oserror(space, e)
+            raise wrap_oserror(space, e, eintr_retry=False)
         else:
             return space.wrap(cur)
 else:
@@ -709,7 +733,7 @@
         else:
             dispatch_filename(rposix.chdir)(space, w_path)
     except OSError as e:
-        raise wrap_oserror2(space, e, w_path)
+        raise wrap_oserror2(space, e, w_path, eintr_retry=False)
 
 @unwrap_spec(mode=c_int, dir_fd=DirFD(rposix.HAVE_MKDIRAT))
 def mkdir(space, w_path, mode=0o777, __kwonly__=None, dir_fd=DEFAULT_DIR_FD):
@@ -730,7 +754,7 @@
         else:
             dispatch_filename(rposix.mkdir)(space, w_path, mode)
     except OSError as e:
-        raise wrap_oserror2(space, e, w_path)
+        raise wrap_oserror2(space, e, w_path, eintr_retry=False)
 
 @unwrap_spec(dir_fd=DirFD(rposix.HAVE_UNLINKAT))
 def rmdir(space, w_path, __kwonly__, dir_fd=DEFAULT_DIR_FD):
@@ -749,7 +773,7 @@
         else:
             dispatch_filename(rposix.rmdir)(space, w_path)
     except OSError as e:
-        raise wrap_oserror2(space, e, w_path)
+        raise wrap_oserror2(space, e, w_path, eintr_retry=False)
 
 @unwrap_spec(code=c_int)
 def strerror(space, code):
@@ -764,7 +788,7 @@
     try:
         cur = os.getlogin()
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.wrap_fsdecoded(cur)
 
 # ____________________________________________________________
@@ -817,7 +841,7 @@
         try:
             rwin32._wputenv(name, value)
         except OSError as e:
-            raise wrap_oserror(space, e)
+            raise wrap_oserror(space, e, eintr_retry=False)
 else:
     def _convertenviron(space, w_env):
         for key, value in os.environ.items():
@@ -828,7 +852,7 @@
         try:
             dispatch_filename_2(rposix.putenv)(space, w_name, w_value)
         except OSError as e:
-            raise wrap_oserror(space, e)
+            raise wrap_oserror(space, e, eintr_retry=False)
 
     def unsetenv(space, w_name):
         """Delete an environment variable."""
@@ -837,7 +861,7 @@
         except KeyError:
             pass
         except OSError as e:
-            raise wrap_oserror(space, e)
+            raise wrap_oserror(space, e, eintr_retry=False)
 
 
 def listdir(space, w_path=None):
@@ -860,7 +884,7 @@
         try:
             result = rposix.listdir(dirname)
         except OSError as e:
-            raise wrap_oserror2(space, e, w_path)
+            raise wrap_oserror2(space, e, w_path, eintr_retry=False)
         return space.newlist_bytes(result)
     try:
         path = space.fsencode_w(w_path)
@@ -874,13 +898,13 @@
         try:
             result = rposix.fdlistdir(os.dup(fd))
         except OSError as e:
-            raise wrap_oserror(space, e)
+            raise wrap_oserror(space, e, eintr_retry=False)
     else:
         dirname = FileEncoder(space, w_path)
         try:
             result = rposix.listdir(dirname)
         except OSError as e:
-            raise wrap_oserror2(space, e, w_path)
+            raise wrap_oserror2(space, e, w_path, eintr_retry=False)
     len_result = len(result)
     result_w = [None] * len_result
     for i in range(len_result):
@@ -895,14 +919,14 @@
     try:
         return space.wrap(rposix.get_inheritable(fd))
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 @unwrap_spec(fd=c_int, inheritable=int)
 def set_inheritable(space, fd, inheritable):
     try:
         rposix.set_inheritable(fd, inheritable)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 _pipe_inhcache = rposix.SetNonInheritableCache()
 
@@ -910,10 +934,15 @@
     "Create a pipe.  Returns (read_end, write_end)."
     try:
         fd1, fd2 = rposix.pipe(rposix.O_CLOEXEC or 0)
+    except OSError as e:
+        raise wrap_oserror(space, e, eintr_retry=False)
+    try:
         _pipe_inhcache.set_non_inheritable(fd1)
         _pipe_inhcache.set_non_inheritable(fd2)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        rposix.c_close(fd2)
+        rposix.c_close(fd1)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.newtuple([space.wrap(fd1), space.wrap(fd2)])
 
 @unwrap_spec(flags=c_int)
@@ -921,7 +950,7 @@
     try:
         fd1, fd2 = rposix.pipe2(flags)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.newtuple([space.wrap(fd1), space.wrap(fd2)])
 
 @unwrap_spec(mode=c_int, dir_fd=DirFD(rposix.HAVE_FCHMODAT),
@@ -951,7 +980,7 @@
             dispatch_filename(rposix.chmod)(space, w_path, mode)
             return
         except OSError as e:
-            raise wrap_oserror2(space, e, w_path)
+            raise wrap_oserror2(space, e, w_path, eintr_retry=False)
 
     try:
         path = space.fsencode_w(w_path)
@@ -960,7 +989,12 @@
             raise oefmt(space.w_TypeError,
                 "argument should be string, bytes or integer, not %T", w_path)
         fd = unwrap_fd(space, w_path)
-        _chmod_fd(space, fd, mode)
+        # NB. CPython 3.5.2: unclear why os.chmod(fd) propagates EINTR
+        # to app-level, but os.fchmod(fd) retries automatically
+        try:
+            os.fchmod(fd, mode)
+        except OSError as e:
+            raise wrap_oserror(space, e, eintr_retry=False)
     else:
         try:
             _chmod_path(path, mode, dir_fd, follow_symlinks)
@@ -969,7 +1003,7 @@
                 # fchmodat() doesn't actually implement follow_symlinks=False
                 # so raise NotImplementedError in this case
                 raise argument_unavailable(space, "chmod", "follow_symlinks")
-            raise wrap_oserror2(space, e, w_path)
+            raise wrap_oserror2(space, e, w_path, eintr_retry=False)
 
 def _chmod_path(path, mode, dir_fd, follow_symlinks):
     if dir_fd != DEFAULT_DIR_FD or not follow_symlinks:
@@ -977,19 +1011,19 @@
     else:
         rposix.chmod(path, mode)
 
-def _chmod_fd(space, fd, mode):
-    try:
-        os.fchmod(fd, mode)
-    except OSError as e:
-        raise wrap_oserror(space, e)
-
-
 @unwrap_spec(fd=c_int, mode=c_int)
 def fchmod(space, fd, mode):
     """\
     Change the access permissions of the file given by file descriptor fd.
     """
-    _chmod_fd(space, fd, mode)
+    # NB. CPython 3.5.2: unclear why os.chmod(fd) propagates EINTR
+    # to app-level, but os.fchmod(fd) retries automatically
+    while True:
+        try:
+            os.fchmod(fd, mode)
+            break
+        except OSError as e:
+            wrap_oserror(space, e, eintr_retry=True)
 
 @unwrap_spec(src_dir_fd=DirFD(rposix.HAVE_RENAMEAT),
         dst_dir_fd=DirFD(rposix.HAVE_RENAMEAT))
@@ -1013,7 +1047,8 @@
         else:
             dispatch_filename_2(rposix.rename)(space, w_src, w_dst)
     except OSError as e:
-        raise wrap_oserror2(space, e, w_filename=w_src, w_filename2=w_dst)
+        raise wrap_oserror2(space, e, w_filename=w_src, w_filename2=w_dst,
+                            eintr_retry=False)
 
 @unwrap_spec(src_dir_fd=DirFD(rposix.HAVE_RENAMEAT),
         dst_dir_fd=DirFD(rposix.HAVE_RENAMEAT))
@@ -1037,7 +1072,8 @@
         else:
             dispatch_filename_2(rposix.replace)(space, w_src, w_dst)
     except OSError as e:
-        raise wrap_oserror2(space, e, w_filename=w_src, w_filename2=w_dst)
+        raise wrap_oserror2(space, e, w_filename=w_src, w_filename2=w_dst,
+                            eintr_retry=False)
 
 @unwrap_spec(mode=c_int, dir_fd=DirFD(rposix.HAVE_MKFIFOAT))
 def mkfifo(space, w_path, mode=0666, __kwonly__=None, dir_fd=DEFAULT_DIR_FD):
@@ -1049,14 +1085,18 @@
   and path should be relative; path will then be relative to that directory.
 dir_fd may not be implemented on your platform.
   If it is unavailable, using it will raise a NotImplementedError."""
-    try:
-        if rposix.HAVE_MKFIFOAT and dir_fd != DEFAULT_DIR_FD:
-            path = space.fsencode_w(w_path)
-            rposix.mkfifoat(path, mode, dir_fd)
-        else:
-            dispatch_filename(rposix.mkfifo)(space, w_path, mode)
-    except OSError as e:
-        raise wrap_oserror2(space, e, w_path)
+    # CPython 3.5.2: why does os.mkfifo() retry automatically if it
+    # gets EINTR, but not os.mkdir()?
+    while True:
+        try:
+            if rposix.HAVE_MKFIFOAT and dir_fd != DEFAULT_DIR_FD:
+                path = space.fsencode_w(w_path)
+                rposix.mkfifoat(path, mode, dir_fd)
+            else:
+                dispatch_filename(rposix.mkfifo)(space, w_path, mode)
+            break
+        except OSError as e:
+            wrap_oserror2(space, e, w_path, eintr_retry=True)
 
 @unwrap_spec(mode=c_int, device=c_int, dir_fd=DirFD(rposix.HAVE_MKNODAT))
 def mknod(space, w_path, mode=0600, device=0,
@@ -1074,14 +1114,16 @@
   and path should be relative; path will then be relative to that directory.
 dir_fd may not be implemented on your platform.
   If it is unavailable, using it will raise a NotImplementedError."""
-    try:
-        if rposix.HAVE_MKNODAT and dir_fd != DEFAULT_DIR_FD:
-            fname = space.fsencode_w(w_path)
-            rposix.mknodat(fname, mode, device, dir_fd)
-        else:
-            dispatch_filename(rposix.mknod)(space, w_path, mode, device)
-    except OSError as e:
-        raise wrap_oserror2(space, e, w_path)
+    while True:
+        try:
+            if rposix.HAVE_MKNODAT and dir_fd != DEFAULT_DIR_FD:
+                fname = space.fsencode_w(w_path)
+                rposix.mknodat(fname, mode, device, dir_fd)
+            else:
+                dispatch_filename(rposix.mknod)(space, w_path, mode, device)
+            break
+        except OSError as e:
+            wrap_oserror2(space, e, w_path, eintr_retry=True)
 
 @unwrap_spec(mask=c_int)
 def umask(space, mask):
@@ -1094,7 +1136,7 @@
     try:
         pid = os.getpid()
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.wrap(pid)
 
 @unwrap_spec(pid=c_int, signal=c_int)
@@ -1103,7 +1145,7 @@
     try:
         rposix.kill(pid, signal)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 @unwrap_spec(pgid=c_int, signal=c_int)
 def killpg(space, pgid, signal):
@@ -1111,7 +1153,7 @@
     try:
         os.killpg(pgid, signal)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 def abort(space):
     """Abort the interpreter immediately.  This 'dumps core' or otherwise fails
@@ -1149,7 +1191,8 @@
         else:
             rposix.link(src, dst)
     except OSError as e:
-        raise wrap_oserror(space, e, filename=src, filename2=dst)
+        raise wrap_oserror(space, e, filename=src, filename2=dst,
+                           eintr_retry=False)
 
 
 @unwrap_spec(dir_fd=DirFD(rposix.HAVE_SYMLINKAT))
@@ -1176,7 +1219,8 @@
         else:
             dispatch_filename_2(rposix.symlink)(space, w_src, w_dst)
     except OSError as e:
-        raise wrap_oserror2(space, e, w_filename=w_src, w_filename2=w_dst)
+        raise wrap_oserror2(space, e, w_filename=w_src, w_filename2=w_dst,
+                            eintr_retry=False)
 
 
 @unwrap_spec(
@@ -1197,7 +1241,7 @@
         else:
             result = call_rposix(rposix.readlink, path)
     except OSError as e:
-        raise wrap_oserror2(space, e, path.w_path)
+        raise wrap_oserror2(space, e, path.w_path, eintr_retry=False)
     w_result = space.newbytes(result)
     if space.isinstance_w(path.w_path, space.w_unicode):
         return space.fsdecode(w_result)
@@ -1245,7 +1289,7 @@
         except:
             # Don't clobber the OSError if the fork failed
             pass
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     if pid == 0:
         run_fork_hooks('child', space)
     else:
@@ -1258,12 +1302,17 @@
 
 def openpty(space):
     "Open a pseudo-terminal, returning open fd's for both master and slave 
end."
+    master_fd = slave_fd = -1
     try:
         master_fd, slave_fd = os.openpty()
         rposix.set_inheritable(master_fd, False)
         rposix.set_inheritable(slave_fd, False)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        if master_fd >= 0:
+            rposix.c_close(master_fd)
+        if slave_fd >= 0:
+            rposix.c_close(slave_fd)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.newtuple([space.wrap(master_fd), space.wrap(slave_fd)])
 
 def forkpty(space):
@@ -1277,12 +1326,16 @@
 
     Wait for completion of a given child process.
     """
-    try:
-        pid, status = os.waitpid(pid, options)
-    except OSError as e:
-        raise wrap_oserror(space, e)
+    while True:
+        try:
+            pid, status = os.waitpid(pid, options)
+            break
+        except OSError as e:
+            wrap_oserror(space, e, eintr_retry=True)
     return space.newtuple([space.wrap(pid), space.wrap(status)])
 
+# missing: waitid()
+
 @unwrap_spec(status=c_int)
 def _exit(space, status):
     os._exit(status)
@@ -1310,7 +1363,7 @@
     try:
         os.execv(command, args)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 
 def _env2interp(space, w_env):
@@ -1355,12 +1408,12 @@
         try:
             rposix.fexecve(fd, args, env)
         except OSError as e:
-            raise wrap_oserror(space, e)
+            raise wrap_oserror(space, e, eintr_retry=False)
     else:
         try:
             os.execve(path, args, env)
         except OSError as e:
-            raise wrap_oserror(space, e)
+            raise wrap_oserror(space, e, eintr_retry=False)
 
 @unwrap_spec(mode=int, path='fsencode')
 def spawnv(space, mode, path, w_argv):
@@ -1368,7 +1421,7 @@
     try:
         ret = os.spawnv(mode, path, args)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.wrap(ret)
 
 @unwrap_spec(mode=int, path='fsencode')
@@ -1378,7 +1431,7 @@
     try:
         ret = os.spawnve(mode, path, args, env)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.wrap(ret)
 
 
@@ -1486,7 +1539,7 @@
         #    something is wrong with the file, when it also
         #    could be the time stamp that gives a problem. */
         # so we use wrap_oserror() instead of wrap_oserror2() here
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 @specialize.arg(1)
 def do_utimes(space, func, arg, utime):
@@ -1503,7 +1556,7 @@
             func(arg, (atime, mtime))
     except OSError as e:
         # see comment above: don't use wrap_oserror2()
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 @specialize.argtype(1)
 def _dispatch_utime(path, times):
@@ -1546,7 +1599,7 @@
     try:
         r = os.uname()
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     l_w = [space.wrap_fsdecoded(i)
            for i in [r[0], r[1], r[2], r[3], r[4]]]
     w_tuple = space.newtuple(l_w)
@@ -1570,7 +1623,7 @@
     try:
         os.setuid(uid)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 @unwrap_spec(euid=c_uid_t)
 def seteuid(space, euid):
@@ -1581,7 +1634,7 @@
     try:
         os.seteuid(euid)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 @unwrap_spec(gid=c_gid_t)
 def setgid(space, gid):
@@ -1592,7 +1645,7 @@
     try:
         os.setgid(gid)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 @unwrap_spec(egid=c_gid_t)
 def setegid(space, egid):
@@ -1603,7 +1656,7 @@
     try:
         os.setegid(egid)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 @unwrap_spec(path='fsencode')
 def chroot(space, path):
@@ -1614,7 +1667,7 @@
     try:
         os.chroot(path)
     except OSError as e:
-        raise wrap_oserror(space, e, path)
+        raise wrap_oserror(space, e, path, eintr_retry=False)
     return space.w_None
 
 def getgid(space):
@@ -1646,7 +1699,7 @@
     try:
         list = os.getgroups()
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.newlist([wrap_gid(space, e) for e in list])
 
 def setgroups(space, w_groups):
@@ -1660,7 +1713,7 @@
     try:
         os.setgroups(list[:])
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 @unwrap_spec(username=str, gid=c_gid_t)
 def initgroups(space, username, gid):
@@ -1673,7 +1726,7 @@
     try:
         os.initgroups(username, gid)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 def getpgrp(space):
     """ getpgrp() -> pgrp
@@ -1690,7 +1743,7 @@
     try:
         os.setpgrp()
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.w_None
 
 def getppid(space):
@@ -1709,7 +1762,7 @@
     try:
         pgid = os.getpgid(pid)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.wrap(pgid)
 
 @unwrap_spec(pid=c_int, pgrp=c_int)
@@ -1721,7 +1774,7 @@
     try:
         os.setpgid(pid, pgrp)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.w_None
 
 @unwrap_spec(ruid=c_uid_t, euid=c_uid_t)
@@ -1733,7 +1786,7 @@
     try:
         os.setreuid(ruid, euid)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 @unwrap_spec(rgid=c_gid_t, egid=c_gid_t)
 def setregid(space, rgid, egid):
@@ -1744,7 +1797,7 @@
     try:
         os.setregid(rgid, egid)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 @unwrap_spec(pid=c_int)
 def getsid(space, pid):
@@ -1755,7 +1808,7 @@
     try:
         sid = os.getsid(pid)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.wrap(sid)
 
 def setsid(space):
@@ -1766,7 +1819,7 @@
     try:
         os.setsid()
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.w_None
 
 @unwrap_spec(fd=c_int)
@@ -1778,7 +1831,7 @@
     try:
         pgid = os.tcgetpgrp(fd)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.wrap(pgid)
 
 @unwrap_spec(fd=c_int, pgid=c_gid_t)
@@ -1790,7 +1843,7 @@
     try:
         os.tcsetpgrp(fd, pgid)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 def getresuid(space):
     """ getresuid() -> (ruid, euid, suid)
@@ -1800,7 +1853,7 @@
     try:
         (ruid, euid, suid) = os.getresuid()
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.newtuple([wrap_uid(space, ruid),
                            wrap_uid(space, euid),
                            wrap_uid(space, suid)])
@@ -1813,7 +1866,7 @@
     try:
         (rgid, egid, sgid) = os.getresgid()
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.newtuple([wrap_gid(space, rgid),
                            wrap_gid(space, egid),
                            wrap_gid(space, sgid)])
@@ -1827,7 +1880,7 @@
     try:
         os.setresuid(ruid, euid, suid)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 @unwrap_spec(rgid=c_gid_t, egid=c_gid_t, sgid=c_gid_t)
 def setresgid(space, rgid, egid, sgid):
@@ -1838,7 +1891,7 @@
     try:
         os.setresgid(rgid, egid, sgid)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 def declare_new_w_star(name):
     if name in ('WEXITSTATUS', 'WSTOPSIG', 'WTERMSIG'):
@@ -1864,7 +1917,7 @@
     try:
         return space.wrap_fsdecoded(os.ttyname(fd))
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 
 def confname_w(space, w_name, namespace):
@@ -1883,7 +1936,7 @@
     try:
         res = os.sysconf(num)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.wrap(res)
 
 @unwrap_spec(fd=c_int)
@@ -1892,7 +1945,7 @@
     try:
         res = os.fpathconf(fd, num)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.wrap(res)
 
 @unwrap_spec(path=path_or_fd(allow_fd=hasattr(os, 'fpathconf')))
@@ -1902,12 +1955,12 @@
         try:
             res = os.fpathconf(path.as_fd, num)
         except OSError as e:
-            raise wrap_oserror(space, e)
+            raise wrap_oserror(space, e, eintr_retry=False)
     else:
         try:
             res = os.pathconf(path.as_bytes, num)
         except OSError as e:
-            raise wrap_oserror2(space, e, path.w_path)
+            raise wrap_oserror2(space, e, path.w_path, eintr_retry=False)
     return space.wrap(res)
 
 def confstr(space, w_name):
@@ -1915,7 +1968,7 @@
     try:
         res = os.confstr(num)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.wrap(res)
 
 @unwrap_spec(
@@ -1959,7 +2012,7 @@
         try:
             os.fchown(fd, uid, gid)
         except OSError as e:
-            raise wrap_oserror(space, e)
+            raise wrap_oserror(space, e, eintr_retry=False)
     else:
         # String case
         try:
@@ -1974,7 +2027,7 @@
                 assert dir_fd == DEFAULT_DIR_FD
                 os.chown(path, uid, gid)
         except OSError as e:
-            raise wrap_oserror2(space, e, w_path)
+            raise wrap_oserror2(space, e, w_path, eintr_retry=False)
 
 
 @unwrap_spec(path='fsencode', uid=c_uid_t, gid=c_gid_t)
@@ -1987,7 +2040,7 @@
     try:
         os.lchown(path, uid, gid)
     except OSError as e:
-        raise wrap_oserror(space, e, path)
+        raise wrap_oserror(space, e, path, eintr_retry=False)
 
 @unwrap_spec(uid=c_uid_t, gid=c_gid_t)
 def fchown(space, w_fd, uid, gid):
@@ -1995,11 +2048,14 @@
 
 Change the owner and group id of the file given by file descriptor
 fd to the numeric uid and gid.  Equivalent to os.chown(fd, uid, gid)."""
+    # same comment than about os.chmod(fd) vs. os.fchmod(fd)
     fd = space.c_filedescriptor_w(w_fd)
-    try:
-        os.fchown(fd, uid, gid)
-    except OSError as e:
-        raise wrap_oserror(space, e)
+    while True:
+        try:
+            os.fchown(fd, uid, gid)
+            break
+        except OSError as e:
+            wrap_oserror(space, e, eintr_retry=True)
 
 def getloadavg(space):
     try:
@@ -2032,7 +2088,7 @@
     try:
         res = os.nice(increment)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.wrap(res)
 
 @unwrap_spec(size=int)
@@ -2045,7 +2101,9 @@
     try:
         return space.newbytes(rurandom.urandom(context, size))
     except OSError as e:
-        raise wrap_oserror(space, e)
+        # 'rurandom' should catch and retry internally if it gets EINTR
+        # (at least in os.read(), which is probably enough in practice)
+        raise wrap_oserror(space, e, eintr_retry=False)
 
 def ctermid(space):
     """ctermid() -> string
@@ -2083,7 +2141,7 @@
         try:
             info = nt._getfileinformation(fd)
         except OSError as e:
-            raise wrap_oserror(space, e)
+            raise wrap_oserror(space, e, eintr_retry=False)
         return space.newtuple([space.wrap(info[0]),
                                space.wrap(info[1]),
                                space.wrap(info[2])])
@@ -2096,7 +2154,7 @@
             raise OperationError(space.w_NotImplementedError,
                                  space.wrap(e.msg))
         except OSError as e:
-            raise wrap_oserror2(space, e, w_path)
+            raise wrap_oserror2(space, e, w_path, eintr_retry=False)
         return space.wrap(result)
 
 
@@ -2228,7 +2286,7 @@
     try:
         flags = rposix.get_status_flags(fd)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
     return space.newbool(flags & rposix.O_NONBLOCK == 0)
 
 @unwrap_spec(fd=c_int, blocking=int)
@@ -2241,4 +2299,4 @@
             flags |= rposix.O_NONBLOCK
         rposix.set_status_flags(fd, flags)
     except OSError as e:
-        raise wrap_oserror(space, e)
+        raise wrap_oserror(space, e, eintr_retry=False)
diff --git a/pypy/module/posix/interp_scandir.py 
b/pypy/module/posix/interp_scandir.py
--- a/pypy/module/posix/interp_scandir.py
+++ b/pypy/module/posix/interp_scandir.py
@@ -28,7 +28,7 @@
     try:
         dirp = rposix_scandir.opendir(path_bytes)
     except OSError as e:
-        raise wrap_oserror2(space, e, w_path)
+        raise wrap_oserror2(space, e, w_path, eintr_retry=False)
     path_prefix = path_bytes
     if len(path_prefix) > 0 and path_prefix[-1] != '/':
         path_prefix += '/'
@@ -85,7 +85,8 @@
                 try:
                     entry = rposix_scandir.nextentry(self.dirp)
                 except OSError as e:
-                    raise self.fail(wrap_oserror2(space, e, 
self.w_path_prefix))
+                    raise self.fail(wrap_oserror2(space, e, self.w_path_prefix,
+                                                  eintr_retry=False))
                 if not entry:
                     raise self.fail()
                 assert rposix_scandir.has_name_bytes(entry)
@@ -235,7 +236,8 @@
         except OSError as e:
             if e.errno == ENOENT:    # not found
                 return -1
-            raise wrap_oserror2(self.space, e, self.fget_path(self.space))
+            raise wrap_oserror2(self.space, e, self.fget_path(self.space),
+                                eintr_retry=False)
         return stat.S_IFMT(st.st_mode)
 
     def is_dir(self, follow_symlinks):
@@ -287,7 +289,8 @@
         try:
             st = self.get_stat_or_lstat(follow_symlinks)
         except OSError as e:
-            raise wrap_oserror2(space, e, self.fget_path(space))
+            raise wrap_oserror2(space, e, self.fget_path(space),
+                                eintr_retry=False)
         return build_stat_result(space, st)
 
     def descr_inode(self, space):
diff --git a/pypy/module/posix/test/test_posix2.py 
b/pypy/module/posix/test/test_posix2.py
--- a/pypy/module/posix/test/test_posix2.py
+++ b/pypy/module/posix/test/test_posix2.py
@@ -7,6 +7,7 @@
 
 from rpython.tool.udir import udir
 from pypy.tool.pytest.objspace import gettestobjspace
+from pypy.interpreter.gateway import interp2app
 from rpython.translator.c.test.test_extfunc import need_sparse_files
 from rpython.rlib import rposix
 
@@ -1365,3 +1366,40 @@
         if os.name == 'posix':
             assert os.open in os.supports_dir_fd  # openat()
 
+
+class AppTestPep475Retry:
+    spaceconfig = {'usemodules': USEMODULES}
+
+    def setup_class(cls):
+        if os.name != 'posix':
+            skip("xxx tests are posix-only")
+        if cls.runappdirect:
+            skip("xxx does not work with -A")
+
+        def fd_data_after_delay(space):
+            g = os.popen("sleep 5 && echo hello", "r")
+            cls._keepalive_g = g
+            return space.wrap(g.fileno())
+
+        cls.w_posix = space.appexec([], GET_POSIX)
+        cls.w_fd_data_after_delay = cls.space.wrap(
+            interp2app(fd_data_after_delay))
+
+    def test_pep475_retry_read(self):
+        import _signal as signal
+        signalled = []
+
+        def foo(*args):
+            signalled.append("ALARM")
+
+        signal.signal(signal.SIGALRM, foo)
+        try:
+            fd = self.fd_data_after_delay()
+            signal.alarm(1)
+            got = self.posix.read(fd, 100)
+            self.posix.close(fd)
+        finally:
+            signal.signal(signal.SIGALRM, signal.SIG_DFL)
+
+        assert signalled != []
+        assert got.startswith(b'h')
diff --git a/pypy/module/select/interp_epoll.py 
b/pypy/module/select/interp_epoll.py
--- a/pypy/module/select/interp_epoll.py
+++ b/pypy/module/select/interp_epoll.py
@@ -7,6 +7,7 @@
 from pypy.interpreter.error import OperationError, oefmt
 from pypy.interpreter.error import exception_from_saved_errno
 from pypy.interpreter.typedef import TypeDef, GetSetProperty
+from pypy.interpreter import timeutils
 from rpython.rtyper.lltypesystem import lltype, rffi
 from rpython.rtyper.tool import rffi_platform
 from rpython.rlib._rsocket_rffi import socketclose, FD_SETSIZE
@@ -156,9 +157,11 @@
     def descr_poll(self, space, timeout=-1.0, maxevents=-1):
         self.check_closed(space)
         if timeout < 0:
-            timeout = -1.0
+            end_time = 0.0
+            itimeout = -1
         else:
-            timeout *= 1000.0
+            end_time = timeutils.monotonic(space) + timeout
+            itimeout = int(timeout * 1000.0 + 0.999)
 
         if maxevents == -1:
             maxevents = FD_SETSIZE - 1
@@ -167,9 +170,18 @@
                         "maxevents must be greater than 0, not %d", maxevents)
 
         with lltype.scoped_alloc(rffi.CArray(epoll_event), maxevents) as evs:
-            nfds = epoll_wait(self.epfd, evs, maxevents, int(timeout))
-            if nfds < 0:
-                raise exception_from_saved_errno(space, space.w_IOError)
+            while True:
+                nfds = epoll_wait(self.epfd, evs, maxevents, itimeout)
+                if nfds < 0:
+                    if get_saved_errno() == errno.EINTR:
+                        space.getexecutioncontext().checksignals()
+                        if itimeout >= 0:
+                            timeout = end_time - timeutils.monotonic(space)
+                            timeout = max(timeout, 0.0)
+                            itimeout = int(timeout * 1000.0 + 0.999)
+                        continue
+                    raise exception_from_saved_errno(space, space.w_IOError)
+                break
 
             elist_w = [None] * nfds
             for i in xrange(nfds):
diff --git a/pypy/module/select/interp_kqueue.py 
b/pypy/module/select/interp_kqueue.py
--- a/pypy/module/select/interp_kqueue.py
+++ b/pypy/module/select/interp_kqueue.py
@@ -180,6 +180,7 @@
                             raise oefmt(space.w_ValueError,
                                         "Timeout must be None or >= 0, got %s",
                                         str(_timeout))
+                        XXX   # fix test_select_signal.py first, for PEP475!
                         sec = int(_timeout)
                         nsec = int(1e9 * (_timeout - sec))
                         rffi.setintfield(timeout, 'c_tv_sec', sec)
diff --git a/pypy/module/select/interp_select.py 
b/pypy/module/select/interp_select.py
--- a/pypy/module/select/interp_select.py
+++ b/pypy/module/select/interp_select.py
@@ -10,6 +10,7 @@
 from pypy.interpreter.gateway import (
     Unwrapper, WrappedDefault, interp2app, unwrap_spec)
 from pypy.interpreter.typedef import TypeDef
+from pypy.interpreter import timeutils
 
 defaultevents = rpoll.POLLIN | rpoll.POLLOUT | rpoll.POLLPRI
 
@@ -49,8 +50,10 @@
 
     @unwrap_spec(w_timeout=WrappedDefault(None))
     def poll(self, space, w_timeout):
+        """WARNING: the timeout parameter is in **milliseconds**!"""
         if space.is_w(w_timeout, space.w_None):
             timeout = -1
+            end_time = 0
         else:
             # we want to be compatible with cpython and also accept things
             # that can be casted to integer (I think)
@@ -61,19 +64,29 @@
                 raise oefmt(space.w_TypeError,
                             "timeout must be an integer or None")
             timeout = space.c_int_w(w_timeout)
+            end_time = timeutils.monotonic(space) + timeout * 0.001
 
         if self.running:
             raise oefmt(space.w_RuntimeError, "concurrent poll() invocation")
-        self.running = True
-        try:
-            retval = rpoll.poll(self.fddict, timeout)
-        except rpoll.PollError as e:
-            message = e.get_msg()
-            raise OperationError(space.w_OSError,
-                                 space.newtuple([space.wrap(e.errno),
-                                                 space.wrap(message)]))
-        finally:
-            self.running = False
+        while True:
+            self.running = True
+            try:
+                retval = rpoll.poll(self.fddict, timeout)
+            except rpoll.PollError as e:
+                if e.errno == errno.EINTR:
+                    space.getexecutioncontext().checksignals()
+                    timeout = int((end_time - timeutils.monotonic(space))
+                                  * 1000.0 + 0.999)   # round up
+                    if timeout < 0:
+                        timeout = 0
+                    continue
+                message = e.get_msg()
+                raise OperationError(space.w_OSError,
+                                     space.newtuple([space.wrap(e.errno),
+                                                     space.wrap(message)]))
+            finally:
+                self.running = False
+            break
 
         retval_w = []
         for fd, revents in retval:
@@ -112,7 +125,7 @@
 
 
 def _call_select(space, iwtd_w, owtd_w, ewtd_w,
-                 ll_inl, ll_outl, ll_errl, ll_timeval):
+                 ll_inl, ll_outl, ll_errl, ll_timeval, timeout):
     fdlistin = fdlistout = fdlisterr = None
     nfds = -1
     if ll_inl:
@@ -122,13 +135,32 @@
     if ll_errl:
         fdlisterr, nfds = _build_fd_set(space, ewtd_w, ll_errl, nfds)
 
-    res = _c.select(nfds + 1, ll_inl, ll_outl, ll_errl, ll_timeval)
+    if ll_timeval:
+        end_time = timeutils.monotonic(space) + timeout
+    else:
+        end_time = 0.0
 
-    if res < 0:
-        errno = _c.geterrno()
-        msg = _c.socket_strerror_str(errno)
-        raise OperationError(space.w_OSError, space.newtuple([
-            space.wrap(errno), space.wrap(msg)]))
+    while True:
+        if ll_timeval:
+            i = int(timeout)
+            rffi.setintfield(ll_timeval, 'c_tv_sec', i)
+            rffi.setintfield(ll_timeval, 'c_tv_usec', int((timeout-i)*1000000))
+
+        res = _c.select(nfds + 1, ll_inl, ll_outl, ll_errl, ll_timeval)
+
+        if res >= 0:
+            break     # normal path
+        err = _c.geterrno()
+        if err != errno.EINTR:
+            msg = _c.socket_strerror_str(err)
+            raise OperationError(space.w_OSError, space.newtuple([
+                space.wrap(err), space.wrap(msg)]))
+        # got EINTR, automatic retry
+        space.getexecutioncontext().checksignals()
+        if timeout > 0.0:
+            timeout = end_time - timeutils.monotonic(space)
+            if timeout < 0.0:
+                timeout = 0.0
 
     resin_w = []
     resout_w = []
@@ -193,15 +225,12 @@
             ll_errl = lltype.malloc(_c.fd_set.TO, flavor='raw')
         if timeout >= 0.0:
             ll_timeval = rffi.make(_c.timeval)
-            i = int(timeout)
-            rffi.setintfield(ll_timeval, 'c_tv_sec', i)
-            rffi.setintfield(ll_timeval, 'c_tv_usec', int((timeout-i)*1000000))
 
         # Call this as a separate helper to avoid a large piece of code
         # in try:finally:.  Needed for calling further _always_inline_
         # helpers like _build_fd_set().
         return _call_select(space, iwtd_w, owtd_w, ewtd_w,
-                            ll_inl, ll_outl, ll_errl, ll_timeval)
+                            ll_inl, ll_outl, ll_errl, ll_timeval, timeout)
     finally:
         if ll_timeval:
             lltype.free(ll_timeval, flavor='raw')
diff --git a/pypy/module/select/test/test_select_signal.py 
b/pypy/module/select/test/test_select_signal.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/select/test/test_select_signal.py
@@ -0,0 +1,48 @@
+
+class AppTestSelectSignal:
+    spaceconfig = {
+        "usemodules": ['select', 'time', 'signal'],
+    }
+
+    def test_pep475_retry(self):
+        import select, time
+        import _signal as signal
+
+        def foo(*args):
+            signalled.append("ALARM")
+
+        # a list of functions that will do nothing more than sleep for 3
+        # seconds
+        cases = [(select.select, [], [], [], 3.0)]
+
+        if hasattr(select, 'poll'):
+            import posix
+            poll = select.poll()
+            cases.append((poll.poll, 3000))    # milliseconds
+
+        if hasattr(select, 'epoll'):
+            epoll = select.epoll()
+            cases.append((epoll.poll, 3.0))
+
+        if hasattr(select, 'kqueue'):
+            kqueue = select.kqueue()
_______________________________________________
pypy-commit mailing list
pypy-commit@python.org
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to