fclose 不可能的正确错误处理(根据联机帮助页)?

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

所以我研究了一段时间的 fclose 联机帮助页,我的结论是如果 fclose 被某些信号中断,根据联机帮助页没有办法恢复...?我错过了什么吗?

通常,对于无缓冲的 POSIX 函数(打开、关闭、写入等),总有一种方法可以通过重新启动调用从信号中断 (EINTR) 中恢复;相比之下,缓冲调用的文档指出,在 fclose 尝试失败后,另一次尝试具有未定义的行为......没有关于如何恢复的提示。如果信号中断 fclose,我只是 "unlucky" 吗?数据可能会丢失,我不能确定文件描述符是否真的关闭了。我知道缓冲区被释放了,但是文件描述符呢? 考虑一下同时使用大量 fd 的大型应用程序,如果 fd 没有被正确释放,将会 运行 出现问题 -> 我认为必须有一个干净的解决方案来解决这个问题。

所以假设我正在编写一个库并且它不允许使用 sigaction 和 SA_RESTART 并且发送了很多信号,如果 fclose 被中断我该如何恢复? 在 fclose 因 EINTR 失败后在循环中调用 close(而不是 fclose)是个好主意吗? fclose 的文档根本没有提到文件描述符的状态; UNDEFINED 虽然不是很有帮助...如果 fd 关闭并且我再次调用 close,可能会出现奇怪的难以调试的副作用,所以我自然而然地宁愿忽略这种情况错误的事情...话又说回来,没有无限数量的可用文件描述符,资源泄漏是某种错误(至少对我而言)。

当然我可以检查一个特定的 fclose 实现,但我无法相信有人设计了 stdio 而没有考虑这个问题?是文档不好还是这个功能的设计不好?

这个极端情况真的让我很烦:(

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.

评论中已经提到了重试 fflush()close() 基础文件描述符的可能性。对于 大型应用程序 ,我更喜欢使用线程的模式,并有一个 专用 信号处理线程,而所有其他线程都有信号阻塞使用pthread_sigmask()。然后,当 fclose() 失败时,您遇到了 真正的 问题。

EINTRclose()

其实close()也有问题,不仅仅是fclose().

POSIX 表示 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 解释为什么重试 close() 失败并 EINTR 不是一个好主意。因此,在 Linux 中,如果 close()EINTR(或 EINPROGRESS)而失败,您将无能为力。

另请注意 close() 在 Linux 中是异步的。例如,有时 umount 可能 return EBUSY 在文件系统上关闭最后打开的描述符后立即,因为它尚未在内核中发布。在此处查看有趣的讨论:page 1, page 2.


EINTRfclose()

POSIX 表示 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.

我相信这意味着即使 close() 失败,fclose() 应该 释放所有资源并且不会产生泄漏。至少对于 glibc and uclibc 实施是正确的。


可靠的错误处理

  • fclose()之前调用fflush()

    由于无法确定fclose()调用fflush()close()时是否失败,因此必须在fclose()之前显式调用fflush()才能确保用户空间缓冲区已成功发送到内核。

  • EINTR后不要重试。

    如果 fclose() 失败 EINTR,您不能重试 close() 也不能重试 fclose()

  • 如有需要请致电fsync()

    • 如果你关心数据完整性,你应该在调用fclose()之前调用fsync()fdatasync() 1.
    • 如果不这样做,请忽略 fclose() 中的 EINTR

备注

  • 如果 fflush()fsync() 成功并且 fclose() 失败 EINTR没有数据丢失 没有发生泄漏

  • 您还应确保 FILE 对象未在来自另一个线程的 fflush()fclose() 调用之间使用 2.


[1] 请参阅 "Everything You Always Wanted to Know About Fsync()" 文章,其中解释了为什么 fsync() 也可能是异步操作。

[2] 你可以在调用fflush()fclose()之前调用flockfile()。它应该与 fclose() 一起正确工作。