RFC: PEP 475, Retry system calls failing with EINTR
HTML version: http://legacy.python.org/dev/peps/pep-0475/ PEP: 475 Title: Retry system calls failing with EINTR Version: $Revision$ Last-Modified: $Date$ Author: Charles-François Natali <cf.natali@gmail.com>, Victor Stinner <victor.stinner@gmail.com> Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 29-July-2014 Python-Version: 3.5 Abstract ======== Retry system calls failing with the ``EINTR`` error and recompute timeout if needed. Rationale ========= Interrupted system calls ------------------------ On POSIX systems, signals are common. Your program must be prepared to handle them. Examples of signals: * The most common signal is ``SIGINT``, signal sent when CTRL+c is pressed. By default, Python raises a ``KeyboardInterrupt`` exception when this signal is received. * When running subprocesses, the ``SIGCHLD`` signal is sent when a child process exits. * Resizing the terminal sends the ``SIGWINCH`` signal to the applications running in the terminal. * Putting the application in background (ex: press CTRL-z and then type the ``bg`` command) sends the ``SIGCONT`` signal. Writing a signal handler is difficult, only "async-signal safe" functions can be called. For example, ``printf()`` and ``malloc()`` are not async-signal safe. When a signal is sent to a process calling a system call, the system call can fail with the ``EINTR`` error to give the program an opportunity to handle the signal without the restriction on signal safe functions. Depending on the platform, on the system call and the ``SA_RESTART`` flag, the system call may or may not fail with ``EINTR``. If the signal handler was set with the ``SA_RESTART`` flag set, the kernel retries some the system call instead of failing with ``EINTR``. For example, ``read()`` is retried, whereas ``select()`` is not retried. The Python function ``signal.signal()`` clears the ``SA_RESTART`` flag when setting the signal handler: all system calls should fail with ``EINTR`` in Python. The problem is that handling ``EINTR`` should be done for all system calls. The problem is similar to handling errors in the C language which does not have exceptions: you must check all function returns to check for error, and usually duplicate the code checking for errors. Python does not have this issue, it uses exceptions to notify errors. Current status -------------- Currently in Python, the code to handle the ``InterruptedError`` exception (``EINTR`` error) is duplicated on case by case. Only a few Python modules handle this exception, and fixes usually took several years to cover a whole module. Example of code retrying ``file.read()`` on ``InterruptedError``:: while True: try: data = file.read(size) break except InterruptedError: continue List of Python modules of the standard library which handle ``InterruptedError``: * ``asyncio`` * ``asyncore`` * ``io``, ``_pyio`` * ``multiprocessing`` * ``selectors`` * ``socket`` * ``socketserver`` * ``subprocess`` Other programming languages like Perl, Java and Go already retry system calls failing with ``EINTR``. Use Case 1: Don't Bother With Signals ------------------------------------- In most cases, you don't want to be interrupted by signals and you don't expect to get ``InterruptedError`` exceptions. For example, do you really want to write such complex code for an "Hello World" example? :: while True: try: print("Hello World") break except InterruptedError: continue ``InterruptedError`` can happen in unexpected places. For example, ``os.close()`` and ``FileIO.close()`` can raises ``InterruptedError``: see the article `close() and EINTR <http://alobbs.com/post/54503240599/close-and-eintr>`_. The `Python issues related to EINTR`_ section below gives examples of bugs caused by "EINTR". The expectation is that Python hides the ``InterruptedError``: retry system calls failing with the ``EINTR`` error. Use Case 2: Be notified of signals as soon as possible ------------------------------------------------------ Sometimes, you expect some signals and you want to handle them as soon as possible. For example, you may want to quit immediatly a program using the ``CTRL+c`` keyboard shortcut. Some signals are not interesting and should not interrupt the the application. There are two options to only interrupt an application on some signals: * Raise an exception in the signal handler, like ``KeyboardInterrupt`` for ``SIGINT`` * Use a I/O multiplexing function like ``select()`` with the Python signal "wakeup" file descriptor: see the function ``signal.set_wakeup_fd()``. Proposition =========== If a system call fails with ``EINTR``, Python must call signal handlers: call ``PyErr_CheckSignals()``. If a signal handler raises an exception, the Python function fails with the exception. Otherwise, the system call is retried. If the system call takes a timeout parameter, the timeout is recomputed. Modified functions ------------------ Example of functions that need to be modified: * ``os.read()``, ``io.FileIO.read()``, ``io.FileIO.readinto()`` * ``os.write()``, ``io.FileIO.write()`` * ``os.waitpid()`` * ``socket.accept()`` * ``socket.connect()`` * ``socket.recv()``, ``socket.recv_into()`` * ``socket.recv_from()`` * ``socket.send()`` * ``socket.sendto()`` * ``time.sleep()`` * ``select.select()`` * ``select.poll()`` * ``select.epoll.poll()`` * ``select.devpoll.poll()`` * ``select.kqueue.control()`` * ``selectors.SelectSelector.select()`` and other selector classes Note: The ``selector`` module already retries on ``InterruptedError``, but it doesn't recompute the timeout yet. Backward Compatibility ====================== Applications relying on the fact that system calls are interrupted with ``InterruptedError`` will hang. The authors of this PEP don't think that such application exist. If such applications exist, they are not portable and are subject to race conditions (deadlock if the signal comes before the system call). These applications must be fixed to handle signals differently, to have a reliable behaviour on all platforms and all Python versions. For example, use a signal handler which raises an exception, or use a wakeup file descriptor. For applications using event loops, ``signal.set_wakeup_fd()`` is the recommanded option to handle signals. The signal handler writes signal numbers into the file descriptor and the event loop is awaken to read them. The event loop can handle these signals without the restriction of signal handlers. Appendix ======== Wakeup file descriptor ---------------------- Since Python 3.3, ``signal.set_wakeup_fd()`` writes the signal number into the file descriptor, whereas it only wrote a null byte before. It becomes possible to handle different signals using the wakeup file descriptor. Linux has a ``signalfd()`` which provides more information on each signal. For example, it's possible to know the pid and uid who sent the signal. This function is not exposed in Python yet (see the `issue 12304 <http://bugs.python.org/issue12304>`_). On Unix, the ``asyncio`` module uses the wakeup file descriptor to wake up its event loop. Multithreading -------------- A C signal handler can be called from any thread, but the Python signal handler should only be called in the main thread. Python has a ``PyErr_SetInterrupt()`` function which calls the ``SIGINT`` signal handler to interrupt the Python main thread. Signals on Windows ------------------ Control events ^^^^^^^^^^^^^^ Windows uses "control events": * ``CTRL_BREAK_EVENT``: Break (``SIGBREAK``) * ``CTRL_CLOSE_EVENT``: Close event * ``CTRL_C_EVENT``: CTRL+C (``SIGINT``) * ``CTRL_LOGOFF_EVENT``: Logoff * ``CTRL_SHUTDOWN_EVENT``: Shutdown The `SetConsoleCtrlHandler() function <http://msdn.microsoft.com/en-us/library/windows/desktop/ms686016%28v=vs.85%29.aspx>`_ can be used to install a control handler. The ``CTRL_C_EVENT`` and ``CTRL_BREAK_EVENT`` events can be sent to a process using the `GenerateConsoleCtrlEvent() function <http://msdn.microsoft.com/en-us/library/windows/desktop/ms683155%28v=vs.85%29.aspx>`_. This function is exposed in Python as ``os.kill()``. Signals ^^^^^^^ The following signals are supported on Windows: * ``SIGABRT`` * ``SIGBREAK`` (``CTRL_BREAK_EVENT``): signal only available on Windows * ``SIGFPE`` * ``SIGILL`` * ``SIGINT`` (``CTRL_C_EVENT``) * ``SIGSEGV`` * ``SIGTERM`` SIGINT ^^^^^^ The default Python signal handler for ``SIGINT`` sets a Windows event object: ``sigint_event``. ``time.sleep()`` is implemented with ``WaitForSingleObjectEx()``, it waits for the ``sigint_event`` object using ``time.sleep()`` parameter as the timeout. So the sleep can be interrupted by ``SIGINT``. ``_winapi.WaitForMultipleObjects()`` automatically adds ``sigint_event`` to the list of watched handles, so it can also be interrupted. ``PyOS_StdioReadline()`` also used ``sigint_event`` when ``fgets()`` failed to check if Ctrl-C or Ctrl-Z was pressed. Links ----- Misc ^^^^ * `glibc manual: Primitives Interrupted by Signals <http://www.gnu.org/software/libc/manual/html_node/Interrupted-Primitives.html>`_ * `Bug #119097 for perl5: print returning EINTR in 5.14 <https://rt.perl.org/Public/Bug/Display.html?id=119097>`_. Python issues related to EINTR ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The main issue is: `handle EINTR in the stdlib <http://bugs.python.org/issue18885>`_. Open issues: * `Add a new signal.set_wakeup_socket() function <http://bugs.python.org/issue22018>`_ * `signal.set_wakeup_fd(fd): set the fd to non-blocking mode <http://bugs.python.org/issue22042>`_ * `Use a monotonic clock to compute timeouts <http://bugs.python.org/issue22043>`_ * `sys.stdout.write on OS X is not EINTR safe <http://bugs.python.org/issue22007>`_ * `platform.uname() not EINTR safe <http://bugs.python.org/issue21772>`_ * `asyncore does not handle EINTR in recv, send, connect, accept, <http://bugs.python.org/issue11266>`_ * `socket.create_connection() doesn't handle EINTR properly <http://bugs.python.org/issue20611>`_ Closed issues: * `Interrupted system calls are not retried <http://bugs.python.org/issue9867>`_ * `Solaris: EINTR exception in select/socket calls in telnetlib <http://bugs.python.org/issue1049450>`_ * `subprocess: Popen.communicate() doesn't handle EINTR in some cases <http://bugs.python.org/issue12493>`_ * `multiprocessing.util._eintr_retry doen't recalculate timeouts <http://bugs.python.org/issue12338>`_ * `file readline, readlines & readall methods can lose data on EINTR <http://bugs.python.org/issue12268>`_ * `multiprocessing BaseManager serve_client() does not check EINTR on recv <http://bugs.python.org/issue17097>`_ * `selectors behaviour on EINTR undocumented <http://bugs.python.org/issue19849>`_ * `asyncio: limit EINTR occurrences with SA_RESTART <http://bugs.python.org/issue19850>`_ * `smtplib.py socket.create_connection() also doesn't handle EINTR properly <http://bugs.python.org/issue21602>`_ * `Faulty RESTART/EINTR handling in Parser/myreadline.c <http://bugs.python.org/issue11650>`_ * `test_httpservers intermittent failure, test_post and EINTR <http://bugs.python.org/issue3771>`_ * `os.spawnv(P_WAIT, ...) on Linux doesn't handle EINTR <http://bugs.python.org/issue686667>`_ * `asyncore fails when EINTR happens in pol <http://bugs.python.org/issue517554>`_ * `file.write and file.read don't handle EINTR <http://bugs.python.org/issue10956>`_ * `socket.readline() interface doesn't handle EINTR properly <http://bugs.python.org/issue1628205>`_ * `subprocess is not EINTR-safe <http://bugs.python.org/issue1068268>`_ * `SocketServer doesn't handle syscall interruption <http://bugs.python.org/issue7978>`_ * `subprocess deadlock when read() is interrupted <http://bugs.python.org/issue17367>`_ * `time.sleep(1): call PyErr_CheckSignals() if the sleep was interrupted <http://bugs.python.org/issue12462>`_ * `siginterrupt with flag=False is reset when signal received <http://bugs.python.org/issue8354>`_ * `need siginterrupt() on Linux - impossible to do timeouts <http://bugs.python.org/issue1089358>`_ * `[Windows] Can not interrupt time.sleep() <http://bugs.python.org/issue581232>`_ Python issues related to signals ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Open issues: * `signal.default_int_handler should set signal number on the raised exception <http://bugs.python.org/issue17182>`_ * `expose signalfd(2) in the signal module <http://bugs.python.org/issue12304>`_ * `missing return in win32_kill? <http://bugs.python.org/issue14484>`_ * `Interrupts are lost during readline PyOS_InputHook processing <http://bugs.python.org/issue3180>`_ * `cannot catch KeyboardInterrupt when using curses getkey() <http://bugs.python.org/issue1687125>`_ * `Deferred KeyboardInterrupt in interactive mode <http://bugs.python.org/issue16151>`_ Closed issues: * `sys.interrupt_main() <http://bugs.python.org/issue753733>`_ Copyright ========= This document has been placed in the public domain. .. Local Variables: mode: indented-text indent-tabs-mode: nil sentence-end-double-space: t fill-column: 70 coding: utf-8 End:
Victor Stinner <victor.stinner@gmail.com>:
Proposition ===========
If a system call fails with ``EINTR``, Python must call signal handlers: call ``PyErr_CheckSignals()``. If a signal handler raises an exception, the Python function fails with the exception. Otherwise, the system call is retried. If the system call takes a timeout parameter, the timeout is recomputed.
Signals are tricky and easy to get wrong, to be sure, but I think it is dangerous for Python to unconditionally commandeer signal handling. If the proposition is accepted, there should be a way to opt out. Marko
Hi, Sorry but I don't understand your remark. What is your problem with retrying syscall on EINTR? Can you please elaborate? What do you mean by "get wrong"? Victor Le dimanche 31 août 2014, Marko Rauhamaa <marko@pacujo.net> a écrit :
Victor Stinner <victor.stinner@gmail.com <javascript:;>>:
Proposition ===========
If a system call fails with ``EINTR``, Python must call signal handlers: call ``PyErr_CheckSignals()``. If a signal handler raises an exception, the Python function fails with the exception. Otherwise, the system call is retried. If the system call takes a timeout parameter, the timeout is recomputed.
Signals are tricky and easy to get wrong, to be sure, but I think it is dangerous for Python to unconditionally commandeer signal handling. If the proposition is accepted, there should be a way to opt out.
Marko
Victor Stinner <victor.stinner@gmail.com>:
Sorry but I don't understand your remark. What is your problem with retrying syscall on EINTR?
The application will often want the EINTR return (exception) instead of having the function resume on its own.
Can you please elaborate? What do you mean by "get wrong"?
Proper handling of signals is difficult and at times even impossible. For example it is impossible to wake up reliably from the select(2) system call when a signal is generated (which is why linux now has pselect). Marko
On 08/31/2014 02:19 PM, Marko Rauhamaa wrote:
Victor Stinner <victor.stinner@gmail.com>:
Sorry but I don't understand your remark. What is your problem with retrying syscall on EINTR?
The application will often want the EINTR return (exception) instead of having the function resume on its own.
Examples? As an ignorant person in this area, I do not know why I would ever want to have EINTR raised instead just getting the results of, say, my read() call. -- ~Ethan~
Le dimanche 31 août 2014, Marko Rauhamaa <marko@pacujo.net> a écrit :
Victor Stinner <victor.stinner@gmail.com <javascript:;>>:
Sorry but I don't understand your remark. What is your problem with retrying syscall on EINTR?
The application will often want the EINTR return (exception) instead of having the function resume on its own.
This case is described as the use case #2 in the PEP, so it is supported. As written in the PEP, if you want to be notified of the signal, set a signal handler which raises an exception. For example the default signal handler for SIGINT raises KeyboardInterrupt.
Can you please elaborate? What do you mean by "get wrong"?
Proper handling of signals is difficult and at times even impossible. For example it is impossible to wake up reliably from the select(2) system call when a signal is generated (which is why linux now has pselect).
In my experience, using signal.set_wakeup_fd() works well with select(), even on Windows. The PEP promotes this. It is even thread safe. I don't know issues of signals with select() (and without a file descriptor used to wake up it). Python now exposes signal.pthread_sigmask(), I don't know if it helps. In my experience, signals don't play well with multithreading. On FreeBSD, the signal is send to a "random" thread. So you must have the same signal mask on all threads if you want to rely on them. But I don't get you point. How does this PEP make the situation worse? Victor
Victor Stinner <victor.stinner@gmail.com>:
But I don't get you point. How does this PEP make the situation worse?
Did I say it would? I just wanted to make sure the system call resumption doesn't become mandatory. Haven't thought through what the exception raising technique would entail. It might be perfectly ok apart from being a change to the signal handler API.
I don't know issues of signals with select() (and without a file descriptor used to wake up it).
A signal handler often sets a flag, which is inspected when select() returns. The problem is when a signal arrives between testing the flag and calling select(). The pselect() system call allows you to block signals and have the system call unblock them correctly to avoid the race. Marko
Ethan Furman <ethan@stoneleaf.us>:
On 08/31/2014 02:19 PM, Marko Rauhamaa wrote:
The application will often want the EINTR return (exception) instead of having the function resume on its own.
Examples?
As an ignorant person in this area, I do not know why I would ever want to have EINTR raised instead just getting the results of, say, my read() call.
Say you are writing data into a file and it takes a long time (because there is a lot of data or the medium is very slow or there is a hardware problem). You might have designed in a signaling scheme to address just this possibility. Then, the system call had better come out right away without trying to complete the full extent of the call. If a signal is received when read() or write() has completed its task partially (> 0 bytes), no EINTR is returned but the partial count. Obviously, Python should take that possibility into account so that raising an exception in the signal handler (as mandated by the PEP) doesn't cause the partial result to be lost on os.read() or os.write(). Marko
Victor Stinner wrote:
As written in the PEP, if you want to be notified of the signal, set a signal handler which raises an exception.
I'm not convinced that this covers all possible use cases. It might be all right if you have control over the signal handler, but what if you don't? I think it's best if the functions in the os module remain thin wrappers that expose the OS functionality as fully and directly as possible. Anything else should be provided by higher-level facilities. -- Greg
Le 1 sept. 2014 00:04, "Marko Rauhamaa" <marko@pacujo.net> a écrit :
Victor Stinner <victor.stinner@gmail.com>:
But I don't get you point. How does this PEP make the situation worse?
Did I say it would? I just wanted to make sure the system call resumption doesn't become mandatory.
The syscall is only retried on EINTR if the signal handler didn't raise an exception. So it is not always retried: "Proposition If a system call fails with ``EINTR``, Python must call signalhandlers: call ``PyErr_CheckSignals()``. If a signal handler raisesan exception, the Python function fails with the exception.Otherwise, the system call is retried. If the system call takes atimeout parameter, the timeout is recomputed."
Haven't thought through what the exception raising technique would entail. It might be perfectly ok apart from being a change to the signal handler API.
I don't think that it is safe to expect an InterruptedError if the signal handler doesn't raise an exception. Many Python module already retry the syscall on EINTR. So I'm not sure that the PEP is really a major change. It's just to make Python more homogeneous, always have the same reliable and portable behaviour. Victor
On Mon, 01 Sep 2014 01:15:12 +0300 Marko Rauhamaa <marko@pacujo.net> wrote:
If a signal is received when read() or write() has completed its task partially (> 0 bytes), no EINTR is returned but the partial count. Obviously, Python should take that possibility into account so that raising an exception in the signal handler (as mandated by the PEP) doesn't cause the partial result to be lost on os.read() or os.write().
If the signal handler is called, the exception *will* be raised. There's no guarantee at which point in the Python code it will be raised (it's implementation-dependent), but it's near impossible to protect regular Python code against such asynchronous exceptions. Which is why you should switch to a wakeup fd scheme as mentioned by Victor, if you want to rely on signals at all. Regards Antoine.
Le 1 sept. 2014 00:17, "Marko Rauhamaa" <marko@pacujo.net> a écrit :
If a signal is received when read() or write() has completed its task partially (> 0 bytes), no EINTR is returned but the partial count. Obviously, Python should take that possibility into account so that raising an exception in the signal handler (as mandated by the PEP) doesn't cause the partial result to be lost on os.read() or os.write().
This case is unrelated to the PEP, the PEP only changes the behaviour when a syscall fails with EINTR. (When Python gets a signal, the C signal handler is immediatly called. The handler sets a flag which is cheched before executing an instruction. The Python signal handler can be called between two Python instructions. In some cases, it may be called earlier in functions checking manually the flag. IMO the exact behaviour is undefined. Python tries to call the Python signal handler as soon as possible, with a low performance overhead.) Victor
On Sun, Aug 31, 2014 at 3:28 PM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Victor Stinner wrote:
As written in the PEP, if you want to be notified of the signal, set a signal handler which raises an exception.
I'm not convinced that this covers all possible use cases. It might be all right if you have control over the signal handler, but what if you don't?
I think it's best if the functions in the os module remain thin wrappers that expose the OS functionality as fully and directly as possible. Anything else should be provided by higher-level facilities.
I'm inclined to agree about keeping the os module thin. If we were to recreate Python today, from scratch, it might make sense to hide this by default, but now there's almost certainly code out there that depends on the current behavior. But I also agree that it's hard to pin down which higher level Python library calls are going to be using system calls. My http://stromberg.dnsalias.org/~strombrg/pypty/ program had a problem with window resizing for a while (SIGWINCH), and although I use it pretty much daily now without problems, I'm still not sure I got 100% of the possibilities covered. Fortunately, wrapping a system call can be as simple as: def retry_on_eintr(function, *args, **kw): ''' Retry a system call until one of the following happens: 1) It succeeds 2) It errors with something other than EINTR ''' while True: try: return function(*args, **kw) except OSError: nothing, extra, nothing2 = sys.exc_info() dummy = nothing dummy = nothing2 if extra.errno == errno.EINTR: continue else: raise Note that os.read() and os.write() need different handling.
On Sun, 31 Aug 2014 20:14:50 -0700, Dan Stromberg <drsalists@gmail.com> wrote:
On Sun, Aug 31, 2014 at 3:28 PM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Victor Stinner wrote:
As written in the PEP, if you want to be notified of the signal, set a signal handler which raises an exception.
I'm not convinced that this covers all possible use cases. It might be all right if you have control over the signal handler, but what if you don't?
I think it's best if the functions in the os module remain thin wrappers that expose the OS functionality as fully and directly as possible. Anything else should be provided by higher-level facilities.
I'm inclined to agree about keeping the os module thin. If we were to recreate Python today, from scratch, it might make sense to hide this by default, but now there's almost certainly code out there that depends on the current behavior.
But I also agree that it's hard to pin down which higher level Python library calls are going to be using system calls. My http://stromberg.dnsalias.org/~strombrg/pypty/ program had a problem with window resizing for a while (SIGWINCH), and although I use it pretty much daily now without problems, I'm still not sure I got 100% of the possibilities covered.
Fortunately, wrapping a system call can be as simple as:
def retry_on_eintr(function, *args, **kw): ''' Retry a system call until one of the following happens: 1) It succeeds 2) It errors with something other than EINTR '''
while True: try: return function(*args, **kw) except OSError: nothing, extra, nothing2 = sys.exc_info() dummy = nothing dummy = nothing2 if extra.errno == errno.EINTR: continue else: raise
Note that os.read() and os.write() need different handling.
Personally, I really want Python to handle EINTR for me. And indeed, that has been what we have been doing for a while now, piece by piece, bug by bug. Victor just wants to systematize and document that, and I think that's a good idea. We've been consistently treating lack of handling of EINTR as a bug. If there are *real* cases where that causes a backward compatibility problem, then we need to know. But so far, we have gotten zero complaints about the cases that we have fixed. --David PS: I recently switched from using selectors to using a timeout on a socket because in that particular application I could, and because reading a socket with a timeout handles EINTR (in recent python versions), whereas reading a non-blocking socket doesn't. Under the hood, a socket with a timeout is a non-blocking socket.
"R. David Murray" <rdmurray@bitdance.com>:
PS: I recently switched from using selectors to using a timeout on a socket because in that particular application I could, and because reading a socket with a timeout handles EINTR (in recent python versions), whereas reading a non-blocking socket doesn't. Under the hood, a socket with a timeout is a non-blocking socket.
Under what circumstances would a nonblocking socket generate an EINTR? I believe the biggest EINTR problem child is file I/O, which is always blocking in linux. Marko
On 31 August 2014 22:38, Victor Stinner <victor.stinner@gmail.com> wrote:
This case is described as the use case #2 in the PEP, so it is supported. As written in the PEP, if you want to be notified of the signal, set a signal handler which raises an exception. For example the default signal handler for SIGINT raises KeyboardInterrupt.
Wait - sigint? Does this mean that (unless the application adds a signal handler) keyboard interrupts will be in effect ignored while in a system call? I'm not sure I like that - I'd rather Ctrl-C always interrupted the program. Specifically, in one-off scripts that *don't* take care to handle all errors appropriately and just show the traceback... Paul
Victor Stinner wrote:
Le 1 sept. 2014 00:17, "Marko Rauhamaa" <marko@pacujo.net <mailto:marko@pacujo.net>> a écrit :
If a signal is received when read() or write() has completed its task partially (> 0 bytes), no EINTR is returned but the partial count. Obviously, Python should take that possibility into account so that raising an exception in the signal handler (as mandated by the PEP) doesn't cause the partial result to be lost on os.read() or os.write().
This case is unrelated to the PEP, the PEP only changes the behaviour when a syscall fails with EINTR.
I think there's a problem here, though. As thing stand, a signal handler that doesn't raise an exception can set a flag, and code after the read() can test it. Under the proposed scheme, the signal handler has to be made to raise an exception so that the read will be broken out of in the EINTR case. But what happens if the read returns *without* an EINTR? The signal handler will still raise an exception, which is either going to clobber the partial return value or mess up the code that does something with it. -- Greg
No, it's the opposite. The PEP doesn't change the default behaviour of SIGINT: CTRL+C always interrupt the program. Victor Le 1 sept. 2014 08:12, "Paul Moore" <p.f.moore@gmail.com> a écrit :
On 31 August 2014 22:38, Victor Stinner <victor.stinner@gmail.com> wrote:
This case is described as the use case #2 in the PEP, so it is supported. As written in the PEP, if you want to be notified of the signal, set a signal handler which raises an exception. For example the default signal handler for SIGINT raises KeyboardInterrupt.
Wait - sigint? Does this mean that (unless the application adds a signal handler) keyboard interrupts will be in effect ignored while in a system call? I'm not sure I like that - I'd rather Ctrl-C always interrupted the program. Specifically, in one-off scripts that *don't* take care to handle all errors appropriately and just show the traceback...
Paul
Le 1 sept. 2014 02:40, "Greg Ewing" <greg.ewing@canterbury.ac.nz> a écrit :
Victor Stinner wrote:
As written in the PEP, if you want to be notified of the signal, set a
signal handler which raises an exception.
I'm not convinced that this covers all possible use cases. It might be all right if you have control over the signal handler, but what if you don't?
Ok, let's say that a syscall is interrupted by a signal, but rhe signal doesn't raise an exception. So your program can only be interrupted if the signal is received during a syscall, right? I don't think that such program is reliable. Python should not promote such design. It should behave the same if the signal is received during a CPU-bound function. Extract of the PEP: Backward Compatibility: Applications relying on the fact that system calls are interruptedwith ``InterruptedError`` will hang. The authors of this PEP don'tthink that such application exist.If such applications exist, they are not portable and are subject torace conditions (deadlock if the signal comes before the system call).These applications must be fixed to handle signals differently, tohave a reliable behaviour on all platforms and all Python versions.For example, use a signal handler which raises an exception, or use awakeup file descriptor.For applications using event loops, ``signal.set_wakeup_fd()`` is therecommanded option to handle signals. The signal handler writes signalnumbers into the file descriptor and the event loop is awaken to readthem. The event loop can handle these signals without the restrictionof signal handlers. Victor
There's no return value, a KeywordInterrupt exception is raised. The PEP wouldn't change this behavior. As for the general behavior: all programming languages/platforms handle EINTR transparently. It's high time for Python to have a sensible behavior in this regard. 2014-09-01 8:38 GMT+01:00 Marko Rauhamaa <marko@pacujo.net>:
Victor Stinner <victor.stinner@gmail.com>:
No, it's the opposite. The PEP doesn't change the default behaviour of SIGINT: CTRL+C always interrupt the program.
Which raises an interesting question: what happens to the os.read() return value if SIGINT is received?
Marko _______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/cf.natali%40gmail.com
Charles-François Natali <cf.natali@gmail.com>:
Which raises an interesting question: what happens to the os.read() return value if SIGINT is received?
There's no return value, a KeywordInterrupt exception is raised. The PEP wouldn't change this behavior.
Slightly disconcerting... but I'm sure overriding SIGINT would cure that. You don't want to lose data if you want to continue running.
As for the general behavior: all programming languages/platforms handle EINTR transparently.
C doesn't. EINTR is there for a purpose. I sure hope Python won't bury it under opaque APIs. The two requirements are: * Allow the application to react to signals immediately in the main flow. * Don't lose information. Marko
2014-09-01 12:15 GMT+01:00 Marko Rauhamaa <marko@pacujo.net>:
Charles-François Natali <cf.natali@gmail.com>:
Which raises an interesting question: what happens to the os.read() return value if SIGINT is received?
There's no return value, a KeywordInterrupt exception is raised. The PEP wouldn't change this behavior.
Slightly disconcerting... but I'm sure overriding SIGINT would cure that. You don't want to lose data if you want to continue running.
As for the general behavior: all programming languages/platforms handle EINTR transparently.
C doesn't. EINTR is there for a purpose.
Python is slightly higher level than C, right? I was referring to Java, go, Haskell... Furthermore, that's not true: many operating systems actually restart syscalls by default (including Linux, man 7 signal): """ Interruption of system calls and library functions by signal handlers If a signal handler is invoked while a system call or library function call is blocked, then either: * the call is automatically restarted after the signal handler returns; or * the call fails with the error EINTR. Which of these two behaviors occurs depends on the interface and whether or not the signal handler was established using the SA_RESTART flag (see sigaction(2)). The details vary across UNIX systems; below, the details for Linux. """ The reason the interpreter is subject to so many EINTR is that we *explicitly* clear SA_RESTART because the C-level signal handler must be handled by the interpreter to have a chance to run the Python-level handlers from the main loop. There are many aspects of signal handling in Python that make it different from C: if you want C semantics, stick to C. I do not want to have to put all blocking syscalls within a try/except loop: have a look at the stdlib code, you'll see it's really a pain and ugly. And look at the number of EINTR-related syscalls we've had.
On Mon, 01 Sep 2014 19:15:33 +1200 Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Victor Stinner wrote:
Le 1 sept. 2014 00:17, "Marko Rauhamaa" <marko@pacujo.net <mailto:marko@pacujo.net>> a écrit :
If a signal is received when read() or write() has completed its task partially (> 0 bytes), no EINTR is returned but the partial count. Obviously, Python should take that possibility into account so that raising an exception in the signal handler (as mandated by the PEP) doesn't cause the partial result to be lost on os.read() or os.write().
This case is unrelated to the PEP, the PEP only changes the behaviour when a syscall fails with EINTR.
I think there's a problem here, though. As thing stand, a signal handler that doesn't raise an exception can set a flag, and code after the read() can test it.
Under the proposed scheme, the signal handler has to be made to raise an exception so that the read will be broken out of in the EINTR case.
The PEP already addresses this remark: ""Applications relying on the fact that system calls are interrupted with ``InterruptedError`` will hang. The authors of this PEP don't think that such application exist. If such applications exist, they are not portable and are subject to race conditions (deadlock if the signal comes before the system call).""" Regards Antoine.
Hi, I'm +1 on the whole PEP.
Writing a signal handler is difficult, only "async-signal safe" functions can be called.
You mean a C signal handler? Python signal handlers are not restricted.
Some signals are not interesting and should not interrupt the the application. There are two options to only interrupt an application on some signals:
* Raise an exception in the signal handler, like ``KeyboardInterrupt`` for ``SIGINT`` * Use a I/O multiplexing function like ``select()`` with the Python signal "wakeup" file descriptor: see the function ``signal.set_wakeup_fd()``.
This section looks a bit incomplete. Some calls such as os.read() or os.write() will (should) return a partial result when interrupted and they already handled >0 bytes. Perhaps other functions have a similar behaviour?
On Unix, the ``asyncio`` module uses the wakeup file descriptor to wake up its event loop.
How about Windows? Regards Antoine.
On Mon, 01 Sep 2014 08:30:27 +0300, Marko Rauhamaa <marko@pacujo.net> wrote:
"R. David Murray" <rdmurray@bitdance.com>:
PS: I recently switched from using selectors to using a timeout on a socket because in that particular application I could, and because reading a socket with a timeout handles EINTR (in recent python versions), whereas reading a non-blocking socket doesn't. Under the hood, a socket with a timeout is a non-blocking socket.
Under what circumstances would a nonblocking socket generate an EINTR?
Windows. Enough said? The exact error message was: BlockingIOError on my non-blocking socket: 'a non-blocking socket operation could not be completed immediately" Needless to say, I was not expecting this, and was about to tear my remaining hair out about having to completely restructure the code in order to be able to handle an EINTR on a read on an FD that I got back from select as ready, until I realized that the way the code had evolved the only thing I still needed the select for was the timeout, and that the EINTR bug in sockets with a timeout had already been fixed (thank goodness I'm able to use python3.4 for this project). I got lucky, but this is clearly a serious problem for writing selectors based code on Windows. This should tell you just about everything you need to know about why we want to fix this problem so that things work cross platform. --David
On Mon, 01 Sep 2014 14:15:52 +0300, Marko Rauhamaa <marko@pacujo.net> wrote:
Charles-François Natali <cf.natali@gmail.com>:
Which raises an interesting question: what happens to the os.read() return value if SIGINT is received?
There's no return value, a KeywordInterrupt exception is raised. The PEP wouldn't change this behavior.
Slightly disconcerting... but I'm sure overriding SIGINT would cure that. You don't want to lose data if you want to continue running.
As for the general behavior: all programming languages/platforms handle EINTR transparently.
C doesn't. EINTR is there for a purpose. I sure hope Python won't bury it under opaque APIs.
The two requirements are:
* Allow the application to react to signals immediately in the main flow.
You don't want to be writing your code in Python then. In Python you *never* get to react immediately to signals. The interpreter sets a flag and calls the python signal handler later. Yes, the call is ASAP, but ASAP is *not* "immediately".
* Don't lose information.
Marko _______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/rdmurray%40bitdance.com
On Mon, 01 Sep 2014 11:47:07 -0400 "R. David Murray" <rdmurray@bitdance.com> wrote:
The two requirements are:
* Allow the application to react to signals immediately in the main flow.
You don't want to be writing your code in Python then. In Python you *never* get to react immediately to signals. The interpreter sets a flag and calls the python signal handler later. Yes, the call is ASAP, but ASAP is *not* "immediately".
Especially if the signal is delivered to another thread (which is OS-dependent), and the main thread is blocked in *another* system call ;-) Regards Antoine.
"R. David Murray" <rdmurray@bitdance.com>:
Windows. Enough said? [...] This should tell you just about everything you need to know about why we want to fix this problem so that things work cross platform.
I feel your pain. Well, not really; I just don't want my linux bliss to be taken away. Marko
"R. David Murray" <rdmurray@bitdance.com>:
On Mon, 01 Sep 2014 14:15:52 +0300, Marko Rauhamaa <marko@pacujo.net> wrote:
* Allow the application to react to signals immediately in the main flow.
You don't want to be writing your code in Python then. In Python you *never* get to react immediately to signals. The interpreter sets a flag and calls the python signal handler later. Yes, the call is ASAP, but ASAP is *not* "immediately".
You don't have to get that philosophical. "Immediately" means, "without delay", "without further I/O". Marko
Victor Stinner <victor.stinner@gmail.com> wrote:
HTML version: http://legacy.python.org/dev/peps/pep-0475/
PEP: 475 Title: Retry system calls failing with EINTR
I think the proposed design for how Python should behave is a good one. But I think this proposal needs to be treated in the same way as any other backwards-incompatible change.
Applications relying on the fact that system calls are interrupted with ``InterruptedError`` will hang. The authors of this PEP don't think that such application exist.
The authors are mistaken here. I have a program still running which was designed around this behaviour. My company won't be inconvenienced by this change because I can't imagine the elderly program ever being ported to Python 3. But I think it's very likely there are other such programs out there.
If such applications exist, they are not portable and are subject to race conditions (deadlock if the signal comes before the system call).
The program is certainly not portable (which is not any kind of a problem), and as pselect is unavailable there is indeed the usual theoretical race (which has not been a problem in practice in the ten years it's been running). (The program handles SIGTERM so that it can do a bit of cleanup before exiting, and it uses the signal-handler-sets-a-flag technique. The call that might be interrupted is sleep(), so the program doesn't strictly _rely_ on the existing behaviour; it would just become very slow to exit.) -M-
On 2 September 2014 07:17, Matthew Woodcraft <matthew@woodcraft.me.uk> wrote:
(The program handles SIGTERM so that it can do a bit of cleanup before exiting, and it uses the signal-handler-sets-a-flag technique. The call that might be interrupted is sleep(), so the program doesn't strictly _rely_ on the existing behaviour; it would just become very slow to exit.)
Making an exception for sleep() (i.e. still letting it throw EINTR) sounds like a reasonable idea to me. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On Mon, 1 Sep 2014 21:17:33 +0000 (UTC) Matthew Woodcraft <matthew@woodcraft.me.uk> wrote:
If such applications exist, they are not portable and are subject to race conditions (deadlock if the signal comes before the system call).
The program is certainly not portable (which is not any kind of a problem), and as pselect is unavailable there is indeed the usual theoretical race (which has not been a problem in practice in the ten years it's been running).
(The program handles SIGTERM so that it can do a bit of cleanup before exiting, and it uses the signal-handler-sets-a-flag technique. The call that might be interrupted is sleep(), so the program doesn't strictly _rely_ on the existing behaviour; it would just become very slow to exit.)
So, if it's just for process exit, just let the signal handler raise an exception and catch the exception at the top-level. Regards Antoine.
Nick Coghlan <ncoghlan@gmail.com> wrote:
On 2 September 2014 07:17, Matthew Woodcraft <matthew@woodcraft.me.uk> wrote:
(The program handles SIGTERM so that it can do a bit of cleanup before exiting, and it uses the signal-handler-sets-a-flag technique. The call that might be interrupted is sleep(), so the program doesn't strictly _rely_ on the existing behaviour; it would just become very slow to exit.)
Making an exception for sleep() (i.e. still letting it throw EINTR) sounds like a reasonable idea to me.
I think people who use sleep() in their programs could benefit from not having to worry about EINTR as much as anyone else. And I don't think there's any reason to suppose that programs using sleep() are more likely than others to want to see EINTR. I think blocking network operations are more likely to be involved. -M-
Antoine Pitrou <solipsis@pitrou.net> wrote:
Matthew Woodcraft <matthew@woodcraft.me.uk> wrote:
(The program handles SIGTERM so that it can do a bit of cleanup before exiting, and it uses the signal-handler-sets-a-flag technique. The call that might be interrupted is sleep(), so the program doesn't strictly _rely_ on the existing behaviour; it would just become very slow to exit.)
So, if it's just for process exit, just let the signal handler raise an exception and catch the exception at the top-level.
I wouldn't recommend that anyone take this advice. The signal might come at some time other than the sleep(), and introducing an exception which can mysteriously appear at abitrary points is not a way to make life easier. Nonetheless I'm sure my program would be easy enough to fix to use set_wakeup_fd() or signalfd() if necessary. I'm not saying we shouldn't make this backwards-incompatible change because it will be hard to update existing programs to cope; I'm saying we shouldn't pretend it doesn't count as a backwards-incompatible change. In any case I think PEP 475 should be explaining what is going to happen to signal.siginterrupt(). Will setting flag=True be supported? If so, will doing so change the behaviour of those parts of the stdlib which have already been modified to retry after EINTR? (I think it would be helpful if we could tell people "if you want the old EINTR behaviour, just do this simple thing". And I suppose siginterrupt flag=True is a candidate for that.) -M-
2014-09-02 23:02 GMT+02:00 Matthew Woodcraft <matthew@woodcraft.me.uk>:
I think people who use sleep() in their programs could benefit from not having to worry about EINTR as much as anyone else.
The behaviour of time.sleep() is worse than what I expected. On UNIX, if select() fails with EINTR, time.sleep() calls PyErr_CheckSignals(). If the signal handler doesn't raise an exception, time.sleep() returns None and just simply ignores the error. But on Windows, it's the opposite. If time.sleep() is interrupt by CTRL+c, time.sleep() raises an InterruptedError... Good luck to write portable code :-p With the PEP 475, time.sleep(secs) now has a well defined behaviour. It sleeps at least "secs" seconds, retry the syscall on EINTR, and raises an exception if the signal handler raises an exception. Victor
2014-09-02 23:03 GMT+02:00 Matthew Woodcraft <matthew@woodcraft.me.uk>:
In any case I think PEP 475 should be explaining what is going to happen to signal.siginterrupt(). Will setting flag=True be supported?
I first proposed to deprecate the function, but Charles-François thinks that it's unrelated to the PEP (it can be addressed later). The function will still be available and work.
If so, will doing so change the behaviour of those parts of the stdlib which have already been modified to retry after EINTR?
I think that the stdlib should not handle InterruptedError exception anymore in the Python code, to simplify the code.
(I think it would be helpful if we could tell people "if you want the old EINTR behaviour, just do this simple thing". And I suppose siginterrupt flag=True is a candidate for that.)
Why do you want the old behaviour? Victor
In article <CAMpsgwabYhXB0OG3UhdX=FUCYOnAJGzpwd-g8sTdauKjzpJAMQ@mail.gmail.com>, Victor Stinner <victor.stinner@gmail.com> wrote:
2014-09-02 23:03 GMT+02:00 Matthew Woodcraft <matthew@woodcraft.me.uk>:
In any case I think PEP 475 should be explaining what is going to happen to signal.siginterrupt(). Will setting flag=True be supported?
I first proposed to deprecate the function, but Charles-François thinks that it's unrelated to the PEP (it can be addressed later).
The function will still be available and work.
If so, will doing so change the behaviour of those parts of the stdlib which have already been modified to retry after EINTR?
I think that the stdlib should not handle InterruptedError exception anymore in the Python code, to simplify the code.
That seems good to me. I think it's worth writing in the PEP. But if Python is going to provide its own higher-level model of signal handling, then I think signal.siginterrupt() will need to be documented in terms of that model; it should be saying something along the lines of "if a signal arrives while Python is blocking in a system call then InterruptedError will be raised", and I suppose it will need to say what happens if the signal handler also raised an exception.
(I think it would be helpful if we could tell people "if you want the old EINTR behaviour, just do this simple thing". And I suppose siginterrupt flag=True is a candidate for that.)
Why do you want the old behaviour?
Purely to avoid breaking existing programs, particularly in ways which will require effort to fix. I think Python's backward-compatibility rules are basically "the behavior of an API must not change without going through the deprecation process". If we're going to be making an exception to that here, then it would be much better to say "here's a simple change to make to keep your old program working", rather than "please rewrite your fiddly signal-handling code to use more modern techniques", however much nicer those modern techniques are. Or perhaps it would be better to not make an exception at all, and instead add an 'interrupt_syscalls' boolean keyword argument to signal.signal(), while strongly recommending that people set it False. -M-
participants (11)
-
Antoine Pitrou
-
Charles-François Natali
-
Dan Stromberg
-
Ethan Furman
-
Greg Ewing
-
Marko Rauhamaa
-
Matthew Woodcraft
-
Nick Coghlan
-
Paul Moore
-
R. David Murray
-
Victor Stinner