LINUX.ORG.RU

Что вы делаете при неудачном close?

 надёжное по


0

3

Системный вызов close возвращает код ошибки:

https://man7.org/linux/man-pages/man2/close.2.html

Опустим тривиальные случаи типа EINTR. Например, возвращается EIO - «An I/O error occurred», что не сильно более информативно, чем «иди на …». Фактически, ничего дельного, кроме разве что отправки лога (или попапа об ошибке, если это гуишное ПО), сделать нельзя. Можно попытаться повторить операцию close. Но откуда мы знаем, что во второй раз она завершится удачно? В общем случае она теоретически может завершиться удачно на (N+1)’й попытке, где N может быть произвольно большим числом.

UPD: а ещё не специфицировано, освободится ли дескриптор файла при EIO, так что в такой ситуации ещё может быть исчерпание лимита открытых файлов.

А вы что делаете в таких случаях, когда вообще непонятно, как программе обработать ошибочное состояние?

★★★★★

Последнее исправление: seiken (всего исправлений: 1)
Ответ на: комментарий от cudeta

Пруф в man close:

https://man7.org/linux/man-pages/man2/close.2.html

Note, however, that a failure return should be used only for diagnostic purposes (i.e., a warning to the application that there may still be I/O pending or there may have been failed I/O) or remedial purposes (e.g., writing the file once more or creating a backup).

Retrying the close() after a failure return is the wrong thing to do, since this may cause a reused file descriptor from another thread to be closed. This can occur because the Linux kernel always releases the file descriptor early in the close operation, freeing it for reuse; the steps that may return an error, such as flushing data to the filesystem or device, occur only later in the close operation.

cudeta
()

Как обычно эскалируем наверх. Проблема не решается в месте ее возникновения (иначе это не проблема), поэтому пусть с этим разбирается вызывающая сторона. Попытки решать на месте возникновения только вызывают продалбывание вспышек. Лучше пусть оно явно с грохотом падает, чем пытается работать с крайне сомнительной корректностью.

dvetutnev
()
Ответ на: комментарий от dvetutnev

Как обычно эскалируем наверх.

Никакой из верхних уровней не знает, что делать с ошибочным close/EIO, в том-то и дело. Если бы знали, я бы не создавал эту тему. Какая-то хрень произошла в системе с вводом/выводом, и никаких выводов о способе восстановления, повтора со стороны приложения нет.

seiken ★★★★★
() автор топика
Ответ на: комментарий от dvetutnev

А что вызывающая сторона может сделать с неудачным close()? Ну хотя бы гипотетически?

Я уж молчу о том, что close() должен вызывать владелец дескриптора и у вызывающей стороны доступа к дескриптору нет.

Единственный осмысленный вариант обработки в вызывающем коде, который мне приходит в голову, это удалить файл и записать его по новой, но это допустимо только в очень малом количестве случаев, а стоит делать ещё в меньшем.

PS: Отвечая на вопрос: лично я close() даже не проверяю, т.к. х.з. как его обрабатывать.

Ivan_qrt ★★★★★
()
Ответ на: комментарий от seiken

Какая-то хрень произошла в системе с вводом/выводом, и никаких выводов о способе восстановления, повтора со стороны приложения нет.

Вот именно. Это или ошибка в ядре, либо аппаратный сбой. В первом случаи что-то делать бесполезно, ибо UB. Во втором можно добить осыпающийся диск раньше, чем с него данные заберут (каскадный эффект).

dvetutnev
()
Последнее исправление: dvetutnev (всего исправлений: 1)
Ответ на: комментарий от dvetutnev

В первом случаи что-то делать бесполезно, ибо UB.

Ну это всё таки не C++. Ядро одно и то же, и есть явные условия (закодированные в ядре), при которых возвращается EIO. Например, драйвер не смог дописать оставшиеся в буферах данные на носитель. Но это вовсе не значит, что приложению надо падать.

seiken ★★★★★
() автор топика
Последнее исправление: seiken (всего исправлений: 1)
Ответ на: комментарий от seiken

Ядро одно и то же, и есть явные условия (закодированные в ядре)

Ядро одно и то же

Вот тут скорее всего ошибка. Как-то сомнительно, что для своего софта ты прибиваешь конкретную сборку/конфигурацию ядра гвоздями к нему. А без этого такие рассуждения теряют смысл.

Если конечно не адепт NixOS, оно так умеет)

dvetutnev
()

