TL; DR: Если ядро Linux теряет буферизованную запись ввода-вывода , есть ли способ для приложения узнать?
Я знаю, что вам нужен fsync()
файл (и его родительский каталог) для долговечности . Вопрос в том, теряет ли ядро грязные буферы, ожидающие записи, из-за ошибки ввода-вывода, как приложение может обнаружить это и восстановить или прервать работу?
Подумайте о приложениях баз данных и т. Д., Где порядок записи и надежность записи могут иметь решающее значение.
Забыли пишет? Как?
Блок слой ли ядро в некоторых обстоятельствах теряют буферном запросы ввода / вывода , которые были успешно представленные write()
, и pwrite()
т.д., с сообщением об ошибке , как:
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(...)
и end_buffer_async_write(...)
вfs/buffer.c
).
В более новых ядрах вместо этого ошибка будет содержать "потерянную запись асинхронной страницы" , например:
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()
файл, чтобы подтвердить его наличие на диске.
Похоже, wait_on_page_writeback_range(...)
вmm/filemap.c
мощи, do_sync_mapping_range(...)
вfs/sync.c
которой зовут очередь sys_sync_file_range(...)
. Он возвращается, -EIO
если не удалось записать один или несколько буферов.
Если, как я предполагаю, это распространяется на fsync()
результат, то, если приложение паникует и выходит из строя, если оно получает ошибку ввода-вывода fsync()
и знает, как заново выполнить свою работу при перезапуске, этого должно быть достаточной защиты?
По-видимому, у приложения нет способа узнать, какие байтовые смещения в файле соответствуют потерянным страницам, чтобы оно могло их переписать, если оно знает как, но если приложение повторяет всю свою ожидающую работу с момента последнего успешного fsync()
файла, и это перезаписывает любые грязные буферы ядра, соответствующие потерянным операциям записи в файл, которые должны очистить все флаги ошибок ввода-вывода на потерянных страницах и позволить fsync()
завершиться следующей, верно?
Существуют ли какие-либо другие, безобидные обстоятельства, при которых fsync()
может произойти возвращение, -EIO
когда спасение и повторная работа были бы слишком радикальными?
Зачем?
Конечно, таких ошибок быть не должно. В этом случае ошибка возникла из-за неудачного взаимодействия между dm-multipath
настройками драйвера по умолчанию и сенсорным кодом, используемым SAN для сообщения о невозможности выделения хранилища с тонким предоставлением. Но это не единственное обстоятельство, при котором они могут произойти - я также видел отчеты об этом, например, из LVM с тонкой подготовкой, который используется libvirt, Docker и другими. Важное приложение, такое как база данных, должно пытаться справиться с такими ошибками, а не слепо продолжать работу, как будто все в порядке.
Если ядро считает, что можно потерять запись и не умереть из-за паники ядра, приложения должны найти способ справиться с этим.
Практическое влияние состоит в том, что я обнаружил случай, когда проблема с несколькими путями в SAN вызвала потерянные записи, которые привели к повреждению базы данных, потому что СУБД не знала, что ее запись была неудачной. Не смешно.
источник
Ответы:
fsync()
возвращается,-EIO
если ядро потеряло запись(Примечание: ранняя часть ссылается на старые ядра; обновлено ниже, чтобы отразить современные ядра)
Похоже, что асинхронная запись буфера при
end_buffer_async_write(...)
сбоях устанавливает-EIO
флаг на странице сбойного грязного буфера для файла :который затем обнаруживается ,
wait_on_page_writeback_range(...)
как вызываетсяdo_sync_mapping_range(...)
при вызове ,sys_sync_file_range(...)
как вызываетсяsys_sync_file_range2(...)
для выполнения вызова библиотеки Сfsync()
.Но только один раз!
Этот комментарий к
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(...)
при тестировании биты ошибок очищаются :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()
до тех пор, пока оно не увенчается успехом, и полагает, что данные находятся на диске, это ужасно неправильно.Я почти уверен, что это источник повреждения данных, который я обнаружил в СУБД. Он пытается повторить попытку
fsync()
и думает, что все будет хорошо, когда ему это удастся.Это разрешено?
Документы POSIX / SuS на
fsync()
самом деле не указывают этого в любом случае:На странице руководства Linux
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);
Что касается синхронизации, я думаю, что это похоже, хотя структура теперь довольно сложна.
filemap_check_errors
вmm/filemap.c
настоящее время делает: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
:Читка предполагает, что да, современные ядра ведут себя так же.
Похоже, это означает, что если
fsync()
(или предположительноwrite()
илиclose()
) возвращается-EIO
, файл находится в каком-то неопределенном состоянии между тем, когда вы в последний раз успешноfsync()
d илиclose()
d, и его последнимwrite()
состоянием десять.Контрольная работа
Я реализовал тестовый пример, чтобы продемонстрировать это поведение .
Последствия
СУБД может справиться с этим путем восстановления после сбоя. Как, черт возьми, обычное пользовательское приложение должно с этим справиться? На
fsync()
странице руководства нет предупреждений, что это означает «fsync-if-you-feel-like-it», и я ожидаю, что многие приложения не справятся с таким поведением.Отчеты об ошибках
дальнейшее чтение
lwn.net затронул это в статье «Улучшенная обработка ошибок блочного уровня» .
Тема списка рассылки postgresql.org .
источник
errno
полностью является конструкцией библиотеки C пользовательского пространства. Различия в возвращаемых значениях между системными вызовами и библиотекой C обычно игнорируют (как это делает Крейг Рингер выше), поскольку возвращаемое значение ошибки надежно идентифицирует, к какому из них (системный вызов или библиотечная функция C) идет ссылка: «-1
сerrno==EIO
"относится к библиотечной функции C, тогда как"-EIO
"относится к системному вызову. Наконец, страницы руководства Linux в Интернете являются наиболее свежими справочниками по страницам руководства Linux.fsync()
/fdatasync()
когда размер транзакции представляет собой полный файл; используяmmap()
/,msync()
когда размер транзакции является записью, выровненной по странице; и используя низкоуровневый I / O,,fdatasync()
и несколько параллельных файловых дескрипторов (один дескриптор и поток на транзакцию) в один и тот же файл в противном случае " . Блокировки описаний открытых файлов (fcntl()
,F_OFD_
), специфичные для Linux , очень полезны с последним.Я не согласен.
write
может вернуться без ошибки, если запись просто поставлена в очередь, но об ошибке будет сообщено при следующей операции, которая потребует фактической записи на диск, то есть при следующейfsync
, возможно, при следующей записи, если система решит очистить кеш и при минимум на последний файл закрыть.Это причина, по которой приложению важно проверить возвращаемое значение close для обнаружения возможных ошибок записи.
Если вам действительно нужна умная обработка ошибок, вы должны исходить из предположения, что все, что было написано с момента последнего успешного выполнения,
fsync
могло дать сбой и что во всем этом по крайней мере что-то не удалось.источник
fsync()
илиclose()
файла, если оно получает-EIO
отwrite()
,fsync()
илиclose()
. Что ж, это весело.write
(2) дает меньше, чем вы ожидаете. Страница руководства очень открыта о семантике успешногоwrite()
вызова:Мы можем сделать вывод, что успешное выполнение
write()
означает просто, что данные достигли средств буферизации ядра. Если сохранить буфер не удается, последующий доступ к дескриптору файла вернет код ошибки. Это может быть последнее средствоclose()
. Страница руководстваclose
системного вызова (2) содержит следующее предложение:Если вашему приложению необходимо сохранить данные, напишите, оно должно использовать
fsync
/fsyncdata
на регулярной основе:источник
fsync()
это необходимо. Но в конкретном случае, когда ядро теряет страницы из-за ошибки ввода-вывода, произойдетfsync()
сбой? При каких обстоятельствах он может потом добиться успеха?fsync()
возврат-EIO
по вопросам ввода-вывода (что было бы хорошо в противном случае?). Таким образом, база данных знает, что часть предыдущей записи не удалась, и может перейти в режим восстановления. Разве это не то, что вам нужно? Какова мотивация вашего последнего вопроса? Вы хотите узнать, какая запись не удалась, или восстановить файловый дескриптор для дальнейшего использования?fsync()
может вернуться ,-EIO
где он находится в безопасности , чтобы повторить попытку, и если это можно сказать разницу.-EIO
. Если каждый файловый дескриптор используется только одним потоком за раз, этот поток может вернуться к последнемуfsync()
и повторитьwrite()
вызовы. Но все же, если ониwrite()
записывают только часть сектора, неизмененная часть все еще может быть повреждена.Используйте флаг O_SYNC при открытии файла. Это обеспечивает запись данных на диск.
Если это вас не удовлетворит, ничего не будет.
источник
O_SYNC
это кошмар для производительности. Это означает, что приложение не может делать ничего другого, пока выполняется дисковый ввод-вывод, если только оно не порождается потоками ввода-вывода. С таким же успехом можно сказать, что интерфейс буферизованного ввода-вывода небезопасен, и всем следует использовать AIO. Разве тихая потеря записи не может быть приемлемой для буферизованного ввода-вывода?O_DATASYNC
только немного лучше в этом отношении)Проверьте возвращаемое значение close. close может завершиться ошибкой, тогда как буферизованная запись кажется успешной.
источник
open()
ING иclose()
ИНГ файл каждые несколько секунд. вот почему у нас естьfsync()
...