From ebeb91a6eafd02032569d6be77ad1d1db2e5fc68 Mon Sep 17 00:00:00 2001 From: Markus Frank Date: Thu, 17 Oct 2024 17:57:48 +0200 Subject: [PATCH 1/3] Add implementation for signal handling. Use generic signal handler in DDG4 for controlled shutdown on CTRL-C. Add test DDG4_SIGINT_handler in examples/DDG4 --- DDCore/include/DD4hep/SignalHandler.h | 47 +++ DDCore/src/SignalHandler.cpp | 308 +++++++++++++++++++ DDG4/include/DDG4/Geant4Interrupts.h | 55 ++++ DDG4/include/DDG4/Geant4Kernel.h | 59 +++- DDG4/python/DDG4.py | 12 + DDG4/src/Geant4Exec.cpp | 3 + DDG4/src/Geant4GeneratorAction.cpp | 8 +- DDG4/src/Geant4Interrupts.cpp | 33 ++ DDG4/src/Geant4Kernel.cpp | 58 +++- DDG4/src/Geant4UIManager.cpp | 3 +- examples/ClientTests/scripts/SiliconBlock.py | 1 + examples/DDG4/CMakeLists.txt | 8 + examples/DDG4/scripts/TestSIGINT.py | 88 ++++++ examples/DDG4/src/TestSignalAction.cpp | 58 ++++ 14 files changed, 714 insertions(+), 27 deletions(-) create mode 100644 DDCore/include/DD4hep/SignalHandler.h create mode 100644 DDCore/src/SignalHandler.cpp create mode 100644 DDG4/include/DDG4/Geant4Interrupts.h create mode 100644 DDG4/src/Geant4Interrupts.cpp create mode 100644 examples/DDG4/scripts/TestSIGINT.py create mode 100644 examples/DDG4/src/TestSignalAction.cpp diff --git a/DDCore/include/DD4hep/SignalHandler.h b/DDCore/include/DD4hep/SignalHandler.h new file mode 100644 index 000000000..383bb8b11 --- /dev/null +++ b/DDCore/include/DD4hep/SignalHandler.h @@ -0,0 +1,47 @@ +//========================================================================== +// AIDA Detector description implementation +//-------------------------------------------------------------------------- +// Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN) +// All rights reserved. +// +// For the licensing terms see $DD4hepINSTALL/LICENSE. +// For the list of contributors see $DD4hepINSTALL/doc/CREDITS. +// +// Author : M.Frank +// +//========================================================================== +#ifndef DD4HEP_DDCORE_SIGNALHANDLER_H +#define DD4HEP_DDCORE_SIGNALHANDLER_H + +/// System include files +#include +#include + +/// Namespace for the AIDA detector description toolkit +namespace dd4hep { + + /// Interruptsback interface class with argument + /** + * \author M.Frank + * \version 1.0 + * \ingroup DD4HEP_CORE + */ + class SignalHandler { + public: + /// User signal handler definition + typedef bool (*signal_handler_t)(void* user_context, int signal); + /// Internal implementation class + class implementation; + + public: + /// Default constructor + SignalHandler(); + /// Default destructor + virtual ~SignalHandler(); + /// (Re-)apply registered interrupt handlers to override potentially later registrations by other libraries + void applyHandlers(); + /// Specialized handler for any signal + bool registerHandler(int sig_num, void* param, signal_handler_t handler); + }; +} // End namespace dd4hep +#endif // DD4HEP_DDCORE_SIGNALHANDLER_H diff --git a/DDCore/src/SignalHandler.cpp b/DDCore/src/SignalHandler.cpp new file mode 100644 index 000000000..c778e3da4 --- /dev/null +++ b/DDCore/src/SignalHandler.cpp @@ -0,0 +1,308 @@ +//========================================================================== +// AIDA Detector description implementation +//-------------------------------------------------------------------------- +// Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN) +// All rights reserved. +// +// For the licensing terms see $DD4hepINSTALL/LICENSE. +// For the list of contributors see $DD4hepINSTALL/doc/CREDITS. +// +// Author : M.Frank +// +//========================================================================== + +// Framework include files +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace dd4hep; + +using signal_handler_t = SignalHandler::signal_handler_t; + +namespace { + static bool s_exit_handler_print = true; + static bool s_exit_handler_active = false; + static bool s_exit_handler_backtrace = false; + static bool s_exit_handler_sleep_on_fatal = false; + + template union func_cast { + void* ptr; + T fun; + explicit func_cast(T t) { fun = t; } + explicit func_cast(void* t) { ptr = t; } + }; +} + +/**@class SignalHandler::implementation + * + * Small class to manipulate default signal handling + * + * \author M.Frank + */ +class SignalHandler::implementation { +protected: + struct sig_entry_t { + void* user_context { nullptr }; + signal_handler_t user_handler { nullptr }; + }; + struct sig_handler_t { + std::string name { }; + struct sigaction old_action { }; + struct sigaction handler_action { }; + std::vector user_handlers { }; + }; + +public: + typedef std::map SigMap; + SigMap m_map; + +public: + /// Default constructor + implementation(); + /// Default destructor + ~implementation(); + /// Singleton accessor + static implementation& instance(); + /// Initialize the exit handler. Subscribe to default signals + void init(); + /// Install handler for a single signal + void install(int num, const std::string& name, struct sigaction& action); + /// Subscribe to a given signal with a user context and a user handler. The context MUST be unique! + int subscribe(int signum, void* user_context, signal_handler_t handler); + /// Unsubscribe from a given signal with a user context identifier + int unsubscribe(int signum, void* user_context); + /// Create simple backtrace + void back_trace(int /* signum */); + /// Static handler callback for system signal handler + static void handler(int signum, siginfo_t *info,void * ); +}; + +/// Default constructor +SignalHandler::implementation::implementation() { +} + +/// Default destructor +SignalHandler::implementation::~implementation() { +} + +/// Singleton accessor +SignalHandler::implementation& SignalHandler::implementation::instance() { + static std::unique_ptr imp; + if ( !imp ) { + imp = std::make_unique(); + } + return *imp; +} + +/// Initialize the exit handler. Subscribe to default signals +void SignalHandler::implementation::init() { + struct sigaction new_action; + sigemptyset(&new_action.sa_mask); + new_action.sa_handler = 0; + new_action.sa_sigaction = handler; + new_action.sa_flags = SA_SIGINFO; + + install(SIGILL, "SIGILL", new_action); + install(SIGINT, "SIGINT", new_action); + install(SIGTERM, "SIGTERM", new_action); + install(SIGHUP, "SIGHUP", new_action); + + install(SIGQUIT, "SIGQUIT", new_action); + install(SIGBUS, "SIGBUS", new_action); + install(SIGXCPU, "SIGXCPU", new_action); + sigaddset(&new_action.sa_mask,SIGSEGV); + sigaddset(&new_action.sa_mask,SIGABRT); + sigaddset(&new_action.sa_mask,SIGFPE); + install(SIGABRT, "SIGABRT", new_action); + install(SIGFPE, "SIGFPE", new_action); + install(SIGSEGV, "SIGSEGV", new_action); +} + +/// Subscribe to a given signal with a user context and a user handler. The context MUST be unique! +int SignalHandler::implementation::subscribe(int signum, void* user_context, signal_handler_t user_handler) { + if ( m_map.empty() ) { + this->init(); + } + auto ihandler = m_map.find(signum); + if ( ihandler == m_map.end() ) { + char text[32]; + struct sigaction new_action; + sigemptyset(&new_action.sa_mask); + new_action.sa_handler = 0; + new_action.sa_sigaction = SignalHandler::implementation::handler; + new_action.sa_flags = SA_SIGINFO; + ::snprintf(text, sizeof(text),"%08X",signum); + install(signum, text, new_action); + ihandler = m_map.find(signum); + } + if ( ihandler != m_map.end() ) { // Should always be true + sig_entry_t entry {user_context, user_handler}; + ihandler->second.user_handlers.emplace_back(entry); + return 1; + } + return 0; +} + +/// Unsubscribe from a given signal with a user context identifier +int SignalHandler::implementation::unsubscribe(int signum, void* user_context) { + auto ihandler = m_map.find(signum); + if ( ihandler != m_map.end() ) { + auto & handlers = ihandler->second.user_handlers; + for( auto it = handlers.begin(); it != handlers.end(); ++it ) { + if ( it->user_context == user_context ) { + handlers.erase(it); + return 1; + } + } + } + return 0; +} + +/// Create simple backtrace +void SignalHandler::implementation::back_trace(int /* signum */) { + if ( s_exit_handler_backtrace ) { + void *bt[256]; + char text[512]; + int bt_size = ::backtrace(bt, sizeof(bt) / sizeof(void *)); + size_t len = ::snprintf(text, sizeof(text), "\n[INFO] (ExitSignalHandler) %s\n", + "---------------------- Backtrace ----------------------\n"); + text[sizeof(text)-2] = '\n'; + text[sizeof(text)-1] = 0; + ::write(STDERR_FILENO, text, len); + len = ::snprintf(text, sizeof(text), "[INFO] Number of elements in backtrace: %d\n", bt_size); + text[sizeof(text)-2] = '\n'; + text[sizeof(text)-1] = 0; + ::write(STDERR_FILENO, text, len); + ::backtrace_symbols_fd(bt, bt_size, STDERR_FILENO); + for (int i = 0; i < bt_size; i++) { + len = ::snprintf(text,sizeof(text),"[INFO] (SignalHandler) %02d --> %p\n", i, bt[i]); + text[sizeof(text)-2] = '\n'; + text[sizeof(text)-1] = 0; + ::write(STDERR_FILENO, text, len); + } + } +} + +/// Install handler for a single signal +void SignalHandler::implementation::install(int num, const std::string& name, struct sigaction& action) { + auto& action_entry = m_map[num]; + int res = ::sigaction (num, &action, &action_entry.old_action); + if ( res != 0 ) { + char text[512]; + auto len = ::snprintf(text,sizeof(text),"Failed to install exit handler for %s", name.c_str()); + text[sizeof(text)-2] = '\n'; + text[sizeof(text)-1] = 0; + ::write(STDERR_FILENO, text, len); + return; + } + action_entry.handler_action = action; + action_entry.name = name; +} + +/// Static handler callback for system signal handler +void SignalHandler::implementation::handler(int signum, siginfo_t *info, void *ptr) { + SigMap& m = instance().m_map; + SigMap::iterator iter_handler = m.find(signum); + s_exit_handler_active = true; + if ( iter_handler != m.end() ) { + __sighandler_t hdlr = iter_handler->second.old_action.sa_handler; + func_cast dsc0(hdlr); + func_cast dsc(dsc0.ptr); + + if ( s_exit_handler_print ) {{ + char text[512]; + size_t len = ::snprintf(text,sizeof(text), + "[FATAL] (SignalHandler) Handle signal: %d [%s] Old action:%p Mem:%p Code:%08X\n", + signum,iter_handler->second.name.c_str(),dsc.ptr,info->si_addr,info->si_code); + text[sizeof(text)-2] = '\n'; + text[sizeof(text)-1] = 0; + ::write(STDERR_FILENO,text,len); + // Debugging hack, if enabled (default: NO) + if ( s_exit_handler_sleep_on_fatal ) { + bool _s_sleep = true; + len = ::snprintf(text,sizeof(text), + "[FATAL] (SignalHandler) Sleeping for debugging.... %s\n", + _s_sleep ? "YES" : "NO"); + text[sizeof(text)-2] = '\n'; + text[sizeof(text)-1] = 0; + ::write(STDERR_FILENO,text,len); + while ( _s_sleep ) ::usleep(100000); + } + } + if ( !iter_handler->second.user_handlers.empty() ) { + auto& handlers = iter_handler->second.user_handlers; + for( auto ih = handlers.rbegin(); ih != handlers.rend(); ++ih ) { + if ( ih->user_handler ) { + bool ret = (*(ih->user_handler))(ih->user_context, signum); + if ( ret ) { + return; + } + // Otherwise continue signal processing and eventually call default handlers + } + // No handler fired: call previously registered signal handler + auto& entry = iter_handler->second.old_action; + if ( entry.sa_handler ) + (*entry.sa_handler)(signum); + else if ( entry.sa_sigaction ) + (*entry.sa_sigaction)(signum, info, ptr); + } + } + if ( signum == SIGSEGV || signum == SIGBUS || signum == SIGILL || signum == SIGABRT ) { + instance().back_trace(signum); + } + else if ( info->si_signo == SIGSEGV || info->si_signo == SIGBUS || info->si_signo == SIGILL || info->si_signo == SIGABRT ) { + instance().back_trace(info->si_signo); + } + } + if ( signum == SIGINT || signum == SIGHUP || signum == SIGFPE || signum == SIGPIPE ) { + if ( dsc.fun && (dsc0.fun != SIG_IGN) ) + dsc.fun(signum, info, ptr); + else if ( signum == SIGHUP ) + ::_exit(signum); + } + else if ( signum == SIGSEGV && hdlr && hdlr != SIG_IGN && hdlr != SIG_DFL ) { + ::_exit(0); + } + else if ( hdlr && hdlr != SIG_IGN && dsc.fun ) { + dsc.fun(signum, info, ptr); + } + else if ( hdlr == SIG_DFL ) { + ::_exit(0); + } + } + s_exit_handler_active = false; +} + +/// Default constructor +SignalHandler::SignalHandler() +{ +} + +/// Default destructor +SignalHandler::~SignalHandler() { +} + +/// (Re-)apply registered interrupt handlers to override potentially later registrations by other libraries +void SignalHandler::applyHandlers() { + auto& imp = implementation::instance(); + struct sigaction old_action { }; + printout(INFO, "SignalHandler", "++ Re-apply signal handlers"); + for( const auto& e : imp.m_map ) { + ::sigaction (e.first, &e.second.handler_action, &old_action); + printout(DEBUG, "SignalHandler", + "++ Re-apply signal handler for %-10s [%3ld entries]", + e.second.name.c_str(), e.second.user_handlers.size()); + } +} + +/// Install handler for any signal +bool SignalHandler::registerHandler(int sig_num, void* param, signal_handler_t handler) { + return implementation::instance().subscribe(sig_num, param, handler) == 1; +} diff --git a/DDG4/include/DDG4/Geant4Interrupts.h b/DDG4/include/DDG4/Geant4Interrupts.h new file mode 100644 index 000000000..2f82a8059 --- /dev/null +++ b/DDG4/include/DDG4/Geant4Interrupts.h @@ -0,0 +1,55 @@ +//========================================================================== +// AIDA Detector description implementation +//-------------------------------------------------------------------------- +// Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN) +// All rights reserved. +// +// For the licensing terms see $DD4hepINSTALL/LICENSE. +// For the list of contributors see $DD4hepINSTALL/doc/CREDITS. +// +// Author : M.Frank +// +//========================================================================== +#ifndef DDG4_GEANT4INTERRUPTS_H +#define DDG4_GEANT4INTERRUPTS_H + +/// Framework include files +#include + +/// System include files + +/// Namespace for the AIDA detector description toolkit +namespace dd4hep { + + /// Namespace for the Geant4 based simulation part of the AIDA detector description toolkit + namespace sim { + + /// Forward declarations + class Geant4Kernel; + + /// Interruptsback interface class with argument + /** + * \author M.Frank + * \version 1.0 + * \ingroup DD4HEP_SIMULATION + */ + class Geant4Interrupts : public SignalHandler { + public: + /// Reference to simulation kernel + Geant4Kernel& m_kernel; + Geant4Kernel& kernel() { return m_kernel; } + + /// Default SIGINT handler: trigger end-of-event-loop in Geant4Kernel object + static bool default_sigint_handler(void* user_context, int signum); + + public: + /// Default constructor + Geant4Interrupts(Geant4Kernel& krnl) : m_kernel(krnl) { } + /// Default destructor + virtual ~Geant4Interrupts() = default; + /// Specialized handler for SIGINT + bool registerHandler_SIGINT(); + }; + } // End namespace sim +} // End namespace dd4hep +#endif // DDG4_GEANT4INTERRUPTS_H diff --git a/DDG4/include/DDG4/Geant4Kernel.h b/DDG4/include/DDG4/Geant4Kernel.h index 03bfeed8d..b956f9b57 100644 --- a/DDG4/include/DDG4/Geant4Kernel.h +++ b/DDG4/include/DDG4/Geant4Kernel.h @@ -21,13 +21,6 @@ #include #include -class DD4hep_End_Of_File : public std::exception { -public: - DD4hep_End_Of_File() : std::exception() {} - virtual const char* what() const noexcept { return "Reached end of input file"; } - -}; - // Forward declarations class G4RunManager; class G4UIdirectory; @@ -36,12 +29,30 @@ class G4VPhysicalVolume; /// Namespace for the AIDA detector description toolkit namespace dd4hep { + // Forward declarations + class Geant4Interrupts; + /// Namespace for the Geant4 based simulation part of the AIDA detector description toolkit namespace sim { // Forward declarations + class Geant4Interrupts; class Geant4ActionPhase; + /// Helper class to indicate the of file + class DD4hep_End_Of_File : public std::exception { + public: + DD4hep_End_Of_File() : std::exception() {} + virtual const char* what() const noexcept { return "Reached end of input file"; } + }; + + /// Helper class to indicate the of file + class DD4hep_Stop_Processing : public std::exception { + public: + DD4hep_Stop_Processing() : std::exception() {} + virtual const char* what() const noexcept { return "Event loop STOP signalled. Processing stops"; } + }; + /// Class, which allows all Geant4Action derivatives to access the DDG4 kernel structures. /** * To implement access to a user specified framework please see class Geant4Context. @@ -59,6 +70,11 @@ namespace dd4hep { typedef std::pair UserFramework; using UserCallbacks = std::vector >; + enum event_loop_status { + EVENTLOOP_HALT = 0, + EVENTLOOP_RUNNING = 1, + }; + protected: /// Reference to the run manager G4RunManager* m_runManager { nullptr }; @@ -94,14 +110,17 @@ namespace dd4hep { /// Property: Names with specialized factories to create G4VSensitiveDetector instances std::map m_sensitiveDetectorTypes; /// Property: Number of events to be executed in batch mode - long m_numEvent = 10; + long m_numEvent = 10; /// Property: Output level - int m_outputLevel = 0; + int m_outputLevel = 0; /// Master property: Number of execution threads in multi threaded mode. - int m_numThreads = 0; + int m_numThreads = 0; /// Master property: Instantiate the Geant4 scoring manager object int m_haveScoringMgr = false; + /// Master property: Flag if event loop is enabled + int m_processEvents = EVENTLOOP_RUNNING; + /// Registered action callbacks on configure UserCallbacks m_actionConfigure { }; @@ -118,8 +137,10 @@ namespace dd4hep { /// Parent reference Geant4Kernel* m_master { nullptr }; - Geant4Kernel* m_shared { nullptr }; + /// Thread context reference Geant4Context* m_threadContext { nullptr }; + /// Interrupt/signal handler: only on master instance + Geant4Interrupts* m_interrupts { nullptr }; bool isMaster() const { return this == m_master; } bool isWorker() const { return this != m_master; } @@ -136,9 +157,6 @@ namespace dd4hep { /// Thread's master context Geant4Kernel& master() const { return *m_master; } - /// Shared action context - Geant4Kernel& shared() const { return *m_shared; } - //bool isMultiThreaded() const { return m_multiThreaded; } bool isMultiThreaded() const { return m_numThreads > 0; } @@ -248,6 +266,19 @@ namespace dd4hep { /// Register terminate callback. Signature: (function)() void register_terminate(const std::function& callback); + /// Access interrupt handler. Will be created on the first call + Geant4Interrupts& interruptHandler() const; + /// Trigger smooth end-of-event-loop with finishing currently processing event + void triggerStop(); + /// Check if event processing should be continued + bool processEvents() const; + /// Install DDG4 default handler for a given signal. If no handler: return false + bool registerInterruptHandler(int sig_num); + /// (Re-)apply registered interrupt handlers to override potentially later registrations by other libraries + /** In this case we overwrite signal handlers applied by Geant4. + */ + void applyInterruptHandlers(); + /// Register action by name to be retrieved when setting up and connecting action objects /** Note: registered actions MUST be unique. * However, not all actions need to registered.... diff --git a/DDG4/python/DDG4.py b/DDG4/python/DDG4.py index 41007834e..8c1c861d5 100644 --- a/DDG4/python/DDG4.py +++ b/DDG4/python/DDG4.py @@ -10,6 +10,7 @@ # ========================================================================== from __future__ import absolute_import, unicode_literals import logging +import signal import cppyy from dd4hep_base import * # noqa: F403 @@ -447,6 +448,17 @@ def ui(self): ui_name = self.master().UI return self.master().globalAction(ui_name) + def registerInterruptHandler(self, signum=signal.SIGINT): + """ + Enable interrupt handling: smooth handling of CTRL-C + - Finish processing of the current event(s) + - Drain the event loop + - Properly finalyze the job + + \author M.Frank + """ + return self.master().registerInterruptHandler(signum) + def addUserInitialization(self, worker, worker_args=None, master=None, master_args=None): """ Configure Geant4 user initialization for optionasl multi-threading mode diff --git a/DDG4/src/Geant4Exec.cpp b/DDG4/src/Geant4Exec.cpp index e1aa5e6fc..de9fbf942 100644 --- a/DDG4/src/Geant4Exec.cpp +++ b/DDG4/src/Geant4Exec.cpp @@ -375,6 +375,7 @@ namespace dd4hep { createClientContext(run); kernel().executePhase("begin-run",(const void**)&run); if ( m_sequence ) m_sequence->begin(run); // Action not mandatory + kernel().applyInterruptHandlers(); } /// End-of-run callback @@ -624,6 +625,7 @@ int Geant4Exec::initialize(Geant4Kernel& kernel) { /// /// Initialize G4 engine /// + kernel.applyInterruptHandlers(); kernel.executePhase("initialize",0); runManager.Initialize(); return 1; @@ -649,6 +651,7 @@ int Geant4Exec::run(Geant4Kernel& kernel) { throw std::runtime_error(format("Geant4Exec","++ Failed to locate UI interface %s.",value.c_str())); } long nevt = kernel.property("NumEvents").value(); + kernel.applyInterruptHandlers(); kernel.runManager().BeamOn(nevt); kernel.executePhase("stop",0); return 1; diff --git a/DDG4/src/Geant4GeneratorAction.cpp b/DDG4/src/Geant4GeneratorAction.cpp index df31ea3d8..dcec7b8fc 100644 --- a/DDG4/src/Geant4GeneratorAction.cpp +++ b/DDG4/src/Geant4GeneratorAction.cpp @@ -119,6 +119,10 @@ void Geant4GeneratorActionSequence::adopt(Geant4GeneratorAction* action) { /// Generator callback void Geant4GeneratorActionSequence::operator()(G4Event* event) { - m_actors(&Geant4GeneratorAction::operator(), event); - m_calls(event); + if ( context()->kernel().processEvents() ) { + m_actors(&Geant4GeneratorAction::operator(), event); + m_calls(event); + return; + } + throw DD4hep_Stop_Processing(); } diff --git a/DDG4/src/Geant4Interrupts.cpp b/DDG4/src/Geant4Interrupts.cpp new file mode 100644 index 000000000..5e593edc3 --- /dev/null +++ b/DDG4/src/Geant4Interrupts.cpp @@ -0,0 +1,33 @@ +//========================================================================== +// AIDA Detector description implementation +//-------------------------------------------------------------------------- +// Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN) +// All rights reserved. +// +// For the licensing terms see $DD4hepINSTALL/LICENSE. +// For the list of contributors see $DD4hepINSTALL/doc/CREDITS. +// +// Author : M.Frank +// +//========================================================================== + +// Framework include files +#include +#include +#include + +/// Default SIGINT handler: trigger end-of-event-loop in Geant4Kernel object +bool dd4hep::sim::Geant4Interrupts::default_sigint_handler(void* user_context, int) { + Geant4Kernel* krnl = (Geant4Kernel*)user_context; + if ( krnl ) { + krnl->triggerStop(); + return true; + } + except("Geant4Interrupts", "+++ Internal error: no user context in default SIGINT handler!"); + return true; +} + +/// Install specialized handler for SIGINT +bool dd4hep::sim::Geant4Interrupts::registerHandler_SIGINT() { + return this->registerHandler(SIGINT, &m_kernel, default_sigint_handler); +} diff --git a/DDG4/src/Geant4Kernel.cpp b/DDG4/src/Geant4Kernel.cpp index dc85386dc..27e789c1f 100644 --- a/DDG4/src/Geant4Kernel.cpp +++ b/DDG4/src/Geant4Kernel.cpp @@ -21,6 +21,7 @@ #include #include +#include #include // Geant4 include files @@ -33,12 +34,14 @@ // C/C++ include files #include #include +#include #include using namespace dd4hep::sim; namespace { - G4Mutex kernel_mutex=G4MUTEX_INITIALIZER; + + G4Mutex kernel_mutex = G4MUTEX_INITIALIZER; std::unique_ptr s_main_instance; void description_unexpected() { try { @@ -98,11 +101,11 @@ Geant4Kernel::Geant4Kernel(Detector& description_ref) declareProperty("SensitiveTypes", m_sensitiveDetectorTypes); declareProperty("RunManagerType", m_runManagerType = "G4RunManager"); declareProperty("DefaultSensitiveType", m_dfltSensitiveDetectorType = "Geant4SensDet"); + m_interrupts = new Geant4Interrupts(*this); m_controlName = "/ddg4/"; m_control = new G4UIdirectory(m_controlName.c_str()); m_control->SetGuidance("Control for named Geant4 actions"); setContext(new Geant4Context(this)); - //m_shared = new Geant4Kernel(description_ref, this, -2); InstanceCount::increment(this); } @@ -120,9 +123,9 @@ Geant4Kernel::Geant4Kernel(Geant4Kernel* krnl, unsigned long ident) m_sensitiveDetectorTypes = m_master->m_sensitiveDetectorTypes; m_dfltSensitiveDetectorType = m_master->m_dfltSensitiveDetectorType; declareProperty("UI",m_uiName = m_master->m_uiName); - declareProperty("OutputLevel", m_outputLevel = m_master->m_outputLevel); - declareProperty("OutputLevels",m_clientLevels = m_master->m_clientLevels); - ::snprintf(text,sizeof(text),"/ddg4.%d/",(int)(m_master->m_workers.size())); + declareProperty("OutputLevel", m_outputLevel = m_master->m_outputLevel); + declareProperty("OutputLevels", m_clientLevels = m_master->m_clientLevels); + ::snprintf(text, sizeof(text), "/ddg4.%d/", (int)(m_master->m_workers.size())); m_controlName = text; m_control = new G4UIdirectory(m_controlName.c_str()); m_control->SetGuidance("Control for thread specific Geant4 actions"); @@ -139,6 +142,7 @@ Geant4Kernel::~Geant4Kernel() { if ( isMaster() ) { detail::releaseObjects(m_globalFilters); detail::releaseObjects(m_globalActions); + detail::deletePtr(m_interrupts); } destroyPhases(); detail::deletePtr(m_runManager); @@ -169,6 +173,40 @@ Geant4Kernel& Geant4Kernel::instance(Detector& description) { return *(s_main_instance.get()); } +/// Access interrupt handler. Will be created on the first call +Geant4Interrupts& Geant4Kernel::interruptHandler() const { + if ( isMaster() ) + return *this->m_interrupts; + return this->m_master->interruptHandler(); +} + +/// Trigger smooth end-of-event-loop with finishing currently processing event +void Geant4Kernel::triggerStop() { + printout(INFO, "Geant4Kernel", + "+++ Stop signal seen. Will finish after current event(s) have been processed."); + printout(INFO, "Geant4Kernel", + "+++ Depending on the complexity of the simulation, this may take some time ..."); + this->m_master->m_processEvents = EVENTLOOP_HALT; +} + +/// Access flag if event loop is enabled +bool Geant4Kernel::processEvents() const { + return this->m_master->m_processEvents == EVENTLOOP_RUNNING; +} + +/// Install DDG4 default handler for a given signal. If no handler: return false +bool Geant4Kernel::registerInterruptHandler(int sig_num) { + if ( sig_num == SIGINT ) { + return interruptHandler().registerHandler_SIGINT(); + } + return false; +} + +/// (Re-)apply registered interrupt handlers to override potentially later registrations by other libraries +void Geant4Kernel::applyInterruptHandlers() { + interruptHandler().applyHandlers(); +} + /// Access thread identifier unsigned long int Geant4Kernel::thread_self() { unsigned long int thr_id = (unsigned long int)::pthread_self(); @@ -181,7 +219,7 @@ Geant4Kernel& Geant4Kernel::createWorker() { unsigned long identifier = thread_self(); Geant4Kernel* w = new Geant4Kernel(this, identifier); m_workers[identifier] = w; - printout(INFO,"Geant4Kernel","+++ Created worker instance id=%ul",identifier); + printout(INFO, "Geant4Kernel", "+++ Created worker instance id=%ul",identifier); return *w; } except("Geant4Kernel", "DDG4: Only the master instance may create workers."); @@ -243,10 +281,10 @@ void Geant4Kernel::defineSensitiveDetectorType(const std::string& type, const st } void Geant4Kernel::printProperties() const { - printout(ALWAYS,"Geant4Kernel","OutputLevel: %d", m_outputLevel); - printout(ALWAYS,"Geant4Kernel","UI: %s", m_uiName.c_str()); - printout(ALWAYS,"Geant4Kernel","NumEvents: %ld",m_numEvent); - printout(ALWAYS,"Geant4Kernel","NumThreads: %d", m_numThreads); + printout(ALWAYS,"Geant4Kernel","OutputLevel: %d", m_outputLevel); + printout(ALWAYS,"Geant4Kernel","UI: %s", m_uiName.c_str()); + printout(ALWAYS,"Geant4Kernel","NumEvents: %ld", m_numEvent); + printout(ALWAYS,"Geant4Kernel","NumThreads: %d", m_numThreads); for( const auto& [name, level] : m_clientLevels ) printout(ALWAYS,"Geant4Kernel","OutputLevel[%s]: %d", name.c_str(), level); } diff --git a/DDG4/src/Geant4UIManager.cpp b/DDG4/src/Geant4UIManager.cpp index b4bb2c3c3..8c4081bbb 100644 --- a/DDG4/src/Geant4UIManager.cpp +++ b/DDG4/src/Geant4UIManager.cpp @@ -251,7 +251,8 @@ void Geant4UIManager::start() { info("++ Start run with %d events.",numEvent); try { context()->kernel().runManager().BeamOn(numEvent); - } catch (DD4hep_End_Of_File& e) { + } + catch (DD4hep_End_Of_File& e) { info("++ End of file reached, ending run..."); context()->kernel().runManager().RunTermination(); } diff --git a/examples/ClientTests/scripts/SiliconBlock.py b/examples/ClientTests/scripts/SiliconBlock.py index 18af99420..d77c9c930 100644 --- a/examples/ClientTests/scripts/SiliconBlock.py +++ b/examples/ClientTests/scripts/SiliconBlock.py @@ -36,6 +36,7 @@ def run(): DDG4.importConstants(kernel.detectorDescription(), debug=False) geant4 = DDG4.Geant4(kernel, tracker='Geant4TrackerCombineAction') + geant4.registerInterruptHandler() geant4.printDetectors() # Configure UI if args.macro: diff --git a/examples/DDG4/CMakeLists.txt b/examples/DDG4/CMakeLists.txt index 94fdc6fde..e447f6642 100644 --- a/examples/DDG4/CMakeLists.txt +++ b/examples/DDG4/CMakeLists.txt @@ -112,4 +112,12 @@ if (DD4HEP_USE_GEANT4) REGEX_FAIL " ERROR ;EXCEPTION;Exception" ) # + # Test G4 SIGINT handler + dd4hep_add_test_reg( DDG4_SIGINT_handler + COMMAND "${CMAKE_INSTALL_PREFIX}/bin/run_test_DDG4.sh" + EXEC_ARGS ${Python_EXECUTABLE} ${DDG4examples_INSTALL}/scripts/TestSIGINT.py + REGEX_PASS "Event loop STOP signalled. Processing stops" + REGEX_FAIL " ERROR ;EXCEPTION" + ) + # endif() diff --git a/examples/DDG4/scripts/TestSIGINT.py b/examples/DDG4/scripts/TestSIGINT.py new file mode 100644 index 000000000..567c087f7 --- /dev/null +++ b/examples/DDG4/scripts/TestSIGINT.py @@ -0,0 +1,88 @@ +# ========================================================================== +# AIDA Detector description implementation +# -------------------------------------------------------------------------- +# Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN) +# All rights reserved. +# +# For the licensing terms see $DD4hepINSTALL/LICENSE. +# For the list of contributors see $DD4hepINSTALL/doc/CREDITS. +# +# ========================================================================== +# +from __future__ import absolute_import, unicode_literals +import logging +# +logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO) +logger = logging.getLogger(__name__) +# +# +""" + + dd4hep simulation example setup using the python configuration + +""" + + +def run(): + import os + import DDG4 + from DDG4 import OutputLevel as Output + from g4units import GeV, keV + + kernel = DDG4.Kernel() + install_dir = os.environ['DD4hepExamplesINSTALL'] + kernel.loadGeometry(str("file:" + install_dir + "/examples/ClientTests/compact/SiliconBlock.xml")) + + DDG4.importConstants(kernel.detectorDescription(), debug=False) + geant4 = DDG4.Geant4(kernel, tracker='Geant4TrackerCombineAction') + geant4.registerInterruptHandler() + geant4.printDetectors() + # Configure UI + geant4.setupUI(typ="tcsh", vis=False, macro=None, ui=False) + + # Configure field + geant4.setupTrackingField(prt=True) + # Configure Event actions + prt = DDG4.EventAction(kernel, 'Geant4ParticlePrint/ParticlePrint') + prt.OutputLevel = Output.DEBUG + prt.OutputType = 3 # Print both: table and tree + kernel.eventAction().adopt(prt) + + generator_output_level = Output.INFO + + # Configure G4 geometry setup + seq, act = geant4.addDetectorConstruction("Geant4DetectorGeometryConstruction/ConstructGeo") + act.DebugMaterials = True + act.DebugElements = False + act.DebugVolumes = True + act.DebugShapes = True + act.DebugSurfaces = True + + # Setup particle gun + gun = geant4.setupGun("Gun", particle='gamma', energy=1 * GeV, multiplicity=1) + gun.direction = (0.0, 0.0, 1.0) + gun.OutputLevel = generator_output_level + kernel.NumEvents = 10 + + act = DDG4.EventAction(kernel, 'TestSignalAction/SigAction', True) + act.signal_event = 3 + kernel.eventAction().add(act) + + # And handle the simulation particles. + part = DDG4.GeneratorAction(kernel, "Geant4ParticleHandler/ParticleHandler") + kernel.generatorAction().adopt(part) + part.SaveProcesses = ['conv', 'Decay'] + part.MinimalKineticEnergy = 1 * keV + part.KeepAllParticles = False + part.PrintEndTracking = True + part.enableUI() + + # Now build the physics list: + phys = geant4.setupPhysics('QGSP_BERT') + phys.dump() + # Start the engine... + geant4.execute() + + +if __name__ == "__main__": + run() diff --git a/examples/DDG4/src/TestSignalAction.cpp b/examples/DDG4/src/TestSignalAction.cpp new file mode 100644 index 000000000..29f2fafe9 --- /dev/null +++ b/examples/DDG4/src/TestSignalAction.cpp @@ -0,0 +1,58 @@ +//========================================================================== +// AIDA Detector description implementation +//-------------------------------------------------------------------------- +// Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN) +// All rights reserved. +// +// For the licensing terms see $DD4hepINSTALL/LICENSE. +// For the list of contributors see $DD4hepINSTALL/doc/CREDITS. +// +// Author : M.Frank +// +//========================================================================== + +// Framework include files +#include "DDG4/Geant4EventAction.h" + +#include +#include + +/// Namespace for the AIDA detector description toolkit +namespace dd4hep { + + /// Namespace for the Geant4 based simulation part of the AIDA detector description toolkit + namespace sim { + + /// Class to print message for debugging + /** Class to print message for debugging + * + * \author M.Frank + * \version 1.0 + * \ingroup DD4HEP_SIMULATION + */ + class TestSignalAction : public Geant4EventAction { + long num_calls { 0 }; + int signal_event { 10000 }; + + public: + /// Standard constructor + TestSignalAction(Geant4Context* context, const std::string& nam) + : Geant4EventAction(context, nam) + { + declareProperty("signal_event", signal_event); + } + /// Default destructor + virtual ~TestSignalAction() = default; + /// Begin-of-event callback + virtual void begin(const G4Event* /* event */) { + if ( ++num_calls == signal_event ) { + always("Sending interrupt signal to self at call %d", ++num_calls); + ::kill(::getpid(), SIGINT); + } + } + }; + } // End namespace sim +} // End namespace dd4hep + +#include "DDG4/Factories.h" +DECLARE_GEANT4ACTION_NS(dd4hep::sim,TestSignalAction) From 9f258ced6c433c8c21469e0f2bff51dc2e7a0f7e Mon Sep 17 00:00:00 2001 From: Markus Frank Date: Thu, 17 Oct 2024 18:01:34 +0200 Subject: [PATCH 2/3] Add implementation for signal handling. Use generic signal handler in DDG4 for controlled shutdown on CTRL-C. Add test DDG4_SIGINT_handler in examples/DDG4 --- examples/DDG4/scripts/TestSIGINT.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/DDG4/scripts/TestSIGINT.py b/examples/DDG4/scripts/TestSIGINT.py index 567c087f7..ab8d08b11 100644 --- a/examples/DDG4/scripts/TestSIGINT.py +++ b/examples/DDG4/scripts/TestSIGINT.py @@ -67,7 +67,7 @@ def run(): act = DDG4.EventAction(kernel, 'TestSignalAction/SigAction', True) act.signal_event = 3 kernel.eventAction().add(act) - + # And handle the simulation particles. part = DDG4.GeneratorAction(kernel, "Geant4ParticleHandler/ParticleHandler") kernel.generatorAction().adopt(part) From d723013a384652a67d13cd2987f618ad2f3f1428 Mon Sep 17 00:00:00 2001 From: Andre Sailer Date: Fri, 18 Oct 2024 16:41:48 +0200 Subject: [PATCH 3/3] SignalHandling: fix some typos --- DDG4/include/DDG4/Geant4Kernel.h | 4 ++-- DDG4/python/DDG4.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DDG4/include/DDG4/Geant4Kernel.h b/DDG4/include/DDG4/Geant4Kernel.h index b956f9b57..2738303e2 100644 --- a/DDG4/include/DDG4/Geant4Kernel.h +++ b/DDG4/include/DDG4/Geant4Kernel.h @@ -39,14 +39,14 @@ namespace dd4hep { class Geant4Interrupts; class Geant4ActionPhase; - /// Helper class to indicate the of file + /// Helper class to indicate the end of the input file class DD4hep_End_Of_File : public std::exception { public: DD4hep_End_Of_File() : std::exception() {} virtual const char* what() const noexcept { return "Reached end of input file"; } }; - /// Helper class to indicate the of file + /// Helper class to indicate the stop of processing class DD4hep_Stop_Processing : public std::exception { public: DD4hep_Stop_Processing() : std::exception() {} diff --git a/DDG4/python/DDG4.py b/DDG4/python/DDG4.py index 8c1c861d5..bbcf53ccc 100644 --- a/DDG4/python/DDG4.py +++ b/DDG4/python/DDG4.py @@ -453,7 +453,7 @@ def registerInterruptHandler(self, signum=signal.SIGINT): Enable interrupt handling: smooth handling of CTRL-C - Finish processing of the current event(s) - Drain the event loop - - Properly finalyze the job + - Properly finalize the job \author M.Frank """