Если можем продолжать дальше - ну и хрен с ним, если не можем - ну assert, что ещё делать. Всё равно, если не смогли закрыть дескриптор, значит он или не мой и повторно пытаться не надо, или что-то в системе пошло уже сильно не так и мы скоро грохнемся и без этого.

SkyMaverick ★★★★★
()
Ответ на: комментарий от Ivan_qrt

Хотя по мне падать из-за ошибки в close() крайне сомнительная идея.

Смотри, чтобы имело смысл использовать какой-то софт должны сложиться два условия:

  1. Софт должен делать то, что от него ожидают.
  2. Софт должен не делать то, чего от него не ожидают.

Оба этих условия обязательны, иначе использование такого софта (в таких условиях) может принести больше ущерба (данные похерить), чем пользы. Поэтому самый оптимальный вариант в непонятных ситуациях это переключение в безопасный режим, самый простой способ это сделать - аварийная остановка, а простое решение работает лучше, старая инженерная мудрость. Пользы конечно уже не приносит, но главное что не ухудшает ситуацию.

Собственно само ядро так и делает: непонятная фигня для которой нет готового решения - kernel panic.

dvetutnev
()
Последнее исправление: dvetutnev (всего исправлений: 1)
Ответ на: комментарий от seiken

Никакой

Упорись в микросервисы %)
Верхний уровень автоматически сделает ретрай, с высокой вероятностью попав на другой инстанс, а сбойный, даже если он не сигнализирует о проблеме, после нескольких косяков просто отрубит circuit breaker'ом.

bo4ok
()
Ответ на: комментарий от dvetutnev

Это справедливо для close() только в том случае, если ты дальше будешь открывать этот файл повторно и целостность его содержимого для тебя критически важна, но при этом проверок целостности там нет. Но тогда не особо понятно, зачем ты собрался закрывать файл, если всё равно его открываешь.

Такое наверное можно себе представить, но это всё-таки скорее исключение, а не правило.

Лично мне куда более логичным кажется падать уже при открытии/записи в следующий файл, если io-проблемы сохраняются, а не на закрытии существующего.

А то так можно договорится до того, что нужно падать если логи не удалось записать.

Ivan_qrt ★★★★★
()
Ответ на: комментарий от dvetutnev

аварийная остановка

А почему сбойный close приводит к поведению, нарушающему спецификацию?

Собственно само ядро так и делает: непонятная фигня для которой нет готового решения - kernel panic.

Если речь о порче памяти - это понятно, что дальше ничего хорошего с системой не будет, но ошибочный код возврата - это не порча памяти, а контролируемая ситуация.

seiken ★★★★★
() автор топика
Ответ на: комментарий от seiken

А почему сбойный close приводит к поведению, нарушающему спецификацию?

А разве обратное доказано? Для всех окружений где это будет использоваться? Отсутствие доказательства некорректности != доказательству корректности.

Если речь о порче памяти - это понятно, что дальше ничего хорошего с системой не будет, но ошибочный код возврата - это не порча памяти, а контролируемая ситуация.

Ошибочный код возврата close гарантирует, что никто не испортил память? В месте вызова этого close обеспечено, что никакой модуль ядра ничего не затер до его вызова? А после? Возможна ситуация, в которой close возвращает ошибку, от того, что кто-то промахнулся в адресной арифметике в модуле ядра и он затер какие-то структуры?

dvetutnev
()
Последнее исправление: dvetutnev (всего исправлений: 4)
Ответ на: комментарий от seiken

Ядро одно и то же, и есть явные условия (закодированные в ядре), при которых возвращается EIO. Например, драйвер не смог дописать оставшиеся в буферах данные на носитель.

Я Вам уже выше писал, но Вы почему-то проигнорировали - просто не надо до этого доводить. Сбрасывайте буфера явно до close(), пока с ошибками ещё хоть что-то можно сделать. А если сбрасывать ничего не надо то close() на валидном дескрипторе не фейлится никогда.

bugfixer ★★★★★
()

как минимум нужно уведомить юзера.

плюнуть в лог сообщением типа «такой херни (тут пишем что за херня) раньше не было, есть варик что твой диск пошел по борозде. не факт что данные сохранились, пытаться сохранить еще раз я не стану и тебе не советую.»

если приложуха гуевая (или туевая) - продублировать сообщение на экранчег. подождать пока юзер нажмет на кнопочку «ок» и закрыть приложение.

ядро, драйвера, железо и вся та хурма, которая называется окружение - вне компетенции твоей приложухи.

