1# -*- coding: utf-8 -*- 2# Copyright (c) 2011-2012 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Signal related functionality.""" 7 8from __future__ import print_function 9 10import signal 11import contextlib 12 13 14def RelaySignal(handler, signum, frame): 15 """Notify a listener returned from getsignal of receipt of a signal. 16 17 Returns: 18 True if it was relayed to the target, False otherwise. 19 False in particular occurs if the target isn't relayable. 20 """ 21 if handler in (None, signal.SIG_IGN): 22 return True 23 elif handler == signal.SIG_DFL: 24 # This scenario is a fairly painful to handle fully, thus we just 25 # state we couldn't handle it and leave it to client code. 26 return False 27 handler(signum, frame) 28 return True 29 30 31def SignalModuleUsable(_signal=signal.signal, _SIGUSR1=signal.SIGUSR1): 32 """Verify that the signal module is usable and won't segfault on us. 33 34 See http://bugs.python.org/issue14173. This function detects if the 35 signals module is no longer safe to use (which only occurs during 36 final stages of the interpreter shutdown) and heads off a segfault 37 if signal.* was accessed. 38 39 This shouldn't be used by anything other than functionality that is 40 known and unavoidably invoked by finalizer code during python shutdown. 41 42 Finally, the default args here are intentionally binding what we need 43 from the signal module to do the necessary test; invoking code shouldn't 44 pass any options, nor should any developer ever remove those default 45 options. 46 47 Note that this functionality is intended to be removed just as soon 48 as all consuming code installs their own SIGTERM handlers. 49 """ 50 # Track any signals we receive while doing the check. 51 received, actual = [], None 52 def handler(signum, frame): 53 received.append([signum, frame]) 54 try: 55 # Play with sigusr1, since it's not particularly used. 56 actual = _signal(_SIGUSR1, handler) 57 _signal(_SIGUSR1, actual) 58 return True 59 except (TypeError, AttributeError, SystemError, ValueError): 60 # The first three exceptions can be thrown depending on the state of the 61 # signal module internal Handlers array; we catch all, and interpret it 62 # as if we were invoked during sys.exit cleanup. 63 # The last exception can be thrown if we're trying to be used in a thread 64 # which is not the main one. This can come up with standard python modules 65 # such as BaseHTTPServer.HTTPServer. 66 return False 67 finally: 68 # And now relay those signals to the original handler. Not all may 69 # be delivered- the first may throw an exception for example. Not our 70 # problem however. 71 for signum, frame in received: 72 actual(signum, frame) 73 74 75@contextlib.contextmanager 76def DeferSignals(*args): 77 """Context Manger to defer signals during a critical block. 78 79 If a signal comes in for the masked signals, the original handler 80 is ran after the critical block has exited. 81 82 Args: 83 args: Which signals to ignore. If none are given, defaults to 84 SIGINT and SIGTERM. 85 """ 86 signals = args 87 if not signals: 88 signals = [signal.SIGINT, signal.SIGTERM, signal.SIGALRM] 89 90 # Rather than directly setting the handler, we first pull the handlers, then 91 # set the new handler. The ordering has to be done this way to ensure that 92 # if someone passes in a bad signum (or a signal lands prior to starting the 93 # critical block), we can restore things to pristine state. 94 handlers = dict((signum, signal.getsignal(signum)) for signum in signals) 95 96 received = [] 97 def handler(signum, frame): 98 received.append((signum, frame)) 99 100 try: 101 for signum in signals: 102 signal.signal(signum, handler) 103 104 yield 105 106 finally: 107 for signum, original in handlers.items(): 108 signal.signal(signum, original) 109 110 for signum, frame in received: 111 RelaySignal(handlers[signum], signum, frame) 112 113 114def StrSignal(sig_num): 115 """Convert a signal number to the symbolic name 116 117 Note: Some signal number have multiple names, so you might get 118 back a confusing result like "SIGIOT|SIGABRT". Since they have 119 the same signal number, it's impossible to say which one is right. 120 121 Args: 122 sig_num: The numeric signal you wish to convert 123 124 Returns: 125 A string of the signal name(s) 126 """ 127 # Handle realtime signals first since they are unnamed. 128 if sig_num >= signal.SIGRTMIN and sig_num < signal.SIGRTMAX: 129 return 'SIGRT_%i' % sig_num 130 131 # Probe the module looking for matching signal constant. 132 sig_names = [] 133 for name, num in signal.__dict__.items(): 134 if name.startswith('SIG') and num == sig_num: 135 sig_names.append(name) 136 if sig_names: 137 return '|'.join(sig_names) 138 else: 139 return 'SIG_%i' % sig_num 140