2015-05-29 10 views
6

Ich habe ein großes Programm, das so widerstandsfähig wie möglich gemacht werden muss, und hat eine große Anzahl von Threads. Ich muss alle Signale SIGBUSSIGSEGV fangen und den Problem-Thread bei Bedarf neu initialisieren oder den Thread deaktivieren, um mit reduzierter Funktionalität fortzufahren.Trapping-Signale in einer Multithread-Umgebung

Mein erster Gedanke ist, eine setjump zu tun, und stellen Sie Signalhandler, die das Problem protokollieren können, und dann eine longjump zurück zu einem Wiederherstellungspunkt im Thread. Es gibt das Problem, dass der Signal-Handler feststellen müsste, aus welchem ​​Thread das Signal stammt, um den geeigneten Sprungpuffer zu verwenden, da ein Zurückspringen zu dem falschen Thread nutzlos wäre.

Hat jemand eine Idee, wie man den störenden Thread im Signalhandler ermittelt?

+1

Sie sollten einen 'sigsetjump()/siglongjmp()' anstelle von 'setjmp()/longjmp()' machen, damit Sie Ihre Signalhandler nicht zurücksetzen müssen. –

+0

Signal-Handler, der auf statisches oder Thread-gespeichertes Objekt zugreift und Standardbibliotheksfunktion aufruft? Das klingt nach UB. – Sebivor

Antwort

2

Mit syscall(SYS_gettid) funktioniert bei mir auf meinem Linux-Box: gcc pt.c -lpthread -Wall -Wextra

//pt.c 
#define _GNU_SOURCE 
#include <stdio.h> 
#include <pthread.h> 
#include <unistd.h> 
#include <sys/syscall.h> 
#include <setjmp.h> 
#include <signal.h> 
#include <string.h> 
#include <ucontext.h> 
#include <stdlib.h> 

static sigjmp_buf jmpbuf[65536]; 

static void handler(int sig, siginfo_t *siginfo, void *context) 
{ 
    //ucontext_t *ucontext = context; 
    pid_t tid = syscall(SYS_gettid); 

    printf("Thread %d in handler, signal %d\n", tid, sig); 
    siglongjmp(jmpbuf[tid], 1); 
} 

static void *threadfunc(void *data) 
{ 
    int index, segvindex = *(int *)data; 
    pid_t tid = syscall(SYS_gettid); 

    for(index = 0; index < 500; index++) { 
     if (sigsetjmp(jmpbuf[tid], 1) == 1) { 
      printf("Recovery of thread %d\n", tid); 
      continue; 
     } 
     printf("Thread %d, index %d\n", tid, index); 
     if (index % 5 == segvindex) { 
      printf("%zu\n", strlen((char *)2)); // SIGSEGV 
     } 
     pthread_yield(); 
    } 
    return NULL; 
} 

int main(void) 
{ 
    pthread_t thread1, thread2, thread3; 
    int segvindex1 = rand() % 5; 
    int segvindex2 = rand() % 5; 
    int segvindex3 = rand() % 5; 
    struct sigaction sact; 

    memset(&sact, 0, sizeof sact); 
    sact.sa_sigaction = handler; 
    sact.sa_flags = SA_SIGINFO; 
    if (sigaction(SIGSEGV, &sact, NULL) < 0) { 
     perror("sigaction"); 
     return 1; 
    } 
    pthread_create(&thread1, NULL, &threadfunc, (void *) &segvindex1); 
    pthread_create(&thread2, NULL, &threadfunc, (void *) &segvindex2); 
    pthread_create(&thread3, NULL, &threadfunc, (void *) &segvindex3); 
    pthread_join(thread1, NULL); 
    pthread_join(thread2, NULL); 
    pthread_join(thread3, NULL); 
    return 0; 
} 

Um mehr tragbar zu sein pthread_self verwendet werden kann. Es ist async-signalsicher.

Aber der Thread, der die SIGSEGV bekam, sollte einen neuen Thread mit async-Signal-sicheren Mitteln starten und sollte keine siglongjmp tun, da dies zum Aufruf von nicht-async-signal-sicheren Funktionen führen könnte.

+0

ist es sicher, syscall im Signalhandler zu verwenden? – shami

+0