ты можешь только более менее разумные версии строить (типа «походу диск сдох», «драйвера писаны криворукими крабообразными», «линус уже не тот», етц), и делиться своими предположениями с юзером.

пытаться продолжать работу - кому нужны эти конвульсии?

olelookoe ★★★
()
Ответ на: комментарий от olelookoe

есть варик что твой диск пошел по борозде.

Откуда такая безаппеляционность? Например - место могло закончиться.

пытаться продолжать работу - кому нужны эти конвульсии?

Тем у кого что-то более сложное чем hello-world.

bugfixer ★★★★★
()

Это оочень хороший и глубокий вопрос. Где-то даже философский.

Как и на остальные философские вопросы - прямого и четкого ответа тут нет, зато есть спектр вариантов, зависящий от условий.

Что чаще всего может стать причиной?

Сбой на нижнем уровне: файловая система, сеть (если речь о сетевом ресурсе), сам диск - что-то засбоило и скорее всего данные пользователя будут испорчены. Потому что очень часто в верхнеуровневой реализации close вызывается сначала flush - так делает FUSE и Java, например.

Что с этим делать?

По обстоятельствам.

Если данных немного и сама обработка происходила в фоне - можно пометить файл как битый или вообще его удалить и запустить процесс заново. Если процесс записи с вызовом сбойного close был запущен пользователем и есть UI - стоит показать сообщение об ошибке и опять же как-то пометить что данные битые.

Ну и самое простое и очевидное: если речь про консольную утилиту то такой сбой это повод просто выйти с кодом ошибки, опять же удалив битые данные.

alex0x08 ★★★
()
Ответ на: комментарий от dvetutnev

Я обычно define-ю аналог, чтобы не запариваться. В общем-то можно и #undef NDEBUG сделать, но это из серии вредных советов.

Имелось ввиду, что если close() обломился и это не драйвер, то, чаще всего, или мы творим какую-то дичь и тогда надо перестать это делать или в системе творится какая-то дичь и зачем тогда уже жить.

SkyMaverick ★★★★★
()
Последнее исправление: SkyMaverick (всего исправлений: 1)
Ответ на: комментарий от dvetutnev

А разве обратное доказано?

В подавляющей части софта, если он проходит достаточные тестовые проверки, считается, что он соответствует спецификации. Т.е. если даже там close вернул EIO, но тесты пройдены, то всё ОК. И для сложных систем типа тех, которым нужна такая сложная ОС, как Линукс, на практике не может быть никакого другого доказательства соответствию спецификации. И даже ПО, которое продаётся для «произвольного Линукса», всё равно тестируется на конкретных дистрибутивах, и ни один вменяемый производитель не будет давать обязывающих гарантий, что его ПО будет работать без ошибок на вообще любом базовом ПО Линукс.

Ошибочный код возврата close гарантирует, что никто не испортил память?

Успешный код возврата точно так же не гарантирует, что никто не испортил память.

seiken ★★★★★
() автор топика
Ответ на: комментарий от bugfixer

Я Вам уже выше писал, но Вы почему-то проигнорировали - просто не надо до этого доводить. Сбрасывайте буфера явно до close(), пока с ошибками ещё хоть что-то можно сделать. А если сбрасывать ничего не надо то close() на валидном дескрипторе не фейлится никогда.

И все это опирается на допущение, что ядро работает корректно. И в этом проблема. Для конкретной версии/конфигурации это можно доказать, но при каждом обновлении это доказательство будет выводиться и прилагаться?

Ты сейчас оцениваешь возможные последствия в зависимости от вероятности возникновения, а надо по ущербу который может быть причинен. Черные лебеди это как раз про это. Возьми хронику любой техногенной аварии, это цепочка маловероятных событий, каждое из которых явных проблем не создает, а возникновение их в определенной последовательности считалось чем то в духе невозможного (как правило об этом просто не подумали). Но это регулярно происходит. Ariane 5 не даст соврать)

Если это браузер поймал ошибку при переоткрытии лога, то пожалуй можно забить. А если это процессинг платежей, то из-за потери логов можно кучу проблем дополнительно огрести.

dvetutnev
()
Последнее исправление: dvetutnev (всего исправлений: 3)
Ответ на: комментарий от dvetutnev

Возьми хронику любой техногенной аварии, это цепочка маловероятных событий, каждое из которых явных проблем не создает, а возникновение их в определенной последовательности считалось чем то в духе невозможного

