Scarab  v3.7.1
Project 8 C++ Utility Library
signal_handler.cc
Go to the documentation of this file.
1 /*
2  * signal_handler.cc
3  *
4  * Created on: Dec 3, 2013
5  * Author: N.S. Oblath
6  */
7 
8 #define SCARAB_API_EXPORTS
9 
10 #include "signal_handler.hh"
11 
12 #include "cancelable.hh"
13 #include "error.hh"
14 #include "logger.hh"
15 
16 #include <execinfo.h>
17 #include <stdexcept>
18 #include <thread>
19 
20 #ifdef _WIN32
21 #include <csignal>
22 #include <Windows.h>
23 //#include "processthreadsapi.h"
24 #endif
25 
26 namespace {
27  // function to catch unhandled exceptions
28  // invoke set_terminate as part of global constant initialization
29  static const bool SET_TERMINATE = std::set_terminate( scarab::signal_handler::handle_terminate );
30 }
31 
32 
33 namespace scarab
34 {
35  LOGGER( slog, "signal_handler" );
36 
37  int signal_handler::s_ref_count = 0;
38 
39  bool signal_handler::s_exited = false;
40  int signal_handler::s_return_code = RETURN_SUCCESS;
41 
42  bool signal_handler::s_handling_sig_abrt = false;
43  bool signal_handler::s_handling_sig_term = false;
44  bool signal_handler::s_handling_sig_int = false;
45  bool signal_handler::s_handling_sig_quit = false;
46 
47 #ifndef _WIN32
48  struct sigaction signal_handler::s_old_sig_abrt_action;
49  struct sigaction signal_handler::s_old_sig_term_action;
50  struct sigaction signal_handler::s_old_sig_int_action;
51  struct sigaction signal_handler::s_old_sig_quit_action;
52 #else // _WIN32
53  signal_handler::handler_t signal_handler::s_old_sig_abrt_handler = SIG_DFL;
54  signal_handler::handler_t signal_handler::s_old_sig_term_handler = SIG_DFL;
55  signal_handler::handler_t signal_handler::s_old_sig_int_handler = SIG_DFL;
56  signal_handler::handler_t signal_handler::s_old_sig_quit_handler = SIG_DFL;
57 #endif
58 
59  std::recursive_mutex signal_handler::s_mutex;
61 
63  {
64  //std::cerr << "signal_handler constructor" << std::endl;
65  ++signal_handler::s_ref_count;
66 
68  }
69 
71  {
72  //std::cerr << "signal_handler destructor" << std::endl;
73  --signal_handler::s_ref_count;
74  if( signal_handler::s_ref_count == 0 )
75  {
77  }
78  }
79 
80  void signal_handler::add_cancelable( std::shared_ptr< scarab::cancelable > a_cancelable )
81  {
82  std::unique_lock< std::recursive_mutex > t_lock( s_mutex );
83  s_cancelers.insert( std::make_pair(a_cancelable.get(), cancelable_wptr_t(a_cancelable) ) );
84  return;
85  }
86 
87  void signal_handler::remove_cancelable( std::shared_ptr< scarab::cancelable > a_cancelable )
88  {
89  std::unique_lock< std::recursive_mutex > t_lock( s_mutex );
90  s_cancelers.erase( a_cancelable.get() );
91  return;
92  }
93 
95  {
96  std::unique_lock< std::recursive_mutex > t_lock( s_mutex );
97  s_cancelers.erase( a_cancelable );
98  return;
99  }
100 
102  {
103  std::unique_lock< std::recursive_mutex > t_lock( s_mutex );
104 
105  // we create a new logger here so that handle_signals() can be called from the signal_handler constructor during static initalization
106  LOGGER( slog_constr, "signal_handler handle_signals" );
107 
108  LDEBUG( slog_constr, "Taking over signal handling for SIGABRT, SIGTERM, SIGINT, and SIGQUIT" );
109 
110 #ifndef _WIN32 // on a POSIX system we use sigaction
111 
112  struct sigaction t_exit_error_action, t_exit_success_action;
113 
114  t_exit_error_action.sa_handler = signal_handler::handle_exit_error;
115  sigemptyset(&t_exit_error_action.sa_mask);
116  t_exit_error_action.sa_flags = 0;
117 
118  t_exit_success_action.sa_handler = signal_handler::handle_exit_success;
119  sigemptyset(&t_exit_success_action.sa_mask);
120  t_exit_success_action.sa_flags = 0;
121 
122  // setup to handle SIGABRT
123  if( ! s_handling_sig_abrt && sigaction( SIGABRT, &t_exit_error_action, &s_old_sig_abrt_action ) != 0 )
124  {
125  LWARN( slog_constr, "Unable to setup handling of SIGABRT: abort() and unhandled exceptions will result in an unclean exit" );
126  }
127  else
128  {
129  LTRACE( slog_constr, "Handling SIGABRT (abort() and unhandled exceptions)" );
130  s_handling_sig_abrt = true;
131  }
132 
133  // setup to handle SIGTERM
134  if( ! s_handling_sig_term && sigaction( SIGTERM, &t_exit_error_action, &s_old_sig_term_action ) != 0 )
135  {
136  LWARN( slog_constr, "Unable to setup handling of SIGTERM: SIGTERM will result in an unclean exit" );
137  }
138  else
139  {
140  LTRACE( slog_constr, "Handling SIGTERM" );
141  s_handling_sig_term = true;
142  }
143 
144  // setup to handle SIGINT
145  if( ! s_handling_sig_int && sigaction( SIGINT, &t_exit_success_action, &s_old_sig_int_action ) != 0 )
146  {
147  LWARN( slog_constr, "Unable to setup handling of SIGINT: ctrl-c cancellation will result in an unclean exit" );
148  }
149  else
150  {
151  LTRACE( slog_constr, "Handling SIGINT (ctrl-c)" );
152  s_handling_sig_int = true;
153  }
154 
155  // setup to handle SIGQUIT
156  if( ! s_handling_sig_quit && sigaction( SIGQUIT, &t_exit_success_action, &s_old_sig_quit_action ) != 0 )
157  {
158  LWARN( slog_constr, "Unable to setup handling of SIGQUIT: ctrl-\\ cancellation will result in an unclean exit" );
159  }
160  else
161  {
162  LTRACE( slog_constr, "Handling SIGQUIT (ctrl-\\)" );
163  s_handling_sig_quit = true;
164  }
165 
166  if( signal(SIGPIPE, SIG_IGN) == SIG_ERR )
167  {
168  throw error() << "Unable to ignore SIGPIPE\n";
169  }
170 
171 #else // _WIN32; on a Windows system we use std::signal
172 
173  // setup to handle SIGABRT
174  if( ! s_handling_sig_abrt )
175  {
176  auto t_sig_ret = signal( SIGABRT, signal_handler::handle_exit_error );
177  if( t_sig_ret == SIG_ERR )
178  {
179  LWARN( slog_constr, "Unable to setup handling of SIGABRT: abort() and unhandled exceptions will result in an unclean exit" );
180  }
181  else
182  {
183  s_handling_sig_abrt = true;
184  s_old_sig_abrt_handler = t_sig_ret;
185  }
186  }
187  if( s_handling_sig_abrt ) LTRACE( slog_constr, "Handling SIGABRT (abort() and unhandled exceptions)" );
188 
189 
190  // setup to handle SIGTERM
191  if( ! s_handling_sig_term )
192  {
193  auto t_sig_ret = signal( SIGTERM, signal_handler::handle_exit_error );
194  if( t_sig_ret == SIG_ERR )
195  {
196  LWARN( slog_constr, "Unable to setup handling of SIGTERM: SIGTERM will result in an unclean exit" );
197  }
198  else
199  {
200  s_handling_sig_term = true;
201  s_old_sig_abrt_handler = t_sig_ret;
202  }
203  }
204  if( s_handling_sig_term ) LTRACE( slog_constr, "Handling SIGTERM" );
205 
206  // setup to handle SIGINT
207  if( ! s_handling_sig_int )
208  {
209  auto t_sig_ret = signal( SIGINT, signal_handler::handle_exit_error );
210  if( t_sig_ret == SIG_ERR )
211  {
212  LWARN( slog_constr, "Unable to setup handling of SIGINT: ctrl-c cancellation will result in an unclean exit" );
213  }
214  else
215  {
216  s_handling_sig_int = true;
217  s_old_sig_abrt_handler = t_sig_ret;
218  }
219  }
220  if( s_handling_sig_int ) LTRACE( slog_constr, "Handling SIGINT (ctrl-c))" );
221 
222 #endif
223  return;
224  }
225 
227  {
228  std::unique_lock< std::recursive_mutex > t_lock( s_mutex );
229 
230  LDEBUG( slog, "Returning signal handling for SIGABRT, SIGTERM, SIGINT, and SIGQUIT" );
231 
232 #ifndef _WIN32
233 
234  if( s_handling_sig_abrt && sigaction( SIGABRT, &s_old_sig_abrt_action, nullptr ) != 0 )
235  {
236  LWARN( slog, "Unable to switch SIGABRT to previous handler" );
237  }
238  else
239  {
240  s_handling_sig_abrt = false;
241  }
242 
243  if( s_handling_sig_term && sigaction( SIGTERM, &s_old_sig_term_action, nullptr ) != 0 )
244  {
245  LWARN( slog, "Unable to switch SIGTERM to previous handler" );
246  }
247  else
248  {
249  s_handling_sig_term = false;
250  }
251 
252  if( s_handling_sig_int && sigaction( SIGINT, &s_old_sig_int_action, nullptr ) != 0 )
253  {
254  LWARN( slog, "Unable to switch SIGINT to previous handler" );
255  }
256  else
257  {
258  s_handling_sig_int = false;
259  }
260 
261  if( s_handling_sig_quit && sigaction( SIGQUIT, &s_old_sig_quit_action, nullptr ) != 0 )
262  {
263  LWARN( slog, "Unable to switch SIGQUIT to previous handler" );
264  }
265  else
266  {
267  s_handling_sig_quit = false;
268  }
269 
270 #else // _WIN32
271 
272  if( s_handling_sig_abrt && signal( SIGABRT, s_old_sig_abrt_handler ) == SIG_ERR )
273  {
274  LWARN( slog, "Unable to switch SIGABRT to previous handler" );
275  }
276  else
277  {
278  s_handling_sig_abrt = false;
279  }
280 
281  if( s_handling_sig_term && signal( SIGTERM, s_old_sig_term_handler ) == SIG_ERR )
282  {
283  LWARN( slog, "Unable to switch SIGTERM to previous handler" );
284  }
285  else
286  {
287  s_handling_sig_term = false;
288  }
289 
290  if( s_handling_sig_int && signal( SIGINT, s_old_sig_int_handler ) == SIG_ERR )
291  {
292  LWARN( slog, "Unable to switch SIGINT to previous handler" );
293  }
294  else
295  {
296  s_handling_sig_int = false;
297  }
298 
299 #endif
300  return;
301  }
302 
303  bool signal_handler::is_handling( int a_signal )
304  {
305  std::unique_lock< std::recursive_mutex > t_lock( s_mutex );
306 
307  bool t_is_handled = false;
308  switch( a_signal )
309  {
310  case SIGABRT:
311  if( s_handling_sig_abrt) t_is_handled = true;
312  break;
313  case SIGTERM:
314  if( s_handling_sig_term) t_is_handled = true;
315  break;
316  case SIGINT:
317  if( s_handling_sig_int) t_is_handled = true;
318  break;
319 #ifndef _WIN32
320  case SIGQUIT:
321  if( s_handling_sig_quit) t_is_handled = true;
322  break;
323 #endif
324  default:
325  break;
326  }
327  return t_is_handled;
328  }
329 
331  {
332  LDEBUG( slog, "Resetting signal_handler" );
333  std::unique_lock< std::recursive_mutex > t_lock( s_mutex );
334 
335  s_exited = false;
336  s_return_code = RETURN_SUCCESS;
337  s_cancelers.clear();
338 
340  return;
341  }
342 
343  [[noreturn]] void signal_handler::handle_terminate() noexcept
344  {
345  terminate( RETURN_ERROR );
346  }
347 
349  {
350  std::unique_lock< std::recursive_mutex > t_lock( s_mutex );
351  LERROR( slog, "Handling signal <" << a_sig << "> as an error condition; return code: " << RETURN_ERROR );
352  exit( RETURN_ERROR );
353  return;
354  }
355 
357  {
358  std::unique_lock< std::recursive_mutex > t_lock( s_mutex );
359  LPROG( slog, "Handling signal <" << a_sig << ">; return code: " << RETURN_SUCCESS );
360  exit( RETURN_SUCCESS );
361  return;
362  }
363 
364  [[noreturn]] void signal_handler::terminate( int a_code ) noexcept
365  {
366  std::unique_lock< std::recursive_mutex > t_lock( s_mutex );
367  print_current_exception( false );
368  if( a_code > 0 )
369  {
370  print_stack_trace( false );
371  }
372  std::cerr << "Exiting abruptly" << std::endl;
373  std::_Exit( a_code );
374  }
375 
376  void signal_handler::exit( int a_code )
377  {
378  std::unique_lock< std::recursive_mutex > t_lock( s_mutex );
379  s_exited = true;
380  s_return_code = a_code;
381  print_current_exception( true );
382  if( a_code > 0 )
383  {
384  print_stack_trace( true );
385  }
386  cancel_all( a_code );
387  return;
388  }
389 
390  void signal_handler::print_current_exception( bool a_use_logging )
391  {
392  // no mutex locking needed here
393 
394  // if there's a current exception, rethrow to print out what()
395  if( auto t_exc_ptr = std::current_exception() )
396  {
397  // we have an exception
398  try
399  {
400  if( a_use_logging ) { LDEBUG( slog, "Rethrowing current exception" ); }
401  else { std::cerr << "Rethrowing current exception" << std::endl; }
402  std::rethrow_exception( t_exc_ptr ); // throw to recognize the type
403  }
404  catch( const std::exception& e ) {
405  if( a_use_logging ) { LERROR( slog, "Caught unhandled exception. what(): " << e.what() ); }
406  else { std::cerr << "Caught unhandled exception. what(): " << e.what() << std::endl; }
407  }
408  catch( ... ) {
409  if( a_use_logging ) LERROR( slog, "Caught unknown (non-std::exception) & unhandled exception." )
410  else { std::cerr << "Caught unknown (non-std::exception) & unhandled exception." << std::endl; }
411  }
412  }
413  return;
414  }
415 
416  void signal_handler::print_stack_trace( bool a_use_logging )
417  {
418  // no mutex locking needed here
419 #ifndef _WIN32 // stack trace printing not implemented for windows
420  void* t_bt_array[50];
421  int t_size = backtrace( t_bt_array, 50 );
422 
423  if( a_use_logging ) { LERROR( slog, "Backtrace returned " << t_size << " frames\n" ); }
424  else { std::cerr << "Backtrace returned " << t_size << " frames\n" << std::endl; }
425 
426  char** t_messages = backtrace_symbols( t_bt_array, t_size );
427 
428  std::stringstream t_bt_str;
429  for( int i = 0; i < t_size && t_messages != nullptr; ++i )
430  {
431  t_bt_str << "[bt]: (" << i << ") " << t_messages[i] << '\n';
432  }
433  if( a_use_logging ) { LERROR( slog, "Backtrace:\n" << t_bt_str.str() ); }
434  else { std::cerr << "Backtrace:\n" << t_bt_str.str() << std::endl; }
435 
436  free( t_messages );
437 #endif
438  return;
439  }
440 
441  void signal_handler::cancel_all( int a_code )
442  {
443  std::unique_lock< std::recursive_mutex > t_lock( s_mutex );
444  LDEBUG( slog, "Canceling all cancelables" );
445 
446  while( ! s_cancelers.empty() )
447  {
448  auto t_canceler_it = s_cancelers.begin();
449  if( ! t_canceler_it->second.expired() )
450  {
451  t_canceler_it->second.lock()->cancel( a_code );
452  }
453  s_cancelers.erase( t_canceler_it );
454  std::this_thread::sleep_for( std::chrono::seconds(1) );
455  }
456 
457 #ifdef _WIN32
458  ExitProcess( a_code );
459 #endif
460 
461  return;
462  }
463 
464 } /* namespace scarab */
std::weak_ptr< cancelable > cancelable_wptr_t
static void terminate(int a_code) noexcept
Main terminate function – does not cleanup memory or threads.
#define LWARN(...)
Definition: logger.hh:393
static bool is_handling(int a_signal)
Check if a signal is handled.
static void add_cancelable(std::shared_ptr< cancelable > a_cancelable)
Static version: add a cancelable object.
#define RETURN_SUCCESS
Definition: macros.hh:12
static cancelers s_cancelers
std::map< cancelable *, cancelable_wptr_t > cancelers
LOGGER(mtlog, "authentication")
static std::recursive_mutex s_mutex
#define RETURN_ERROR
Definition: macros.hh:13
#define LPROG(...)
Definition: logger.hh:392
#define LERROR(...)
Definition: logger.hh:394
#define LTRACE(...)
Definition: logger.hh:388
Contains the logger class and macros, based on Kasper&#39;s KLogger class.
static void print_current_exception(bool a_use_logging)
Prints the current exception, if there is one.
static void cancel_all(int a_code)
Asynchronous call to cancel all cancelables with the given exit code.
static void exit(int a_code)
Main exit function – cleanly exits.
static void handle_exit_error(int a_sig)
Handler for error signals – cleanly exits.
#define LDEBUG(...)
Definition: logger.hh:389
Base class for a cancelable object (i.e. an object that can be canceled by scarab::signal_handler or ...
Definition: cancelable.hh:51
static void remove_cancelable(std::shared_ptr< cancelable > a_cancelable)
Static version: remove a cancelable object.
static void unhandle_signals()
Stop handling signals.
static void reset()
Remove all cancelables and signal handling.
static void handle_exit_success(int a_sig)
Handler for success signals – cleanly exits.
static void handle_terminate() noexcept
Handler for std::terminate – does not cleanup memory or threads.
static void print_stack_trace(bool a_use_logging)
Prints the stack trace.
static void handle_signals()
Start handling signals.