recv () не прерывается сигналом в многопоточной среде - PullRequest
16 голосов
/ 27 августа 2010

У меня есть поток, который находится в блокирующем цикле recv(), и я хочу завершить его (предположим, это нельзя изменить на select() или любой другой асинхронный подход).

У меня также есть обработчик сигнала, который перехватывает SIGINT, и теоретически он должен вернуть recv() с ошибкой, а errno установить в EINTR.

Но это не так, и я предполагаю, что это связано с тем, что приложение является многопоточным. Существует также другая нить, которая пока ожидает вызова pthread_join().

Что здесь происходит?

EDIT:

ОК, теперь я явно доставляю сигнал всем блокирующим потокам recv() через pthread_kill() из основного потока (что приводит к тому, что установлен тот же глобальный обработчик сигнала SIGINT, хотя несколько вызовов являются доброкачественными). Но recv() вызов все еще не разблокирован.

EDIT:

Я написал пример кода, который воспроизводит проблему.

  1. Основной поток соединяет сокет с неправильно работающим удаленным хостом, который не пропускает соединение.
  2. Все сигналы заблокированы.
  3. Чтение темы начато.
  4. Main разблокирует и устанавливает обработчик для SIGINT.
  5. Чтение ветки разблокирует и устанавливает обработчик для SIGUSR1.
  6. Обработчик сигнала основного потока отправляет SIGUSR1 в поток чтения.

Интересно, что если я заменим recv() на sleep(), он прервется просто отлично.

PS

В качестве альтернативы вы можете просто открыть UDP-сокет вместо использования сервера.

клиент

#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>

static void
err(const char *msg)
{
    perror(msg);
    abort();
}

static void
blockall()
{
    sigset_t ss;
    sigfillset(&ss);
    if (pthread_sigmask(SIG_BLOCK, &ss, NULL))
        err("pthread_sigmask");
}

static void
unblock(int signum)
{
    sigset_t ss;
    sigemptyset(&ss);
    sigaddset(&ss, signum);
    if (pthread_sigmask(SIG_UNBLOCK, &ss, NULL))
        err("pthread_sigmask");
}

void
sigusr1(int signum)
{
    (void)signum;
    printf("%lu: SIGUSR1\n", pthread_self());
}

void*
read_thread(void *arg)
{
    int sock, r;
    char buf[100];

    unblock(SIGUSR1);
    signal(SIGUSR1, &sigusr1);
    sock = *(int*)arg;
    printf("Thread (self=%lu, sock=%d)\n", pthread_self(), sock);
    r = 1;
    while (r > 0)
    {
        r = recv(sock, buf, sizeof buf, 0);
        printf("recv=%d\n", r);
    }
    if (r < 0)
        perror("recv");
    return NULL;
}

int sock;
pthread_t t;

void
sigint(int signum)
{
    int r;
    (void)signum;
    printf("%lu: SIGINT\n", pthread_self());
    printf("Killing %lu\n", t);
    r = pthread_kill(t, SIGUSR1);
    if (r)
    {
        printf("%s\n", strerror(r));
        abort();
    }
}

int
main()
{
    pthread_attr_t attr;
    struct sockaddr_in addr;

    printf("main thread: %lu\n", pthread_self());
    memset(&addr, 0, sizeof addr);
    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (socket < 0)
        err("socket");
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);
    if (inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr) <= 0)
        err("inet_pton");
    if (connect(sock, (struct sockaddr *)&addr, sizeof addr))
        err("connect");

    blockall();
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    if (pthread_create(&t, &attr, &read_thread, &sock))
        err("pthread_create");
    pthread_attr_destroy(&attr);
    unblock(SIGINT);
    signal(SIGINT, &sigint);

    if (sleep(1000))
        perror("sleep");
    if (pthread_join(t, NULL))
        err("pthread_join");
    if (close(sock))
        err("close");

    return 0;
}

Сервер

import socket
import time