А почему ты решил, что твоё терминирование ПО не создаст новую проблему из того же класса «маловероятных событий»? Ведь ты не знаешь, почему close вернул EIO. И вообще, для ответственных систем важнее graceful degradation, нежели ничем не обоснованный пессимизм и суицид.

seiken ★★★★★
() автор топика
Последнее исправление: seiken (всего исправлений: 1)
Ответ на: комментарий от dvetutnev

Ээээ, так вроде это вариант по умолчанию. Потому что это могут быть логи атаки.

Это до первого случая, когда прод упал из-за того, что логи не записались.

Вариант по-умолчанию это вполне логичный, но это не значит, что его не надо менять. Тут всё зависит от соотношения цены простоя и цены ошибки. Если простой стоит мало, а цена ошибки огромна, то можно падать на любой чих. Если цена простоя велика, то об ошибках надо сигнализировать другими методами.

Ivan_qrt ★★★★★
()
Ответ на: комментарий от seiken

А почему ты решил, что твоё терминирование ПО не создаст новую проблему из того же класса «маловероятных событий»?

Почему же не создает, создает. Тут уже Фобос-грунт не даст соврать) И в принципе тот же пятый ариан. Конкретно в этом случаи ошибка в том, что не предусмотрели вариант что делать если ПО перешло в терминальный режим. Т.е. ошибка на системном уровне проектирования.

dvetutnev
()
Последнее исправление: dvetutnev (всего исправлений: 1)
Ответ на: комментарий от Ivan_qrt

Вариант по-умолчанию это вполне логичный, но это не значит, что его не надо менять

Ну да. Для каждой задачи своя оптимальная стратегия. Где-то лучше частично потерять функционал и продолжить работать. Где лучше все остановить, зажечь красную лампочку и ждать дежурный персонал.

dvetutnev
()
Ответ на: комментарий от seiken

Вот это тут ну совсем ни при чём.

Причем, причем. Софтовый модуль перенесли из одной системы в другую. Он начал сбоить. Также твой код может запуститься на другом ядре, которое не гарантирует, что все будет пучком при завершении close с ошибкой.

dvetutnev
()
Ответ на: комментарий от dvetutnev

Конкретно в этом случаи ошибка в том, что не предусмотрели вариант что делать если ПО перешло в терминальный режим. Т.е. ошибка на системном уровне проектирования.

С чего ты решил, что не предусмотрели? Ещё как предусмотрели, просто терминальный режим может быть только для серьёзных аварийных случаев, к коим close/EIO не относится. А твой пример с Арианом вообще не из-за ошибки в алгоритме, а из-за хреновой системы типов/компилятора и/или недостатка тестирования. Если я правильно помню детали, обычный набор тестов на граничные значения типа данных вскрыли бы проблему.

seiken ★★★★★
() автор топика
Ответ на: комментарий от dvetutnev

Также твой код может запуститься на другом ядре, которое не гарантирует, что все будет пучком при завершении close с ошибкой.

Я тебе уже писал выше. На этом другом ядре может не быть гарантий, что «все будет пучком» даже если close вернёт удачу.

seiken ★★★★★
() автор топика
Ответ на: комментарий от dvetutnev

Не каждому и не каждый день приходиться писать софт марсохода или подсчета денег.

Ответственные системы не ограничиваются космосом и банками. И во многих вообще нет никаких динамически выделяемых ресурсов, и соотв. проблемы с close нет бай дизайн. Вот там можно много чего статически верифицировать.

seiken ★★★★★
() автор топика

Мы тут целый день спорим о результате close. А чего с его аргументом? Есть тест мокающий этот вызов и проверяющий, что для каждого fd close вызывается ровно раз и каждый fb входит в множество валидных дескрипторов (т.е. полученный при открытии)?

dvetutnev
()
Ответ на: комментарий от dvetutnev

На мой взгляд, это не относится к юнит-тестированию, потому что зависит от реализации и API ввода/вывода ОС. Все ошибочные значения из этого API обрабатываются. А тестировать сам Линукс мне не интересно, этим занимаются отдельные конторы. Или никто. Таков мир Линукса.

seiken ★★★★★
() автор топика

вы можете воспроизвести ошибку с EIO ? оно вроде гипотетично может быть на полностью забитой ntfs

если нет, но боитесь - то только падать..в смысле что exit

иначе, по результатам n-кратного воспроизведения и осмысления ;-) скорее всего забить болт ругнувшись в журнал..

MKuznetsov ★★★★★
()
Ответ на: комментарий от dvetutnev

