recv() wird in einer Multithread-Umgebung nicht durch ein Signal unterbrochen

Lesezeit: 8 Minuten

Benutzer-Avatar
Alex B

Ich habe einen Thread, der in einer Sperre sitzt recv() Schleife und ich möchte beenden (angenommen, dies kann nicht geändert werden select() oder jeder andere asynchrone Ansatz).

Ich habe auch einen Signal-Handler, der fängt SIGINT und theoretisch sollte es machen recv() Rückkehr mit Fehler und errno einstellen EINTR.

Aber das tut es nicht, was meiner Meinung nach etwas damit zu tun hat, dass die Anwendung Multithreading ist. Es gibt auch noch einen Thread, der mittlerweile auf einen wartet pthread_join() Anruf.

Was passiert hier?

BEARBEITEN:

OK, jetzt gebe ich das Signal explizit an alle Blocker weiter recv() Fäden über pthread_kill() aus dem Hauptthread (was zu derselben globalen SIGINT Signal-Handler installiert, obwohl mehrere Aufrufe harmlos sind). Aber recv() Anruf ist immer noch nicht entsperrt.

BEARBEITEN:

Ich habe ein Codebeispiel geschrieben, das das Problem reproduziert.

  1. Der Haupt-Thread verbindet einen Socket mit einem sich schlecht benehmenden Remote-Host, der die Verbindung nicht loslässt.
  2. Alle Signale blockiert.
  3. Thread lesen Thread wird gestartet.
  4. Main entsperrt und installiert Handler für SIGINT.
  5. Lesen Sie Thread entsperrt und installiert Handler für SIGUSR1.
  6. Der Signalhandler des Haupt-Threads sendet a SIGUSR1 zum Lesethread.

Interessanterweise, wenn ich ersetzen recv() mit sleep() es wird einfach unterbrochen.

PS

Alternativ können Sie einfach einen UDP-Socket öffnen, anstatt einen Server zu verwenden.

Klient

#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;
}

Server

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)

  • Ich stoße auf etwas Ähnliches (obwohl es in gewisser Hinsicht anders ist) mit einer Single-Thread-Anwendung (einem einzelnen Prozess ohne anderen Thread). Daher glaube ich, dass Multithreading hier nicht die Ursache für das Verhalten ist.

    – Hibou57

    30. März 2013 um 19:25 Uhr

Normalerweise unterbrechen Signale Systemrufe nicht mit EINTR. Historisch gesehen gab es zwei mögliche Signalübermittlungsverhalten: das BSD-Verhalten (Systemaufrufe werden automatisch neu gestartet, wenn sie durch ein Signal unterbrochen werden) und das Unix System V-Verhalten (Systemaufrufe geben -1 mit zurück errno einstellen EINTR bei Unterbrechung durch ein Signal). Linux (der Kernel) hat letzteres übernommen, aber die Entwickler der GNU C-Bibliothek hielten das BSD-Verhalten (zu Recht) für viel vernünftiger, und so auf modernen Linux-Systemen signal (was eine Bibliotheksfunktion ist) führt zum BSD-Verhalten.

POSIX erlaubt beide Verhaltensweisen, daher ist es ratsam, immer zu verwenden sigaction wo Sie die Einstellung vornehmen können SA_RESTART kennzeichnen oder weglassen, je nach gewünschtem Verhalten. Siehe die Dokumentation für sigaction hier:

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

  • @Alex – Also mit sigaction und Einstellen der SA_RESTART Flagge hat Ihr Problem gelöst?

    – Hanno Fietz

    3. Dezember 2010 um 11:27 Uhr

  • Weglassen der SA_RESTART flag würde das gewünschte Verhalten von OP geben.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    3. Dezember 2010 um 11:31 Uhr

  • Tatsächlich werden unter Linux / Ubuntu 12.04 beide Verhaltensweisen aufgedeckt: recv wird auf Strg-C neu gestartet, während poll wird zurückkehren EINT auf dem gleichen Strg-C. Aber mit sigaction Anstatt von signal tatsächlich den Fall lösen. Das heisst sigaction ist der richtige Weg, um vorhersehbares Verhalten mit Signalen zu erhalten, die während Systemaufrufen auftreten; daher ist es in der Tat ratsam, immer zu verwenden sigaction (Werde ich seitdem).

    – Hibou57

    30. März 2013 um 20:02 Uhr


  • poll und select sind insofern besonders, als sie nie neu starten. Dies wird von POSIX festgelegt.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    30. März 2013 um 20:06 Uhr

