LINUX.ORG.RU

Как правильно «будить» процесс, если он залочен блокирующей функцией?

 , , , ,


0

4

Допустим, у меня есть сервер, который в бесконечном цикле что-то читает с помощью блокирующей функции (например, read или accept). И вот наступает момент, когда новой информации не поступает (то есть процессу read нечего больше читать, например) и нам нужно этот процесс корректно завершить с помощью SIGTERM, отправленного через терминал. Если я напишу обработчик этого сигнала и установлю его изначально с помощью системного вызова signal, то я ничего особо сделать не смогу (чтобы, например, закрыть используемые файлы, мне нужно делать их дескрипторы глобальными переменными, что не очень хорошо и т.д. и т.п.) - обработчик закончит своё выполнение и всё вернётся обратно на строчку с блокирующим вызовом. Какой принцип обработки таких случаев считается хорошим? Самый простое решение такой проблемы - использовать неблокирующие вызовы, но неужели нельзя никак «разбудить» такой процесс? Я пытался найти решение этой проблемы, но там обычно предлагают поставить таймер и таким образом просто завершать процесс, когда он долго ничего не читает (хотя опять же, тут нужно писать обработчик сигнала, из-за чего возникают проблемы, уже описанные мной выше). Мне такой подход не очень нравится - я хочу, чтобы сервер мог ждать нового сообщения хоть месяц. Вот пример кода, если не очень понятно, что я имею в виду:

void sig_handler(int num) {
    if(num == SIGTERM) {
        // Тут я что-то сделаю и обратно попаду на строчку с accept,
        // после чего продолжу там висеть, пока не придёт новый запрос
    }
}

int main(void) {
    ...
    signal(SIGTERM, sig_handler);
    ...
    while(1) {
        // Пока новый запрос не придёт, процесс будет висеть на этой строчке
        int ns = accept(s, (struct sockaddr*)&clnt_addr, 
        &clnt_addrlen);
    ...
    }
    ...
}

Используй не signal() а sigaction(). А за поведение, на которое ты жалуешься, там отвечает флаг SA_RESTART. Если его не ставить - accept висеть не будет, и вернёт EINTR после первого же сигнала. Хотя я подозреваю что и у signal() будет такое же поведение, но не проверял, возможно он и правда работает как будто SA_RESTART установлен.

if(num == SIGTERM) {

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

В обработчике сигнала обычно записывают единицу в какую-нить volatile переменную и больше ничего не делают. А наличие этой единицы проверяет уже основной код программы там где надо. Например у тебя можно сделать volatile int exit_flag; и вместо while(1) сделать while(!exit_flag)

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 3)

Во-первых, ты ман читал?

$ man signal
...
DESCRIPTION
       WARNING:  the  behavior of signal() varies across UNIX
       versions, and has also varied historically across different
       versions of Linux.  Avoid its use: use sigaction(2) in‐
       stead.  See Portability below.
...

Вот и делай что написано.

Во-вторых, в обработчике сигнала не надо ничего делать. Разве что выставить какой-нить флаг, что сигнал был. Твой accept завершится с ошибкой EINTR, проверяй эту ошибку и делай выход, закрытие файлов и всё другое.

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

Но в таком режиме есть один нюанс.

  1. Если у вас многопоточное приложение - то сигнал может прийти в любой поток, если мы не запретили обратное
  2. Чтобы read/write могли обломиться по таймауту есть соответствующие функции для блокирующего режима
  3. Вы можете исходно работать через асинхронный режим
PPP328 ★★★★★
()
Ответ на: комментарий от vbr

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

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

А я кому написал, что многопоток сразу идет на весь алфавит, потому что «прерывание» придёт только в один незапрещенный поток? Если у вас многопоток с флагом - то там вообще нельзя полагаться на то что read/write/accept «порвёт» и вы из него вылетите. Поэтому там нужна связка из select/poll чтобы поймать событие и только после этого дергать блокирующие функции (и то, можно перестраховаться и там сделать асинк)

А отвечая на ваш вопрос иначе если в другом потоке тоже цикл while (!exited) то не случится ничего критичного, если он прокрутится ещё раз из-за того что тут еще не обновилась переменная. Так-то на неё бы еще volatile повесить.

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

А в чём претензия?) Это форум - я задал на нём вопрос. Ну вот как-то я не надыбал в интернете, что если юзануть signal вместо sigaction, то блокирующие вызовы перестают реагировать на сигналы. Да, в man’е это написано, но только косвенно и в разделе «History», который я до этого момента вообще всегда игнорировал. Я с помощью этой темы много нового узнал, что и является целью форума, как мне кажется…

Kevsh
() автор топика

EINTR The system call was interrupted by a signal that was caught before a valid connection arrived; see signal(7).

Interruption of system calls and library functions by signal handlers

   If a signal handler is invoked while a system call or library
   function call is blocked, then either:

   •  the call is automatically restarted after the signal handler
      returns; or

   •  the call fails with the error EINTR.

   Which of these two behaviors occurs depends on the interface and
   whether or not the signal handler was established using the
   SA_RESTART flag (see sigaction(2)).  The details vary across UNIX
   systems; below, the details for Linux.

the call fails with the error EINTR:

Socket interfaces: accept(2), connect(2), recv(2), recvfrom(2), recvmmsg(2), recvmsg(2), send(2), sendto(2), and sendmsg(2), unless a timeout has been set on the socket (see below).

anonymous
()