13

What is the recommended etiquette when it comes to EINTR in libraries?

I'm currently writing a function that does some file system tasks with the POSIX API, but a lot of the calls I use can potentially return EINTR. Additionally, the function can block under some circumstances. (For those interested, it implements a locking mechanism.)

In the interests of making this as general as possible, I would like to know what is the proper way to deal with an interrupted system call.

  • From most sources I've read, people typically retry the call and continue on with their business. However, I'm not sure that's the right thing to do here, since there may be legitimate reasons to interrupt my function given that it can take a significant amount of time. Furthermore, it means the EINTR would simply get swallowed by the function and the caller would lose any indication that it occurred.

  • My current strategy is to immediately abort the operation if I receive EINTR and notify the caller about it. This way, the caller can decide if they want to retry my function, right? (Or perhaps my understanding of signals is flawed?)

2
  • 1
    What is your library doing? I tend to approve your current strategy (of aborting operation on EINTR and reporting that to the caller), but it may depend upon what is your library doing... Dec 31 '14 at 14:18
  • 1
    In this case it's performing a locking operation.
    – Rufflewind
    Dec 31 '14 at 14:29
4

Always leave the decision of how to handle EINTR to the user, and make it easy to resume the operation as appropriate.

Usually the best way to do that is to return from your library function to the caller upon EINTR, but in some cases a callback or some other implemention might be better - which way is best depends on other factors, but always let the user control retry and resume.

This means that if your library code can partially succeed before getting an EINTR, then you should think carefully what the user might need to know about that partial success, or if the user might need to resume the operation from where it failed. You might need to return additional information or provide an interface for resuming from any place where it might be appropriate to.

This is why system calls like read and write nowadays return partial success - because it is very frustrating as a user to be told:

You tried to write "foo" and we successfully wrote either nothing, or "f", or "fo", and you don't get to know which. Have fun! Hope your system can handle restarting the whole write after any of those!

Of course, in some cases, we should write systems to handle situations exactly like that - for example, perhaps after a partial write you always recreate the file, or reopen the network connection, or you use some byte to mean "starting over" - so it depends on what use cases your library targets.

If a library function does several operations, and there is no way to know at which of them it failed, and those operations are not all safely and efficiently idempotent, that basically makes a library unusable for code that needs to be robust.

If all steps in a library function are safely and efficiently idempotent, or the whole thing is atomic - like acquiring a lock - then just letting the user know that an EINTR happened is enough.

Also, if we retry on EINTR, then we might break signal handling. At the low level, signal handlers can only safely use a limited set of features, and so in many cases a signal handler will just set a boolean indicating that the signal was received, and then return, expecting that when the code resumes, it will exit out of whatever it was doing. If we get an EINTR and then we retry instead of returning control to the user, we might be keeping the code from doing that.

What to do after an EINTR is a whole program decision - the right answer cannot be known without knowing what the program is doing and how the program is meant to respond to a signal, and it has effects on the rest of the program.

Knowing how or if the user might need to resume, and helping the user do so if it is needed, is a library responsibility - the right answer cannot be known without knowing what the library is doing.

4
  • How do you think about EINTR on close() case?
    – Eonil
    Jul 8 '20 at 1:43
  • @Eonil when close() fails with an EINTR, the right program action depends on what the program is doing: in some cases the right action is "ignore the failure, the program is about to exit anyway", in some cases the right action is "log an error message, we can't do anything about it", in some cases the right action is "trigger an emergency alarm and stop/crash everything", in some cases the right action is "try again" and so on. So the above ideas apply: if we're writing a library that calls close, we should empower the programmer that is using the library to handle it as appropriate.
    – mtraceur
    Jul 9 '20 at 20:12
  • @mtraceur Retrying close on Linux (and Mac OSx IIRC) is not an option. Linux will always invalidate and reuse the FD, which means you can close the FD another thread just obtained. More info - github.com/boostorg/beast/issues/1445 Apr 30 at 18:46
  • @DonchoGunchev And that's a great example of why when we're writing a library for general use on UNIX-like/POSIX-like systems, we should empower the programmer calling our library to choose the error handling response, instead of hardcoding one. If you're using my library on an operating system or in a situation where retrying a close is always dangerous (because the FD might have been reused or whatever), then I haven't forced it on you. Meanwhile if someone else finds or creates a situation where retrying a close is a reasonable option, then I haven't locked them out of doing it.
    – mtraceur
    May 1 at 17:57
4

Signals in Unix

when some other process sends your process Signal, your program will stop what it’s doing...

1) Run the handler code you wrote. You have no idea what your program might be doing when the signal arrives. That’s the idea with signals, they can be completely asynchronous.

2) When the signal handler is done, it typically just does a return, and your program continues where it left off, as if nothing had happened.

Found some useful info in Richard Stevens

A characteristic of earlier UNIX systems is that if a process caught a signal while the process was blocked in a "slow" system call, the system call was interrupted. The system call returned an error and errno was set to EINTR. This was done under the assumption that since a signal occurred and the process caught it, there is a good chance that something has happened that should wake up the blocked system call.

POSIX.1 semantics for interrupted reads and writes changed with the 2001 version of the standard. Earlier versions gave implementations a choice for how to deal with reads and writes that have processed partial amounts of data. If read has received and transferred data to an application's buffer, but has not yet received all that the application requested and is then interrupted, the operating system could either fail the system call with errno set to EINTR or allow the system call to succeed, returning the partial amount of data received. Similarly, if write is interrupted after transferring some of the data in an application's buffer, the operation system could either fail the system call with errno set to EINTR or allow the system call to succeed, returning the partial amount of data written. Historically, implementations derived from System V fail the system call, whereas BSD-derived implementations return partial success. With the 2001 version of the POSIX.1 standard, the BSD-style semantics are required.

The problem with interrupted system calls is that we now have to handle the error return explicitly. The typical code sequence (assuming a read operation and assuming that we want to restart the read even if it's interrupted) would be

again:
    if ((n = read(fd, buf, BUFFSIZE)) < 0) {
        if (errno == EINTR)
            goto again;     /* just an interrupted system call */
        /* handle other errors */
    }

To prevent applications from having to handle interrupted system calls, 4.2BSD introduced the automatic restarting of certain interrupted system calls.

1
  • 4
    This doesn't answer the question. which was how libraries should approach the problem. If I know that I want to ignore EINTR in certain operations, I can do it with this call retry (goto again). But as a library developer, I don't know whether this is what my library user wants.
    – Messa
    Jul 22 '15 at 8:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.