diff --git a/openems.cpp b/openems.cpp index 120d250..33f23ae 100644 --- a/openems.cpp +++ b/openems.cpp @@ -20,6 +20,7 @@ #include #include #include "tools/array_ops.h" +#include "tools/signal.h" #include "tools/useful.h" #include "FDTD/operator_cylinder.h" #include "FDTD/operator_cylindermultigrid.h" @@ -899,14 +900,18 @@ int openEMS::SetupFDTD() timeval startTime; gettimeofday(&startTime,NULL); + Signal::SetupHandlerForSIGINT(SIGNAL_EXIT_FORCE); + if (m_CSX==NULL) { cerr << "openEMS::SetupFDTD: Error: CSXCAD is not set!" << endl; + Signal::SetupHandlerForSIGINT(SIGNAL_ORIGINAL); return 3; } if (m_CSX==NULL) { cerr << "openEMS::SetupFDTD: Error: CSXCAD is not set!" << endl; + Signal::SetupHandlerForSIGINT(SIGNAL_ORIGINAL); return 3; } std::string ec = m_CSX->Update(); @@ -927,7 +932,10 @@ int openEMS::SetupFDTD() //*************** setup operator ************// if (SetupOperator()==false) + { + Signal::SetupHandlerForSIGINT(SIGNAL_ORIGINAL); return 2; + } // default material averaging is quarter cell averaging FDTD_Op->SetQuarterCellMaterialAvg(); @@ -942,6 +950,7 @@ int openEMS::SetupFDTD() if (m_Exc==NULL) { cerr << "openEMS::SetupFDTD: Error, excitation is not defined! Abort!" << endl; + Signal::SetupHandlerForSIGINT(SIGNAL_ORIGINAL); return 3; } @@ -950,7 +959,11 @@ int openEMS::SetupFDTD() if (!CylinderCoords) FDTD_Op->AddExtension(new Operator_Ext_TFSF(FDTD_Op)); - if (FDTD_Op->SetGeometryCSX(m_CSX)==false) return(2); + if (FDTD_Op->SetGeometryCSX(m_CSX)==false) + { + Signal::SetupHandlerForSIGINT(SIGNAL_ORIGINAL); + return(2); + } SetupBoundaryConditions(); @@ -1065,6 +1078,7 @@ int openEMS::SetupFDTD() if (m_no_simulation) { // simulation was disabled (to generate debug output only) + Signal::SetupHandlerForSIGINT(SIGNAL_ORIGINAL); return 1; } @@ -1079,7 +1093,10 @@ int openEMS::SetupFDTD() //setup all processing classes if (SetupProcessing()==false) + { + Signal::SetupHandlerForSIGINT(SIGNAL_ORIGINAL); return 2; + } // Cleanup all unused material storages... FDTD_Op->CleanupMaterialStorage(); @@ -1093,6 +1110,7 @@ int openEMS::SetupFDTD() PA->DumpBoxes2File("box_dump_"); } + Signal::SetupHandlerForSIGINT(SIGNAL_ORIGINAL); return 0; } @@ -1118,12 +1136,19 @@ bool openEMS::CheckAbortCond() if (m_Abort) //abort was set externally return true; + //check whether SIGINT is received + if (Signal::ReceivedSIGINT()) + { + cerr << "openEMS::CheckAbortCond(): Received SIGINT, aborting simulation gracefully..." << endl; + return true; + } + //check whether the file "ABORT" exist in current working directory ifstream ifile("ABORT"); if (ifile) { ifile.close(); - cerr << "openEMS::CheckAbortCond(): Found file \"ABORT\", aborting simulation..." << endl; + cerr << "openEMS::CheckAbortCond(): Found file \"ABORT\", aborting simulation gracefully..." << endl; return true; } @@ -1134,6 +1159,8 @@ void openEMS::RunFDTD() { cout << "Running FDTD engine... this may take a while... grab a cup of coffee?!?" << endl; + Signal::SetupHandlerForSIGINT(SIGNAL_EXIT_GRACEFUL); + //special handling of a field processing, needed to realize the end criteria... ProcessFields* ProcField = new ProcessFields(NewEngineInterface()); PA->AddProcessing(ProcField); @@ -1232,6 +1259,8 @@ void openEMS::RunFDTD() //*************** postproc ************// PA->PostProcess(); + + Signal::SetupHandlerForSIGINT(SIGNAL_ORIGINAL); } bool openEMS::DumpStatistics(const string& filename, double time) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 8b62b8a..f2d48cf 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -4,6 +4,7 @@ set(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/AdrOp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ErrorMsg.cpp ${CMAKE_CURRENT_SOURCE_DIR}/array_ops.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/signal.cpp ${CMAKE_CURRENT_SOURCE_DIR}/global.cpp ${CMAKE_CURRENT_SOURCE_DIR}/hdf5_file_reader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/hdf5_file_writer.cpp diff --git a/tools/signal.cpp b/tools/signal.cpp new file mode 100644 index 0000000..312a4de --- /dev/null +++ b/tools/signal.cpp @@ -0,0 +1,263 @@ +/* +* Copyright (C) 2023 Yifeng Li +* +* 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 . +*/ + +#include +#include +#include +#include "signal.h" + +void Signal::SetupHandlerForSIGINT(int type) +{ + m_sigintAbort = 0; + +#ifndef WIN32 + UnixSetupHandlerForSIGINT(type); +#else + Win32SetupHandlerForConsoleCtrl(type); +#endif +} + +#ifndef WIN32 +void Signal::UnixSetupHandlerForSIGINT(int type) +{ + if (type == SIGNAL_ORIGINAL && m_sigHandlerOriginal) + { + // If we're acting as a shared library and 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. Thus, we save the + // original handler and restore it after the end of SetupFDTD() + // or RunFDTD() to minimize the disruption. + auto retval = std::signal(SIGINT, m_sigHandlerOriginal); + if (retval == SIG_ERR) + { + fprintf(stderr, "Signal::UnixSetupHandlerForSIGINT(): " + "Failed to restore signal handler!\n"); + } + m_sigHandlerOriginal = NULL; + } + else if (type == SIGNAL_EXIT_GRACEFUL) + { + m_sigHandlerOriginal = std::signal(SIGINT, UnixGracefulExitHandler); + if (m_sigHandlerOriginal == SIG_ERR) + { + fprintf(stderr, "Signal::UnixSetupHandlerForSIGINT(): " + "Failed to set UnixGracefulExitHandler!\n"); + m_sigHandlerOriginal = NULL; + } + } + else if (type == SIGNAL_EXIT_FORCE) + { + m_sigHandlerOriginal = std::signal(SIGINT, UnixForceExitHandler); + if (m_sigHandlerOriginal == SIG_ERR) + { + fprintf(stderr, "Signal::UnixSetupHandlerForSIGINT(): " + "Failed to set UnixForceExitHandler!\n"); + m_sigHandlerOriginal = NULL; + } + } +} + +void Signal::UnixGracefulExitHandler(int signal) +{ + m_sigintAbort = 1; + + // C standard only guarantees that a sig_atomic_t variable is safe + // to read or write, but it's not necessarily safe to increment by + // one, and also not safe to set one sig_atomic_t depending on the + // result of another sig_atomic_t. + // + // Thus, we switch the signal handler itself instead of recording + // the number of times SIGINT is raised. + auto retval = std::signal(SIGINT, UnixForceExitHandler); + if (retval == SIG_ERR) + { + SafeStderrWrite("\nSignal::UnixGracefulExitHandler(): " + "Failed to set UnixForceExitHandler!"); + } + else + { + SafeStderrWrite("\nSignal::UnixGracefulExitHandler(): " + "Gracefully aborting simulation " + "now, this may take a few seconds...\n" + "Signal::UnixGracefulExitHandler(): " + "To force-exit, send Ctrl-C again, " + "but simulation results may be lost.\n"); + } +} + +void Signal::UnixForceExitHandler(int signal) +{ + SafeStderrWrite("\nSignal::UnixForceExitHandler(): " + "Force-exit simulation process now!\n"); + + // By convention, if a program is (uncleanly) aborted due to + // an external signal, preferably it should return 128 + signal. + // For SIGINT, it's 130. + std::_Exit(128 + signal); +} + +#else + +void Signal::Win32SetupHandlerForConsoleCtrl(int type) +{ + if (type == SIGNAL_ORIGINAL || m_sigHandlerRegistered) + { + // On Windows, SetConsoleCtrlHandler appends a new ConsoleCtrlHandler + // in addition to the existing handlers. Thus, we need to record + // the ConsoleCtrlHandler installed by us (instead of getting the + // pre-existing handlers on Unix). Then, before we install a new + // signal handler, we need to use the argument "Add == FALSE" to + // remove the handler we previously installed. + // + // We also need to do the same in case that we're restoring the + // ConsoleCtrlHandler to the original state (note how on Unix, the + // if expression uses "AND", but on Windows, the if expression uses + // "OR". + BOOL success = SetConsoleCtrlHandler(m_sigHandlerRegistered, FALSE); + m_sigHandlerRegistered = NULL; + + if (!success) + { + fprintf(stderr, "Signal::Win32SetupHandlerForConsoleCtrl(): " + "Failed to unregister ConsoleCtrlHandler!\n"); + return; + } + } + + // Assume m_sigHandlerRegistered has already been unregistered. + if (type == SIGNAL_EXIT_GRACEFUL) + { + m_sigHandlerRegistered = (PHANDLER_ROUTINE) Win32GracefulExitHandler; + BOOL success = SetConsoleCtrlHandler(m_sigHandlerRegistered, TRUE); + + if (!success) + { + fprintf(stderr, "Signal::Win32SetupHandlerForConsoleCtrl(): " + "Failed to register Win32GracefulExitHandler!\n"); + } + } + else if (type == SIGNAL_EXIT_FORCE) + { + m_sigHandlerRegistered = (PHANDLER_ROUTINE) Win32ForceExitHandler; + BOOL success = SetConsoleCtrlHandler(m_sigHandlerRegistered, TRUE); + + if (!success) + { + fprintf(stderr, "Signal::Win32SetupHandlerForConsoleCtrl(): " + "Failed to register Win32ForceExitHandler!\n"); + } + } +} + +BOOL Signal::Win32GracefulExitHandler(DWORD fdwCtrlType) +{ + m_sigintAbort = 1; + + // unregister the current handler + BOOL success = SetConsoleCtrlHandler(m_sigHandlerRegistered, FALSE); + if (!success) + { + SafeStderrWrite("Signal::Win32GracefulExitHandler(): " + "Failed to unregister Win32GracefulExitHandler!\n"); + return true; + } + + // install a new handler + m_sigHandlerRegistered = (PHANDLER_ROUTINE) Win32ForceExitHandler; + success = SetConsoleCtrlHandler(m_sigHandlerRegistered, TRUE); + if (!success) + { + SafeStderrWrite("Signal::Win32GracefulExitHandler(): " + "Failed to register Win32ForceExitHandler!\n"); + } + else + { + SafeStderrWrite("\nSignal::Win32GracefulExitHandler(): " + "Gracefully aborting simulation " + "now, this may take a few seconds...\n" + "Signal::Win32GracefulExitHandler(): " + "To force-exit, send Ctrl-C again, " + "but simulation results may be lost.\n"); + } + + return true; +} + +BOOL Signal::Win32ForceExitHandler(DWORD fdwCtrlType) +{ + SafeStderrWrite("\nSignal::Win32ForceExitHandler(): " + "Force-exit simulation process now!\n"); + + // On Windows, the exit code for SIGINT is always 3. + std::_Exit(3); + + // unreachable + return true; +} +#endif + +bool Signal::ReceivedSIGINT(void) +{ + if (m_sigintAbort) + return true; + else + return false; +} + +void Signal::SafeStderrWrite(const char *buf) +{ +#ifdef WIN32 + // On Windows, using any kind of system calls in a ANSI C signal + // handler is prohibited, in this case, this function should return + // immediately without doing anything. But, when the official way + // SetConsoleCtrlHandler() is used (instead of using ANSI C signals), + // there's no such restriction. + fprintf(stderr, "%s", buf); + fflush(stderr); + return; +#else + // On Unix, in a signal handler, it's unsafe to use normal I/O + // functions such as iostream, puts(), printf(), fprintf(). The + // only safe option is the system call write(). + size_t buf_len = strlen(buf); + ssize_t bytes = 0; + + while (buf_len > 0) + { + bytes = write(STDERR_FILENO, buf, buf_len); + if (bytes < 0) + { + // write failure, nothing we can do. + return; + } + + if ((size_t) bytes > buf_len) + { + // Assertion: This should never happen. bytes is + // always less or equal to buf_len, and buf_len + // will never underflow under any circumstances + // (unless the write system call is broken). + return; + } + + buf += bytes; // advance buffer position + buf_len -= (size_t) bytes; // decrement limiter + } + return; // write completed. +#endif +} diff --git a/tools/signal.h b/tools/signal.h new file mode 100644 index 0000000..c65b511 --- /dev/null +++ b/tools/signal.h @@ -0,0 +1,59 @@ +/* +* Copyright (C) 2023 Yifeng Li +* +* 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 . +*/ + +#ifndef SIGNAL_H +#define SIGNAL_H + +#include + +#ifndef WIN32 +#include +#else +#include +#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