s = socket.socket(socket.AF_INET)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('127.0.0.1',8888))
s.listen(1)
c = []
while True:
    (conn, addr) =  s.accept()
    c.append(conn)

Ответы [ 5 ]

21 голосов
/ 27 сентября 2010

Обычно сигналы не прерывают системные вызовы с EINTR. Исторически существовали два возможных поведения доставки сигнала: поведение BSD (системные вызовы автоматически перезапускаются при прерывании сигналом) и поведение Unix System V (системные вызовы возвращают -1 с errno, установленным на EINTR при прерывании сигналом). Linux (ядро) переняла последнее, но разработчики библиотеки GNU C (правильно) сочли поведение BSD намного более нормальным, и поэтому в современных системах Linux вызов signal (который является библиотечной функцией) приводит к BSD поведение.

POSIX допускает любое поведение, поэтому рекомендуется всегда использовать sigaction, где вы можете установить флаг SA_RESTART или опустить его в зависимости от желаемого поведения. См. Документацию для sigaction здесь:

http://www.opengroup.org/onlinepubs/9699919799/functions/sigaction.html

6 голосов
/ 27 августа 2010

В многопоточном приложении нормальные сигналы могут быть доставлены в любой поток произвольно.Используйте pthread_kill, чтобы отправить сигнал определенной интересующей нити.

4 голосов
/ 27 августа 2010

Обработчик сигнала вызывается в том же потоке, который ожидает в recv ()?Вам может понадобиться явно замаскировать SIGINT во всех других потоках с помощью pthread_sigmask ()

1 голос
/ 27 сентября 2010

Как упоминается в сообщении <R..>, действительно можно изменить активность сигналов.Я часто создаю свою собственную «сигнальную» функцию, которая использует sigaction.Вот что я использую

</p> <pre><code>typedef void Sigfunc(int); static Sigfunc* _signal(int signum, Sigfunc* func) { struct sigaction act, oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; if (signum != SIGALRM) act.sa_flags |= SA_NODEFER; //SA_RESTART; if (sigaction(signum, &act, &oact) < 0) return (SIG_ERR); return oact.sa_handler; }

Атрибут, о котором идет речь выше, это «or» поля sa_flags.Это из справочной страницы для 'sigaction': SA_RESTART обеспечивает BSD-подобное поведение, позволяющее перезапускать системные вызовы через сигналы.SA_NODEFER означает, что сигнал может быть получен из его собственного обработчика сигнала.

Когда вызовы сигнала заменены на "_signal", поток прерывается.Вывод выводит «прерванный системный вызов», и recv вернул -1, когда был отправлен SIGUSR1.Программа вообще остановилась с тем же выводом при отправке SIGINT, но в конце была вызвана прерывание.

Я не писал серверную часть кода, я просто изменил тип сокета на "DGRAM, UDP"разрешить запуск клиента.

0 голосов
/ 25 февраля 2015

Вы можете установить тайм-аут в Linux recv: Linux: есть ли чтение или запись из сокета с тайм-аутом?

Когда вы получите сигнал, вызов в классе выполняется с помощью командыполучить.

void* signalThread( void* ptr )
{
    CapturePkts* cap=(CapturePkts*)ptr;
    sigset_t sigSet=cap->getSigSet();
    int sig=-1;
    sigwait(&sigSet,&sig); //signalThread: signal capture thread enabled;
    cout << "signal=" << sig << " caught,ending process" << endl;
    cap->setDone();
    return 0;
}

class CapturePkts
{
     CapturePkts() : _done(false) {}

     sigset_t getSigSet() { return _sigSet; }

     void setDone() {_done=true;}

     bool receive( uint8_t *buffer, int32_t bufSz, int32_t &nbytes)
     {
         bool ret=true;
         while( ! _done ) {
         nbytes = ::recv( _sockid, buffer, bufSz, 0 );
         if(nbytes < 1 ) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
               nbytes=0; //wait for next read event
            else
               ret=false;
         }
         return ret;
     }

     private:
     sigset_t _sigSet;
     bool _done;
};
...