Correct error handling for fclose is not possible (according to the man page)?

So, I study fclose manpage for me, and I concluded that if fclose is interrupted by some kind of signal, according to the man page, there is no way to recover ...? Am I missing a point?

Usually, with unbuffered POSIX functions (open, close, write, etc.), there is ALWAYS a way to recover from a signal interruption (EINTR) by restarting the call; unlike the documentation for buffered calls, it says that after an unsuccessful Fclose attempt, another attempt has undefined behavior ... there is no hint of HOW it will be restored. Am I just “out of luck” if the signal interrupts fclose? Data may be lost, and I cannot be sure if the file descriptor is really closed or not. I know the buffer is freed, but what about a file descriptor? Think of large-scale applications that use a lot of fd at the same time and run into problems if fd are not properly freed → I would suggest that there must be a CLEAN solution to solve this problem.

So let me assume that I am writing a library and I am not allowed to use sigaction and SA_RESTART, and a lot of signals are sent, how can I recover if fclose is interrupted? Would it be nice to call in a loop (instead of fclose) after fclose failed with EINTR? The fclose documentation simply does not mention the state of the file descriptor; UNDEFINED is not very useful, though ... if fd is closed, and I cause it to close again, there may be strange hard to debug side effects, so naturally I would rather ignore this case by doing the wrong thing ... again, there is no unlimited amount file descriptors, and resource leakage is a kind of error (at least for me).

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

This corner case really bothers me :(

+7
c linux fclose stdio
source share
2 answers

EINTR and close()

Actually there are problems with close() , and not just with fclose() .

POSIX states that close() returns an EINTR , which usually means the application can retry the call. However, on Linux, things get more complicated. See this article at LWN as well as this post .

[...] semantics of POSIX EINTR on Linux is not real. The file descriptor passed to close() is uninstalled at an early stage in the processing of the system call, and the same descriptor can already be transferred to another thread by the time close() returns.

This blog post and this answer explain why it is not a good idea to try close() again with EINTR . So on Linux, you cannot do anything if close() failed with EINTR (or EINPROGRESS ).

Also note that close() is asynchronous on Linux. For example, sometimes umount may return EBUSY immediately after closing the last open descriptor in the file system, since it has not yet been released in the kernel. See an interesting discussion here: page 1 , page 2 .


EINTR and fclose()

POSIX state for fclose() :

After calling fclose() any use of the stream results in undefined behavior.

Whether the call is made, the stream must be removed from the file, and any buffer set by the setbuf() or setvbuf() function must be disconnected from the stream. If the associated buffer was automatically assigned, it must be freed.

I believe that even if close() fails, fclose() should free up all resources and not leak. This is true, at least for glibc and uclibc .


Reliable error handling

  • Call fflush() to fclose() .

    Since you cannot determine if fclose() fails when calling fflush() or close() , you must explicitly call fflush() before fclose() to ensure that the user space buffer has been successfully sent to the kernel.

  • Do not retry after EINTR .

    If fclose() fails with EINTR , you cannot try close() again, nor can you try fclose() again.

  • Call fsync() if you need to.

    • If you need data integrity, you must call fsync() or fdatasync() before calling fclose() 1 .
    • If you do not, just ignore the EINTR from fclose() .

Notes

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

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


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

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

+3
source share

Think of large-scale applications that use a lot of fd at the same time and run into problems if fd are not properly released - → I would suggest that there must be a CLEAN solution to solve this problem.

The ability to repeat fflush() and then close() in the base file descriptor has already been mentioned in the comments. For a large-scale application, I would prefer the template to use threads and have one dedicated signal processing thread, while all other threads block signals using pthread_sigmask() . Then when fclose() fails, you have a real problem.

+2
source share

All Articles