236 lines
7.6 KiB
Python
236 lines
7.6 KiB
Python
'''
|
|
LICENSING
|
|
-------------------------------------------------
|
|
|
|
daemoniker: Cross-platform daemonization tools.
|
|
Copyright (C) 2016 Muterra, Inc.
|
|
|
|
Contributors
|
|
------------
|
|
Nick Badger
|
|
badg@muterra.io | badg@nickbadger.com | nickbadger.com
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, write to the
|
|
Free Software Foundation, Inc.,
|
|
51 Franklin Street,
|
|
Fifth Floor,
|
|
Boston, MA 02110-1301 USA
|
|
|
|
------------------------------------------------------
|
|
'''
|
|
|
|
# Global dependencies
|
|
import os
|
|
import sys
|
|
import signal
|
|
import logging
|
|
import atexit
|
|
import traceback
|
|
import shutil
|
|
|
|
# Intra-package dependencies
|
|
from .utils import platform_specificker
|
|
from .utils import default_to
|
|
|
|
from ._signals_common import _SighandlerCore
|
|
|
|
from .exceptions import DaemonikerSignal
|
|
from .exceptions import SignalError
|
|
from .exceptions import SIGINT
|
|
from .exceptions import SIGTERM
|
|
from .exceptions import SIGABRT
|
|
|
|
_SUPPORTED_PLATFORM = platform_specificker(
|
|
linux_choice = True,
|
|
win_choice = False,
|
|
cygwin_choice = False,
|
|
osx_choice = True,
|
|
# Dunno if this is a good idea but might as well try
|
|
other_choice = True
|
|
)
|
|
|
|
if _SUPPORTED_PLATFORM:
|
|
import fcntl
|
|
import pwd
|
|
import grp
|
|
import resource
|
|
|
|
|
|
# ###############################################
|
|
# Boilerplate
|
|
# ###############################################
|
|
|
|
|
|
import logging
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Control * imports.
|
|
__all__ = [
|
|
# 'Inquisitor',
|
|
]
|
|
|
|
|
|
# ###############################################
|
|
# Library
|
|
# ###############################################
|
|
|
|
|
|
def _restore_any_previous_handler(signum, maybe_handler, force_clear=False):
|
|
''' Makes sure that a previous handler was actually set, and then
|
|
restores it.
|
|
|
|
maybe_handler is the cached potential handler. If it's the constant
|
|
(ZeroDivisionError) we've been using to denote nothingness, then
|
|
maybe_handler will either:
|
|
|
|
1. do nothing, if force_clear == False
|
|
2. restore signal.SIG_DEFL, if force_clear == True
|
|
'''
|
|
# Nope, wasn't previously set
|
|
if maybe_handler == ZeroDivisionError:
|
|
# Restore the default if we're forcing it
|
|
if force_clear:
|
|
signal.signal(signum, signal.SIG_DFL)
|
|
# (Do nothing otherwise)
|
|
|
|
# It was previously set, so re-set it.
|
|
else:
|
|
signal.signal(signum, maybe_handler)
|
|
|
|
|
|
class SignalHandler1(_SighandlerCore):
|
|
''' Signal handling system using lightweight wrapper around built-in
|
|
signal.signal handling.
|
|
'''
|
|
def __init__(self, pid_file, sigint=None, sigterm=None, sigabrt=None):
|
|
''' Creates a signal handler, using the passed callables. None
|
|
will assign the default handler (raise in main). passing
|
|
IGNORE_SIGNAL constant will result in the signal being noop'd.
|
|
'''
|
|
self.sigint = sigint
|
|
self.sigterm = sigterm
|
|
self.sigabrt = sigabrt
|
|
|
|
# Yeah, except this isn't used at all (just here for cross-platform
|
|
# consistency)
|
|
self._pidfile = pid_file
|
|
|
|
# Assign these to impossible values so we can compare against them
|
|
# later, when deciding whether or not to restore a previous handler.
|
|
# Don't use None, in case it gets used to denote a default somewhere.
|
|
# This is deliberately unconventional.
|
|
self._old_sigint = ZeroDivisionError
|
|
self._old_sigterm = ZeroDivisionError
|
|
self._old_sigabrt = ZeroDivisionError
|
|
|
|
self._running = False
|
|
|
|
def start(self):
|
|
''' Starts signal handling.
|
|
'''
|
|
if self._running:
|
|
raise RuntimeError('SignalHandler is already running.')
|
|
|
|
# Initialize stuff to values that are impossible for signal.signal.
|
|
# Don't use None, in case it gets used to denote a default somewhere.
|
|
# This is deliberately unconventional.
|
|
old_sigint = ZeroDivisionError
|
|
old_sigterm = ZeroDivisionError
|
|
old_sigabrt = ZeroDivisionError
|
|
|
|
try:
|
|
# First we need to make closures around all of our attributes, so
|
|
# they can be updated after we start listening to signals
|
|
def sigint_closure(signum, frame):
|
|
return self.sigint(signum)
|
|
def sigterm_closure(signum, frame):
|
|
return self.sigterm(signum)
|
|
def sigabrt_closure(signum, frame):
|
|
return self.sigabrt(signum)
|
|
|
|
# Now simply register those with signal.signal
|
|
old_sigint = signal.signal(signal.SIGINT, sigint_closure)
|
|
old_sigterm = signal.signal(signal.SIGTERM, sigterm_closure)
|
|
old_sigabrt = signal.signal(signal.SIGABRT, sigabrt_closure)
|
|
|
|
# If that fails, restore previous state and reraise
|
|
except:
|
|
_restore_any_previous_handler(signal.SIGINT, old_sigint)
|
|
_restore_any_previous_handler(signal.SIGTERM, old_sigterm)
|
|
_restore_any_previous_handler(signal.SIGABRT, old_sigabrt)
|
|
raise
|
|
|
|
# If that succeeds, set self._running and cache old handlers
|
|
else:
|
|
self._old_sigint = old_sigint
|
|
self._old_sigterm = old_sigterm
|
|
self._old_sigabrt = old_sigabrt
|
|
self._running = True
|
|
|
|
def stop(self):
|
|
''' Stops signal handling, returning all signal handlers to
|
|
their previous handlers, or restoring their defaults if
|
|
something went fishy.
|
|
'''
|
|
try:
|
|
_restore_any_previous_handler(
|
|
signal.SIGINT,
|
|
self._old_sigint,
|
|
force_clear = True
|
|
)
|
|
_restore_any_previous_handler(
|
|
signal.SIGTERM,
|
|
self._old_sigterm,
|
|
force_clear = True
|
|
)
|
|
_restore_any_previous_handler(
|
|
signal.SIGABRT,
|
|
self._old_sigabrt,
|
|
force_clear = True
|
|
)
|
|
|
|
# If we get an exception there, just force restoring all defaults and
|
|
# reraise
|
|
except:
|
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
|
signal.signal(signal.SIGTERM, signal.SIG_DFL)
|
|
signal.signal(signal.SIGABRT, signal.SIG_DFL)
|
|
raise
|
|
|
|
finally:
|
|
# See notes above re: using ZeroDivisionError instead of None
|
|
self._old_sigint = ZeroDivisionError
|
|
self._old_sigterm = ZeroDivisionError
|
|
self._old_sigabrt = ZeroDivisionError
|
|
self._running = False
|
|
|
|
@staticmethod
|
|
def _default_handler(signum, *args):
|
|
''' The default signal handler for Unix.
|
|
'''
|
|
# Just parallel the sighandlers that are available in Windows, because
|
|
# it is definitely the limiting factor here
|
|
sigs = {
|
|
signal.SIGABRT: SIGABRT,
|
|
signal.SIGINT: SIGINT,
|
|
signal.SIGTERM: SIGTERM,
|
|
}
|
|
|
|
try:
|
|
exc = sigs[signum]
|
|
except KeyError:
|
|
exc = DaemonikerSignal
|
|
|
|
raise exc()
|