135 lines
3.6 KiB
Python
135 lines
3.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
This module contains utilities added by billiard, to keep
|
|
"non-core" functionality out of ``.util``."""
|
|
from __future__ import absolute_import
|
|
|
|
import os
|
|
import signal
|
|
import sys
|
|
|
|
import pickle as pypickle
|
|
try:
|
|
import cPickle as cpickle
|
|
except ImportError: # pragma: no cover
|
|
cpickle = None # noqa
|
|
|
|
from .exceptions import RestartFreqExceeded
|
|
from .five import monotonic
|
|
|
|
if sys.version_info < (2, 6): # pragma: no cover
|
|
# cPickle does not use absolute_imports
|
|
pickle = pypickle
|
|
pickle_load = pypickle.load
|
|
pickle_loads = pypickle.loads
|
|
else:
|
|
pickle = cpickle or pypickle
|
|
pickle_load = pickle.load
|
|
pickle_loads = pickle.loads
|
|
|
|
# cPickle.loads does not support buffer() objects,
|
|
# but we can just create a StringIO and use load.
|
|
if sys.version_info[0] == 3:
|
|
from io import BytesIO
|
|
else:
|
|
try:
|
|
from cStringIO import StringIO as BytesIO # noqa
|
|
except ImportError:
|
|
from StringIO import StringIO as BytesIO # noqa
|
|
|
|
EX_SOFTWARE = 70
|
|
|
|
TERMSIGS_DEFAULT = (
|
|
'SIGHUP',
|
|
'SIGQUIT',
|
|
'SIGTERM',
|
|
'SIGUSR1',
|
|
'SIGUSR2'
|
|
)
|
|
|
|
TERMSIGS_FULL = (
|
|
'SIGHUP',
|
|
'SIGQUIT',
|
|
'SIGTRAP',
|
|
'SIGABRT',
|
|
'SIGEMT',
|
|
'SIGSYS',
|
|
'SIGPIPE',
|
|
'SIGALRM',
|
|
'SIGTERM',
|
|
'SIGXCPU',
|
|
'SIGXFSZ',
|
|
'SIGVTALRM',
|
|
'SIGPROF',
|
|
'SIGUSR1',
|
|
'SIGUSR2',
|
|
)
|
|
|
|
#: set by signal handlers just before calling exit.
|
|
#: if this is true after the sighandler returns it means that something
|
|
#: went wrong while terminating the process, and :func:`os._exit`
|
|
#: must be called ASAP.
|
|
_should_have_exited = [False]
|
|
|
|
|
|
def pickle_loads(s, load=pickle_load):
|
|
# used to support buffer objects
|
|
return load(BytesIO(s))
|
|
|
|
|
|
def maybe_setsignal(signum, handler):
|
|
try:
|
|
signal.signal(signum, handler)
|
|
except (OSError, AttributeError, ValueError, RuntimeError):
|
|
pass
|
|
|
|
|
|
def _shutdown_cleanup(signum, frame):
|
|
# we will exit here so if the signal is received a second time
|
|
# we can be sure that something is very wrong and we may be in
|
|
# a crashing loop.
|
|
if _should_have_exited[0]:
|
|
os._exit(EX_SOFTWARE)
|
|
maybe_setsignal(signum, signal.SIG_DFL)
|
|
_should_have_exited[0] = True
|
|
sys.exit(-(256 - signum))
|
|
|
|
|
|
def reset_signals(handler=_shutdown_cleanup, full=False):
|
|
for sig in TERMSIGS_FULL if full else TERMSIGS_DEFAULT:
|
|
try:
|
|
signum = getattr(signal, sig)
|
|
except AttributeError:
|
|
pass
|
|
else:
|
|
current = signal.getsignal(signum)
|
|
if current is not None and current != signal.SIG_IGN:
|
|
maybe_setsignal(signum, handler)
|
|
|
|
|
|
class restart_state(object):
|
|
RestartFreqExceeded = RestartFreqExceeded
|
|
|
|
def __init__(self, maxR, maxT):
|
|
self.maxR, self.maxT = maxR, maxT
|
|
self.R, self.T = 0, None
|
|
|
|
def step(self, now=None):
|
|
now = monotonic() if now is None else now
|
|
R = self.R
|
|
if self.T and now - self.T >= self.maxT:
|
|
# maxT passed, reset counter and time passed.
|
|
self.T, self.R = now, 0
|
|
elif self.maxR and self.R >= self.maxR:
|
|
# verify that R has a value as the result handler
|
|
# resets this when a job is accepted. If a job is accepted
|
|
# the startup probably went fine (startup restart burst
|
|
# protection)
|
|
if self.R: # pragma: no cover
|
|
self.R = 0 # reset in case someone catches the error
|
|
raise self.RestartFreqExceeded("%r in %rs" % (R, self.maxT))
|
|
# first run sets T
|
|
if self.T is None:
|
|
self.T = now
|
|
self.R += 1
|