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.
Sie sollten einen 'sigsetjump()/siglongjmp()' anstelle von 'setjmp()/longjmp()' machen, damit Sie Ihre Signalhandler nicht zurücksetzen müssen. –
Signal-Handler, der auf statisches oder Thread-gespeichertes Objekt zugreift und Standardbibliotheksfunktion aufruft? Das klingt nach UB. – Sebivor