Правильная обработка ошибок для fclose невозможна (согласно man-странице)?

Так что я изучаю man-страницу fclose довольно долго, и я пришел к выводу, что если fclose прерывается каким-либо сигналом, то, согласно man-странице, нет никакого способа восстановить...? Я что-то упустил?

Обычно с небуферизованными функциями POSIX (открытие, закрытие, запись и т. Д.) ВСЕГДА существует способ восстановления после прерывания сигнала (EINTR) путем перезапуска вызова; В отличие от этого, документация буферизованных вызовов гласит, что после неудачной попытки fclose другая попытка имеет неопределенное поведение... нет никаких намеков на то, КАК вместо этого восстановить. Мне просто "не повезло", если сигнал прерывает fclose? Данные могут быть потеряны, и я не могу быть уверен, что дескриптор файла действительно закрыт или нет. Я знаю, что буфер освобожден, но как насчет файлового дескриптора? Подумайте о крупномасштабных приложениях, которые используют множество файлов fd одновременно и столкнутся с проблемами, если файлы fd не будут должным образом освобождены.

Итак, давайте предположим, что я пишу библиотеку, и ей не разрешено использовать sigaction и SA_RESTART, и отправляется множество сигналов. Как мне восстановить систему, если fclose прервана? Было бы хорошей идеей вызывать close в цикле (вместо fclose) после сбоя fclose с EINTR? В документации fclose просто не упоминается состояние дескриптора файла; UNDEFINED не очень полезен, хотя... если fd закрыт, и я снова вызываю close, могут возникнуть странные, трудно отлаживаемые побочные эффекты, поэтому, естественно, я предпочел бы проигнорировать этот случай, как если бы поступил неправильно... тогда опять не существует неограниченного числа файловых дескрипторов, а утечка ресурсов является своего рода ошибкой (по крайней мере, для меня).

Конечно, я мог бы проверить одну конкретную реализацию fclose, но я не могу поверить, что кто-то разработал stdio и не думал об этой проблеме? Это плохая документация или дизайн этой функции?

Этот угловой случай действительно беспокоит меня:(

2 ответа

Решение

EINTR а также close()

На самом деле, есть также проблемы с close() не только с fclose(),

POSIX утверждает, что close() возвращается EINTR, что обычно означает, что приложение может повторить вызов. Однако в Linux все сложнее. Смотрите эту статью на LWN, а также этот пост.

[...] POSIX EINTR семантика на самом деле невозможна в Linux. Файловый дескриптор передан close() отменен в начале обработки системного вызова, и к тому времени этот же дескриптор уже мог быть передан другому потоку close() возвращается.

Этот пост в блоге и этот ответ объясняют, почему не стоит повторять попытку close() не удалось с EINTR, Так что в Linux вы ничего не можете сделать, если close() не удалось с EINTR (или же EINPROGRESS).

Также обратите внимание, что close() является асинхронным в Linux. Например, иногда umount может вернуться EBUSY сразу после закрытия последнего открытого дескриптора в файловой системе, поскольку он еще не выпущен в ядре. Смотрите интересное обсуждение здесь: страница 1, страница 2.


EINTR а также fclose()

POSIX состояния для fclose():

После звонка fclose() любое использование потока приводит к неопределенному поведению.

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

Я считаю, что это означает, что даже если close() не удалось, fclose() должен освободить все ресурсы и не производить утечек. Это верно по крайней мере для реализаций glibc и uclibc.


Надежная обработка ошибок

  • Вызов fflush() до fclose() ,

    Поскольку вы не можете определить, если fclose() не удалось, когда он позвонил fflush() или же close(), вы должны явно позвонить fflush() до fclose() чтобы убедиться, что буфер пространства пользователя был успешно отправлен ядру.

  • Не повторять после EINTR ,

    Если fclose() не удалось с EINTR, вы не можете повторить close() а также не могу повторить fclose(),

  • Вызов fsync() если тебе надо.

    • Если вы заботитесь о целостности данных, вы должны позвонить fsync() или же fdatasync() перед звонком fclose() 1
    • Если нет, просто игнорируйте EINTR от fclose(),

Заметки

  • Если fflush() а также fsync() удалось и fclose() не удалось с EINTR, данные не теряются и не происходит утечки.

  • Вы также должны убедиться, что FILE объект не используется между fflush() а также fclose() звонки из другого потока 2.


[1] См. Статью "Все, что вы всегда хотели знать о Fsync()", в которой объясняется, почему fsync() также может быть асинхронной операцией.

[2] Вы можете позвонить flockfile() перед звонком fflush() а также fclose() , Должно работать с fclose() правильно.

Подумайте о крупномасштабных приложениях, которые используют множество файлов fd одновременно и столкнутся с проблемами, если файлы fd не будут должным образом освобождены -> я бы предположил, что должно быть ЧИСТОЕ решение этой проблемы.

Возможность повторить fflush() а потом close() О базовом файловом дескрипторе уже упоминалось в комментариях. Для крупномасштабного приложения я бы предпочел, чтобы шаблон использовал потоки и имел один выделенный поток обработки сигналов, в то время как все другие потоки блокировали сигналы, используя pthread_sigmask(), Тогда, когда fclose() не удается, у вас есть реальная проблема.

Другие вопросы по тегам