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 POSIXEINTR
semantics are not really possible on Linux. The file descriptor passed toclose()
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 timeclose()
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()
orsetvbuf()
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()
beforefclose()
.Since you can’t determine if
fclose()
failed when it calledfflush()
orclose()
, you have to explicitly callfflush()
beforefclose()
to ensure that userspace buffer was successfully sent to kernel.Don’t retry after
EINTR
.If
fclose()
failed withEINTR
, you can not retryclose()
and also can not retryfclose()
.Call
fsync()
if you need.- If you care about data integrity, you should call
fsync()
orfdatasync()
before callingfclose()
1. - If you don’t, just ignore
EINTR
fromfclose()
.
- If you care about data integrity, you should call
Notes
If
fflush()
andfsync()
succeeded andfclose()
failed withEINTR
, no data is lost and no leaks occur.You should also ensure that the
FILE
object is not used betweenfflush()
andfclose()
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.