@ user252127 Der Linux-spezifische 'syscall (SYS_gettid)' ist async-sicher. Es erfüllt alle Regeln async-signalsicher. Da es Linux-spezifisch ist, ist es nicht auf der [POSIX-Liste] (http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03_03) von async-signal-sicheren Funktionen. 'getpid()', das fast dasselbe tut, ist auf der Liste. – 4566976

0

Wenn ein Thread-Programm ein synchrones Signal empfängt - d. H. Eines, das von etwas erzeugt wird, das das Programm gemacht hat, z. B. Dereferenzierung eines schlechten Zeigers - empfängt der Thread, der das Problem verursacht hat, das Signal.

Ich habe ein System verwendet, das explizit dieses Verhalten garantiert, aber ich weiß nicht, ob es allgemein ist. Wenn natürlich der störende Thread das Signal blockiert hat, wie in einem Paradigma, bei dem ein Thread alle Signale behandelt, wird er natürlich zum Signalverarbeitungs-Thread gehen.

+0

Das muss wahr sein, damit dies funktioniert, aber was dann - Sie müssen wieder aus dem Handler springen und müssen bestimmen, wohin Sie gehen. Ich sehe keine Möglichkeit, den Stapel fram zu verwenden, um zu helfen – camelccc

+0

Die Linux-Signal (7) man-Seite (http://linux.die.net/man/7/signal) scheint zu implizieren, dass der Thread, der SIGSEGV usw. verursacht, sein wird derjenige, der das Signal verarbeitet: "Ein Signal kann für einen Prozess als Ganzes (z. B. beim Senden mit kill (2)) oder für einen bestimmten Thread (z. B. bestimmte Signale wie SIGSEGV und SIGFPE, das als Folge der Ausführung eines bestimmten Maschinensprachbefehls erzeugt wird, sind Thread-gerichtet, ebenso wie Signale, die mit pthread_kill (3) auf einen bestimmten Thread gerichtet sind. " –

4

Ich werde Sie davon ausgehen, habe schon gedacht, diese durch und haben einen extrem guten Grund zu glauben, dass Ihr Programm mehr elastisch sein wird nach einer SIGSEGV zu wiederholen versucht - in dem Bewusstsein segfaults Highlight Probleme mit baumelnden Zeiger und andere Missbräuche, die auch unvorhersehbare Stellen in Ihrem Prozessadressraum verfälschen können, ohne dass ein Fehler auftritt.

Da Sie dies sehr gründlich durchdacht haben, und Sie haben festgestellt (irgendwie), dass die besondere Art und Weise Ihre Anwendung segfolds die Korruption der Buchhaltungsdaten zum Abbrechen und Neustarten von Threads möglicherweise nicht verbergen kann, und Sie haben perfekt Abbruchlogik für diese Threads (auch außerordentlich selten), lassen Sie uns voran gehen und das Problem angehen.

Der SIGSEGV-Handler unter Linux wird im Thread der fehlgeschlagenen Anweisung (man 7-Signal) ausgeführt. Wir können pthread_self() nicht aufrufen, da es nicht async-sicher ist, aber das Internet scheint weitgehend zuzustimmen, dass syscall (man 2 syscall) sicher ist, sodass wir die Thread-ID über syscall SYS_gettid erhalten können. Also werden wir eine Zuordnung von pthread_t's (pthread_self) zu pid's (gettid()) pflegen. Da write() auch sicher ist, können wir SEGV einfangen, die aktuelle Thread-ID in eine Pipe schreiben und dann anhalten, bis pthread_cancel uns beendet.

Wir brauchen auch einen Monitorfaden, der im Auge behält, wenn die Dinge birnenförmig sind. Der Monitor-Thread überwacht das Leseende der Pipe auf Informationen zum abgeschlossenen Thread und startet ihn möglicherweise neu.

Da ich denke, vorgibt, SIGSEGV zu behandeln, ist daft, ich werde die Strukturen hier anrufen, die so daft_thread_t, etc. someone_please_fix_me Ihren gebrochenen Code darstellt. Der Monitor-Thread ist main(). Wenn ein Thread default ist, wird er vom Signal-Handler abgefangen, schreibt seine ID in eine Pipe; Der Monitor liest die Pipe, bricht den Thread mit pthread_cancel und pthread_join ab und startet ihn neu.

#include <assert.h> 
#include <errno.h> 
#include <pthread.h> 
#include <signal.h> 
#include <stdlib.h> 
#include <stdio.h> 
#include <string.h> 
#include <sys/syscall.h> 

#define MAX_DAFT_THREADS (1024) // arbitrary 

#define CHECK_OSCALL(call, onfail) { \ 
    if ((call) == -1) { \ 
     char buf[512]; \ 
     strerror_r(errno, buf, sizeof(buf)); \ 
     fprintf(stderr, "%[email protected]%d failed: %s\n", __FILE__, __LINE__, buf); \ 
     onfail; \ 
    } \ 
} 

