jjzjj

c - 编写程序以处理导致 Linux 上丢失写入的 I/O 错误

coder 2023-04-28 原文

TL;DR:如果 Linux 内核丢失了缓冲的 I/O 写入,应用程序有什么方法可以发现吗?

我知道您必须对文件(及其父目录)进行 fsync() 以确保持久性。问题是如果内核由于 I/O 错误而丢失了等待写入的脏缓冲区,应用程序如何检测到这一点并恢复或中止?

想想数据库应用程序等,其中写入顺序和写入持久性至关重要。

丢失的写入?怎么样?

Linux内核的 block 层在某些情况下会丢失缓冲的已经被write()pwrite()成功提交的I/O请求code> 等,错误如下:

Buffer I/O error on device dm-0, logical block 12345
lost page write due to I/O error on dm-0

(参见 end_buffer_write_sync(...) and end_buffer_async_write(...) in fs/buffer.c)。

On newer kernels the error will instead contain "lost async page 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()s 文件以确认它在磁盘上时发现它。

看起来像 wait_on_page_writeback_range(...) in mm/filemap.c可能来自 do_sync_mapping_range(...) in fs/sync.csys_sync_file_range(...) 轮流调用.如果无法写入一个或多个缓冲区,则返回 -EIO

如果,正如我所猜测的那样,这会传播到 fsync() 的结果,那么如果应用程序在从 fsync( ) 并且知道重新启动时如何重新工作,这应该是足够的保障吗?

应用程序可能无法知道文件中的哪些字节偏移对应于丢失的页面,因此如果它知道如何重写它们,但如果应用程序重复所有待处理的工作,因为文件的最后一个成功的 fsync(),并重写与文件丢失写入相对应的任何脏内核缓冲区,这应该清除丢失页面上的任何 I/O 错误标志并允许下一个 fsync() 来完成 - 对吧?

还有其他无害的情况,fsync() 可能会返回 -EIO,在这种情况下,救助和重做工作会过于激烈?

为什么?

当然这样的错误不应该发生。在这种情况下,错误是由 dm-multipath 驱动程序的默认值与 SAN 用来报告分配精简配置存储失败的检测代码之间的不幸交互引起的。但这并不是它们可能发生的唯一情况——我还看到了来自精简配置 LVM 的报告,例如 libvirt、Docker 等使用的。像数据库这样的关键应用程序应该尝试处理此类错误,而不是一味地装作万事大吉。

如果内核认为在不因内核 panic 而死的情况下丢失写入是可以的,则应用程序必须找到应对方法。

实际影响是,我发现了一个案例,即 SAN 的多路径问题导致写入丢失,导致数据库损坏,因为 DBMS 不知道其写入已失败。不好玩。

最佳答案

fsync() 如果内核丢失写入,则返回 -EIO

(注意:早期部分引用了旧内核;下面更新以反射(reflect)现代内核)

看起来像 async buffer write-out in end_buffer_async_write(...) failures set an -EIO flag on the failed dirty buffer page for the file :

set_bit(AS_EIO, &page->mapping->flags);
set_buffer_write_io_error(bh);
clear_buffer_uptodate(bh);
SetPageError(page);

然后由 wait_on_page_writeback_range(...) 检测到,由 do_sync_mapping_range(...) 调用,由 sys_sync_file_range(...)sys_sync_file_range2(...) 调用以实现 C 库调用 fsync()

但只有一次!

This comment on sys_sync_file_range

168  * SYNC_FILE_RANGE_WAIT_BEFORE and SYNC_FILE_RANGE_WAIT_AFTER will detect any
169  * I/O errors or ENOSPC conditions and will return those to the caller, after
170  * clearing the EIO and ENOSPC flags in the address_space.

建议当 fsync() 返回 -EIO 或(手册页中未记录)-ENOSPC 时,它会清除错误状态,因此即使页面从未被写入,后续 fsync() 也会报告成功。

