class gSup(gThread): def __init__(self, thread, signal, interval=5, timeout=600): self.thread = thread self.interval = interval self.timeout = timeout self.signal = signal super(gSup, self).__init__() def start_wait_child(self): self._ready_event = Event() self.signal.connect(self._on_thread_ready, sender=self.thread) self.thread.start() self._ready_event.wait() assert self._ready_event.ready() return self.thread def _on_thread_ready(self, **kwargs): self._ready_event.send(1) self.signal.disconnect(self._on_thread_ready) def run(self): self.debug("starting") interval = self.interval thread = self.start_wait_child() self.info("started") timeout = self.timeout critical = self.critical while not self.should_stop: try: pong = thread.ping(timeout) except (self.Timeout, Exception), exc: critical(SUP_ERROR_PING_TIMEOUT % (exc, )) _exit(0) if not pong: critical(SUP_ERROR_NOT_STARTED) _exit(0) sleep(interval)
class Branch(gThread): controller_cls = '.controller.Controller' httpd_cls = '.httpd.HttpServer' supervisor_cls = '.supervisor.Supervisor' intsup_cls = '.intsup.gSup' _components_ready = {} _components_shutdown = {} _presence_ready = 0 _ready = False def __init__(self, addrport='', id=None, loglevel=logging.INFO, logfile=None, without_httpd=False, numc=2, sup_interval=None, ready_event=None, colored=None, **kwargs): self.id = id or gen_unique_id() if isinstance(addrport, basestring): addr, _, port = addrport.partition(':') addrport = (addr, int(port) if port else 8000) self.addrport = addrport self.connection = celery.broker_connection() self.without_httpd = without_httpd self.logfile = logfile self.loglevel = loglevel self.numc = numc self.ready_event = ready_event self.exit_request = Event() self.colored = colored or term.colored(enabled=False) self.httpd = None gSup = find_symbol(self, self.intsup_cls) if not self.without_httpd: self.httpd = MockSup(instantiate(self, self.httpd_cls, addrport), signals.httpd_ready) self.supervisor = gSup(instantiate(self, self.supervisor_cls, sup_interval), signals.supervisor_ready) self.controllers = [gSup(instantiate(self, self.controller_cls, id='%s.%s' % (self.id, i), connection=self.connection, branch=self), signals.controller_ready) for i in xrange(1, numc + 1)] c = [self.supervisor] + self.controllers + [self.httpd] c = self.components = list(filter(None, c)) self._components_ready = dict(zip([z.thread for z in c], [False] * len(c))) for controller in self.controllers: if hasattr(controller.thread, 'presence'): self._components_ready[controller.thread.presence] = False self._components_shutdown = dict(self._components_ready) super(Branch, self).__init__() def _component_ready(self, sender=None, **kwargs): if not self._ready: self._components_ready[sender] = True if all(self._components_ready.values()): signals.branch_ready.send(sender=self) if self.ready_event: self.ready_event.send() self.ready_event = None self._ready = True def on_ready(self, **kwargs): pass def prepare_signals(self): signals.controller_ready.connect(self._component_ready) signals.httpd_ready.connect(self._component_ready) signals.supervisor_ready.connect(self._component_ready) signals.presence_ready.connect(self._component_ready) signals.branch_ready.connect(self.on_ready) signals.thread_post_shutdown.connect(self._component_shutdown) def run(self): state.is_branch = True signals.branch_startup_request.send(sender=self) self.prepare_signals() self.info('Starting with id %r', self.id) [g.start() for g in self.components] self.exit_request.wait() def stop(self): self.exit_request.send(1) super(Branch, self).stop() def after(self): for component in reversed(self.components): if self._components_ready[component.thread]: try: component.stop() except KeyboardInterrupt: pass except BaseException, exc: component.error('Error in shutdown: %r', exc)
class gThread(LogMixin): AlreadyStarted = AlreadyStartedError Timeout = Timeout #: Name of the thread, used in logs and such. name = None #: Greenlet instance of the thread, set when the thread is started. g = None #: Set when the thread is requested to stop. should_stop = False #: Set this to False if it is not possible to join the thread. joinable = True _exit_event = None _ping_queue = None _timers = None extra_startup_steps = 0 extra_shutdown_steps = 0 def __init__(self): self.name = self.name or self.__class__.__name__ # the exit event is sent when the thread exits, # and used by `join` to detect this. self._exit_event = Event() # we maintain a list of timers started by the thread, # so these can be cancelled at shutdown. self._timers = [] def before(self): """Called at the beginning of :meth:`start`.""" pass def run(self): raise NotImplementedError("gThreads must implement 'run'") def after(self): """Call after the thread has shut down.""" pass def start_periodic_timer(self, interval, fun, *args, **kwargs): """Apply function every ``interval`` seconds. :param interval: Interval in seconds (int/float). :param fun: The function to apply. :param \*args: Additional arguments to pass. :keyword \*\*kwargs: Additional keyword arguments to pass. :returns: entry object, with ``cancel`` and ``kill`` methods. """ entry = timer(interval, fun, *args, **kwargs) self._timers.append(entry) return entry def start(self): """Spawn green thread, and returns :class:`~eventlet.greenthread.GreenThread` instance.""" if self.g is None: self.before() signals.thread_pre_start.send(sender=self) self._ping_queue = Queue() g = self.g = self.spawn(self._crashsafe, self.run) g.link(self._on_exit) self.debug('%s spawned', self.name) signals.thread_post_start.send(sender=self) return g raise self.AlreadyStarted('cannot start thread twice') def stop(self, join=True, timeout=1e100): """Shutdown the thread. This will also cancel+kill any periodic timers registered by the thread. :keyword join: Given that the thread is :attr:`joinable`, if true will also wait until the thread exits (by calling :meth:`join`). :keyword timeout: Timeout for join (default is 1e+100). """ self.debug('shutdown initiated') signals.thread_pre_shutdown.send(sender=self) self.should_stop = True for entry in self._timers: self.debug('killing timer %r' % (entry, )) entry.cancel() entry.kill() if join and self.joinable: try: self.join(timeout) except self.Timeout: self.error( 'exceeded exit timeout (%s), will try to kill', timeout) self.kill() self.after() signals.thread_post_shutdown.send(sender=self) def join(self, timeout=None): """Wait until the thread exits. :keyword timeout: Timeout in seconds (int/float). :raises eventlet.Timeout: if the thread can't be joined before the provided timeout. """ with self.Timeout(timeout): signals.thread_pre_join.send(sender=self, timeout=timeout) self.debug('joining (%s)', timeout) self._exit_event.wait() signals.thread_post_join.send(sender=self) def respond_to_ping(self): try: self._ping_queue.get_nowait().send() except Empty: pass def _do_ping(self, timeout=None): with self.Timeout(timeout): event = Event() self._ping_queue.put_nowait(event) event.wait() return event.ready() def ping(self, timeout=None): if self.g and self._ping_queue is not None: return self._do_ping(timeout) return False def _on_exit(self, g): # called when the thread exits to unblock `join`. self._exit_event.send() def kill(self): """Kill the green thread.""" if self.g is not None: self.g.kill() def _crashsafe(self, fun, *args, **kwargs): try: fun(*args, **kwargs) signals.thread_exit.send(sender=self) self.debug('exiting') except Exception, exc: self.error('Thread crash detected: %r', exc) os._exit(0) except self.Timeout, exc: self.error('Thread raised timeout: %r', exc) os._exit(0)
def _do_ping(self, timeout=None): with self.Timeout(timeout): event = Event() self._ping_queue.put_nowait(event) event.wait() return event.ready()