通常,使用无缓冲的POSIX功能(打开,关闭,写入等),总有一种方法可以通过重新启动呼叫来从信号中断(EINTR)中恢复;相反,缓冲调用的文档说明在fclose尝试失败后,另一次尝试有未定义的行为…没有关于如何恢复的提示.如果信号中断fclose,我只是“不幸”吗?数据可能会丢失,我无法确定文件描述符是否实际关闭.我知道缓冲区已被释放,但文件描述符呢?
考虑大量使用fd的批量应用程序,如果没有正确释放fd,会遇到问题 – >我认为必须有一个CLEAN解决方案来解决这个问题.
所以我们假设我正在编写一个库并且不允许使用sigaction和SA_RESTART并且发送了大量信号,如果fclose被中断,我该如何恢复?
fclose与EINTR失败后,在循环(而不是fclose)中调用close是不是一个好主意? fclose的文档根本没有提到文件描述符的状态;虽然UNDEFINED不是很有帮助…如果fd关闭并且我再次打电话给关闭,可能会发生奇怪的难以调试的副作用,所以我宁愿忽略这种情况,因为做了错误的事情……然后,那里没有无限数量的文件描述符可用,资源泄漏是某种错误(至少对我而言).
当然我可以检查fclose的一个具体实现,但我不相信有人设计stdio并且没有考虑这个问题?它只是文档是坏的还是这个函数的设计?
这个角落案件真的让我烦恼:(
解决方法
事实上,close()也存在问题,而不仅仅是fclose().
POSIX声明close()
返回EINTR,这通常意味着应用程序可能会重试该呼叫.但是Linux中的事情比较复杂.见LWN上的this article和this post.
[...]@H_403_23@ the POSIX
EINTR@H_403_23@ semantics are not really possible on Linux. The file descriptor passed to
close()@H_403_23@ 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()@H_403_23@ returns.
This blog post和this answer解释了为什么用EINTR重试close()失败并不是一个好主意.所以在Linux中,如果close()因EINTR(或EINPROGRESS)失败,你就无能为力.
另请注意,close()在Linux中是异步的.例如,有时umount可能会在关闭文件系统上的最后一个打开的描述符后立即返回EBUSY,因为它尚未在内核中释放.请参阅此处有趣的讨论:page 1,page 2.
EINTR和fclose()
POSIX指出fclose()
:
After the call to
fclose()@H_403_23@,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()@H_403_23@ or
setvbuf()@H_403_23@ function shall be disassociated from the stream. If the associated buffer was automatically allocated,it shall be deallocated.
我相信这意味着即使close()失败,fclose()也应该释放所有资源并且不会产生泄漏.至少对于glibc和uclibc实现来说这是真的.
可靠的错误处理
>在fclose()之前调用fflush().
由于在调用fflush()或close()时无法确定fclose()是否失败,因此必须在fclose()之前显式调用fflush()以确保成功将用户空间缓冲区发送到内核.
>不要在EINTR之后重试.
如果fclose()使用EINTR失败,则无法重试close(),也无法重试fclose().
>如果需要,请调用fsync().
>如果您关心数据完整性,则应在调用fclose()1之前调用fsync()或fdatasync().
>如果不这样做,只需忽略fclose()中的EINTR即可.
笔记
>如果fflush()和fsync()成功并且fclose()因EINTR而失败,则不会丢失任何数据且不会发生泄漏.
>您还应确保在来自另一个线程2的fflush()和fclose()调用之间不使用FILE对象.
[1]参见“Everything You Always Wanted to Know About Fsync()”文章,该文章解释了为什么fsync()也可能是异步操作.