Permalink
Cannot retrieve contributors at this time
487 lines (378 sloc)
16.2 KB
| 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 <vstinner@python.org> | |
| BDFL-Delegate: Antoine Pitrou <solipsis@pitrou.net> | |
| Status: Final | |
| Type: Standards Track | |
| Content-Type: text/x-rst | |
| Created: 29-Jul-2014 | |
| Python-Version: 3.5 | |
| Resolution: https://mail.python.org/pipermail/python-dev/2015-February/138018.html | |
| Abstract | |
| ======== | |
| System call wrappers provided in the standard library should be retried | |
| automatically when they fail with ``EINTR``, to relieve application code | |
| from the burden of doing so. | |
| By system calls, we mean the functions exposed by the standard C library | |
| pertaining to I/O or handling of other system resources. | |
| Rationale | |
| ========= | |
| Interrupted system calls | |
| ------------------------ | |
| On POSIX systems, signals are common. Code calling system calls must be | |
| prepared to handle them. Examples of signals: | |
| * The most common signal is ``SIGINT``, the 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 C signal handler is difficult: only "async-signal-safe" | |
| functions can be called (for example, ``printf()`` and ``malloc()`` | |
| are not async-signal safe), and there are issues with reentrancy. | |
| Therefore, when a signal is received by a process during the execution | |
| of 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. | |
| This behaviour is system-dependent: on certain systems, using the | |
| ``SA_RESTART`` flag, some system calls are retried automatically instead | |
| of failing with ``EINTR``. Regardless, Python's ``signal.signal()`` | |
| function clears the ``SA_RESTART`` flag when setting the signal handler: | |
| all system calls will probably fail with ``EINTR`` in Python. | |
| Since receiving a signal is a non-exceptional occurrence, robust POSIX code | |
| must be prepared to handle ``EINTR`` (which, in most cases, means | |
| retry in a loop in the hope that the call eventually succeeds). | |
| Without special support from Python, this can make application code | |
| much more verbose than it needs to be. | |
| Status in Python 3.4 | |
| -------------------- | |
| In Python 3.4, handling the ``InterruptedError`` exception (``EINTR``'s | |
| dedicated exception class) is duplicated at every call site on a case-by-case | |
| basis. Only a few Python modules actually 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 in the standard library which handle | |
| ``InterruptedError``: | |
| * ``asyncio`` | |
| * ``asyncore`` | |
| * ``io``, ``_pyio`` | |
| * ``multiprocessing`` | |
| * ``selectors`` | |
| * ``socket`` | |
| * ``socketserver`` | |
| * ``subprocess`` | |
| Other programming languages like Perl, Java and Go retry system calls | |
| failing with ``EINTR`` at a lower level, so that libraries and applications | |
| needn't bother. | |
| 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 a "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()`` may raise ``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 in this use case is that Python hides the | |
| ``InterruptedError`` and retries system calls automatically. | |
| Use Case 2: Be notified of signals as soon as possible | |
| ------------------------------------------------------ | |
| Sometimes yet, you expect some signals and you want to handle them as | |
| soon as possible. For example, you may want to immediately quit a | |
| program using the ``CTRL+c`` keyboard shortcut. | |
| Besides, some signals are not interesting and should not disrupt the | |
| application. There are two options to interrupt an application on | |
| only *some* signals: | |
| * Set up a custom signal handler which raises an exception, such as | |
| ``KeyboardInterrupt`` for ``SIGINT``. | |
| * Use a I/O multiplexing function like ``select()`` together with Python's | |
| signal wakeup file descriptor: see the function ``signal.set_wakeup_fd()``. | |
| The expectation in this use case is for the Python signal handler to be | |
| executed timely, and the system call to fail if the handler raised an | |
| exception -- otherwise restart. | |
| Proposal | |
| ======== | |
| This PEP proposes to handle EINTR and retries at the lowest level, i.e. | |
| in the wrappers provided by the stdlib (as opposed to higher-level | |
| libraries and applications). | |
| Specifically, when a system call fails with ``EINTR``, its Python wrapper | |
| must call the given signal handler (using ``PyErr_CheckSignals()``). | |
| If the signal handler raises an exception, the Python wrapper bails out | |
| and fails with the exception. | |
| If the signal handler returns successfully, the Python wrapper retries the | |
| system call automatically. If the system call involves a timeout parameter, | |
| the timeout is recomputed. | |
| Modified functions | |
| ------------------ | |
| Example of standard library functions that need to be modified to comply | |
| with this PEP: | |
| * ``open()``, ``os.open()``, ``io.open()`` | |
| * functions of the ``faulthandler`` module | |
| * ``os`` functions: | |
| - ``os.fchdir()`` | |
| - ``os.fchmod()`` | |
| - ``os.fchown()`` | |
| - ``os.fdatasync()`` | |
| - ``os.fstat()`` | |
| - ``os.fstatvfs()`` | |
| - ``os.fsync()`` | |
| - ``os.ftruncate()`` | |
| - ``os.mkfifo()`` | |
| - ``os.mknod()`` | |
| - ``os.posix_fadvise()`` | |
| - ``os.posix_fallocate()`` | |
| - ``os.pread()`` | |
| - ``os.pwrite()`` | |
| - ``os.read()`` | |
| - ``os.readv()`` | |
| - ``os.sendfile()`` | |
| - ``os.wait3()`` | |
| - ``os.wait4()`` | |
| - ``os.wait()`` | |
| - ``os.waitid()`` | |
| - ``os.waitpid()`` | |
| - ``os.write()`` | |
| - ``os.writev()`` | |
| - special cases: ``os.close()`` and ``os.dup2()`` now ignore ``EINTR`` error, | |
| the syscall is not retried | |
| * ``select.select()``, ``select.poll.poll()``, ``select.epoll.poll()``, | |
| ``select.kqueue.control()``, ``select.devpoll.poll()`` | |
| * ``socket.socket()`` methods: | |
| - ``accept()`` | |
| - ``connect()`` (except for non-blocking sockets) | |
| - ``recv()`` | |
| - ``recvfrom()`` | |
| - ``recvmsg()`` | |
| - ``send()`` | |
| - ``sendall()`` | |
| - ``sendmsg()`` | |
| - ``sendto()`` | |
| * ``signal.sigtimedwait()``, ``signal.sigwaitinfo()`` | |
| * ``time.sleep()`` | |
| (Note: the ``selector`` module already retries on ``InterruptedError``, but it | |
| doesn't recompute the timeout yet) | |
| ``os.close``, ``close()`` methods and ``os.dup2()`` are a special case: they | |
| will ignore ``EINTR`` instead of retrying. The reason is complex but involves | |
| behaviour under Linux and the fact that the file descriptor may really be | |
| closed even if EINTR is returned. See articles: | |
| * `Returning EINTR from close() <http://lwn.net/Articles/576478/>`_ | |
| * `(LKML) Re: [patch 7/7] uml: retry host close() on EINTR | |
| <http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html>`_ | |
| * `close() and EINTR <http://alobbs.com/post/54503240599/close-and-eintr>`_ | |
| The ``socket.socket.connect()`` method does not retry ``connect()`` for | |
| non-blocking sockets if it is interrupted by a signal (fails with ``EINTR``). | |
| The connection runs asynchronously in background. The caller is responsible | |
| to wait until the socket becomes writable (ex: using ``select.select()``) | |
| and then call ``socket.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)`` | |
| to check if the connection succeeded (``getsockopt()`` returns ``0``) or failed. | |
| InterruptedError handling | |
| ------------------------- | |
| Since interrupted system calls are automatically retried, the | |
| ``InterruptedError`` exception should not occur anymore when calling those | |
| system calls. Therefore, manual handling of ``InterruptedError`` as | |
| described in `Status in Python 3.4`_ can be removed, which will simplify | |
| standard library code. | |
| 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 applications exist, since they would be exposed to | |
| other issues such as race conditions (there is an opportunity for deadlock | |
| if the signal comes before the system call). Besides, such code would | |
| be non-portable. | |
| In any case, those applications must be fixed to handle signals differently, | |
| to have a reliable behaviour on all platforms and all Python versions. | |
| A possible strategy is to set up a signal handler raising a well-defined | |
| exception, or use a wakeup file descriptor. | |
| For applications using event loops, ``signal.set_wakeup_fd()`` is the | |
| recommended option to handle signals. Python's low-level signal handler | |
| will write signal numbers into the file descriptor and the event loop | |
| will be awaken to read them. The event loop can handle those signals | |
| without the restriction of signal handlers (for example, the loop can | |
| be woken up in any thread, not just the main thread). | |
| 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 distinguish between signals using the wakeup file | |
| descriptor. | |
| Linux has a ``signalfd()`` system call 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 | |
| `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 Python | |
| signal handlers will always be called in the main Python thread. | |
| Python's C API provides the ``PyErr_SetInterrupt()`` function which calls | |
| the ``SIGINT`` signal handler in order to interrupt the main Python 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>`_ | |
| Implementation | |
| ============== | |
| The implementation is tracked in `issue 23285 | |
| <http://bugs.python.org/issue23285>`_. It was committed on | |
| February 07, 2015. | |
| 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: |