In einer Multithread-Anwendung können normale Signale willkürlich an jeden Thread geliefert werden. Verwenden pthread_kill um das Signal an den spezifischen Thread von Interesse zu senden.

  • Aus irgendeinem Grund funktioniert dies nicht (Empfangsanruf wird nicht unterbrochen). Muss ich etwas Besonderes tun, um Handler und/oder Signalmasken für einen Thread zu signalisieren?

    – Alex B

    13. September 2010 um 4:43 Uhr

  • Haben Sie bestätigt, ob der Signal-Handler aufgerufen wurde, und wenn ja, von welchem ​​​​Thread? Wird das Signal nach dem empfangen recv() Anruf abgeschlossen?

    – bdonlan

    13. September 2010 um 11:37 Uhr

Wird der Signalhandler im selben Thread aufgerufen, der in recv() wartet? Möglicherweise müssen Sie SIGINT in allen anderen Threads über pthread_sigmask() explizit maskieren.

  • Richtig – ein prozessgesteuertes Signal wird an einen beliebigen Thread geliefert, der nicht blockiert ist, also besteht die Lösung darin, es in allen anderen Threads zu blockieren.

    – Café

    27. August 2010 um 8:30 Uhr

  • Der Vollständigkeit halber: Ein neuer Thread erbt die Signalmaske von seinem Elternteil. Sie können es im übergeordneten Element blockieren und nach der Thread-Erstellung entblocken. Es kann die einzige Lösung sein (die ich kenne), wenn Sie auf eine Bibliothek angewiesen sind, die Threads erstellt, die Sie nicht ändern können.

    – jweyrich

    28. August 2010 um 3:41 Uhr


  • Ich glaube nicht, dass das für mich funktioniert. Ich brauche den Hauptthread, um den blockierenden Thread zu stoppen, was bedeutet, dass der Signalhandler in dem Thread installiert werden sollte, der nicht aufruft recv().

    – Alex B

    13. September 2010 um 4:42 Uhr

Benutzer-Avatar
sorton9999

Wie in dem Beitrag von angedeutet <R..> ist es durchaus möglich, die Signalaktivitäten zu verändern. Ich erstelle oft meine eigene “Signal”-Funktion, die sigaction verwendet. Hier ist, was ich benutze

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;
}

Das obige Attribut ist die ‘or’-Verknüpfung des sa_flags-Feldes. Dies ist aus der Manpage für ‘sigaction’: SA_RESTART bietet das BSD-ähnliche Verhalten, Systemaufrufe über Signale hinweg neu starten zu können. SA_NODEFER bedeutet, dass das Signal von seinem eigenen Signalhandler empfangen werden kann.

Wenn die Signalaufrufe durch “_signal” ersetzt werden, wird der Thread unterbrochen. Die Ausgabe gibt “unterbrochener Systemaufruf” aus und recv gab eine -1 zurück, als SIGUSR1 gesendet wurde. Beim Senden von SIGINT stoppte das Programm mit der gleichen Ausgabe, aber am Ende wurde der Abort aufgerufen.

Ich habe den Serverteil des Codes nicht geschrieben, ich habe nur den Socket-Typ in “DGRAM, UDP” geändert, damit der Client starten kann.

Benutzer-Avatar
edW

Sie können ein Timeout für Linux recv festlegen: Linux: Gibt es ein Lesen oder Recv vom Socket mit Timeout?

Wenn Sie ein Signal erhalten, rufen Sie die Klasse auf, die den Empfang durchführt.

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;
};

1175900cookie-checkrecv() wird in einer Multithread-Umgebung nicht durch ein Signal unterbrochen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy