diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index 6f71799..bf7dc14 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -17,7 +17,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-24.04, ubuntu-22.04, macos-15, macos-14, macos-13] + os: [ubuntu-24.04, ubuntu-22.04, macos-15-intel, macos-15, macos-14] steps: diff --git a/CMakeLists.txt b/CMakeLists.txt index d794b09..a4be0d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,7 +85,7 @@ set( SPDLOG_BUILD_SHARED TRUE ) set( SPDLOG_INSTALL TRUE ) FetchContent_Declare( spdlog GIT_REPOSITORY https://github.com/project8/spdlog.git - GIT_TAG v1.x_p8 + GIT_TAG v1.x_p8_r1 ) FetchContent_MakeAvailable( spdlog ) # if Scarab is being built as a submodule, the parent might need to know where to find spdlog diff --git a/VERSION b/VERSION index d82b118..bf7f15f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3 13 4 +3 13 5 diff --git a/changelog.md b/changelog.md index bdbcbc8..0d2382b 100644 --- a/changelog.md +++ b/changelog.md @@ -9,6 +9,22 @@ Types of changes: Added, Changed, Deprecated, Removed, Fixed, Security ## [Unreleased] +## [3.13.5] - 2025-12-22 + +### Changed + +- Signal handling now requires a dedicated thread +- Updated GHA runners +- Removed local loggers + +### Fixed + +- Tied spdlog to a tag and not a branch +- Updated spdlog fork from upstream +- signal_handler signal handling functions use only approved function calls +- Check if spdlog thread pool exists before making a new one + + ## [3.13.4] - 2025-11-04 ### Fixed diff --git a/library/logger/logger.cc b/library/logger/logger.cc index ff64a21..2bafa05 100644 --- a/library/logger/logger.cc +++ b/library/logger/logger.cc @@ -37,7 +37,12 @@ namespace scarab spd_initializer( a_pattern ), f_sink() { - spdlog::init_thread_pool(8192, 1); + // We check before creating the new thread pool because we've had problems in some situations (e.g. when started from Python) + // with a new thread pool being created and the old one being killed, resulting in strange errors during execution + if( ! spdlog::thread_pool() ) + { + spdlog::init_thread_pool(8192, 1); + } f_sink = std::make_shared< spdlog::sinks::stdout_color_sink_mt >(); f_sink->set_pattern( f_pattern ); auto at_exit_fcn = [](){ logger::stop_using_spd_async(); }; @@ -201,7 +206,7 @@ namespace scarab void logger::reset_using_spd_async() { #ifdef SCARAB_LOGGER_DEBUG - std::cerr << "Resetting use of spd async" << std::endl; + std::cerr << "[logger::reset_using_spd_async()] Resetting use of spd async" << std::endl; #endif logger::using_spd_async().store(true); std::set< logger* >& t_all_loggers = logger::all_loggers(); diff --git a/library/logger/logger.hh b/library/logger/logger.hh index d92f171..cd68d2d 100644 --- a/library/logger/logger.hh +++ b/library/logger/logger.hh @@ -188,7 +188,7 @@ namespace scarab f_initializer_ptr( nullptr ) { #ifdef SCARAB_LOGGER_DEBUG - std::cout << "Logger <" << a_name << "> was initialized from <" << a_file << ":" << a_line << ">; type: " << ::scarab::type( *this ) << std::endl; + std::cerr << "[logger_type constructor] Logger <" << a_name << "> was initialized from <" << a_file << ":" << a_line << ">; type: " << ::scarab::type( *this ) << std::endl; #endif // Start the backend, but only once static initializer_x s_init; @@ -223,13 +223,13 @@ namespace scarab /// Creates a local scarab::logger object with variable name a_logger /// Uses spdlog's asynchronous logger; spdlog logger name will be a_name. -#define LOCAL_LOGGER( a_logger, a_name ) \ - ::scarab::logger_type< ::scarab::spd_initializer_async_stdout_color_mt > a_logger( a_name, __FILE_NAME__, __LINE__ ); +//#define LOCAL_LOGGER( a_logger, a_name ) \ +// ::scarab::logger_type< ::scarab::spd_initializer_async_stdout_color_mt > a_logger( a_name, __FILE_NAME__, __LINE__ ); /// Creates a local single-threaded (non-asynchronous) scarab::logger object with variable name a_logger /// Uses spdlog's basic logger; spdlog logger name will be a_name. -#define LOCAL_LOGGER_ST( a_logger, a_name ) \ - ::scarab::logger_type< ::scarab::spd_initializer_stdout_color > a_logger( a_name, __FILE_NAME__, __LINE__ ); +//#define LOCAL_LOGGER_ST( a_logger, a_name ) \ +// ::scarab::logger_type< ::scarab::spd_initializer_stdout_color > a_logger( a_name, __FILE_NAME__, __LINE__ ); // Logging functions #ifdef NDEBUG diff --git a/library/utility/indexed_factory.hh b/library/utility/indexed_factory.hh index a936f70..5fd3a56 100644 --- a/library/utility/indexed_factory.hh +++ b/library/utility/indexed_factory.hh @@ -212,7 +212,7 @@ namespace scarab void indexed_factory< XIndexType, XBaseType, XArgs... >::register_class( const XIndexType& a_index, const base_registrar< XBaseType, XArgs... >* a_registrar ) { // A local (non-static) logger is created inside this function to avoid static initialization order problems - LOCAL_LOGGER( slog_ind_factory_reg, "indexed_factory_register"); + LOGGER( slog_ind_factory_reg, "indexed_factory_register"); std::unique_lock< std::mutex > t_lock( this->f_factory_mutex ); FactoryCIt it = fMap->find(a_index); @@ -235,7 +235,7 @@ namespace scarab void indexed_factory< XIndexType, XBaseType, XArgs... >::remove_class(const XIndexType& a_index ) { // A local (non-static) logger is created inside this function to avoid static destruction problems - LOCAL_LOGGER( slog_ind_factory_rem, "indexed_factory_remove"); + LOGGER( slog_ind_factory_rem, "indexed_factory_remove"); LTRACE( slog_ind_factory_rem, "Removing indexed_factory for class " << a_index << " from " << this ); /* #ifndef NDEBUG @@ -353,7 +353,7 @@ namespace scarab void indexed_factory< XIndexType, XBaseType, void >::register_class( const XIndexType& a_index, const base_registrar< XBaseType >* a_registrar ) { // A local (non-static) logger is created inside this function to avoid static initialization order problems - LOCAL_LOGGER( slog_ind_factory_reg, "indexed_factory_register"); + LOGGER( slog_ind_factory_reg, "indexed_factory_register"); std::unique_lock< std::mutex > t_lock( this->f_factory_mutex ); FactoryCIt it = fMap->find(a_index); @@ -377,7 +377,7 @@ namespace scarab void indexed_factory< XIndexType, XBaseType, void >::remove_class(const XIndexType& a_index ) { // A local (non-static) logger is created inside this function to avoid static destruction problems - LOCAL_LOGGER( slog_ind_factory_rem, "indexed_factory_remove"); + LOGGER( slog_ind_factory_rem, "indexed_factory_remove"); LTRACE( slog_ind_factory_rem, "Removing indexed_factory for class " << a_index << " from " << this ); //#ifndef NDEBUG // if( ELevel::eTrace >= f_global_threshold ) diff --git a/library/utility/signal_handler.cc b/library/utility/signal_handler.cc index 5ccb7af..6ba6ea8 100644 --- a/library/utility/signal_handler.cc +++ b/library/utility/signal_handler.cc @@ -15,13 +15,8 @@ #include #include -#include - -#ifdef _WIN32 -#include -#include -//#include "processthreadsapi.h" -#endif +#include +#include namespace { // function to catch unhandled exceptions @@ -39,37 +34,41 @@ namespace scarab bool signal_handler::s_exited = false; int signal_handler::s_return_code = RETURN_SUCCESS; - bool signal_handler::s_handling_sig_abrt = false; - bool signal_handler::s_handling_sig_term = false; - bool signal_handler::s_handling_sig_int = false; - bool signal_handler::s_handling_sig_quit = false; - -#ifndef _WIN32 - struct sigaction signal_handler::s_old_sig_abrt_action; - struct sigaction signal_handler::s_old_sig_term_action; - struct sigaction signal_handler::s_old_sig_int_action; - struct sigaction signal_handler::s_old_sig_quit_action; -#else // _WIN32 - signal_handler::handler_t signal_handler::s_old_sig_abrt_handler = SIG_DFL; - signal_handler::handler_t signal_handler::s_old_sig_term_handler = SIG_DFL; - signal_handler::handler_t signal_handler::s_old_sig_int_handler = SIG_DFL; - signal_handler::handler_t signal_handler::s_old_sig_quit_handler = SIG_DFL; -#endif - - std::recursive_mutex signal_handler::s_mutex; + std::mutex signal_handler::s_cancelers_mutex; signal_handler::cancelers signal_handler::s_cancelers; - signal_handler::signal_handler() + bool signal_handler::s_is_handling = false; + + std::thread signal_handler::s_waiting_thread; + + std::mutex signal_handler::s_handle_mutex; + + signal_handler::signal_map_t signal_handler::s_handled_signals = { + // handled_signal_info args: name, is handling, old action, indicates error, return code + {SIGABRT, signal_handler::handled_signal_info{"SIGABRT", false, {}, true, RETURN_ERROR}}, + {SIGTERM, signal_handler::handled_signal_info{"SIGTERM", false, {}, false, RETURN_SUCCESS}}, + {SIGINT, signal_handler::handled_signal_info{"SIGINT", false, {}, false, RETURN_SUCCESS}}, + {SIGQUIT, signal_handler::handled_signal_info{"SIGQUIT", false, {}, false, RETURN_SUCCESS}} + }; + + volatile sig_atomic_t signal_handler::s_signal_received = 0; + + signal_handler::signal_handler( bool start_waiting_thread ) { - //std::cerr << "signal_handler constructor" << std::endl; ++signal_handler::s_ref_count; + if( signal_handler::s_ref_count == 1 ) + { + signal_handler::start_handling_signals(); + } + if( start_waiting_thread && ! signal_handler::s_waiting_thread.joinable() ) + { + signal_handler::start_waiting_thread(); + } - signal_handler::handle_signals(); } signal_handler::~signal_handler() { - //std::cerr << "signal_handler destructor" << std::endl; --signal_handler::s_ref_count; if( signal_handler::s_ref_count == 0 ) { @@ -79,28 +78,28 @@ namespace scarab void signal_handler::add_cancelable( std::shared_ptr< scarab::cancelable > a_cancelable ) { - std::unique_lock< std::recursive_mutex > t_lock( s_mutex ); + std::unique_lock< std::mutex > t_lock( s_cancelers_mutex ); s_cancelers.insert( std::make_pair(a_cancelable.get(), cancelable_wptr_t(a_cancelable) ) ); return; } void signal_handler::remove_cancelable( std::shared_ptr< scarab::cancelable > a_cancelable ) { - std::unique_lock< std::recursive_mutex > t_lock( s_mutex ); + std::unique_lock< std::mutex > t_lock( s_cancelers_mutex ); s_cancelers.erase( a_cancelable.get() ); return; } void signal_handler::remove_cancelable( scarab::cancelable* a_cancelable ) { - std::unique_lock< std::recursive_mutex > t_lock( s_mutex ); + std::unique_lock< std::mutex > t_lock( s_cancelers_mutex ); s_cancelers.erase( a_cancelable ); return; } void signal_handler::print_cancelables() { - std::unique_lock< std::recursive_mutex > t_lock( s_mutex ); + std::unique_lock< std::mutex > t_lock( s_cancelers_mutex ); LOGGER( slog_print, "signal_handler print_cancelables" ); for( const auto& [t_key, t_value] : s_cancelers ) { @@ -109,245 +108,164 @@ namespace scarab return; } - void signal_handler::handle_signals() + void signal_handler::start_handling_signals() { - std::unique_lock< std::recursive_mutex > t_lock( s_mutex ); + if( signal_handler::get_is_handling() || signal_handler::is_waiting() ) return; - // we create a new logger here so that handle_signals() can be called from the signal_handler constructor during static initalization - LOGGER( slog_constr, "signal_handler handle_signals" ); + std::unique_lock< std::mutex > t_lock( s_handle_mutex ); + LOGGER( slog_constr, "signal_handler::start_handling_signals" ); LDEBUG( slog_constr, "Taking over signal handling for SIGABRT, SIGTERM, SIGINT, and SIGQUIT" ); -#ifndef _WIN32 // on a POSIX system we use sigaction - - struct sigaction t_exit_error_action, t_exit_success_action; - - t_exit_error_action.sa_handler = signal_handler::handle_exit_error; - sigemptyset(&t_exit_error_action.sa_mask); - t_exit_error_action.sa_flags = 0; + struct sigaction t_action; + t_action.sa_handler = signal_handler::handle_signal; + sigemptyset(&t_action.sa_mask); + t_action.sa_flags = 0; - t_exit_success_action.sa_handler = signal_handler::handle_exit_success; - sigemptyset(&t_exit_success_action.sa_mask); - t_exit_success_action.sa_flags = 0; - - // setup to handle SIGABRT - if( ! s_handling_sig_abrt && sigaction( SIGABRT, &t_exit_error_action, &s_old_sig_abrt_action ) != 0 ) - { - LWARN( slog_constr, "Unable to setup handling of SIGABRT: abort() and unhandled exceptions will result in an unclean exit" ); - } - else + for( auto it = signal_handler::s_handled_signals.begin(); it != signal_handler::s_handled_signals.end(); ++it ) { - LTRACE( slog_constr, "Handling SIGABRT (abort() and unhandled exceptions)" ); - s_handling_sig_abrt = true; + if( ! it->second.handling && sigaction( it->first, &t_action, &it->second.old_action ) != 0 ) + { + LWARN( slog_constr, "Unable to setup handling of " << it->second.name ); + } + else + { + LTRACE( slog_constr, "Handling " << it->second.name ); + it->second.handling = true; + } } - // setup to handle SIGTERM - if( ! s_handling_sig_term && sigaction( SIGTERM, &t_exit_error_action, &s_old_sig_term_action ) != 0 ) - { - LWARN( slog_constr, "Unable to setup handling of SIGTERM: SIGTERM will result in an unclean exit" ); - } - else - { - LTRACE( slog_constr, "Handling SIGTERM" ); - s_handling_sig_term = true; - } + signal_handler::s_is_handling = true; - // setup to handle SIGINT - if( ! s_handling_sig_int && sigaction( SIGINT, &t_exit_success_action, &s_old_sig_int_action ) != 0 ) - { - LWARN( slog_constr, "Unable to setup handling of SIGINT: ctrl-c cancellation will result in an unclean exit" ); - } - else - { - LTRACE( slog_constr, "Handling SIGINT (ctrl-c)" ); - s_handling_sig_int = true; - } + return; + } - // setup to handle SIGQUIT - if( ! s_handling_sig_quit && sigaction( SIGQUIT, &t_exit_success_action, &s_old_sig_quit_action ) != 0 ) - { - LWARN( slog_constr, "Unable to setup handling of SIGQUIT: ctrl-\\ cancellation will result in an unclean exit" ); - } - else - { - LTRACE( slog_constr, "Handling SIGQUIT (ctrl-\\)" ); - s_handling_sig_quit = true; - } + bool signal_handler::is_handling() + { + return signal_handler::get_is_handling(); + } - if( signal(SIGPIPE, SIG_IGN) == SIG_ERR ) - { - throw error() << "Unable to ignore SIGPIPE\n"; - } + void signal_handler::unhandle_signals() + { + if( ! signal_handler::get_is_handling() ) return; -#else // _WIN32; on a Windows system we use std::signal + if( signal_handler::is_waiting() ) signal_handler::abort_wait(); + + std::unique_lock< std::mutex > t_lock( s_handle_mutex ); + + LDEBUG( slog, "Returning signal handling for SIGABRT, SIGTERM, SIGINT, and SIGQUIT" ); - // setup to handle SIGABRT - if( ! s_handling_sig_abrt ) + for( auto it = signal_handler::s_handled_signals.begin(); it != signal_handler::s_handled_signals.end(); ++it ) { - auto t_sig_ret = signal( SIGABRT, signal_handler::handle_exit_error ); - if( t_sig_ret == SIG_ERR ) + if( it->second.handling && sigaction( it->first, &it->second.old_action, nullptr ) != 0 ) { - LWARN( slog_constr, "Unable to setup handling of SIGABRT: abort() and unhandled exceptions will result in an unclean exit" ); + LWARN( slog, "Unable to switch " << it->second.name << " to previous handler" ); } else { - s_handling_sig_abrt = true; - s_old_sig_abrt_handler = t_sig_ret; + LTRACE( slog, "Stopped handling " << it->second.name ); + it->second.handling = false; } } - if( s_handling_sig_abrt ) LTRACE( slog_constr, "Handling SIGABRT (abort() and unhandled exceptions)" ); + signal_handler::s_is_handling = false; - // setup to handle SIGTERM - if( ! s_handling_sig_term ) + return; + } + + int signal_handler::wait_for_signals( bool call_exit ) + { + // Try to lock the mutex; if it didn't lock, then return + std::unique_lock< std::mutex > t_lock( s_handle_mutex, std::defer_lock_t() ); + if( ! t_lock.try_lock() ) return -1; + LTRACE( slog, "Started wait_for_signals" ); + + while( signal_handler::s_signal_received == 0 ) { - auto t_sig_ret = signal( SIGTERM, signal_handler::handle_exit_error ); - if( t_sig_ret == SIG_ERR ) - { - LWARN( slog_constr, "Unable to setup handling of SIGTERM: SIGTERM will result in an unclean exit" ); - } - else - { - s_handling_sig_term = true; - s_old_sig_abrt_handler = t_sig_ret; - } + std::this_thread::sleep_for( std::chrono::milliseconds(500)) ; } - if( s_handling_sig_term ) LTRACE( slog_constr, "Handling SIGTERM" ); - // setup to handle SIGINT - if( ! s_handling_sig_int ) + LTRACE( slog, "past wait-for-signals loop" ); + + // Handle situation where a signal was recieved + if( s_signal_received > 0 ) { - auto t_sig_ret = signal( SIGINT, signal_handler::handle_exit_error ); - if( t_sig_ret == SIG_ERR ) + // this is a valid signal + LDEBUG( slog, "Exiting loop for signals after getting signal " << s_signal_received ); + if( signal_handler::s_handled_signals.count( int(signal_handler::s_signal_received) ) > 0 ) { - LWARN( slog_constr, "Unable to setup handling of SIGINT: ctrl-c cancellation will result in an unclean exit" ); + handled_signal_info& sig_info = signal_handler::s_handled_signals.at( int(signal_handler::s_signal_received) ); + if( sig_info.indicates_error ) + { + LERROR( slog, "Received signal " << sig_info.name << " <" << s_signal_received << "> as an error condition; return code: " << sig_info.return_code ); + } + else + { + LDEBUG( slog, "Received signal " << sig_info.name << " <" << s_signal_received << "> as an exit condition; return code: " << sig_info.return_code ); + } + + // Call exit to cancel cancelables if requested + if( call_exit ) exit( sig_info.return_code ); } else { - s_handling_sig_int = true; - s_old_sig_abrt_handler = t_sig_ret; + LERROR( slog, "Received unknown signal <" << s_signal_received << ">; using with return code " << RETURN_ERROR ); + if( call_exit ) exit( RETURN_ERROR ); } + + return s_signal_received; } - if( s_handling_sig_int ) LTRACE( slog_constr, "Handling SIGINT (ctrl-c))" ); -#endif - return; + // Handle the situation where no signal was received; wait loop was aborted + // s_signal_received < 0 --> requested to exit wait for signals + LDEBUG( slog, "Exited loop waiting for signals (wait aborted)" ); + return s_signal_received; + } - void signal_handler::unhandle_signals() + void signal_handler::abort_wait() { - std::unique_lock< std::recursive_mutex > t_lock( s_mutex ); - - LDEBUG( slog, "Returning signal handling for SIGABRT, SIGTERM, SIGINT, and SIGQUIT" ); - -#ifndef _WIN32 - - if( s_handling_sig_abrt && sigaction( SIGABRT, &s_old_sig_abrt_action, nullptr ) != 0 ) - { - LWARN( slog, "Unable to switch SIGABRT to previous handler" ); - } - else - { - s_handling_sig_abrt = false; - } - - if( s_handling_sig_term && sigaction( SIGTERM, &s_old_sig_term_action, nullptr ) != 0 ) - { - LWARN( slog, "Unable to switch SIGTERM to previous handler" ); - } - else - { - s_handling_sig_term = false; - } - - if( s_handling_sig_int && sigaction( SIGINT, &s_old_sig_int_action, nullptr ) != 0 ) - { - LWARN( slog, "Unable to switch SIGINT to previous handler" ); - } - else - { - s_handling_sig_int = false; - } - - if( s_handling_sig_quit && sigaction( SIGQUIT, &s_old_sig_quit_action, nullptr ) != 0 ) - { - LWARN( slog, "Unable to switch SIGQUIT to previous handler" ); - } - else - { - s_handling_sig_quit = false; - } + LDEBUG( slog, "Requesting stop of signal wait" ); + signal_handler::s_signal_received = -1; + return; + } -#else // _WIN32 + bool signal_handler::is_waiting() + { + std::unique_lock< std::mutex > t_lock( s_handle_mutex, std::defer_lock_t() ); + return ! t_lock.try_lock(); + } - if( s_handling_sig_abrt && signal( SIGABRT, s_old_sig_abrt_handler ) == SIG_ERR ) - { - LWARN( slog, "Unable to switch SIGABRT to previous handler" ); - } - else - { - s_handling_sig_abrt = false; - } - - if( s_handling_sig_term && signal( SIGTERM, s_old_sig_term_handler ) == SIG_ERR ) - { - LWARN( slog, "Unable to switch SIGTERM to previous handler" ); - } - else - { - s_handling_sig_term = false; - } - - if( s_handling_sig_int && signal( SIGINT, s_old_sig_int_handler ) == SIG_ERR ) - { - LWARN( slog, "Unable to switch SIGINT to previous handler" ); - } - else - { - s_handling_sig_int = false; - } - -#endif + void signal_handler::start_waiting_thread() + { + if( signal_handler::s_waiting_thread.joinable() ) return; + signal_handler::s_waiting_thread = std::thread( [](){scarab::signal_handler::wait_for_signals();} ); return; } - bool signal_handler::is_handling( int a_signal ) + void signal_handler::join_waiting_thread() { - std::unique_lock< std::recursive_mutex > t_lock( s_mutex ); + s_waiting_thread.join(); + s_waiting_thread = std::thread(); + return; + } - bool t_is_handled = false; - switch( a_signal ) - { - case SIGABRT: - if( s_handling_sig_abrt) t_is_handled = true; - break; - case SIGTERM: - if( s_handling_sig_term) t_is_handled = true; - break; - case SIGINT: - if( s_handling_sig_int) t_is_handled = true; - break; -#ifndef _WIN32 - case SIGQUIT: - if( s_handling_sig_quit) t_is_handled = true; - break; -#endif - default: - break; - } - return t_is_handled; + bool signal_handler::waiting_thread_joinable() + { + return signal_handler::s_waiting_thread.joinable(); } void signal_handler::reset() { LDEBUG( slog, "Resetting signal_handler" ); - std::unique_lock< std::recursive_mutex > t_lock( s_mutex ); + signal_handler::unhandle_signals(); // calls abort_wait() if waiting + + s_waiting_thread = std::thread(); s_exited = false; s_return_code = RETURN_SUCCESS; s_cancelers.clear(); - signal_handler::unhandle_signals(); return; } @@ -356,44 +274,36 @@ namespace scarab terminate( RETURN_ERROR ); } - void signal_handler::handle_exit_error( int a_sig ) + void signal_handler::handle_signal( int a_sig ) { - std::unique_lock< std::recursive_mutex > t_lock( s_mutex ); - LERROR( slog, "Handling signal <" << a_sig << "> as an error condition; return code: " << RETURN_ERROR ); - exit( RETURN_ERROR ); - return; - } + signal_handler::s_signal_received = a_sig; + + const char str[] = "[signal_handler::handle_signal()] Received a signal\n"; + write( STDERR_FILENO, str, sizeof(str)-1 ); - void signal_handler::handle_exit_success( int a_sig ) - { - std::unique_lock< std::recursive_mutex > t_lock( s_mutex ); - LPROG( slog, "Handling signal <" << a_sig << ">; return code: " << RETURN_SUCCESS ); - exit( RETURN_SUCCESS ); return; } [[noreturn]] void signal_handler::terminate( int a_code ) noexcept { - std::unique_lock< std::recursive_mutex > t_lock( s_mutex ); print_current_exception( false ); if( a_code > 0 ) { print_stack_trace( false ); } - std::cerr << "Exiting abruptly" << std::endl; std::_Exit( a_code ); } void signal_handler::exit( int a_code ) { - std::unique_lock< std::recursive_mutex > t_lock( s_mutex ); s_exited = true; s_return_code = a_code; print_current_exception( true ); - if( a_code > 0 ) - { - print_stack_trace( true ); - } + // Removed stack trace printing because it's just the trace at the point exit() is called, which is often the wait_for_signals(), which isn't very useful + //if( a_code > 0 ) + //{ + // print_stack_trace( true ); + //} cancel_all( a_code ); return; } @@ -443,7 +353,6 @@ namespace scarab void signal_handler::print_stack_trace( bool a_use_logging ) { // no mutex locking needed here -#ifndef _WIN32 // stack trace printing not implemented for windows void* t_bt_array[50]; int t_size = backtrace( t_bt_array, 50 ); @@ -461,15 +370,17 @@ namespace scarab else { std::cerr << "Backtrace:\n" << t_bt_str.str() << std::endl; } free( t_messages ); -#endif return; } void signal_handler::cancel_all( int a_code ) { - std::unique_lock< std::recursive_mutex > t_lock( s_mutex ); + std::unique_lock< std::mutex > t_lock( s_cancelers_mutex ); LDEBUG( slog, "Canceling all cancelables" ); + // Cause the waiting loop to abort if it's running + if( signal_handler::s_signal_received == 0 ) signal_handler::s_signal_received = -1; + while( ! s_cancelers.empty() ) { auto t_canceler_it = s_cancelers.begin(); @@ -481,10 +392,6 @@ namespace scarab std::this_thread::sleep_for( std::chrono::seconds(1) ); } -#ifdef _WIN32 - ExitProcess( a_code ); -#endif - return; } diff --git a/library/utility/signal_handler.hh b/library/utility/signal_handler.hh index dbbd571..1fae750 100644 --- a/library/utility/signal_handler.hh +++ b/library/utility/signal_handler.hh @@ -14,10 +14,9 @@ #include #include #include - -#ifndef _WIN32 -#include // for struct sigaction, which is in signal.h but not csignal -#endif +#include +#include +#include #ifndef _GNU_SOURCE #define _GNU_SOURCE @@ -69,30 +68,80 @@ namespace scarab Responsibility for canceling a cancelable is given to the signal_handler using add_cancelable(), and it's taken away using remove_cancelable(). + Note that the interface for `signal_handler` uses all static functions, so there is no need to create an extra instance of + the class to use any part of that interface. So the creation and destruction of instances can be used to control + whether or not signals are handled, and the rest of the interface can be used without an instance. + # Signal Handling - To perform the tasks of exiting and terminating applications, `signal_handler` allows the user to control when signals - are handled, and when they aren't. In most cases, a user will control signal handling through the existence of - a `signal_handler` instance. The user should create the instance of `signal_handler` when they want `signal_handler` - to start handling those signals, and it should be destructed when handling the signals should cease. - Here are several examples of how this could be done: + The process of signal handling has several steps, some of which are automated: + + 1. Create the signal handler + 2. Start handling signals --- the default function that gets called when a signal is raised is replaced with signal_handler's function + 3. Start the thread waiting for signals + 4. Run the application, including the possibility of raising a signal + 5. Join the thread waiting for signals + 6. Stop handling signals -- the default signal-handling function is put back in place + 7. Signal handler is destructed + + In practice, the user has several options for performing these steps in application code. + The recommended method is the fully automatic method described here. + For other options on thread management, see below. + + 1. Create the signal handler with the default argument for `start_waiting_thread` (true) + * Creates the signal handler + * Starts handling signals + * Runs the signal-waiting thread + 2. Run the application + 3. Join the signal-waiting thread with `signal_handler::join_waiting_thread()` + 4. Let signal handler be destructed automatically + + ## Controling the scope of signal handling and which signals are handled + + Here are several examples of how the lifetime of the signal_handler can be managed: 1. The instance is created as a global static object at static initialization time. Signals will be handled for the entire - time the application is running. + time the application is running. 2. The instance is created in the `main()` function of the application and exists until the application exits. 3. The instance is created in some function for a specific limited time to cover a particular action. This could be for the entire scope of the function, or it could be destructed when signal handling is no longer wanted. Note that signals are handled as long as one or more instances of `signal_handler` exist. If two instance exist, and one - goes out of scope, signals are still handled until the second instance is destructed. + goes out of scope, signals are still handled until the second instance is destructed. Signals are only reacted to + while the watcher function/thread is active. If there is a delay between when the watching function/thread exits and when + the signal_handler is destructed (or unhandle_signals() is called), during that period signals will not cause + the application to exit. + + By default the signals handled are SIGABRT (abort() and unhandled exceptions), SIGTERM (shell command kill), + SIGINT (ctrl-c), and SIGQUIT (ctrl-\). + For customized signal handling, the set of signals can be edited by modifying the handled signals map + (via signal_handler::handled_signals()). In addition to this typical behavior, `signal_handler` includes an interface for more fine-grained control over when signals are handled while the `signal_handler` exists (i.e. handle_signals(), unhandle_signals(), and is_handling()), but the recommended use case is as described above, where signal handling is controled by the existence of a `signal_handler` instance. - Note that the interface for `signal_handler` uses all static functions, so there is no need to create an extra instance of - the class to use any part of that interface. So the creation and destruction of instances can be used to control - whether or not signals are handled, and the rest of the interface can be used without an instance. + ## Waiting for signals + + The process of waiting for signals requires a separate thread, and that thread can be managed in three ways: + + 1. Automatically -- The thread is started when the first `signal_handler` is constructed. + The thread should still be joined with `signal_handler::join_waiting_thread()` + at the appropriate time (after all application functionality has started). + This option is the default, but can be avoided by setting the `start_waiting_thread` argument to the + `signal_handler` constructor to false. + 2. Manually with internal thread management -- Create the `signal_handler` with `start_waiting_thread` set to false, + and then use `signal_handler::start_waiting_thread()` to manually start the thread. + Use `signal_handler::join_waiting_thread()` to join the thread at the appropriate time. + 3. Manually with user thread management -- Create the `signal_handler` with `start_waiting_thread` set to false, + and then start your own thread to run `signal_handler::wait_for_signals()`, which is a blocking call to wait + for the arrival of signals. + + The waiting for signals is cancelled with `signal_handler::abort_wait()` regardless of hwo the waiting thread is managed. + + ## Examples + + For simple examples of creating a signal_handler and running a watcher thread, see the test_raise_sig*.cc files. # Cancelables @@ -110,11 +159,29 @@ namespace scarab terminate(), exit(), or cancel_all(), respectively. cancel_all() will only affect the canceled objects. exit() perform a clean exit using cancel_all(), in addition to setting a return code, though it assumes the application will take care of actually exiting. And terminate() will immediately exit the application using std::_Exit(). + + # Design details for signal handling + + We need a way to turn a raised signal into a call to cancel_all() (via exit()). However, a POSIX signal handler + is only allowed to call a limited class of functions that are async-signal-safe (see https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html#tag_16_04). + So we need an asynchronous method for getting the information about the raised signal to exit() instead. + We are allowed to set variables of type volatile sig_atomic_t (an integer), so we can use that as an indicator + that exit() needs to be called. We can call exit() in a function that runs in a separate thread and watches the value of the + volatile sig_atomic_t indicator. So our functionality has two parts: + + 1. "handling" signals: we replace the default handler functions for the four signals of interest + (SIGABRT, SIGTERM, SIGINT, and SIGQUIT). It prints a simple message to the error stream (write() is allowed) and + sets the value of s_signal_received, a volatile sig_atomic_t static variable. + 2. "watching" for signals: The function watch_for_signals() starts a loop that waits for s_signal_received to be nonzero. + It then breaks out of the loop and calls exit() with the appropriate return value (depending on the specific signal raised). + The watch loop can be broken out of manually by calling abort_wait(), which sets s_signal_received to a negative number. + All signals are positive integers in the POSIX standard, so the negative number differentiates an abort (exit() is not called) + from a handled signal (exit() is called). */ class SCARAB_API signal_handler { public: - signal_handler(); + signal_handler( bool start_waiting_thread = true ); signal_handler( const signal_handler& ) = delete; signal_handler( signal_handler&& ) = delete; virtual ~signal_handler(); @@ -134,21 +201,36 @@ namespace scarab /// Remove all cancelables and signal handling static void reset(); - /// Start handling signals - static void handle_signals(); - /// Stop handling signals + /// Fine-grained signal-handling control; called from the constructor + /// Uses handle_signal() as the signal-handling function for the relevant signals. + static void start_handling_signals(); + /// Fine-grained signal-handling control; called from the destructor + /// Returns the previous signal-handling functions to the signals. static void unhandle_signals(); + static bool is_handling(); // just calls get_is_handling(); is here to have parallel syntax to waiting + + /// Blocking call to handle signals; waits for a signal + /// Returns immediately if it's already been called; otherwise blocks then returns the signal or error value + /// Has an option to not call the exit() function; defaults to true (i.e. exit() will be called) + static int wait_for_signals( bool call_exit = true ); + //static int wait_on_signals(); + /// Requests the aborting of waiting for a signal; causes do_handle_signal() to return + static void abort_wait(); /// Check if a signal is handled - static bool is_handling( int a_signal ); + static bool is_waiting(); + + /// Starts the thread waiting on signals (internally using wait_for_signals() and an internal thread storage) + static void start_waiting_thread(); + /// Joins the internal wait-for-signals thread + static void join_waiting_thread(); + /// Checks if the internal thread is in use + static bool waiting_thread_joinable(); /// Handler for std::terminate -- does not cleanup memory or threads [[noreturn]] static void handle_terminate() noexcept; - /// Handler for error signals -- cleanly exits - static void handle_exit_error( int a_sig ); - - /// Handler for success signals -- cleanly exits - static void handle_exit_success( int a_sig ); + /// Handler for the relevant signals (SIGABRT, SIGTERM, SIGINT, and SIGQUIT by default) + static void handle_signal( int a_sig ); /// Main terminate function -- does not cleanup memory or threads [[noreturn]] static void terminate( int a_code ) noexcept; @@ -176,31 +258,38 @@ namespace scarab typedef cancelers::iterator cancelers_it_t; static cancelers s_cancelers; - static std::recursive_mutex s_mutex; + static std::mutex s_cancelers_mutex; public: + struct handled_signal_info + { + std::string name; + bool handling; + struct sigaction old_action; + bool indicates_error; + int return_code; + }; + typedef std::map signal_map_t; + + static sig_atomic_t get_signal_received() { return s_signal_received; } + static signal_map_t handled_signals() { return s_handled_signals; } + mv_accessible_static_noset( int, ref_count ); mv_accessible_static_noset( bool, exited ); mv_accessible_static( int, return_code ); - mv_accessible_static_noset( bool, handling_sig_abrt ); - mv_accessible_static_noset( bool, handling_sig_term ); - mv_accessible_static_noset( bool, handling_sig_int ); - mv_accessible_static_noset( bool, handling_sig_quit ); - -#ifndef _WIN32 - mv_referrable_static( struct sigaction, old_sig_abrt_action ); - mv_referrable_static( struct sigaction, old_sig_term_action ); - mv_referrable_static( struct sigaction, old_sig_int_action ); - mv_referrable_static( struct sigaction, old_sig_quit_action ); -#else // _WIN32 - typedef void (*handler_t)(int); - mv_referrable_static( handler_t, old_sig_abrt_handler ); - mv_referrable_static( handler_t, old_sig_term_handler ); - mv_referrable_static( handler_t, old_sig_int_handler ); - mv_referrable_static( handler_t, old_sig_quit_handler ); -#endif + mv_accessible_static_noset( bool, is_handling ); + + mv_referrable_static( std::thread, waiting_thread ); + + protected: + static std::mutex s_handle_mutex; + + static volatile sig_atomic_t s_signal_received; + + static signal_map_t s_handled_signals; + }; } /* namespace scarab */ diff --git a/python/signal_handler_pybind.hh b/python/signal_handler_pybind.hh index 908277b..7af3469 100644 --- a/python/signal_handler_pybind.hh +++ b/python/signal_handler_pybind.hh @@ -18,15 +18,21 @@ namespace scarab_pybind pybind11::return_value_policy::take_ownership, SCARAB_BIND_CALL_GUARD_STREAMS ) +// .def("set_wp_potentially_slicing", +// [](WpOwner &self, py::handle obj) { +// self.set_wp(py::potentially_slicing_weak_ptr(obj)); +// }) .def_static( "add_cancelable", - [](std::shared_ptr a_cancelable) { - scarab::signal_handler::add_cancelable(pybind11::potentially_slicing_weak_ptr(pybind11::cast(a_cancelable)).lock()); + [](pybind11::handle a_cancelable) { + scarab::signal_handler::add_cancelable(pybind11::potentially_slicing_weak_ptr(a_cancelable).lock()); }, "add a cancelable object to the list to be canceled in the event of a SIGINT" ) .def_static( "remove_cancelable", static_cast)>( &scarab::signal_handler::remove_cancelable ), "remove a cancelable object from the list to be canceled in the event of a SIGINT" ) .def_static( "reset", &scarab::signal_handler::reset, "remove all cancelable objects", SCARAB_BIND_CALL_GUARD_STREAMS ) .def_static( "cancel_all", &scarab::signal_handler::cancel_all, "cancel all cancelable objects known to the signal_handler", SCARAB_BIND_CALL_GUARD_STREAMS_AND_GIL ) .def_static( "print_cancelables", &scarab::signal_handler::print_cancelables, "print pointers to the cancelables known to the signal_handler", SCARAB_BIND_CALL_GUARD_STREAMS ) + .def_static( "start_waiting_thread", &scarab::signal_handler::start_waiting_thread, "starts the thread that waits for signals", SCARAB_BIND_CALL_GUARD_STREAMS ) + .def_static( "join_waiting_thread", &scarab::signal_handler::join_waiting_thread, "joins the thread that waits for signals", SCARAB_BIND_CALL_GUARD_STREAMS_AND_GIL ) ; return all_members; diff --git a/testing/applications/test_raise_sigabrt.cc b/testing/applications/test_raise_sigabrt.cc index 07c49cc..ffde286 100644 --- a/testing/applications/test_raise_sigabrt.cc +++ b/testing/applications/test_raise_sigabrt.cc @@ -37,13 +37,24 @@ #include "logger.hh" #include +#include +LOGGER_ST( testlog, "test_raise_sigabrt" ); int main(int , char ** ) { - scarab::signal_handler t_handler; - + scarab::signal_handler t_sh( false ); + + LINFO( testlog, "Starting to wait-on-signals thread" ); + scarab::signal_handler::start_waiting_thread(); + + std::this_thread::sleep_for( std::chrono::seconds(1) ); + + LINFO( testlog, "Raising SIGABRT" ); raise( SIGABRT ); + LINFO( testlog, "Raised" ); + scarab::signal_handler::join_waiting_thread(); + return( EXIT_SUCCESS ); } diff --git a/testing/applications/test_raise_sigint.cc b/testing/applications/test_raise_sigint.cc index 28bd0e6..15c404a 100644 --- a/testing/applications/test_raise_sigint.cc +++ b/testing/applications/test_raise_sigint.cc @@ -24,13 +24,22 @@ #include "logger.hh" #include +#include +LOGGER_ST( testlog, "test_raise_sigint" ); int main(int , char ** ) { - scarab::signal_handler t_handler; + scarab::signal_handler t_sh; + // let the signal-waiting thread start up + std::this_thread::sleep_for( std::chrono::seconds(1) ); + + LINFO( testlog, "Raising SIGINT" ); raise( SIGINT ); + LINFO( testlog, "Raised" ); + scarab::signal_handler::join_waiting_thread(); + return( EXIT_SUCCESS ); } diff --git a/testing/applications/test_raise_sigquit.cc b/testing/applications/test_raise_sigquit.cc index 5864013..1c20521 100644 --- a/testing/applications/test_raise_sigquit.cc +++ b/testing/applications/test_raise_sigquit.cc @@ -24,13 +24,22 @@ #include "logger.hh" #include +#include +LOGGER_ST( testlog, "test_raise_sigquit" ); int main(int , char ** ) { - scarab::signal_handler t_handler; + scarab::signal_handler t_sh; + // let the signal-waiting thread start up + std::this_thread::sleep_for( std::chrono::seconds(1) ); + + LINFO( testlog, "Raising SIGQUIT" ); raise( SIGQUIT ); + LINFO( testlog, "Raised" ); + scarab::signal_handler::join_waiting_thread(); + return( EXIT_SUCCESS ); } diff --git a/testing/applications/test_raise_sigterm.cc b/testing/applications/test_raise_sigterm.cc index eca3b5d..938b1c6 100644 --- a/testing/applications/test_raise_sigterm.cc +++ b/testing/applications/test_raise_sigterm.cc @@ -37,13 +37,24 @@ #include "logger.hh" #include +#include +LOGGER_ST( testlog, "test_raise_sigterm" ); int main(int , char ** ) { - scarab::signal_handler t_handler; + scarab::signal_handler t_sh; + LINFO( testlog, "Starting to wait-on-signals thread" ); + std::thread t_sh_thread( [](){scarab::signal_handler::wait_for_signals();} ); + + std::this_thread::sleep_for( std::chrono::seconds(1) ); + + LINFO( testlog, "Raising SIGTERM" ); raise( SIGTERM ); + LINFO( testlog, "Raised" ); + t_sh_thread.join(); + return( EXIT_SUCCESS ); } diff --git a/testing/test_logger.cc b/testing/test_logger.cc index 6c2c800..d028970 100644 --- a/testing/test_logger.cc +++ b/testing/test_logger.cc @@ -85,7 +85,7 @@ TEST_CASE( "logger", "[logger]" ) //REQUIRE( compare_and_clear( t_errstream, "test FATAL" ) ); } - +/* TEST_CASE( "local_logger", "[logger]" ) { LOCAL_LOGGER( tlog, "test_logger" ); @@ -117,4 +117,5 @@ TEST_CASE( "async_logging_stop", "[logger]" ) t_async = std::dynamic_pointer_cast( tlog.spdlogger() ); REQUIRE( t_async ); -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/testing/test_signal_handler.cc b/testing/test_signal_handler.cc index 615d5cf..b645130 100644 --- a/testing/test_signal_handler.cc +++ b/testing/test_signal_handler.cc @@ -18,7 +18,7 @@ #include "catch2/catch_test_macros.hpp" -#include +#include using scarab::testing::compare_and_clear; @@ -27,70 +27,100 @@ TEST_CASE( "signal_handler", "[utility]" ) { LOGGER( tlog, "test_signal_handler" ); - SECTION( "not_handling" ) - { - REQUIRE_FALSE( scarab::signal_handler::get_handling_sig_abrt() ); - REQUIRE_FALSE( scarab::signal_handler::get_handling_sig_term() ); - REQUIRE_FALSE( scarab::signal_handler::get_handling_sig_int() ); - REQUIRE_FALSE( scarab::signal_handler::is_handling( SIGABRT ) ); - REQUIRE_FALSE( scarab::signal_handler::is_handling( SIGTERM ) ); - REQUIRE_FALSE( scarab::signal_handler::is_handling( SIGINT ) ); - #ifndef _WIN32 - REQUIRE_FALSE( scarab::signal_handler::get_handling_sig_quit() ); - REQUIRE_FALSE( scarab::signal_handler::is_handling( SIGQUIT ) ); - #endif + // Thread safety note + // From: https://github.com/catchorg/Catch2/blob/devel/docs/thread-safety.md + // Assertion macros are thread safe. + // Section macros, generator macros, and test-case macros are not thread safe. + // So starting a test case or section and then spawning a thread inside is ok; + // Having multiple threads running in different sections or test cases in parallel is not ok. + // Calling REQUIRE in a spawned thread is dangerous because if it is false, the exception thrown will not be caught properly, bringing down the whole process. + // Instead, call CHECK in a thread, which will record the proper failures/successes, but not crash the program. + SECTION( "handling" ) + { REQUIRE( scarab::signal_handler::get_ref_count() == 0 ); + REQUIRE_FALSE( scarab::signal_handler::is_handling() ); + REQUIRE( scarab::signal_handler::get_signal_received() == 0 ); + REQUIRE_FALSE( scarab::signal_handler::waiting_thread_joinable() ); + + scarab::signal_handler t_handler( false ); + REQUIRE( scarab::signal_handler::get_ref_count() == 1 ); + REQUIRE( scarab::signal_handler::is_handling() ); + REQUIRE_FALSE( scarab::signal_handler::is_waiting() ); + REQUIRE_FALSE( scarab::signal_handler::waiting_thread_joinable() ); + + t_handler.unhandle_signals(); + REQUIRE_FALSE( scarab::signal_handler::is_handling() ); } - scarab::signal_handler t_handler; - REQUIRE( scarab::signal_handler::get_ref_count() == 1 ); - - SECTION( "handling" ) + SECTION( "handle while in scope" ) { - REQUIRE( scarab::signal_handler::get_handling_sig_abrt() ); - REQUIRE( scarab::signal_handler::get_handling_sig_term() ); - REQUIRE( scarab::signal_handler::get_handling_sig_int() ); - REQUIRE( scarab::signal_handler::is_handling( SIGABRT ) ); - REQUIRE( scarab::signal_handler::is_handling( SIGTERM ) ); - REQUIRE( scarab::signal_handler::is_handling( SIGINT ) ); - #ifndef _WIN32 - REQUIRE( scarab::signal_handler::get_handling_sig_quit() ); - REQUIRE( scarab::signal_handler::is_handling( SIGQUIT ) ); - #endif - - scarab::signal_handler t_handler_2; - REQUIRE( scarab::signal_handler::get_ref_count() == 2 ); - - REQUIRE( scarab::signal_handler::get_handling_sig_abrt() ); - REQUIRE( scarab::signal_handler::get_handling_sig_term() ); - REQUIRE( scarab::signal_handler::get_handling_sig_int() ); - #ifndef _WIN32 - REQUIRE( scarab::signal_handler::get_handling_sig_quit() ); - #endif + REQUIRE( scarab::signal_handler::get_ref_count() == 0 ); + REQUIRE_FALSE( scarab::signal_handler::is_handling() ); + REQUIRE_FALSE( scarab::signal_handler::waiting_thread_joinable() ); + + { + scarab::signal_handler t_handler( false ); + REQUIRE( scarab::signal_handler::get_ref_count() == 1 ); + REQUIRE( scarab::signal_handler::is_handling() ); + REQUIRE_FALSE( scarab::signal_handler::waiting_thread_joinable() ); + } // t_handler destructed, which should unhandle signals + REQUIRE( scarab::signal_handler::get_ref_count() == 0 ); + REQUIRE_FALSE( scarab::signal_handler::is_handling() ); + REQUIRE_FALSE( scarab::signal_handler::waiting_thread_joinable() ); + + } + + SECTION( "wait then abort -- user wait thread" ) + { + scarab::signal_handler t_handler( false ); + REQUIRE( scarab::signal_handler::is_handling() ); + REQUIRE_FALSE( scarab::signal_handler::waiting_thread_joinable() ); + + // start handling threads + std::thread t_handler_thread( [](){ scarab::signal_handler::wait_for_signals(); } ); + std::this_thread::sleep_for( std::chrono::seconds(1) ); + + scarab::signal_handler::abort_wait(); + t_handler_thread.join(); + REQUIRE( scarab::signal_handler::get_signal_received() == -1 ); + REQUIRE( scarab::signal_handler::is_handling() ); + REQUIRE_FALSE( scarab::signal_handler::waiting_thread_joinable() ); } - REQUIRE( scarab::signal_handler::get_ref_count() == 1 ); + SECTION( "wait then abort -- manual wait thread" ) + { + scarab::signal_handler t_handler( false ); + REQUIRE( scarab::signal_handler::is_handling() ); + REQUIRE_FALSE( scarab::signal_handler::waiting_thread_joinable() ); + + // start handling threads + scarab::signal_handler::start_waiting_thread(); + std::this_thread::sleep_for( std::chrono::seconds(2) ); + REQUIRE( scarab::signal_handler::waiting_thread_joinable() ); + + scarab::signal_handler::abort_wait(); + scarab::signal_handler::join_waiting_thread(); + REQUIRE( scarab::signal_handler::get_signal_received() == -1 ); + REQUIRE( scarab::signal_handler::is_handling() ); + REQUIRE_FALSE( scarab::signal_handler::waiting_thread_joinable() ); + } - SECTION( "handling_switch" ) + SECTION( "wait then abort -- automatic wait thread" ) { - scarab::signal_handler::unhandle_signals(); - REQUIRE_FALSE( scarab::signal_handler::get_handling_sig_abrt() ); - REQUIRE_FALSE( scarab::signal_handler::get_handling_sig_term() ); - REQUIRE_FALSE( scarab::signal_handler::get_handling_sig_int() ); - #ifndef _WIN32 - REQUIRE_FALSE( scarab::signal_handler::get_handling_sig_quit() ); - #endif - - scarab::signal_handler::handle_signals(); - REQUIRE( scarab::signal_handler::get_handling_sig_abrt() ); - REQUIRE( scarab::signal_handler::get_handling_sig_term() ); - REQUIRE( scarab::signal_handler::get_handling_sig_int() ); - #ifndef _WIN32 - REQUIRE( scarab::signal_handler::get_handling_sig_quit() ); - #endif + scarab::signal_handler t_handler; + std::this_thread::sleep_for( std::chrono::seconds(2) ); + REQUIRE( scarab::signal_handler::is_handling() ); + REQUIRE( scarab::signal_handler::waiting_thread_joinable() ); + + scarab::signal_handler::abort_wait(); + scarab::signal_handler::join_waiting_thread(); + REQUIRE( scarab::signal_handler::get_signal_received() == -1 ); + REQUIRE( scarab::signal_handler::is_handling() ); + REQUIRE_FALSE( scarab::signal_handler::waiting_thread_joinable() ); } + /* SECTION( "printing" ) { @@ -116,6 +146,8 @@ TEST_CASE( "signal_handler", "[utility]" ) } } */ + scarab::signal_handler t_handler( false ); + scarab::cancelable t_cancel; REQUIRE_FALSE( t_cancel.is_canceled() ); @@ -127,6 +159,7 @@ TEST_CASE( "signal_handler", "[utility]" ) { t_handler.cancel_all( 0 ); REQUIRE( t_cancel.is_canceled() ); + REQUIRE( scarab::signal_handler::get_signal_received() == -1 ); } SECTION( "terminating" )