DR: 如果 Linux 内核丢失了一个缓冲的 I/O 写入,有什么办法可以让应用程序找到答案吗?
我知道为了持久性 ,必须使用 fsync()
文件(及其父目录)。问题是由于 I/O 错误导致的 如果内核丢失了挂起写操作的脏缓冲区,应用程序如何检测到这个错误并恢复或中止?
考虑数据库应用程序等,其中写入顺序和写入持久性可能是至关重要的。
Linux 内核的块层可以在某些情况下 输了缓冲由 write()
、 pwrite()
等成功提交的 I/O 请求,出现如下错误:
Buffer I/O error on device dm-0, logical block 12345
lost page write due to I/O error on dm-0
(见 ABC2中的 ABC0和 end_buffer_async_write(...)
)。
Buffer I/O error on dev dm-0, logical block 12345, lost async page write
由于应用程序的 write()
已经返回,没有错误,似乎没有办法向应用程序报告错误。
我不太熟悉内核源代码,但是我知道 好好想想在缓冲区上设置 AS_EIO
,如果执行异步写操作,这个缓冲区就不会被写出来:
set_bit(AS_EIO, &page->mapping->flags);
set_buffer_write_io_error(bh);
clear_buffer_uptodate(bh);
SetPageError(page);
但我不清楚应用程序是否或如何能够在以后的 fsync()
文件确认它在磁盘上时发现这一点。
它看起来像 ABC1中的 wait_on_page_writeback_range(...)
可能由 ABC3中的 do_sync_mapping_range(...)
转而由 sys_sync_file_range(...)
调用。如果无法写入一个或多个缓冲区,则返回 -EIO
。
如果正如我猜测的那样,这会传播到 fsync()
的结果,那么如果应用程序在从 fsync()
获得一个 I/O 错误并且知道如何在重启时重新做它的工作时惊慌失措并退出,这应该是足够的保护措施了吧?
应用程序大概没有办法知道 哪个字节偏移量对应的文件丢失的页面,所以它可以重写它们,如果它知道如何,但是如果应用程序重复所有的挂起的工作,自上次成功的 fsync()
的文件,并重写任何脏内核缓冲区对文件丢失的写,这应该清除丢失的页面上的任何 I/O 错误标志,并允许下一个 fsync()
完成-对不对?
那么,是否存在其他无害的情况,使得 fsync()
可能返回 -EIO
,而在这种情况下,跳出和重做工作会过于激烈?
当然,这样的错误不应该发生。在这种情况下,错误产生于 dm-multipath
驱动程序的缺省值与 SAN 用于报告分配瘦配置存储失败的检测代码之间的不幸交互。但是这并不是 可以发生的唯一情况——例如,我还看到了从瘦供应 LVM 发出的关于 可以的报告,libvirt、 Docker 等使用了这种报告。像数据库这样的关键应用程序应该尝试处理这样的错误,而不是盲目地继续下去,好像一切都很好。
如果 内核认为丢失写操作不会因内核恐慌而死亡,那么应用程序必须找到应对的方法。
实际的影响是,我发现了一个案例,SAN 的多路径问题导致丢失写操作,最终导致数据库损坏,因为 DBMS 不知道它的写操作已经失败。一点都不好玩。