И все это опирается на допущение, что ядро работает корректно. И в этом проблема.

Как мило. Я глубоко убеждён что в софте такого уровня сложности есть баги. Да что уж там - я пока ещё даже в учебниках (учебниках, Карл!) безглючных hello-world не видел: Вы много раз встречали код возврата из hello-world отличный от нуля?

Side note: Вы вообще - с этой планеты?

Ты сейчас оцениваешь возможные последствия в зависимости от вероятности возникновения, а надо по ущербу который может быть причинен.

Аж подпрыгнул от таких заявлений… На секундочку - последние 20+ лет я в мире финтеха варюсь… Да, мы не реакторами управляем, но наши ошибки иногда конторе стоят очень дорого (бывает что десятки, а иногда и сотни миллионов убитых енотов). Посему - позволю себе утверждать что пару вещей про damage control я таки знаю.

Если это браузер поймал ошибку при переоткрытии лога, то пожалуй можно забить. А если это процессинг платежей, то из-за потери логов можно кучу проблем дополнительно огрести.

Да что Вы говорите? А как это вяжется с Вашим предложением «падать» по каждому чиху? Нет? Противоречий не видите?

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

flush() before close().

А на случай, если оба вернут EIO, у меня проездной!

А вы что делаете в таких случаях, когда вообще непонятно, как программе обработать ошибочное состояние?

Т.к. ничего осмысленного сделать один хрен нельзя, вообще не проверяю код возврата. И flush() перед close() тоже не вызываю. UPD: и исключения из деструкторов по возможности тоже стараюсь не бросать. =)

pr849
()
Последнее исправление: pr849 (всего исправлений: 3)
Ответ на: комментарий от pr849

flush() before close().

А на случай, если оба вернут EIO, у меня проездной!

Если за’flush()’ить не удалось, а записать ну очень нужно / хочется (например это что-то критичное типа transaction logs) - close() дергать ни в коем случае нельзя. Одна из осмысленных вещей что можно сделать - retry every X seconds, параллельно сыпля severe warning’ами которые видят operations «мне плохо - спасите / помогите!» в надежде что «кавалерия» таки прибежит.

Т.к. ничего осмысленного сделать один хрен нельзя

См. выше.

вообще не проверяю код возврата

Имхо - не самая лучшая стратегия: как минимум такое нужно логировать даже если данные не критичны.

и исключения из деструкторов по возможности тоже стараюсь не бросать.

Этого делать вообще никогда нельзя ))

bugfixer ★★★★★
()
Последнее исправление: bugfixer (всего исправлений: 1)
Ответ на: комментарий от bugfixer

Аж подпрыгнул от таких заявлений… На секундочку - последние 20+ лет я в мире финтеха варюсь

20+ лет я

Я рад, что ты в хорошей физической форме) Если ты двадцать с лишним лет финтех пилишь, то твой возраст близок к моему. И вроде бы пора уже переболеть такой детской болезнью, как козыряние своим опытом. Хотя бы потому что с другой стороны монитора это совсем не очевидно.

Да что Вы говорите? А как это вяжется с Вашим предложением «падать» по каждому чиху?

Например так, что сперва в моих постах в этой ветке нужно подсчитать сколько раз встречается слово всегда прежде чем искать взаимоисключающие параграфы)

Ошибки делятся на два группы: ожидаемые и нет. Первая отличается от второй, что для нее нет сценариев обработки. Предполагается, что во время работы системы они не возникают, а если возникли то продолжать работу не имеет смысл ибо свойства среды не соответствуют тем, под которые софт разработан. Работать в условиях непрозрачного состояния (мы же не знаем, что конкретно привело в него) идея крайне сомнительная. Отвалившаяся сеть - ожидаемая ошибка, отвалившая ФС - уже не факт. Для браузера отвалившеюся ФС можно рассматривать как ожидаемую ошибку, ущерб ограничен (это всего лишь логи браузера), сценарий обработки - игнор (показать страничку юзеру, но не записать историю предпочтительней чем вообще ничего не показать). А вот для финтеха изменить стейт, но не сохранить историю изменений уже не очень, лучше пусть он вообще не меняется, это хотя бы дополнительный ущерб не создает. Если ты столько работал в финтехе, то по идее должен понимать, что бизнес за потерю ТАКИХ логов по головке не погладит. Ибо как проводить разбор полет, когда что-то пойдет не так? Да, что такое и для чего нужен WAL лог я в курсе.

dvetutnev
()