果然wait_on_page_writeback_range(...) clears the error bits when it tests them :

301         /* Check for outstanding write errors */
302         if (test_and_clear_bit(AS_ENOSPC, &mapping->flags))
303                 ret = -ENOSPC;
304         if (test_and_clear_bit(AS_EIO, &mapping->flags))
305                 ret = -EIO;

所以如果应用程序期望它可以重试 fsync() 直到它成功并相信数据在磁盘上,那就大错特错了。

我很确定这是我在 DBMS 中发现的数据损坏的根源。它重试 fsync() 并认为成功后一切都会好起来的。

允许这样做吗?

POSIX/SuS docs on fsync()无论哪种方式都不要真正指定:

If the fsync() function fails, outstanding I/O operations are not guaranteed to have been completed.

Linux's man-page for fsync()只是没有说明失败时会发生什么。

所以看来 fsync() 错误的意思是“我不知道你写的东西发生了什么,可能有效与否,最好再试一次确定”。

较新的内核

4.9 end_buffer_async_write在页面上设置 -EIO,只是通过 mapping_set_error

    buffer_io_error(bh, ", lost async page write");
    mapping_set_error(page->mapping, -EIO);
    set_buffer_write_io_error(bh);
    clear_buffer_uptodate(bh);
    SetPageError(page);

在同步方面,我认为它是相似的,尽管结构现在非常复杂。 mm/filemap.c 中的 filemap_check_errors 现在可以:

    if (test_bit(AS_EIO, &mapping->flags) &&
        test_and_clear_bit(AS_EIO, &mapping->flags))
            ret = -EIO;

效果差不多。错误检查似乎都经过filemap_check_errors进行测试和清除:

    if (test_bit(AS_EIO, &mapping->flags) &&
        test_and_clear_bit(AS_EIO, &mapping->flags))
            ret = -EIO;
    return ret;

我在笔记本电脑上使用 btrfs,但是当我创建 ext4 环回以在 /mnt/tmp 上进行测试并设置对其进行性能探测:

sudo dd if=/dev/zero of=/tmp/ext bs=1M count=100
sudo mke2fs -j -T ext4 /tmp/ext
sudo mount -o loop /tmp/ext /mnt/tmp

sudo perf probe filemap_check_errors

sudo perf record -g -e probe:end_buffer_async_write -e probe:filemap_check_errors dd if=/dev/zero of=/mnt/tmp/test bs=4k count=1 conv=fsync

我在 perf report -T 中找到以下调用堆栈:

        ---__GI___libc_fsync
           entry_SYSCALL_64_fastpath
           sys_fsync
           do_fsync
           vfs_fsync_range
           ext4_sync_file
           filemap_write_and_wait_range
           filemap_check_errors

通读表明,是的,现代内核的行为相同。

这似乎意味着如果 fsync()(或者可能是 write()close())返回 -EIO ,文件在你上次成功 fsync()d 或 close()d 和最近一次 write( )十个状态。

测试

I've implemented a test case to demonstrate this behaviour .

意义

DBMS 可以通过进入崩溃恢复来解决这个问题。一个普通的用户应用程序到底应该如何处理这个问题? fsync() 手册页没有警告它意味着“fsync-if-you-feel-like-it”,我预计很多应用程序无法处理这种行为很好。

错误报告

进一步阅读

lwn.net touched on this in the article "Improved block-layer error handling" .

postgresql.org mailing list thread .

关于c - 编写程序以处理导致 Linux 上丢失写入的 I/O 错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42434872/

有关c - 编写程序以处理导致 Linux 上丢失写入的 I/O 错误的更多相关文章

  1. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  2. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  3. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  4. ruby - 在 Ruby 中编写命令行实用程序 - 2

    我想用ruby​​编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序

  5. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  6. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  7. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  8. Ruby 写入和读取对象到文件 - 2

    好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

  9. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  10. ruby-on-rails - 迷你测试错误 : "NameError: uninitialized constant" - 2

    我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test

随机推荐