Skip to content
Advertisement

Proper error handling for fclose impossible (according to manpage)?

So I’m studying fclose manpage for quite I while and my conclusion is that if fclose is interrupted by some signal, according to the manpage there is no way to recover…? Am I missing some point?

Usually, with unbuffered POSIX functions (open, close, write, etc…) there is ALWAYS a way to recover from signal interruption (EINTR) by restarting the call; in contrast documentation of buffered calls states that after a failed fclose attempt another try has undefined behavior… no hint about HOW to recover instead. Am I just “unlucky” if a signal interrupts fclose? Data might be lost and I can’t be sure whether the file descriptor is actually closed or not. I do know that the buffer is deallocated, but what about the file descriptor? Think about large scale applications that use lot’s of fd’s simultaneously and would run into problems if fd’s are not properly freed -> I would assume there must be a CLEAN solution to this problem.

So let’s assume I’m writing a library and it’s not allowed to use sigaction and SA_RESTART and lots of signals are sent, how do I recover if fclose is interrupted? Would it be a good idea to call close in a loop (instead of fclose) after fclose failed with EINTR? Documentation of fclose simply doesn’t mention the state of the file descriptor; UNDEFINED is not very helpful though… if fd is closed and I call close again, weird hard-to-debug side-effects could occur so naturally I would rather ignore this case as doing the wrong thing… then again, there is no unlimited number of file descriptors available, and resource leakage is some sort of bug (at least to me).

Of course I could check one specific implementation of fclose but I can’t believe someone designed stdio and didn’t think about this problem? Is it just the documentation that is bad or the design of this function?

This corner case really bugs me 🙁

Advertisement

Answer

EINTR and close()

In fact, there are also problems with close(), not only with fclose().

POSIX states that close() returns EINTR, which usually means that application may retry the call. However things are more complicated in linux. See this article on LWN and also this post.

[...] the POSIX EINTR semantics are not really possible on Linux. The file descriptor passed to close() is de-allocated early in the processing of the system call and the same descriptor could already have been handed out to another thread by the time close() returns.

This blog post and this answer explain why it’s not a good idea to retry close() failed with EINTR. So in Linux, you can do nothing meaningful if close() failed with EINTR (or EINPROGRESS).

Also note that close() is asynchronous in Linux. E.g., sometimes umount may return EBUSY immediately after closing last opened descriptor on filesystem since it’s not yet released in kernel. See interesting discussion here: page 1, page 2.


EINTR and fclose()

POSIX states for fclose():

After the call to fclose(), any use of stream results in undefined behavior.

Whether or not the call succeeds, the stream shall be disassociated from the file and any buffer set by the setbuf() or setvbuf() function shall be disassociated from the stream. If the associated buffer was automatically allocated, it shall be deallocated.

I believe it means that even if close() failed, fclose() should free all resources and produce no leaks. It’s true at least for glibc and uclibc implementations.


Reliable error handling

  • Call fflush() before fclose().

    Since you can’t determine if fclose() failed when it called fflush() or close(), you have to explicitly call fflush() before fclose() to ensure that userspace buffer was successfully sent to kernel.

  • Don’t retry after EINTR.

    If fclose() failed with EINTR, you can not retry close() and also can not retry fclose().

  • Call fsync() if you need.

    • If you care about data integrity, you should call fsync() or fdatasync() before calling fclose() 1.
    • If you don’t, just ignore EINTR from fclose().

Notes

  • If fflush() and fsync() succeeded and fclose() failed with EINTR, no data is lost and no leaks occur.

  • You should also ensure that the FILE object is not used between fflush() and fclose() calls from another thread 2.


[1] See “Everything You Always Wanted to Know About Fsync()” article which explains why fsync() may also be an asynchronous operation.

[2] You can call flockfile() before calling fflush() and fclose(). It should work with fclose() correctly.

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement