d9e5a47058
Spent hours on trying to cleverly use the existing TerminationFlag (with minor modifications to the code that checks it, and some additional code to set and reset it), but could not get it to work. This is simpler, but sure, using a global variable is ugly of course. At least the new MobileTerminationFlag is very specific in semantics and only used in the mobile apps. Change-Id: I0775fdfa7880750ca12c6fd7ec41d3d3ceb2f0ad
326 lines
8.4 KiB
C++
326 lines
8.4 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include "SigUtil.hpp"
|
|
|
|
#if !defined(__ANDROID__)
|
|
# include <execinfo.h>
|
|
#endif
|
|
#include <csignal>
|
|
#include <sys/poll.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/uio.h>
|
|
#include <unistd.h>
|
|
|
|
#include <atomic>
|
|
#include <cassert>
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <mutex>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <thread>
|
|
|
|
#include <Socket.hpp>
|
|
#include "Common.hpp"
|
|
#include "Log.hpp"
|
|
|
|
std::atomic<bool> TerminationFlag(false);
|
|
std::atomic<bool> DumpGlobalState(false);
|
|
|
|
#if MOBILEAPP
|
|
std::atomic<bool> MobileTerminationFlag(false);
|
|
#endif
|
|
|
|
#if !MOBILEAPP
|
|
std::atomic<bool> ShutdownRequestFlag(false);
|
|
|
|
std::mutex SigHandlerTrap;
|
|
|
|
namespace SigUtil
|
|
{
|
|
const char *signalName(const int signo)
|
|
{
|
|
switch (signo)
|
|
{
|
|
#define CASE(x) case SIG##x: return "SIG" #x
|
|
CASE(HUP);
|
|
CASE(INT);
|
|
CASE(QUIT);
|
|
CASE(ILL);
|
|
CASE(ABRT);
|
|
CASE(FPE);
|
|
CASE(KILL);
|
|
CASE(SEGV);
|
|
CASE(PIPE);
|
|
CASE(ALRM);
|
|
CASE(TERM);
|
|
CASE(USR1);
|
|
CASE(USR2);
|
|
CASE(CHLD);
|
|
CASE(CONT);
|
|
CASE(STOP);
|
|
CASE(TSTP);
|
|
CASE(TTIN);
|
|
CASE(TTOU);
|
|
CASE(BUS);
|
|
#ifdef SIGPOLL
|
|
CASE(POLL);
|
|
#endif
|
|
CASE(PROF);
|
|
CASE(SYS);
|
|
CASE(TRAP);
|
|
CASE(URG);
|
|
CASE(VTALRM);
|
|
CASE(XCPU);
|
|
CASE(XFSZ);
|
|
#ifdef SIGEMT
|
|
CASE(EMT);
|
|
#endif
|
|
#ifdef SIGSTKFLT
|
|
CASE(STKFLT);
|
|
#endif
|
|
#if defined(SIGIO) && SIGIO != SIGPOLL
|
|
CASE(IO);
|
|
#endif
|
|
#ifdef SIGPWR
|
|
CASE(PWR);
|
|
#endif
|
|
#ifdef SIGLOST
|
|
CASE(LOST);
|
|
#endif
|
|
CASE(WINCH);
|
|
#if defined(SIGINFO) && SIGINFO != SIGPWR
|
|
CASE(INFO);
|
|
#endif
|
|
#undef CASE
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
static
|
|
void handleTerminationSignal(const int signal)
|
|
{
|
|
bool hardExit = false;
|
|
const char *domain;
|
|
if (!ShutdownRequestFlag && signal == SIGINT)
|
|
{
|
|
domain = " Shutdown signal received: ";
|
|
ShutdownRequestFlag = true;
|
|
}
|
|
else if (!TerminationFlag)
|
|
{
|
|
domain = " Forced-Termination signal received: ";
|
|
TerminationFlag = true;
|
|
}
|
|
else
|
|
{
|
|
domain = " ok, ok - hard-termination signal received: ";
|
|
hardExit = true;
|
|
}
|
|
Log::signalLogPrefix();
|
|
Log::signalLog(domain);
|
|
Log::signalLog(signalName(signal));
|
|
Log::signalLog("\n");
|
|
|
|
if (!hardExit)
|
|
SocketPoll::wakeupWorld();
|
|
else
|
|
{
|
|
::signal (signal, SIG_DFL);
|
|
::raise (signal);
|
|
}
|
|
}
|
|
|
|
void requestShutdown()
|
|
{
|
|
ShutdownRequestFlag = true;
|
|
SocketPoll::wakeupWorld();
|
|
}
|
|
|
|
void setTerminationSignals()
|
|
{
|
|
struct sigaction action;
|
|
|
|
sigemptyset(&action.sa_mask);
|
|
action.sa_flags = 0;
|
|
action.sa_handler = handleTerminationSignal;
|
|
|
|
sigaction(SIGINT, &action, nullptr);
|
|
sigaction(SIGTERM, &action, nullptr);
|
|
sigaction(SIGQUIT, &action, nullptr);
|
|
sigaction(SIGHUP, &action, nullptr);
|
|
}
|
|
|
|
static char FatalGdbString[256] = { '\0' };
|
|
|
|
static
|
|
void handleFatalSignal(const int signal)
|
|
{
|
|
std::unique_lock<std::mutex> lock(SigHandlerTrap);
|
|
|
|
Log::signalLogPrefix();
|
|
Log::signalLog(" Fatal signal received: ");
|
|
Log::signalLog(signalName(signal));
|
|
Log::signalLog("\n");
|
|
|
|
if (std::getenv("LOOL_DEBUG"))
|
|
{
|
|
Log::signalLog(FatalGdbString);
|
|
LOG_ERR("Sleeping 30s to allow debugging.");
|
|
sleep(30);
|
|
}
|
|
|
|
struct sigaction action;
|
|
|
|
sigemptyset(&action.sa_mask);
|
|
action.sa_flags = 0;
|
|
action.sa_handler = SIG_DFL;
|
|
|
|
sigaction(signal, &action, nullptr);
|
|
|
|
dumpBacktrace();
|
|
|
|
// let default handler process the signal
|
|
kill(Poco::Process::id(), signal);
|
|
}
|
|
|
|
void dumpBacktrace()
|
|
{
|
|
char header[32];
|
|
sprintf(header, "Backtrace %d:\n", getpid());
|
|
|
|
#if !defined(__ANDROID__)
|
|
const int maxSlots = 50;
|
|
void *backtraceBuffer[maxSlots];
|
|
int numSlots = backtrace(backtraceBuffer, maxSlots);
|
|
if (numSlots > 0)
|
|
{
|
|
char **symbols = backtrace_symbols(backtraceBuffer, numSlots);
|
|
if (symbols != nullptr)
|
|
{
|
|
struct iovec ioVector[maxSlots*2+1];
|
|
ioVector[0].iov_base = static_cast<void*>(header);
|
|
ioVector[0].iov_len = std::strlen(static_cast<const char*>(ioVector[0].iov_base));
|
|
for (int i = 0; i < numSlots; i++)
|
|
{
|
|
ioVector[1+i*2+0].iov_base = symbols[i];
|
|
ioVector[1+i*2+0].iov_len = std::strlen(static_cast<const char *>(ioVector[1+i*2+0].iov_base));
|
|
ioVector[1+i*2+1].iov_base = const_cast<void*>(static_cast<const void*>("\n"));
|
|
ioVector[1+i*2+1].iov_len = 1;
|
|
}
|
|
|
|
if (writev(STDERR_FILENO, ioVector, numSlots*2+1) == -1)
|
|
{
|
|
LOG_SYS("Failed to dump backtrace to stderr.");
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
LOG_SYS("Backtrace not available on Android.");
|
|
#endif
|
|
|
|
if (std::getenv("LOOL_DEBUG"))
|
|
{
|
|
LOG_ERR("Sleeping 30s to allow debugging.");
|
|
sleep(30);
|
|
}
|
|
}
|
|
|
|
void setFatalSignals()
|
|
{
|
|
struct sigaction action;
|
|
|
|
sigemptyset(&action.sa_mask);
|
|
action.sa_flags = 0;
|
|
action.sa_handler = handleFatalSignal;
|
|
|
|
sigaction(SIGSEGV, &action, nullptr);
|
|
sigaction(SIGBUS, &action, nullptr);
|
|
sigaction(SIGABRT, &action, nullptr);
|
|
sigaction(SIGILL, &action, nullptr);
|
|
sigaction(SIGFPE, &action, nullptr);
|
|
|
|
// prepare this in advance just in case.
|
|
std::ostringstream stream;
|
|
stream << "\nFatal signal! Attach debugger with:\n"
|
|
<< "sudo gdb --pid=" << Poco::Process::id() << "\n or \n"
|
|
<< "sudo gdb --q --n --ex 'thread apply all backtrace full' --batch --pid="
|
|
<< Poco::Process::id() << "\n";
|
|
std::string streamStr = stream.str();
|
|
assert (sizeof (FatalGdbString) > strlen(streamStr.c_str()) + 1);
|
|
strncpy(FatalGdbString, streamStr.c_str(), sizeof(FatalGdbString)-1);
|
|
FatalGdbString[sizeof(FatalGdbString)-1] = '\0';
|
|
}
|
|
|
|
static
|
|
void handleUserSignal(const int signal)
|
|
{
|
|
Log::signalLogPrefix();
|
|
Log::signalLog(" User signal received: ");
|
|
Log::signalLog(signalName(signal));
|
|
Log::signalLog("\n");
|
|
if (signal == SIGUSR1)
|
|
{
|
|
DumpGlobalState = true;
|
|
SocketPoll::wakeupWorld();
|
|
}
|
|
}
|
|
|
|
void setUserSignals()
|
|
{
|
|
struct sigaction action;
|
|
|
|
sigemptyset(&action.sa_mask);
|
|
action.sa_flags = 0;
|
|
action.sa_handler = handleUserSignal;
|
|
|
|
sigaction(SIGUSR1, &action, nullptr);
|
|
}
|
|
|
|
/// Kill the given pid with SIGTERM. Returns true when the pid does not exist any more.
|
|
bool killChild(const int pid)
|
|
{
|
|
LOG_DBG("Killing PID: " << pid);
|
|
if (kill(pid, SIGTERM) == 0 || errno == ESRCH)
|
|
{
|
|
// Killed or doesn't exist.
|
|
return true;
|
|
}
|
|
|
|
LOG_SYS("Error when trying to kill PID: " << pid << ". Will wait for termination.");
|
|
|
|
const int sleepMs = 50;
|
|
const int count = std::max(CHILD_REBALANCE_INTERVAL_MS / sleepMs, 2);
|
|
for (int i = 0; i < count; ++i)
|
|
{
|
|
if (kill(pid, 0) == 0 || errno == ESRCH)
|
|
{
|
|
// Doesn't exist.
|
|
return true;
|
|
}
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(sleepMs));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#endif // !MOBILEAPP
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|