openEMS/tools/signal.h

60 lines
1.5 KiB
C
Raw Normal View History

Handle SIGINT for openEMS and Python, with graceful exit support. Currently, openEMS doesn't have any special code to handle SIGINT (which is raised by pressing Control-C). By default, the program is terminated without saving data. This worked okay in the past, but now its limitations are becoming obvious. 1. When openEMS is used as a Python module, Control-C stops working because SIGINT is now managed by Python in order to generate KeyboardInterrupt exceptions, normally this isn't a problem, but if we are running an external C++ (Cython) function such as openEMS, the Python interpreter mainloop has no control until we return. As a result, SIGINT is received but never handled. In Cython, programs are expected to call PyErr_CheckSignals() in its blocking loop periodically to temporally transfer control back to Python to handle signals. But this introduces a dependency of Cython in the FDTD mainloop. 2. During a simulation, it's not possible to abort it gracefully by pressing Control-C, this is a limitation of openEMS itself, it's always a force exit. Currently the only supported method for graceful exit is creating a file called "ABORT" in the simulation directory. If we already need to implement a signal handler, adding a graceful exit at the same time would be a good idea. This commit installs SIGINT handlers during SetupFDTD() and RunFDTD(). 1. In RunFDTD(), if SIGINT is received once, a status flag is set, which is then checked in CheckAbortCond(), allowing a graceful exit with the same effect of an "ABORT" file. If SIGINT is received twice, openEMS force exit without saving data (just like the old default behavior). 2. In SetupFDTD(), if SIGINT is received, openEMS immediately force exit without saving data, identical to the old behavior. In a huge simulation, initializing and compressing operators may have a long time. so we want an early exit before RunFDTD(). 3. Before RunFDTD() and SetupFDTD() return, the original signal handler for SIGINT is restored. This is important since when we're acting as a shared library. When a program (such as the Python interpreter) calls us, changing the SIGINT handler unilaterally may overwrite the original handler and affect the functionality of the original program. For example, Python would never be able to raise KeyboardInterrupt again. Thus, we save the original handler and restore it later. Signed-off-by: Yifeng Li <tomli@tomli.me>
2023-05-07 09:59:13 +00:00
/*
* Copyright (C) 2023 Yifeng Li <tomli@tomli.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SIGNAL_H
#define SIGNAL_H
#include <csignal>
#ifndef WIN32
#include <unistd.h>
#else
#include <windows.h>
#endif
enum
{
SIGNAL_ORIGINAL,
SIGNAL_EXIT_GRACEFUL,
SIGNAL_EXIT_FORCE,
};
class Signal
{
public:
static void SetupHandlerForSIGINT(int type);
static bool ReceivedSIGINT(void);
private:
inline static volatile std::sig_atomic_t m_sigintAbort = 0;
static void SafeStderrWrite(const char *buf);
#ifndef WIN32
inline static void (*m_sigHandlerOriginal)(int) = NULL;
static void UnixSetupHandlerForSIGINT(int type);
static void UnixGracefulExitHandler(int signal);
static void UnixForceExitHandler(int signal);
#else
inline static PHANDLER_ROUTINE m_sigHandlerRegistered = NULL;
static void Win32SetupHandlerForConsoleCtrl(int type);
static BOOL Win32GracefulExitHandler(DWORD fdwCtrlType);
static BOOL Win32ForceExitHandler(DWORD fdwCtrlType);
#endif
};
#endif