System call wrappers provided in the standard library should be retriedautomatically when they fail withEINTR, to relieve application codefrom the burden of doing so.
By system calls, we mean the functions exposed by the standard C librarypertaining to I/O or handling of other system resources.
On POSIX systems, signals are common. Code calling system calls must beprepared to handle them. Examples of signals:
SIGINT, the signal sent when CTRL+c ispressed. By default, Python raises aKeyboardInterrupt exceptionwhen this signal is received.SIGCHLD signal is sent when achild process exits.SIGWINCH signal to theapplications running in the terminal.bg command) sends theSIGCONT signal.Writing a C signal handler is difficult: only “async-signal-safe”functions can be called (for example,printf() andmalloc()are not async-signal safe), and there are issues with reentrancy.Therefore, when a signal is received by a process during the executionof a system call, the system call can fail with theEINTR error togive the program an opportunity to handle the signal without therestriction on signal-safe functions.
This behaviour is system-dependent: on certain systems, using theSA_RESTART flag, some system calls are retried automatically insteadof failing withEINTR. Regardless, Python’ssignal.signal()function clears theSA_RESTART flag when setting the signal handler:all system calls will probably fail withEINTR in Python.
Since receiving a signal is a non-exceptional occurrence, robust POSIX codemust be prepared to handleEINTR (which, in most cases, meansretry in a loop in the hope that the call eventually succeeds).Without special support from Python, this can make application codemuch more verbose than it needs to be.
In Python 3.4, handling theInterruptedError exception (EINTR’sdedicated exception class) is duplicated at every call site on a case-by-casebasis. Only a few Python modules actually handle this exception,and fixes usually took several years to cover a whole module. Example ofcode retryingfile.read() onInterruptedError:
whileTrue:try:data=file.read(size)breakexceptInterruptedError:continue
List of Python modules in the standard library which handleInterruptedError:
asyncioasyncoreio,_pyiomultiprocessingselectorssocketsocketserversubprocessOther programming languages like Perl, Java and Go retry system callsfailing withEINTR at a lower level, so that libraries and applicationsneedn’t bother.
In most cases, you don’t want to be interrupted by signals and youdon’t expect to getInterruptedError exceptions. For example, doyou really want to write such complex code for a “Hello World”example?
whileTrue:try:print("Hello World")breakexceptInterruptedError:continue
InterruptedError can happen in unexpected places. For example,os.close() andFileIO.close() may raiseInterruptedError:see the articleclose() and EINTR.
ThePython issues related to EINTR section below gives examples ofbugs caused byEINTR.
The expectation in this use case is that Python hides theInterruptedError and retries system calls automatically.
Sometimes yet, you expect some signals and you want to handle them assoon as possible. For example, you may want to immediately quit aprogram using theCTRL+c keyboard shortcut.
Besides, some signals are not interesting and should not disrupt theapplication. There are two options to interrupt an application ononlysome signals:
KeyboardInterrupt forSIGINT.select() together with Python’ssignal wakeup file descriptor: see the functionsignal.set_wakeup_fd().The expectation in this use case is for the Python signal handler to beexecuted timely, and the system call to fail if the handler raised anexception – otherwise restart.
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-levellibraries and applications).
Specifically, when a system call fails withEINTR, its Python wrappermust call the given signal handler (usingPyErr_CheckSignals()).If the signal handler raises an exception, the Python wrapper bails outand fails with the exception.
If the signal handler returns successfully, the Python wrapper retries thesystem call automatically. If the system call involves a timeout parameter,the timeout is recomputed.
Example of standard library functions that need to be modified to complywith this PEP:
open(),os.open(),io.open()faulthandler moduleos 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()os.close() andos.dup2() now ignoreEINTR error,the syscall is not retriedselect.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: theselector module already retries onInterruptedError, but itdoesn’t recompute the timeout yet)
os.close,close() methods andos.dup2() are a special case: theywill ignoreEINTR instead of retrying. The reason is complex but involvesbehaviour under Linux and the fact that the file descriptor may really beclosed even if EINTR is returned. See articles:
Thesocket.socket.connect() method does not retryconnect() fornon-blocking sockets if it is interrupted by a signal (fails withEINTR).The connection runs asynchronously in background. The caller is responsibleto wait until the socket becomes writable (ex: usingselect.select())and then callsocket.socket.getsockopt(socket.SOL_SOCKET,socket.SO_ERROR)to check if the connection succeeded (getsockopt() returns0) or failed.
Since interrupted system calls are automatically retried, theInterruptedError exception should not occur anymore when calling thosesystem calls. Therefore, manual handling ofInterruptedError asdescribed inStatus in Python 3.4 can be removed, which will simplifystandard library code.
Applications relying on the fact that system calls are interruptedwithInterruptedError will hang. The authors of this PEP don’tthink that such applications exist, since they would be exposed toother issues such as race conditions (there is an opportunity for deadlockif the signal comes before the system call). Besides, such code wouldbe 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-definedexception, or use a wakeup file descriptor.
For applications using event loops,signal.set_wakeup_fd() is therecommended option to handle signals. Python’s low-level signal handlerwill write signal numbers into the file descriptor and the event loopwill be awaken to read them. The event loop can handle those signalswithout the restriction of signal handlers (for example, the loop canbe woken up in any thread, not just the main thread).
Since Python 3.3,signal.set_wakeup_fd() writes the signal numberinto the file descriptor, whereas it only wrote a null byte before.It becomes possible to distinguish between signals using the wakeup filedescriptor.
Linux has asignalfd() system call which provides more information oneach signal. For example, it’s possible to know the pid and uid who sentthe signal. This function is not exposed in Python yet (seeissue 12304).
On Unix, theasyncio module uses the wakeup file descriptor towake up its event loop.
A C signal handler can be called from any thread, but Pythonsignal handlers will always be called in the main Python thread.
Python’s C API provides thePyErr_SetInterrupt() function which callstheSIGINT signal handler in order to interrupt the main Python thread.
Windows uses “control events”:
CTRL_BREAK_EVENT: Break (SIGBREAK)CTRL_CLOSE_EVENT: Close eventCTRL_C_EVENT: CTRL+C (SIGINT)CTRL_LOGOFF_EVENT: LogoffCTRL_SHUTDOWN_EVENT: ShutdownTheSetConsoleCtrlHandler() functioncan be used to install a control handler.
TheCTRL_C_EVENT andCTRL_BREAK_EVENT events can be sent to aprocess using theGenerateConsoleCtrlEvent() function.This function is exposed in Python asos.kill().
The following signals are supported on Windows:
SIGABRTSIGBREAK (CTRL_BREAK_EVENT): signal only available on WindowsSIGFPESIGILLSIGINT (CTRL_C_EVENT)SIGSEGVSIGTERMThe default Python signal handler forSIGINT sets a Windows eventobject:sigint_event.
time.sleep() is implemented withWaitForSingleObjectEx(), itwaits for thesigint_event object usingtime.sleep() parameteras the timeout. So the sleep can be interrupted bySIGINT.
_winapi.WaitForMultipleObjects() automatically addssigint_event to the list of watched handles, so it can also beinterrupted.
PyOS_StdioReadline() also usedsigint_event whenfgets()failed to check if Ctrl-C or Ctrl-Z was pressed.
The main issue is:handle EINTR in the stdlib.
Open issues:
Closed issues:
Open issues:
Closed issues:
The implementation is tracked inissue 23285. It was committed onFebruary 07, 2015.
This document has been placed in the public domain.
Source:https://github.com/python/peps/blob/main/peps/pep-0475.rst
Last modified:2025-02-01 08:59:27 GMT