sem_wait не разблокируется с помощью EINTR

Я новичок в семафорах и хочу добавить многопоточность в мою программу, но я не могу обойти следующую проблему: sem_wait() должен иметь возможность получать EINTR и разблокировать, если я не установил флаг SA_RESTART. Я отправляю SIGUSR1 рабочему потоку, который блокирует в sem_wait(), он получает сигнал и прерывается, но затем продолжает блокироваться и поэтому никогда не даст мне код возврата -1 вместе с errno = EINTR, Тем не менее, если я сделаю sem_post из основного потока, он разблокируется, даст мне ERTR с ошибкой, но RC равен 0. Я полностью озадачен этим поведением. Это какая-то странная реализация NetBSD или я что-то здесь не так делаю? Согласно man-странице, sem_wait соответствует POSIX.1 (ISO/IEC 9945-1:1996). Простой код:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <pthread.h>
#include <semaphore.h>

typedef struct workQueue_s
{
   int full;
   int empty;
   sem_t work;
   int sock_c[10];
} workQueue_t;

void signal_handler( int sig )
{
   switch( sig )
   {
      case SIGUSR1:
      printf( "Signal: I am pthread %p\n", pthread_self() );
      break;
   }
}

extern int errno;
workQueue_t queue;
pthread_t workerbees[8];

void *BeeWork( void *t )
{
   int RC;
   pthread_t tid;
   struct sigaction sa;
   sa.sa_handler = signal_handler;
   sigaction( SIGUSR1, &sa, NULL );

   printf( "Bee: I am pthread %p\n", pthread_self() );
   RC = sem_wait( &queue.work );
   printf( "Bee: got RC = %d and errno = %d\n", RC, errno );

   RC = sem_wait( &queue.work );
   printf( "Bee: got RC = %d and errno = %d\n", RC, errno );
   pthread_exit( ( void * ) t );
}

int main()
{
   int RC;
   long tid = 0;
   pthread_attr_t attr;
   pthread_attr_init( &attr );
   pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE );

   queue.full = 0;
   queue.empty = 0;
   sem_init( &queue.work, 0, 0 );

   printf( "I am pthread %p\n", pthread_self() );
   pthread_create( &workerbees[tid], &attr, BeeWork, ( void * ) tid );
   pthread_attr_destroy( &attr );

   sleep( 2 );
   sem_post( &queue.work );
   sleep( 2 );
   pthread_kill( workerbees[tid], SIGUSR1 );
   sleep( 2 );

   // Remove this and sem_wait will stay blocked
   sem_post( &queue.work );
   sleep( 2 );
   return( 0 );
}

Я знаю, что printf не вслух в обработчике сигналов, но, черт возьми, если я его уберу, то получу те же результаты.

Вот результаты без sem_post:

I am pthread 0x7f7fffc00000
Bee: I am pthread 0x7f7ff6c00000
Bee: got RC = 0 and errno = 0
Signal: I am pthread 0x7f7ff6c00000

И с помощью sem_post:

I am pthread 0x7f7fffc00000
Bee: I am pthread 0x7f7ff6c00000
Bee: got RC = 0 and errno = 0
Signal: I am pthread 0x7f7ff6c00000
Bee: got RC = 0 and errno = 4

Я знаю, что мне на самом деле не нужно разблокировать, и я могу просто сделать выход из основного, но я все равно хочу видеть, как это работает. Причина, по которой я использую sem_wait, заключается в том, что я хочу сохранить рабочие потоки живыми и разбудить тот, кто ждет самого длинного из основного потока с помощью sem_post, как только появится новое клиентское соединение от Postfix. Я не хочу делать pthread_create все время, так как я буду принимать звонки несколько раз в секунду, и я не хочу терять скорость и делать Postfix невосприимчивым к новым клиентам smtpd. Это Policydaemon для Postfix, и сервер довольно занят.

Я что-то здесь упускаю? NetBSD только запутался с этим?

