1 cotyledon._service_manager.ServiceManager
1.1 add()
service_id = uuid.uuid4()
self._services[service_id] = _service.ServiceConfig(service_id, service, workers, args, kwargs)
1.2 run()
def run(self):
"""Start and supervise services workers
This method will start and supervise all children processes
until the master process asked to shutdown by a SIGTERM.
All spawned processes are part of the same unix process group.
"""
# 删NOTIFY_SOCKET,确保只通知一次
self._systemd_notify_once()
self._child_supervisor = _utils.spawn(self._child_supervisor_thread)
self._wait_forever()
1.3 register_hooks()
if on_terminate is not None:
_utils.check_callable(on_terminate, 'on_terminate')
self._hooks['terminate'].append(on_terminate)
if on_reload is not None:
_utils.check_callable(on_reload, 'on_reload')
self._hooks['reload'].append(on_reload)
if on_new_worker is not None:
_utils.check_callable(on_new_worker, 'on_new_worker')
self._hooks['new_worker'].append(on_new_worker)
if on_dead_worker is not None:
_utils.check_callable(on_dead_worker, 'on_dead_worker')
self._hooks['dead_worker'].append(on_dead_worker)
1.4 _child_supervisor_thread
def _child_supervisor_thread(self):
while not self._dead.is_set():
self._got_sig_chld.wait()
self._got_sig_chld.clear()
info = self._get_last_worker_died()
while info is not None:
if self._dead.is_set():
return
service_id, worker_id = info
self._start_worker(service_id, worker_id)
info = self._get_last_worker_died()
self._adjust_workers()
1.5 _get_last_worker_died
for service_id in list(self._running_services.keys()):
# We copy the list to clean the orignal one
processes = list(self._running_services[service_id].items())
for process, worker_id in processes:
if not process.is_alive():
self._run_hooks('dead_worker', service_id, worker_id,
process.exitcode)
if process.exitcode < 0:
sig = _utils.signal_to_name(process.exitcode)
LOG.info('Child %(pid)d killed by signal %(sig)s',
dict(pid=process.pid, sig=sig))
else:
LOG.info('Child %(pid)d exited with status %(code)d',
dict(pid=process.pid, code=process.exitcode))
del self._running_services[service_id][process]
return service_id, worker_id
1.6 init
def __init__(self, wait_interval=0.01, graceful_shutdown_timeout=60):
"""Creates the ServiceManager object
:param wait_interval: time between each new process spawn
:type wait_interval: float
"""
if self._process_runner_already_created:
raise RuntimeError("Only one instance of ServiceManager per "
"application is allowed")
ServiceManager._process_runner_already_created = True
super(ServiceManager, self).__init__()
# We use OrderedDict to start services in adding order
self._services = collections.OrderedDict()
self._running_services = collections.defaultdict(dict)
self._forktimes = []
self._graceful_shutdown_timeout = graceful_shutdown_timeout
self._wait_interval = wait_interval
self._dead = threading.Event()
# NOTE(sileht): Set it on startup, so first iteration
# will spawn initial workers
self._got_sig_chld = threading.Event()
self._got_sig_chld.set()
self._child_supervisor = None
self._hooks = {
'terminate': [],
'reload': [],
'new_worker': [],
'dead_worker': [],
}
_utils.setproctitle("%s: master process [%s]" %
(_utils.get_process_name(), " ".join(sys.argv)))
# Try to create a session id if possible
try:
os.setsid()
except (OSError, AttributeError):
pass
self._death_detection_pipe = multiprocessing.Pipe(duplex=False)
signal.signal(signal.SIGINT, self._fast_exit)
if os.name == 'posix':
signal.signal(signal.SIGCHLD, self._signal_catcher)
1.7 _adjust_worker()
def _adjust_workers(self):
for service_id, conf in self._services.items():
running_workers = len(self._running_services[service_id])
if running_workers < conf.workers:
for worker_id in range(running_workers, conf.workers):
self._start_worker(service_id, worker_id)
elif running_workers > conf.workers:
for worker_id in range(running_workers, conf.workers):
self._stop_worker(service_id, worker_id)
1.8 _start_worker()
def _start_worker(self, service_id, worker_id):
self._slowdown_respawn_if_needed()
if os.name == "posix":
fds = [self.signal_pipe_w, self.signal_pipe_r]
else:
fds = []
# Create and run a new service
p = _utils.spawn_process(
_service.ServiceWorker.create_and_wait,
self._services[service_id],
service_id,
worker_id,
self._death_detection_pipe,
self._hooks['new_worker'],
self._graceful_shutdown_timeout,
fds_to_close=fds)
self._running_services[service_id][p] = worker_id
2 ServiceConfig
2.1 init
class ServiceConfig(object):
def __init__(self, service_id, service, workers, args, kwargs):
self.service = service
self.workers = workers
self.args = args
self.kwargs = kwargs
self.service_id = service_id
3 cotyledon.oslo_config_glue
3.1 setup
主要功能:给servicemanager各阶段注册回调函数
# 加载log_options和graceful_shutdown_time
conf.register_opts(service_opts)
# Set cotyledon options from oslo config options
# 将上述加载两个配置项赋值给service_manager
_load_service_manager_options(service_manager, conf)
def _service_manager_reload():
# 重新加载配置文件
_configfile_reload(conf, reload_method)
_load_service_manager_options(service_manager, conf)
if os.name != "posix":
# NOTE(sileht): reloading can't be supported oslo.config is not pickle
# But we don't care SIGHUP is not support on window
return
# 给创worker和reload注册回调函数
service_manager.register_hooks(
on_new_worker=functools.partial(
_new_worker_hook, conf, reload_method),
on_reload=_service_manager_reload)
4 cotyledon._utils.SignalManager
4.1 _wait_forever
def _wait_forever(self):
# Wait forever
while True:
# Check if signals have been received
if os.name == "posix":
self._empty_signal_pipe()
self._run_signal_handlers()
if os.name == "posix":
# NOTE(sileht): we cannot use threading.Event().wait(),
# threading.Thread().join(), or time.sleep() because signals
# can be missed when received by non-main threads
# (https://bugs.python.org/issue5315)
# So we use select.select() alone, we will receive EINTR or
# will read data from signal_r when signal is emitted and
# cpython calls PyErr_CheckSignals() to run signals handlers
# That looks perfect to ensure handlers are run and run in the
# main thread
try:
select.select([self.signal_pipe_r], [], [])
except select.error as e:
if e.args[0] != errno.EINTR:
raise
else:
# NOTE(sileht): here we do only best effort
# and wake the loop periodically, set_wakeup_fd
# doesn't work on non posix platform so
# 1 seconds have been picked with the advice of a dice.
time.sleep(1)
# NOTE(sileht): We emulate SIGCHLD, _service_manager
# will just check often for dead child
self._signals_received.append(SIGCHLD)
4.2 init
def __init__(self):
# Setup signal fd, this allows signal to behave correctly
if os.name == 'posix':
self.signal_pipe_r, self.signal_pipe_w = os.pipe()
self._set_nonblock(self.signal_pipe_r)
self._set_nonblock(self.signal_pipe_w)
signal.set_wakeup_fd(self.signal_pipe_w)
self._signals_received = collections.deque()
signal.signal(signal.SIGINT, signal.SIG_DFL)
if os.name == 'posix':
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
signal.signal(signal.SIGTERM, self._signal_catcher)
signal.signal(signal.SIGALRM, self._signal_catcher)
signal.signal(signal.SIGHUP, self._signal_catcher)
else:
# currently a noop on window...
signal.signal(signal.SIGTERM, self._signal_catcher)
# FIXME(sileht): should allow to catch signal CTRL_BREAK_EVENT,
# but we to create the child process with CREATE_NEW_PROCESS_GROUP
# to make this work, so current this is a noop for later fix
signal.signal(signal.SIGBREAK, self._signal_catcher)
分类:openstack