/*********************** daft thread accounting *****************/ 
typedef void* (*threadproc_t)(void* arg); 

struct daft_thread_t { 
    threadproc_t start_routine; 
    void* start_routine_arg; 
    pthread_t pthread; 
    pid_t tid; 
}; 

struct daft_thread_accounting_info_t { 
    int monitor_pipe[2]; 
    pthread_mutex_t info_lock; 
    size_t daft_thread_count; 
    struct daft_thread_t daft_threads[MAX_DAFT_THREADS]; 
}; 

static struct daft_thread_accounting_info_t g_thread_accounting; 

void daft_thread_accounting_info_init(struct daft_thread_accounting_info_t* inf) 
{ 
    memset(inf, 0, sizeof(*inf)); 
    pthread_mutex_init(&inf->info_lock, NULL); 
    CHECK_OSCALL(pipe(inf->monitor_pipe), abort()); 
} 

struct daft_thread_wrapper_data_t { 
    struct daft_thread_t* thread_info; 
}; 

static void* daft_thread_wrapper(void* arg) 
{ 
    struct daft_thread_t* wrapper = arg; 
    wrapper->tid = gettid(); 
    return (*wrapper->start_routine)(wrapper->start_routine_arg); 
} 

static void start_daft_thread(threadproc_t proc, void* arg) 
{ 
    struct daft_thread_t* info; 
    pthread_mutex_lock(&g_thread_accounting.info_lock); 
    assert (g_thread_accounting.daft_thread_count < MAX_DAFT_THREADS); 
    info = &g_thread_accounting.daft_threads[g_thread_accounting.daft_thread_count++]; 
    pthread_mutex_unlock(&g_thread_accounting.info_lock); 
    info->start_routine = proc; 
    info->start_routine_arg = arg; 
    CHECK_OSCALL(pthread_create(&info->pthread, NULL, daft_thread_wrapper, info), abort()); 
} 

static struct daft_thread_t* find_thread_by_tid(pid_t thread_id) 
{ 
    int k; 
    struct daft_thread_t* info = NULL; 
    pthread_mutex_lock(&g_thread_accounting.info_lock); 
    for (k = 0; k < g_thread_accounting.daft_thread_count; ++k) { 
     if (g_thread_accounting.daft_threads[k].tid == thread_id) { 
      info = &g_thread_accounting.daft_threads[k]; 
      break; 
     } 
    } 
    pthread_mutex_unlock(&g_thread_accounting.info_lock); 
    return info; 
} 

static void restart_daft_thread(struct daft_thread_t* info) 
{ 
    void* unused; 
    CHECK_OSCALL(pthread_cancel(info->pthread), abort()); 
    CHECK_OSCALL(pthread_join(info->pthread, &unused), abort()); 
    info->tid = 0; 
    CHECK_OSCALL(pthread_create(&info->pthread, NULL, daft_thread_wrapper, info), abort()); 
} 

/************* signal handling stuff **************/ 
struct sigdeath_notify_info { 
    int signum; 
    pid_t tid; 
}; 

static void sigdeath_handler(int signum, siginfo_t* info, void* ctx) 
{ 
    int z; 
    struct sigdeath_notify_info inf = { 
     .signum = signum, 
     .tid = gettid() 
    }; 
    z = write(g_thread_accounting.monitor_pipe[1], &inf, sizeof(inf)); 
    assert (z == sizeof(inf)); // or else SIGABRT. Are we handling that too? Hope  not. 
    pause(); // returning doesn't do us any good. 
} 

