# vim:fileencoding=utf-8:noet from __future__ import (unicode_literals, division, absolute_import, print_function) from threading import Thread, Lock, Event from types import MethodType from powerline.lib.monotonic import monotonic from powerline.segments import Segment class MultiRunnedThread(object): daemon = True def __init__(self): self.thread = None def is_alive(self): return self.thread and self.thread.is_alive() def start(self): self.shutdown_event.clear() self.thread = Thread(target=self.run) self.thread.daemon = self.daemon self.thread.start() def join(self, *args, **kwargs): if self.thread: return self.thread.join(*args, **kwargs) return None class ThreadedSegment(Segment, MultiRunnedThread): min_sleep_time = 0.1 update_first = True interval = 1 daemon = False argmethods = ('render', 'set_state') def __init__(self): super(ThreadedSegment, self).__init__() self.run_once = True self.crashed = False self.crashed_value = None self.update_value = None self.updated = False def __call__(self, pl, update_first=True, **kwargs): if self.run_once: self.pl = pl self.set_state(**kwargs) update_value = self.get_update_value(True) elif not self.is_alive(): # Without this we will not have to wait long until receiving bug “I # opened vim, but branch information is only shown after I move # cursor”. # # If running once .update() is called in __call__. self.start() update_value = self.get_update_value(self.do_update_first) else: update_value = self.get_update_value(not self.updated) if self.crashed: return self.crashed_value return self.render(update_value, update_first=update_first, pl=pl, **kwargs) def set_update_value(self): try: self.update_value = self.update(self.update_value) except Exception as e: self.exception('Exception while updating: {0}', str(e)) self.crashed = True except KeyboardInterrupt: self.warn('Caught keyboard interrupt while updating') self.crashed = True else: self.crashed = False self.updated = True def get_update_value(self, update=False): if update: self.set_update_value() return self.update_value def run(self): if self.do_update_first: start_time = monotonic() while True: self.shutdown_event.wait(max(self.interval - (monotonic() - start_time), self.min_sleep_time)) if self.shutdown_event.is_set(): break start_time = monotonic() self.set_update_value() else: while not self.shutdown_event.is_set(): start_time = monotonic() self.set_update_value() self.shutdown_event.wait(max(self.interval - (monotonic() - start_time), self.min_sleep_time)) def shutdown(self): self.shutdown_event.set() if self.daemon and self.is_alive(): # Give the worker thread a chance to shutdown, but don’t block for # too long self.join(0.01) def set_interval(self, interval=None): # Allowing “interval” keyword in configuration. # Note: Here **kwargs is needed to support foreign data, in subclasses # it can be seen in a number of places in order to support # .set_interval(). interval = interval or getattr(self, 'interval') self.interval = interval def set_state(self, interval=None, update_first=True, shutdown_event=None, **kwargs): self.set_interval(interval) self.shutdown_event = shutdown_event or Event() self.do_update_first = update_first and self.update_first self.updated = self.updated or (not self.do_update_first) def startup(self, pl, **kwargs): self.run_once = False self.pl = pl self.daemon = pl.use_daemon_threads self.set_state(**kwargs) if not self.is_alive(): self.start() def critical(self, *args, **kwargs): self.pl.critical(prefix=self.__class__.__name__, *args, **kwargs) def exception(self, *args, **kwargs): self.pl.exception(prefix=self.__class__.__name__, *args, **kwargs) def info(self, *args, **kwargs): self.pl.info(prefix=self.__class__.__name__, *args, **kwargs) def error(self, *args, **kwargs): self.pl.error(prefix=self.__class__.__name__, *args, **kwargs) def warn(self, *args, **kwargs): self.pl.warn(prefix=self.__class__.__name__, *args, **kwargs) def debug(self, *args, **kwargs): self.pl.debug(prefix=self.__class__.__name__, *args, **kwargs) def argspecobjs(self): for name in self.argmethods: try: yield name, getattr(self, name) except AttributeError: pass def additional_args(self): return (('interval', self.interval),) _omitted_args = { 'render': (0,), 'set_state': ('shutdown_event',), } def omitted_args(self, name, method): ret = self._omitted_args.get(name, ()) if isinstance(getattr(self, name, None), MethodType): ret = tuple((i + 1 if isinstance(i, int) else i for i in ret)) return ret class KwThreadedSegment(ThreadedSegment): update_first = True argmethods = ('render', 'set_state', 'key', 'render_one') def __init__(self): super(KwThreadedSegment, self).__init__() self.updated = True self.update_value = ({}, set()) self.write_lock = Lock() self.new_queries = [] @staticmethod def key(**kwargs): return frozenset(kwargs.items()) def render(self, update_value, update_first, key=None, after_update=False, **kwargs): queries, crashed = update_value if key is None: key = self.key(**kwargs) if key in crashed: return self.crashed_value try: update_state = queries[key][1] except KeyError: with self.write_lock: self.new_queries.append(key) if self.do_update_first or self.run_once: if after_update: self.error('internal error: value was not computed even though update_first was set') update_state = None else: return self.render( update_value=self.get_update_value(True), update_first=False, key=key, after_update=True, **kwargs ) else: update_state = None return self.render_one(update_state, **kwargs) def update_one(self, crashed, updates, key): try: updates[key] = (monotonic(), self.compute_state(key)) except Exception as e: self.exception('Exception while computing state for {0!r}: {1}', key, str(e)) crashed.add(key) except KeyboardInterrupt: self.warn('Interrupt while computing state for {0!r}', key) crashed.add(key) def update(self, old_update_value): updates = {} crashed = set() update_value = (updates, crashed) queries = old_update_value[0] new_queries = self.new_queries with self.write_lock: self.new_queries = [] for key, (last_query_time, state) in queries.items(): if last_query_time < monotonic() < last_query_time + self.interval: updates[key] = (last_query_time, state) else: self.update_one(crashed, updates, key) for key in new_queries: self.update_one(crashed, updates, key) return update_value def set_state(self, interval=None, update_first=True, shutdown_event=None, **kwargs): self.set_interval(interval) self.do_update_first = update_first and self.update_first self.shutdown_event = shutdown_event or Event() @staticmethod def render_one(update_state, **kwargs): return update_state _omitted_args = { 'render': ('update_value', 'key', 'after_update'), 'set_state': ('shutdown_event',), 'render_one': (0,), }