139
class ServerWrapper(object):
140
def __init__(self, server, workers):
142
self.workers = workers
143
self.children = set()
147
class ProcessLauncher(object):
150
self.sigcaught = None
152
rfd, self.writepipe = os.pipe()
153
self.readpipe = eventlet.greenio.GreenPipe(rfd, 'r')
155
signal.signal(signal.SIGTERM, self._handle_signal)
156
signal.signal(signal.SIGINT, self._handle_signal)
158
def _handle_signal(self, signo, frame):
159
self.sigcaught = signo
162
# Allow the process to be killed again and die from natural causes
163
signal.signal(signal.SIGTERM, signal.SIG_DFL)
164
signal.signal(signal.SIGINT, signal.SIG_DFL)
166
def _pipe_watcher(self):
167
# This will block until the write end is closed when the parent
171
LOG.info(_('Parent process has died unexpectedly, exiting'))
175
def _child_process(self, server):
176
# Setup child signal handlers differently
178
signal.signal(signal.SIGTERM, signal.SIG_DFL)
179
raise SignalExit(signal.SIGTERM)
181
signal.signal(signal.SIGTERM, _sigterm)
182
# Block SIGINT and let the parent send us a SIGTERM
183
# signal.signal(signal.SIGINT, signal.SIG_IGN)
184
# This differs from the behavior in nova in that we dont ignore this
185
# It allows the non-wsgi services to be terminated properly
186
signal.signal(signal.SIGINT, _sigterm)
188
# Reopen the eventlet hub to make sure we don't share an epoll
189
# fd with parent and/or siblings, which would be bad
190
eventlet.hubs.use_hub()
192
# Close write to ensure only parent has it open
193
os.close(self.writepipe)
194
# Create greenthread to watch for parent to close pipe
195
eventlet.spawn(self._pipe_watcher)
197
# Reseed random number generator
200
launcher = Launcher()
201
launcher.run_server(server)
203
def _start_child(self, wrap):
204
if len(wrap.forktimes) > wrap.workers:
205
# Limit ourselves to one process a second (over the period of
206
# number of workers * 1 second). This will allow workers to
207
# start up quickly but ensure we don't fork off children that
208
# die instantly too quickly.
209
if time.time() - wrap.forktimes[0] < wrap.workers:
210
LOG.info(_('Forking too fast, sleeping'))
213
wrap.forktimes.pop(0)
215
wrap.forktimes.append(time.time())
219
# NOTE(johannes): All exceptions are caught to ensure this
220
# doesn't fallback into the loop spawning children. It would
221
# be bad for a child to spawn more children.
224
self._child_process(wrap.server)
225
except SignalExit as exc:
226
signame = {signal.SIGTERM: 'SIGTERM',
227
signal.SIGINT: 'SIGINT'}[exc.signo]
228
LOG.info(_('Caught %s, exiting'), signame)
230
except SystemExit as exc:
232
except BaseException:
233
LOG.exception(_('Unhandled exception'))
240
LOG.info(_('Started child %d'), pid)
242
wrap.children.add(pid)
243
self.children[pid] = wrap
247
def launch_server(self, server, workers=1):
248
wrap = ServerWrapper(server, workers)
250
LOG.info(_('Starting %d workers'), wrap.workers)
251
while self.running and len(wrap.children) < wrap.workers:
252
self._start_child(wrap)
254
def _wait_child(self):
256
# Don't block if no child processes have exited
257
pid, status = os.waitpid(0, os.WNOHANG)
260
except OSError as exc:
261
if exc.errno not in (errno.EINTR, errno.ECHILD):
265
if os.WIFSIGNALED(status):
266
sig = os.WTERMSIG(status)
267
LOG.info(_('Child %(pid)d killed by signal %(sig)d'), locals())
269
code = os.WEXITSTATUS(status)
270
LOG.info(_('Child %(pid)d exited with status %(code)d'), locals())
272
if pid not in self.children:
273
LOG.warning(_('pid %d not in child list'), pid)
276
wrap = self.children.pop(pid)
277
wrap.children.remove(pid)
281
"""Loop waiting on children to die and respawning as necessary."""
283
wrap = self._wait_child()
285
# Yield to other threads if no children have exited
286
# Sleep for a short time to avoid excessive CPU usage
288
eventlet.greenthread.sleep(.01)
291
while self.running and len(wrap.children) < wrap.workers:
292
self._start_child(wrap)
295
signame = {signal.SIGTERM: 'SIGTERM',
296
signal.SIGINT: 'SIGINT'}[self.sigcaught]
297
LOG.info(_('Caught %s, stopping children'), signame)
299
for pid in self.children:
301
os.kill(pid, signal.SIGTERM)
302
except OSError as exc:
303
if exc.errno != errno.ESRCH:
306
# Wait for children to die
308
LOG.info(_('Waiting on %d children to exit'), len(self.children))
131
313
class Service(object):
132
314
"""Service object for binaries running on hosts.
138
320
def __init__(self, host, binary, topic, manager, report_interval=None,
139
321
periodic_interval=None, periodic_fuzzy_delay=None,
322
service_name=None, *args, **kwargs):
142
324
self.binary = binary
143
325
self.topic = topic
144
326
self.manager_class_name = manager
145
327
manager_class = importutils.import_class(self.manager_class_name)
146
self.manager = manager_class(host=self.host, *args, **kwargs)
328
self.manager = manager_class(host=self.host,
329
service_name=service_name,
147
331
self.report_interval = report_interval
148
332
self.periodic_interval = periodic_interval
149
333
self.periodic_fuzzy_delay = periodic_fuzzy_delay