16 июня 2008 г.

Блокирующие вызовы и ошибка EINTR

Недавно столкнулся с ошибкой, связанной с неправильной обработкой возвращаемого значения функции sem_wait().

Функция sem_wait() уменьшает текущее значение семафора на 1. Если прежнее значение семафора было 0, то поток, выполняющий функцию sem_wait, блокируется до изменения значения семафора. При успешном выполнении — функция возвращает 0; при ошибке — вернет -1, а errno будет содержать код ошибки.

Вот пример неправильной обработки возвращаемого значения:

int res = sem_wait(&sem);
if (0 != res)
{
  // Ошибка!
  // Сообщаем об ошибке и выходим.
  ...
  return;
}

// Ошибки нет - продолжаем работу.
...


Что здесь неправильно? Дело в том, что функция sem_wait() является блокирующим системным вызовом. Поэтому выполнение этой функции может быть прервано, если выполняющийся поток получит какой-нибудь сигнал (например, сигнал о завершении другого потока). В этом случае функция вернет -1, а errno будет равна EINTR.

При этом никакой ошибки на самом деле нет, это просто способ дать программе обработать пришедший сигнал. Это необходимо учитывать, и вызов sem_wait() лучше всего делать в цикле. Например, таком:

int res;
do
{
  res = sem_wait(&sem);
}
while (0 != res && EINTR == errno);

if (0 != res)
{
  // Ошибка!
  // Сообщаем об ошибке и выходим.
  ...
  return;
}

// Ошибки нет - продолжаем работу.
...

Это касается и других блокирующих функций. Например, select, wait, waitpid и др. Возможность прерывания функции с ошибкой EINTR обычно описана в man'е.

2 комментария:

  1. для этой конструкции есть макрос, можно написать и вот так, более красиво:
    int res;
    res = TEMP_FAILURE_RETRY (sem_wait(&sem));
    if (0 != res)
    {
    // Ошибка!
    // Сообщаем об ошибке и выходим.
    ...
    return;
    }
    однако в случае если вызов select и возникает EINTR, то select сбрасывает в 0 все дескрипторы для которых он ждет данные, так как данных типа небыло. И перед повторным вызовом нужно инициализировать все структуры которые ему передаются. Поэтому для select нельзя использовать этот макрос. Вчера об это шишку набил )))

    ОтветитьУдалить
  2. Интересное замечание. Спасибо.

    ОтветитьУдалить