From 45aa451be5702230ca4264636000f9d4c31bf546 Mon Sep 17 00:00:00 2001 From: Matthias Volk Date: Thu, 27 Feb 2020 20:42:59 +0100 Subject: [PATCH] Signal handler supporting termination after waiting period --- src/storm-cli-utilities/cli.cpp | 3 + src/storm/utility/resources.h | 150 ++++++++++++++++++++++++++------ 2 files changed, 128 insertions(+), 25 deletions(-) diff --git a/src/storm-cli-utilities/cli.cpp b/src/storm-cli-utilities/cli.cpp index c7b18fb7a..673e6797f 100644 --- a/src/storm-cli-utilities/cli.cpp +++ b/src/storm-cli-utilities/cli.cpp @@ -204,6 +204,9 @@ namespace storm { if (resources.isTimeoutSet()) { storm::utility::resources::setCPULimit(resources.getTimeoutInSeconds()); } + + // register signal handler to handle aborts + storm::utility::resources::installSignalHandler(); } void setLogLevel() { diff --git a/src/storm/utility/resources.h b/src/storm/utility/resources.h index 2858f6332..3b9138150 100644 --- a/src/storm/utility/resources.h +++ b/src/storm/utility/resources.h @@ -8,36 +8,70 @@ #include #include "storm-config.h" - #include "storm/utility/OsDetection.h" - #include "storm/utility/macros.h" namespace storm { namespace utility { namespace resources { - - static const int STORM_EXIT_GENERALERROR = -1; - static const int STORM_EXIT_TIMEOUT = -2; - static const int STORM_EXIT_MEMOUT = -3; - + + // Maximal waiting time after abort signal before terminating + static const int maxWaitTime = 3; + + // Flag whether the program should terminate + static bool terminate = false; + // Store last signal code + static int lastSignal = 0; + + /*! + * Check whether the program should terminate (due to some abort signal). + * @return True iff program should terminate. + */ + inline bool isTerminate() { + return terminate; + } + + /*! Get the error code. + * Default is 0. If a signal was handled, the corresponding signal code is returned. + * @return Error code. + */ + inline int getErrorCode() { + return lastSignal; + } + + /*! + * Get CPU limit. + * @return CPU limit in seconds. + */ inline std::size_t getCPULimit() { rlimit rl; getrlimit(RLIMIT_CPU, &rl); return rl.rlim_cur; } + /*! + * Set CPU limit. + * @param seconds CPU limit in seconds. + */ inline void setCPULimit(std::size_t seconds) { rlimit rl; getrlimit(RLIMIT_CPU, &rl); rl.rlim_cur = seconds; setrlimit(RLIMIT_CPU, &rl); } - + + /*! + * Get used CPU time. + * @return CPU time in seconds. + */ inline std::size_t usedCPU() { return std::size_t(clock()) / CLOCKS_PER_SEC; } - + + /*! + * Get memory limit. + * @return Memory limit in MB. + */ inline std::size_t getMemoryLimit() { #if defined LINUX rlimit rl; @@ -48,7 +82,11 @@ namespace storm { return 0; #endif } - + + /*! + * Set memory limit. + * @param megabytes Memory limit in MB. + */ inline void setMemoryLimit(std::size_t megabytes) { #if defined LINUX rlimit rl; @@ -56,11 +94,15 @@ namespace storm { rl.rlim_cur = megabytes * 1024 * 1024; setrlimit(RLIMIT_AS, &rl); #else - (void)megabytes; + (void) megabytes; // To silence "unused" warning STORM_LOG_WARN("Setting a memory limit is not supported for your operating system."); #endif } - + + /*! + * Exit without cleanup. + * @param errorCode Error code to return. + */ inline void quickest_exit(int errorCode) { #if defined LINUX std::quick_exit(errorCode); @@ -70,25 +112,83 @@ namespace storm { std::abort(); #endif } - + + /*! + * Set timeout by raising an alarm after timeout seconds. + * @param timeout Timeout in seconds. + */ + inline void setTimeoutAlarm(uint_fast64_t timeout) { + alarm(timeout); + } + + /*! + * Signal handler for aborts, etc. + * After the first signal the handler waits a number of seconds to let the program print preliminary results + * which were computed up this point. If the waiting time is exceeded or if a second signal was raised + * in the mean time, the program immediately terminates. + * @param signal Exit code of signal. + */ inline void signalHandler(int signal) { - if (signal == SIGXCPU) { - std::cerr << "Timeout." << std::endl; - quickest_exit(STORM_EXIT_TIMEOUT); - } else if (signal == ENOMEM) { - std::cerr << "Out of memory" << std::endl; - quickest_exit(STORM_EXIT_MEMOUT); + if (!isTerminate()) { + // First time we get an abort signal + // We give the program a number of seconds to print results obtained so far before termination + std::cerr << "ERROR: The program received signal " << signal << " and will be aborted in " << maxWaitTime << "s." << std::endl; + terminate = true; + // Remember original signal such that the program returns the correct original signal + lastSignal = signal; + // Trigger a new signal after waitTime + setTimeoutAlarm(maxWaitTime); } else { - std::cerr << "Unknown abort in resource limitation module." << std::endl; - quickest_exit(STORM_EXIT_GENERALERROR); + // Second time we get a signal + // We now definitely have to terminate as fast as possible + if (getErrorCode() == SIGXCPU) { + std::cerr << "TIMEOUT." << std::endl; + } else if (getErrorCode() == ENOMEM) { + std::cerr << "OUT OF MEMORY." << std::endl; + } else if (getErrorCode() == SIGABRT || getErrorCode() == SIGINT) { + std::cerr << "ABORT." << std::endl; + } else { + std::cerr << "Received signal " << signal << std::endl; + } + quickest_exit(getErrorCode()); } } - + + /*! + * Register some signal handlers to detect and correctly handle abortion (due to timeout for example). + */ inline void installSignalHandler() { - std::signal(SIGXCPU, signalHandler); - std::signal(ENOMEM, signalHandler); + // We use the newer sigaction instead of signal + struct sigaction sa; + sa.sa_handler = signalHandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + + // CPU Limit + if (sigaction(SIGXCPU, &sa, nullptr) == -1) { + std::cerr << "FATAL: Installing a signal handler failed." << std::endl; + } + // Memory out + if (sigaction(ENOMEM, &sa, nullptr) == -1) { + std::cerr << "FATAL: Installing a signal handler failed." << std::endl; + } + if (sigaction(SIGSEGV, &sa, nullptr) == -1) { + std::cerr << "FATAL: Installing a signal handler failed." << std::endl; + } + if (sigaction(SIGABRT, &sa, nullptr) == -1) { + std::cerr << "FATAL: Installing a signal handler failed." << std::endl; + } + if (sigaction(SIGINT, &sa, nullptr) == -1) { + std::cerr << "FATAL: Installing a signal handler failed." << std::endl; + } + if (sigaction(SIGTERM, &sa, nullptr) == -1) { + std::cerr << "FATAL: Installing a signal handler failed." << std::endl; + } + if (sigaction(SIGALRM, &sa, nullptr) == -1) { + std::cerr << "FATAL: Installing a signal handler failed." << std::endl; + } } - + } } }