Системный вызов, прерванный сигналом, все еще должен быть завершен

Многие системные вызовы, такие как close( fd ), могут быть прерваны сигналом. В этом случае обычно возвращается -1 и errno устанавливается EINTR.

Вопрос в том, что правильно делать? Скажем, я все еще хочу, чтобы этот fd был закрыт.

Что я могу придумать:

while( close( fd ) == -1 )
  if( errno != EINTR ) {
    ReportError();
    break;
  }

Может ли кто-нибудь предложить лучший/более элегантный/стандартный способ справиться с этой ситуацией?

UPDATE: Как заметил mux, флаг SA_ RESTART может использоваться при установке обработчика сигнала. Может ли кто-нибудь сказать мне, какие функции гарантированно перезапускаются во всех системах POSIX (не только Linux)?

Ответ 1

Некоторые системные вызовы перезапускаются, что означает, что ядро ​​перезапустит вызов, если прервано, если флаг SA_RESTART используется при установке обработчика сигнала, сигнал (7) man говорит:

Если заблокированный вызов одного из следующих интерфейсов прерывается обработчиком сигнала , то вызов будет автоматически перезапущен после сигнала        обработчик возвращает, если использовался флаг SA_RESTART; в противном случае вызов завершится с ошибкой EINTR:

Не упоминается, если close() перезагружается, но это:

прочитайте (2), readv (2), напишите (2), writev (2), ioctl (2), откройте (2), подождите (2), wait3 (2), wait4 (2), waitid (2) и waitpid, accept (2), connect (2), recv (2), recvfrom (2), recvmsg (2), send (2), sendto (2) и sendmsg (2) flock (2) и fcntl (2) mq_receive (3), mq_timedreceive (3), mq_send (3), и mq_timedsend (3) sem_wait (3) и sem_timedwait (3) futex (2)

Обратите внимание, что эти сведения, в частности список не перезапускаемых вызовов, зависят от Linux

Я разместил релевантный вопрос о том, какие системные вызовы перезагружаемы, и если он указан POSIX где-то, он указан POSIX, но он необязательный, поэтому вы должны проверить список не перезапускаемых вызовов для вашей ОС, если он там нет он должен быть перезапущен. Это мой вопрос: Как узнать, перезагружен ли системный вызов Linux или нет?

Обновление: закрытие - это особый случай, когда он не перезапускается и не должен быть повторен в Linux, см. этот ответ для получения дополнительной информации: fooobar.com/questions/449305/...

Ответ 2

Для записи: по существу, каждый UNIX, close() не следует повторять, если он возвращает EINTR. НЕ помещает петлю повторной попытки EINTR на место, подобное тому, что было бы для waitpid() или read(). См. Эту страницу для более подробной информации: http://austingroupbugs.net/view.php?id=529 В Linux, Solaris, BSD и других, повторная попытка close() неверна. HP-UX - единственная обычная (!) Система, которую я мог найти, которая требует этого.

EINTR означает что-то совсем другое для read() и select() и waitpid() и т.д., чем для close(). Для большинства вызовов вы повторяете EINTR, потому что вы просили сделать что-то, что блокирует, и если вы были прерваны, это означает, что этого не произошло, поэтому повторите попытку. Для close() запрошенное действие предназначалось для удаления записи из таблицы fd, которая мгновенно, без ошибок, и всегда будет выполняться независимо от того, что возвращает close(). [*] Единственная причина, по которой close() блокирует заключается в том, что иногда для специальной семантики (например, TCP linger) она может дождаться завершения операции ввода-вывода перед возвратом. Если close возвращает EINTR, это означает, что вы попросили его подождать, но он не смог. Однако fd все еще был закрыт; вы просто потеряли свой шанс подождать.

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

[*] Существует предостережение: если другой поток процесса находится внутри блокировки syscall на одном и том же fd, ну, это зависит.

Ответ 3

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

while (((rc = close (fd)) == -1) && (errno == EINTR));
if (rc == -1)
    complainBitterly (errno);

Предполагая, что вы используете более читаемый код в дополнение к более короткому, просто создайте функцию:

int closeWithRetry (int fd);

и разместите там свой читаемый код. Тогда на самом деле не имеет значения, как долго это длится, это все еще один лайнер, где вы его называете, но вы можете сделать тело функции очень читаемым:

int closeWithRetry (int fd) {
    // Initial close attempt.

    int rc = close (fd);

    // As long as you failed with EINTR, keep trying.
    // Possibly with a limit (count or time-based).

    while ((rc == -1) && (errno == EINTR))
        rc = close (fd);

    // Once either success or non-retry failure, return error code.

    return rc;
}