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