Signal handler attributes (#12426)

This PR purpose is to make the crash report process thread safe.
main changes include:

1. `setupSigSegvHandler()` is introduced to initialize the signal handler.
This function first initializes the signal handler mutex (if not initialized yet)
and then registers the process to the signal handler. 

2. **sigsegvHandler** flags :
SA_NODEFER - don't add the signal to the process signal mask. We use this
flag because we want to be able to handle a second call to the signal manually.
removed SA_RESETHAND: this flag resets the signal handler function upon the first
entrance to the registered function. The reason to use this flag is to protect from
recursively entering the signal handler by the same thread. But, it also means
that if a second thread crashes while handling a signal, the process will be
terminated immediately and we won't get the crash report.
In this PR we discard this flag. The signal handler guard described below purpose
is to solve the above issues.

3. Add a **signal handler lock** with ERRORCHECK attributes. 
The lock's purpose is to ensure that only one thread generates a crash report.
Once a second thread enters the signal handler it will be blocked.
We use the ERRORCHECK lock in order to protect from possible deadlock in
case the thread handling the crash gets a signal. In the latest scenario, we log
what we have collected until the handler crashed.

At the end of the crash report we reset the signal handler SIG_DFL, with no flags, and
rethrow the signal to generate a core dump (if enabled) and exit the process.

During the work on this PR we wanted to understand the historical reasons for
how crash is handled.
With respect to the choice of the flag, we believe the **SA_RESETHAND** was not
added for any specific purpose.
**SA_ONSTACK** which is removed here from bugReportEnd(), was originally also
set in the initial registration to signal handler, but removed in 3ada43e73. In addition,
it was removed from another location in deee2c1ef with the following description,
which is also relevant to why it should be removed from bugReportEnd:

> it seems to be some valgrind bug with SA_ONSTACK.
> SA_ONSTACK seems unneeded since WD is not recursive (SA_NODEFER was removed),
> also, not sure if it's even valid without a call to sigaltstack()
This commit is contained in:
meiravgri 2023-08-20 19:16:45 +03:00 committed by GitHub
parent 44cc0fcb9d
commit fe47c2027b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 64 additions and 34 deletions

View File

@ -2528,9 +2528,9 @@ static int updateAofAutoGCEnabled(const char **err) {
static int updateSighandlerEnabled(const char **err) {
UNUSED(err);
if (server.crashlog_enabled)
setupSignalHandlers();
setupSigSegvHandler();
else
removeSignalHandlers();
removeSigSegvHandlers();
return 1;
}

View File

@ -66,7 +66,10 @@ typedef ucontext_t sigcontext_t;
/* Globals */
static int bug_report_start = 0; /* True if bug report header was already logged. */
static pthread_mutex_t bug_report_start_mutex = PTHREAD_MUTEX_INITIALIZER;
/* Mutex for a case when two threads crash at the same time. */
static pthread_mutex_t signal_handler_lock;
static pthread_mutexattr_t signal_handler_lock_attr;
static volatile int signal_handler_lock_initialized = 0;
/* Forward declarations */
void bugReportStart(void);
void printCrashReport(void);
@ -1063,7 +1066,7 @@ void _serverAssert(const char *estr, const char *file, int line) {
}
// remove the signal handler so on abort() we will output the crash report.
removeSignalHandlers();
removeSigSegvHandlers();
bugReportEnd(0, 0);
}
@ -1159,7 +1162,7 @@ void _serverPanic(const char *file, int line, const char *msg, ...) {
}
// remove the signal handler so on abort() we will output the crash report.
removeSignalHandlers();
removeSigSegvHandlers();
bugReportEnd(0, 0);
}
@ -2116,9 +2119,19 @@ void invalidFunctionWasCalled(void) {}
typedef void (*invalidFunctionWasCalledType)(void);
void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
static void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
UNUSED(secret);
UNUSED(info);
/* Check if it is safe to enter the signal handler. second thread crashing at the same time will deadlock. */
if(pthread_mutex_lock(&signal_handler_lock) == EDEADLK) {
/* If this thread already owns the lock (meaning we crashed during handling a signal)
* log that the crash report can't be generated. */
serverLog(LL_WARNING,
"Crashed running signal handler. Can't continue to generate the crash report");
/* gracefully exit */
bugReportEnd(1, sig);
return;
}
bugReportStart();
serverLog(LL_WARNING,
@ -2171,6 +2184,47 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
bugReportEnd(1, sig);
}
void setupSigSegvHandler(void) {
/* Initialize the signal handler lock.
Attempting to initialize an already initialized mutex or mutexattr results in undefined behavior. */
if (!signal_handler_lock_initialized) {
/* Set signal handler with error checking attribute. re-lock within the same thread will error. */
pthread_mutexattr_init(&signal_handler_lock_attr);
pthread_mutexattr_settype(&signal_handler_lock_attr, PTHREAD_MUTEX_ERRORCHECK);
pthread_mutex_init(&signal_handler_lock, &signal_handler_lock_attr);
signal_handler_lock_initialized = 1;
}
struct sigaction act;
sigemptyset(&act.sa_mask);
/* SA_NODEFER to disables adding the signal to the signal mask of the
* calling process on entry to the signal handler unless it is included in the sa_mask field. */
/* SA_SIGINFO flag is set to raise the function defined in sa_sigaction.
* Otherwise, sa_handler is used. */
act.sa_flags = SA_NODEFER | SA_SIGINFO;
act.sa_sigaction = sigsegvHandler;
if(server.crashlog_enabled) {
sigaction(SIGSEGV, &act, NULL);
sigaction(SIGBUS, &act, NULL);
sigaction(SIGFPE, &act, NULL);
sigaction(SIGILL, &act, NULL);
sigaction(SIGABRT, &act, NULL);
}
}
void removeSigSegvHandlers(void) {
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_NODEFER | SA_RESETHAND;
act.sa_handler = SIG_DFL;
sigaction(SIGSEGV, &act, NULL);
sigaction(SIGBUS, &act, NULL);
sigaction(SIGFPE, &act, NULL);
sigaction(SIGILL, &act, NULL);
sigaction(SIGABRT, &act, NULL);
}
void printCrashReport(void) {
/* Log INFO and CLIENT LIST */
logServerInfo();
@ -2218,7 +2272,7 @@ void bugReportEnd(int killViaSignal, int sig) {
/* Make sure we exit with the right signal at the end. So for instance
* the core will be dumped if enabled. */
sigemptyset (&act.sa_mask);
act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND;
act.sa_flags = 0;
act.sa_handler = SIG_DFL;
sigaction (sig, &act, NULL);
kill(getpid(),sig);

View File

@ -6514,37 +6514,13 @@ static void sigShutdownHandler(int sig) {
void setupSignalHandlers(void) {
struct sigaction act;
/* When the SA_SIGINFO flag is set in sa_flags then sa_sigaction is used.
* Otherwise, sa_handler is used. */
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
act.sa_handler = sigShutdownHandler;
sigaction(SIGTERM, &act, NULL);
sigaction(SIGINT, &act, NULL);
sigemptyset(&act.sa_mask);
act.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
act.sa_sigaction = sigsegvHandler;
if(server.crashlog_enabled) {
sigaction(SIGSEGV, &act, NULL);
sigaction(SIGBUS, &act, NULL);
sigaction(SIGFPE, &act, NULL);
sigaction(SIGILL, &act, NULL);
sigaction(SIGABRT, &act, NULL);
}
return;
}
void removeSignalHandlers(void) {
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_NODEFER | SA_RESETHAND;
act.sa_handler = SIG_DFL;
sigaction(SIGSEGV, &act, NULL);
sigaction(SIGBUS, &act, NULL);
sigaction(SIGFPE, &act, NULL);
sigaction(SIGILL, &act, NULL);
sigaction(SIGABRT, &act, NULL);
setupSigSegvHandler();
}
/* This is the signal handler for children process. It is currently useful

View File

@ -3016,7 +3016,6 @@ int processCommand(client *c);
int processPendingCommandAndInputBuffer(client *c);
int processCommandAndResetClient(client *c);
void setupSignalHandlers(void);
void removeSignalHandlers(void);
int createSocketAcceptHandler(connListener *sfd, aeFileProc *accept_handler);
connListener *listenerByType(const char *typename);
int changeListener(connListener *listener);
@ -3704,7 +3703,8 @@ void _serverPanic(const char *file, int line, const char *msg, ...)
void _serverPanic(const char *file, int line, const char *msg, ...);
#endif
void serverLogObjectDebugInfo(const robj *o);
void sigsegvHandler(int sig, siginfo_t *info, void *secret);
void setupSigSegvHandler(void);
void removeSigSegvHandlers(void);
const char *getSafeInfoString(const char *s, size_t len, char **tmp);
dict *genInfoSectionDict(robj **argv, int argc, char **defaults, int *out_all, int *out_everything);
void releaseInfoSectionDict(dict *sec);