2 ответа

Решение

Мой пост о поведении в Linux, но я думаю, что у вас может быть похожее поведение, или, по крайней мере, я подумал, что это может быть полезным. Если нет, дайте мне знать, я уберу этот бесполезный "шум".

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

[pid  6984] futex(0x6020e8, FUTEX_WAIT_PRIVATE, 0, NULL <unfinished ...>
[pid  6983] rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
[pid  6983] rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
[pid  6983] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
[pid  6983] nanosleep({2, 0}, 0x7fffe5794a70) = 0
[pid  6983] tgkill(6983, 6984, SIGUSR1 <unfinished ...>
[pid  6984] <... futex resumed> )       = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
[pid  6983] <... tgkill resumed> )      = 0
[pid  6984] --- SIGUSR1 {si_signo=SIGUSR1, si_code=SI_TKILL, si_pid=6983, si_uid=500} ---
[pid  6983] rt_sigprocmask(SIG_BLOCK, [CHLD],  <unfinished ...>
[pid  6984] rt_sigreturn( <unfinished ...>
[pid  6983] <... rt_sigprocmask resumed> [], 8) = 0
[pid  6984] <... rt_sigreturn resumed> ) = -1 EINTR (Interrupted system call)

увидеть строки с ERESTARTSYS и EINTR: прерывание вызова системы на самом деле rt_sigreturn resumedне futex (системный вызов, лежащий в основе sem_wait), как вы и ожидали. Я должен сказать, что был довольно озадачен, но чтение этого человека дало некоторые интересные подсказки (сигнал от человека 7):

   If  a blocked call to one of the following interfaces is interrupted by
   a signal handler, then the call will be automatically  restarted  after
   the  signal  handler returns if the SA_RESTART flag was used; otherwise
   the call will fail with the error EINTR:
[...]

       * futex(2)  FUTEX_WAIT  (since  Linux  2.6.22;  beforehand,  always
         failed with EINTR).

Итак, я полагаю, что у вас есть ядро ​​с похожим поведением (смотрите документацию по netBSD?), И вы можете заметить, что системный вызов автоматически перезагружается, и у вас нет шансов увидеть его.

Тем не менее, я полностью удалил sem_post() из вашей программы и просто послал сигнал, чтобы "сломать" sem_wait() и глядя на strace, который я видел (фильтрация в потоке пчелы):

[pid  8309] futex(0x7fffc0470990, FUTEX_WAIT_PRIVATE, 0, NULL <unfinished ...>
[pid  8309] <... futex resumed> )       = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
[pid  8309] --- SIGUSR1 {si_signo=SIGUSR1, si_code=SI_TKILL, si_pid=8308, si_uid=500} ---
[pid  8309] rt_sigreturn()              = -1 EINTR (Interrupted system call)
[pid  8309] madvise(0x7fd5f6019000, 8368128, MADV_DONTNEED) = 0
[pid  8309] _exit(0)

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

Bee: got RC = -1 and errno = Interrupted system call

Спасибо за ваш ответ OznOg, если я удалю последний sem_post и заставлю последний сон немного подольше, я получу это с помощью ktrace:

PSIG  SIGUSR1 caught handler=0x40035c mask=(): code=SI_LWP sent by pid=10631, uid=0)
CALL  write(1,0x7f7ff7e04000,0x24)
GIO   fd 1 wrote 36 bytes "Signal: I am pthread 0x7f7ff7800000\n"
RET   write 36/0x24
CALL  setcontext(0x7f7ff7bff970)
RET   setcontext JUSTRETURN
CALL  ___lwp_park50(0,0,0x7f7ff7e01100,0x7f7ff7e01100)
RET   __nanosleep50 0
CALL  exit(0)
RET   ___lwp_park50 -1 errno 4 Interrupted system call

Похоже, sem_wait будет возвращаться только с помощью выхода или sem_post....

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