xref: /aosp_15_r20/external/autotest/utils/frozen_chromite/lib/signals.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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