static void register_signal_handlers() 
{ 
    struct sigaction sa = {}; 
    sa.sa_sigaction = sigdeath_handler; 
    sa.sa_flags = SA_SIGINFO; 
    CHECK_OSCALL(sigaction(SIGSEGV, &sa, NULL), abort()); 
    CHECK_OSCALL(sigaction(SIGBUS, &sa, NULL), abort()); 
} 

pid_t gettid() { return (pid_t) syscall(SYS_gettid); } 

/** This is the code that segfaults randomly. Kwality with a 'k'. */ 
static void* someone_please_fix_me(void* arg) 
{ 
    char* i_think_this_address_looks_nice = (char*) 42; 
    sleep(1 + rand() % 200); 
    i_think_this_address_looks_nice[0] = 'q'; // ugh 
    return NULL; 
} 

// main() will serve as the monitor thread here 
int main() 
{ 
    int k; 
    struct sigdeath_notify_info death; 
    daft_thread_accounting_info_init(&g_thread_accounting); 
    register_signal_handlers(); 
    for (k = 0; k < 200; ++k) { 
     start_daft_thread(someone_please_fix_me, (void*) k); 
    } 
    while (read(g_thread_accounting.monitor_pipe[0], &death, sizeof(death)) == sizeof(death)) { 
     struct daft_thread_t* info = find_thread_by_tid(death.tid); 
     if (info == NULL) { 
      fprintf(stderr, "*** thread_id %u not found\n", death.tid); 
      continue; 
     } 
     fprintf(stderr, "Thread %u (%d) died of %d, restarting.\n", 
      death.tid, (int) info->start_routine_arg, death.signum); 
     restart_daft_thread(info); 
    } 
    fprintf(stderr, "Shouldn't get here.\n"); 
    return 0; 
} 

Wenn Sie nicht darüber nachgedacht haben: Der Versuch von SIGSEGV zu erholen, ist außerordentlich riskant - ich stark raten. Threads teilen sich einen Adressraum. Der Thread, der segfoldt, hat möglicherweise auch andere Threaddaten oder globale Buchhaltungsdaten wie die Buchhaltung von malloc() beschädigt. Ein weitaus sicherer Ansatz - vorausgesetzt, der fehlerhafte Code ist irreparabel beschädigt, muss aber verwendet werden - besteht darin, den fehlerhaften Code hinter einer Prozessgrenze zu isolieren, z. B. durch fork(), bevor der fehlerhafte Code aufgerufen wird. Sie müssen dann SIGCLD abfangen und mit dem Absturz des Prozesses oder dem normalen Abbruch zusammen mit einer Reihe anderer Fallstricke fertig werden, aber Sie müssen sich zumindest keine Sorgen über zufällige Korruption machen. Natürlich ist die beste Option, den blutigen Code zu reparieren, so dass Sie keine Segmentfehler beobachten.

+0

Die wichtigste Zeile in allen oben genannten ist die letzte. Threads "brechen" nicht nur - Sie müssen schlechten Code schreiben. Wenn Sie eine Art "Monitor" -Thread hinzufügen, ist es nur noch eine Sache, die Sie testen, debuggen, warten und natürlich einen zusätzlichen Thread, der brechen könnte :( –

+0

Fehlerhafter Code sollte immer behoben sein, und Probleme wie diese protokolliert werden , - obwohl das Schreiben in eine Memory-Mapped-Datei, während nicht genug Speicherplatz vorhanden ist, ein Seg-Fehler ist, der wiederherstellbar ist. Fehler können auch in einer geschlossenen Bibliothek auftreten. – camelccc

+0

@camelccc alle wahr. Beachten Sie, dass ich eine praktikable Lösung für Ihr Problem angeboten habe. Trotz meiner Vorsichtsmaßregeln :) Eine geschlossene Bibliothek ist perfekt in der Lage, den Adressraum Ihres Prozesses zu beschädigen, aber die Unfähigkeit, ihren Quellcode zu lesen, schützt Sie nicht. Daher behalte ich die "Gabel" -Empfehlung für diesen Fall bei. re: mmap: Ich dachte, mmap failure gab SIGBUS eigentlich, aber das könnte falsch sein. – lyngvi

Verwandte Themen