| # Copyright 2021 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Signal related functionality.""" |
| |
| import signal |
| import contextlib |
| |
| |
| def RelaySignal(handler, signum, frame): |
| """Notify a listener returned from getsignal of receipt of a signal. |
| |
| Returns: |
| True if it was relayed to the target, False otherwise. |
| False in particular occurs if the target isn't relayable. |
| """ |
| if handler in (None, signal.SIG_IGN): |
| return True |
| elif handler == signal.SIG_DFL: |
| # This scenario is a fairly painful to handle fully, thus we just |
| # state we couldn't handle it and leave it to client code. |
| return False |
| handler(signum, frame) |
| return True |
| |
| |
| def SignalModuleUsable(_signal=signal.signal, _SIGUSR1=signal.SIGUSR1): |
| """Verify that the signal module is usable and won't segfault on us. |
| |
| See http://bugs.python.org/issue14173. This function detects if the |
| signals module is no longer safe to use (which only occurs during |
| final stages of the interpreter shutdown) and heads off a segfault |
| if signal.* was accessed. |
| |
| This shouldn't be used by anything other than functionality that is |
| known and unavoidably invoked by finalizer code during python shutdown. |
| |
| Finally, the default args here are intentionally binding what we need |
| from the signal module to do the necessary test; invoking code shouldn't |
| pass any options, nor should any developer ever remove those default |
| options. |
| |
| Note that this functionality is intended to be removed just as soon |
| as all consuming code installs their own SIGTERM handlers. |
| """ |
| # Track any signals we receive while doing the check. |
| received, actual = [], None |
| |
| def handler(signum, frame): |
| received.append([signum, frame]) |
| |
| try: |
| # Play with sigusr1, since it's not particularly used. |
| actual = _signal(_SIGUSR1, handler) |
| _signal(_SIGUSR1, actual) |
| return True |
| except (TypeError, AttributeError, SystemError, ValueError): |
| # The first three exceptions can be thrown depending on the state of the |
| # signal module internal Handlers array; we catch all, and interpret it |
| # as if we were invoked during sys.exit cleanup. |
| # The last exception can be thrown if we're trying to be used in a thread |
| # which is not the main one. This can come up with standard python modules |
| # such as BaseHTTPServer.HTTPServer. |
| return False |
| finally: |
| # And now relay those signals to the original handler. Not all may |
| # be delivered- the first may throw an exception for example. Not our |
| # problem however. |
| for signum, frame in received: |
| actual(signum, frame) |
| |
| |
| @contextlib.contextmanager |
| def DeferSignals(*args): |
| """Context Manger to defer signals during a critical block. |
| |
| If a signal comes in for the masked signals, the original handler |
| is ran after the critical block has exited. |
| |
| Args: |
| args: Which signals to ignore. If none are given, defaults to |
| SIGINT and SIGTERM. |
| """ |
| signals = args |
| if not signals: |
| signals = [signal.SIGINT, signal.SIGTERM, signal.SIGALRM] |
| |
| # Rather than directly setting the handler, we first pull the handlers, then |
| # set the new handler. The ordering has to be done this way to ensure that |
| # if someone passes in a bad signum (or a signal lands prior to starting the |
| # critical block), we can restore things to pristine state. |
| handlers = dict((signum, signal.getsignal(signum)) for signum in signals) |
| |
| received = [] |
| |
| def handler(signum, frame): |
| received.append((signum, frame)) |
| |
| try: |
| for signum in signals: |
| signal.signal(signum, handler) |
| |
| yield |
| |
| finally: |
| for signum, original in handlers.items(): |
| signal.signal(signum, original) |
| |
| for signum, frame in received: |
| RelaySignal(handlers[signum], signum, frame) |
| |
| |
| def StrSignal(sig_num): |
| """Convert a signal number to the symbolic name |
| |
| Note: Some signal number have multiple names, so you might get |
| back a confusing result like "SIGIOT|SIGABRT". Since they have |
| the same signal number, it's impossible to say which one is right. |
| |
| Args: |
| sig_num: The numeric signal you wish to convert |
| |
| Returns: |
| A string of the signal name(s) |
| """ |
| # Handle realtime signals first since they are unnamed. |
| if sig_num >= signal.SIGRTMIN and sig_num < signal.SIGRTMAX: |
| return 'SIGRT_%i' % sig_num |
| |
| # Probe the module looking for matching signal constant. |
| sig_names = [] |
| for name, num in signal.__dict__.items(): |
| # Filter out SIG_DFL and related constants. |
| if name.startswith('SIG') and name[3] != '_' and num == sig_num: |
| sig_names.append(name) |
| if sig_names: |
| return '|'.join(sig_names) |
| else: |
| return 'SIG_%i' % sig_num |