blob: a6049179fb50f362ef809c36e55c9e864bf709a9 [file] [log] [blame]
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Safe signal handling library.
Safe signal handling is hard. This module provides some tools to make
it a little easier.
Nothing in this module is thread safe.
For reference, see signal(7) and
https://docs.python.org/2/library/signal.html
Python and signals:
Python does not provide default signal handlers for SIGTERM or SIGHUP.
This means that if you send SIGTERM to a Python process, it won't run
any finally suites, __exit__() methods, or atexit functions!
In general, anything process specific does not need to be cleaned up if
the process is exiting. This includes file descriptors (open files) and
allocated memory.
Anything external to the process needs to be cleaned up. This includes
lock files and IO transactions (storage or network).
Subprocesses may or may not need to be cleaned up. The orphaned
subprocesses will be adopted and reaped by PID 1 or a subreaper.
However, the signal, e.g. SIGTERM, will not be sent to subprocesses
unless explicitly set to do so.
It is possible to receive another signal while handling a signal. After
a signal handler returns, control returns to where the signal was
received. In other words, signal handler calls go onto a "stack",
although signal handlers cannot return values.
A exception raised by a signal handler that escapes the signal handler
call will be raised where the signal was received.
It is possible to receive a signal while handling an exception,
including an exception raised while handling a signal.
If multiple signals are received at once and their handlers all raise
exceptions, you can probably expect Python to exit without running any
finally suites, __exit__() methods, or atexit functions.
It is possible to set signal handlers inside a signal handler. Please
do not do that.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import logging
import signal
import contextlib2
logger = logging.getLogger(__name__)
class handle_signals(object):
"""Context manager chaining multiple SignalHandlerContext.
This is single use.
"""
def __init__(self, signums, handler):
self._handler = handler
self._signums = signums
self._stack = contextlib2.ExitStack()
def __enter__(self):
stack = self._stack.__enter__()
for signum in self._signums:
stack.enter_context(handle_signal(signum, self._handler))
def __exit__(self, exc_type, exc_val, exc_tb):
return self._stack.__exit__(exc_type, exc_val, exc_tb)
class handle_signal(object):
"""Signal handler context.
This context manager sets a signal handler during the execution of
the suite and restores the original signal handler when exiting.
See signal.signal() for values of signum and handler.
This is reusable and reentrant.
"""
def __init__(self, signum, handler):
self._handler = handler
self._signum = signum
self._old_handlers = []
def __enter__(self):
old = signal.signal(self._signum, self._handler)
self._old_handlers.append(old)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
old = self._old_handlers.pop()
signal.signal(self._signum, old)
return False