示例#1
0
class PeriodicTask(VTask):
    """Task that executes `execute` at a specified interval
    
    You must either override the `INTERVAL` (seconds) class attribute, or
    pass a --{OPT_PREFIX}-interval in order for your task to run.
    """
    INTERVAL = None

    execute_duration_ms = samples(
        windows=[60, 240],
        types=[SampleType.AVG, SampleType.MAX, SampleType.MIN])
    n_iterations = counter()
    n_slow_iterations = counter()
    n_try_later = counter()

    interval = option(type=float,
                      metavar='SECONDS',
                      default=lambda cls: cls.INTERVAL,
                      help='How often this task should run [%(default)s] (s)')

    def execute(self, context=None):
        """Override this to perform some custom action periodically."""
        self.logger.debug('execute')

    def initTask(self):
        super(PeriodicTask, self).initTask()
        assert self.interval is not None

        # Register an event that we can more smartly wait on in case shutdown
        # is requested while we would be `sleep()`ing
        self.stop_event = Event()

    def stop(self):
        self.stop_event.set()
        super(PeriodicTask, self).stop()

    def _runloop(self):
        t0 = time.time()
        while not self.service._stop:
            try:
                self.execute()
            except TryLater:
                self.n_try_later.increment()
                continue

            self.n_iterations.increment()
            self.execute_duration_ms.add((time.time() - t0) * 1000)
            to_sleep = (t0 + self.interval) - time.time()
            if to_sleep > 0:
                if self.stop_event.wait(to_sleep):
                    return
            else:
                self.n_slow_iterations.increment()

            t0 = time.time()
示例#2
0
class CommandTask(TwistedTask):
    """A task that provides a useful API for executing other commands.

    Python's Popen() can be hard to use, especially if you are executing long
    running child processes, and need to handle various stdout, stderr, and
    process exit events asynchronously.

    This particular implementation relies on Twisted's ProcessProtocol, but it
    wraps it in a way that makes it mostly opaque.
    """
    LOOPLESS = True
    OPT_PREFIX = 'cmd'

    kill_timeout = option(type=float,
                          default=10.0,
                          help="Default shutdown kill timeout for outstanding "
                          "commands [%(default)s]")
    started = counter()
    finished = counter()

    def run(self,
            command,
            on_stdout=None,
            on_stderr=None,
            on_exit=None,
            line_buffered=True,
            kill_timeout=None,
            env=None):
        """Call this function to start a new child process running `command`.
        
        Additional callbacks, such as `on_stdout`, `on_stderr`, and `on_exit`,
        can be provided, that will receive a variety of parameters on the
        appropriate events.

        Line buffering can be disabled by passing `line_buffered`=False.

        Also, a custom `kill_timeout` (seconds) may be set that overrides the
        task default, in the event that a shutdown is received and you want
        to allow more time for the command to shut down."""
        self.logger.debug("task starting %s...", command)
        if isinstance(command, six.string_types):
            command = command.split(" ")

        # wrap on_exit with helper to remove registered comments
        on_exit = functools.partial(self._procExited, on_exit)

        proto = _ProcessProtocolAdapter(on_stdout,
                                        on_stderr,
                                        on_exit,
                                        line_buffered=line_buffered)

        if twisted.python.threadable.isInIOThread():
            result = self.reactor.spawnProcess(proto,
                                               executable=command[0],
                                               args=command)
        else:
            result = twisted.internet.threads.blockingCallFromThread(
                self.reactor,
                self.reactor.spawnProcess,
                proto,
                executable=command[0],
                args=command,
                env=env)

        self.outstanding[result] = kill_timeout

        self.started.increment()
        return result

    def initTask(self):
        super(CommandTask, self).initTask()
        self.outstanding = {}

    def _procExited(self, on_exit, proto, trans, reason):
        self.logger.debug("%s closed for %s", trans, reason)
        if on_exit is not None:
            on_exit(reason)

        self.outstanding.pop(trans)

        self.finished.increment()
        return None

    def join(self):
        """Overridden to block for process workers to shutdown / be killed."""
        # TODO: Conditions instead of sleep polling?
        while len(self.outstanding) > 0:
            time.sleep(0.250)

    def _killOutstanding(self, trans):
        if trans in self.outstanding:
            self.logger.info("Sending SIGKILL to %s", trans)
            trans.signalProcess(signal.SIGKILL)

    def stop(self):
        # twisted is pretty smart; the default signal handlers it installs
        # propagate SIGTERM to its children, so while we don't need to manually
        # TERM, we might still need to set some kill timeouts
        super(CommandTask, self).stop()

        for trans, kill_timeout in self.outstanding.items():
            if kill_timeout is None:
                kill_timeout = self.kill_timeout

            self.logger.info("Enqueuing kill for %s in %.1fs", trans,
                             kill_timeout)
            args = (kill_timeout, self._killOutstanding, trans)
            if twisted.python.threadable.isInIOThread():
                self.reactor.callLater(*args)
            else:
                self.reactor.callFromThread(self.reactor.callLater, *args)

    def isDoneWithReactor(self):
        """Overridden to keep reactor running until all commands finish."""
        return len(self.outstanding) == 0
示例#3
0
class TornadoHTTPTask(TornadoTask):
    """A loopless task that implements an HTTP server using Tornado.
    
    It is loopless because it depends on tornado's separate IOLoop task.  You
    will need to subclass this to do something more useful."""
    LOOPLESS = True
    OPT_PREFIX = 'http'
    DEFAULT_PORT = 0
    DEFAULT_HOST = ''
    DEFAULT_SOCK = ''

    requests = counter()
    #latency = samples(windows=[60, 3600],
    #                  types=[SampleType.AVG, SampleType.MIN, SampleType.MAX])

    host = option(metavar='HOST',
                  default=lambda cls: cls.DEFAULT_HOST,
                  help='Address to bind server to [%(default)s]')
    port = option(metavar='PORT',
                  default=lambda cls: cls.DEFAULT_PORT,
                  help='Port to run server on [%(default)s]')
    sock = option(metavar='PATH',
                  default=lambda cls: cls.DEFAULT_SOCK,
                  help='Default path to use for local file socket '
                  '[%(default)s]')
    group = option(name='sock-group',
                   metavar='GROUP',
                   default='',
                   help='Group to create unix files as [%(default)s]')

    def getApplicationConfig(self):
        """Override this to register custom handlers / routes."""
        return [
            ('/', HelloWorldHandler),
        ]

    def initTask(self):
        super(TornadoHTTPTask, self).initTask()

        self.app = tornado.web.Application(self.getApplicationConfig(),
                                           log_function=self.tornadoRequestLog)

        self.server = tornado.httpserver.HTTPServer(self.app)

        if self.sock:
            assert self.host == self.DEFAULT_HOST, \
                "Do not specify host *and* sock (%s, %s)" % \
                (self.host, self.sock)
            assert int(self.port) == self.DEFAULT_PORT, \
                "Do not specify port *and* sock (%s, %s)" % \
                (self.port, self.DEFAULT_PORT)

            gid, mode = -1, 0o600
            if self.group != '':
                e = grp.getgrnam(self.group)
                gid, mode = e.gr_gid, 0o660

            sock = tornado.netutil.bind_unix_socket(self.sock, mode=mode)
            if gid != -1:
                os.chown(self.sock, -1, gid)
            self.server.add_sockets([sock])
        else:
            self.server.listen(self.port, self.host)

        self.bound_addrs = []
        for sock in itervalues(self.server._sockets):
            sockaddr = sock.getsockname()
            self.bound_addrs.append(sockaddr)
            self.logger.info("%s Server Started on %s (port %s)", self.name,
                             sockaddr[0], sockaddr[1])

    @property
    def bound_v4_addrs(self):
        return [a[0] for a in self.bound_addrs if len(a) == 2]

    @property
    def bound_v6_addrs(self):
        return [a[0] for a in self.bound_addrs if len(a) == 4]

    def tornadoRequestLog(self, handler):
        self.requests.increment()

    def stop(self):
        super(TornadoHTTPTask, self).stop()
        self.server.stop()
示例#4
0
class PeriodicTask(VTask):
    """Task that executes `execute` at a specified interval

    You must either override the `INTERVAL` (seconds) class attribute, or
    pass a --{OPT_PREFIX}-interval in order for your task to run.
    """
    INTERVAL = None

    execute_duration_ms = samples(windows=[60, 240],
       types=[SampleType.AVG, SampleType.MAX, SampleType.MIN])
    n_iterations = counter()
    n_slow_iterations = counter()
    n_try_later = counter()

    interval = option(type=float, metavar='SECONDS',
                      default=lambda cls: cls.INTERVAL,
                      help='How often this task should run [%(default)s] (s)')

    def execute(self, context=None):
        """Override this to perform some custom action periodically."""
        self.logger.debug('execute')

    def execute_async(self):
        f = Future()

        if self.running:
            # There's a race condition here.  If the task has thrown but the
            # thread(s) haven't stopped yet, you can enqueue a future that will
            # never complete.
            self.__futures.put(f)
        else:
            # If the task has stopped (e.g., due to a previous error),
            # fail the future now and don't insert it into the queue.
            f.set_exception(RuntimeError("Worker not running"))

        return f

    def has_pending(self):
        return self.__futures.qsize() > 0

    def initTask(self):
        # Register an event that we can more smartly wait on in case shutdown
        # is requested while we would be `sleep()`ing
        self.stop_event = Event()
        self.__futures = queue.Queue()

        super(PeriodicTask, self).initTask()

        assert self.interval is not None, \
            "INTERVAL must be defined on %s or --%s-interval passed" % \
            (self.name, self.name)

    def stop(self):
        self.stop_event.set()
        super(PeriodicTask, self).stop()

    def _runloop(self):
        timer = Timer()
        timer.start()
        while not self.service._stop:
            try:
                result = self.execute()

                # On a successful result, notify all blocked futures.
                # Use pop like this to avoid race conditions.
                while self.__futures.qsize():
                    f = self.__futures.get()
                    f.set_result(result)

            except TryLater as e:
                if self._handle_try_later(e):
                    return

                continue
            except Exception as e:
                # On unhandled exceptions, set the exception on any async
                # blocked execute calls.
                while self.__futures.qsize():
                    f = self.__futures.get()
                    f.set_exception(e)
                raise

            self.n_iterations.increment()
            self.execute_duration_ms.add(timer.elapsed * 1000)
            to_sleep = self.interval - timer.elapsed
            if to_sleep > 0:
                if self.stop_event.wait(to_sleep):
                    return
            else:
                self.n_slow_iterations.increment()

            timer.start()

    def _handle_try_later(self, e):
        self.n_try_later.increment()
        if e.after is not None:
            self.logger.debug("TryLater (%s) thrown.  Retrying in %.2fs",
                e.message, e.after)
        else:
            self.logger.debug("TryLater (%s) thrown.  Retrying now",
                e.message)
        return self.stop_event.wait(e.after)
示例#5
0
class QueueTask(VTask):
    """Task that calls `execute` for all work put on its `queue`"""
    MAX_ITEMS = 0
    WORKERS = 1
    max_items = option(type=int,
                       default=lambda cls: cls.MAX_ITEMS,
                       help='Set a bounded queue length.  This may '
                       'cause unexpected deadlocks. [%(default)s]')
    workers = option(type=int,
                     default=lambda cls: cls.WORKERS,
                     help='Number of threads to spawn to work on items from '
                     'its queue. [%(default)s]')

    execute_duration_ms = samples(
        windows=[60, 240],
        types=[SampleType.AVG, SampleType.MAX, SampleType.MIN])
    n_trylater = counter()
    n_completed = counter()
    n_unhandled = counter()

    def execute(self, item, context):
        """Implement this in your QueueTask subclasses"""
        raise NotImplementedError()

    def _makeQueue(self):
        """Override this if you need a custom Queue implementation"""
        return queue.Queue(maxsize=self.max_items)

    def initTask(self):
        super(QueueTask, self).initTask()
        self.queue = self._makeQueue()
        self.counters['queue_depth'] = \
            CallbackCounter(lambda: self.queue.qsize())
        self._shutdown_sentinel = object()

    def stop(self):
        super(QueueTask, self).stop()
        self.queue.put(self._shutdown_sentinel)

    def submit(self, item):
        """Enqueue `item` into this task's Queue.  Returns a `Future`"""
        future = Future()
        work = ExecuteContext(item=item, future=future)
        self.queue.put(work)
        return future

    def map(self, items, timeout=None):
        """Enqueues `items` into the queue"""
        futures = map(self.submit, items)
        return [f.result(timeout) for f in futures]

    def _runloop(self):
        while not self.service._stop:
            try:
                item = self.queue.get(timeout=1.0)
                if item is self._shutdown_sentinel:
                    self.queue.put(item)
                    break
            except queue.Empty:
                continue

            # Create an ExecuteContext if we didn't have one
            if isinstance(item, ExecuteContext):
                context = item
                item = context.item
                context.raw_wrapped = False
            else:
                context = ExecuteContext(item=item)
                context.raw_wrapped = True

            try:
                context.start()
                result = self.execute(item, context)
                self.work_success(context, result)
            except TryLater:
                self.work_retry(context)
            except Exception as ex:
                self.work_fail(context, ex)

            finally:
                self.queue.task_done()

    def work_success(self, context, result):
        self.n_completed.increment()
        self.execute_duration_ms.add(context.elapsed * 1000.0)
        context.set_result(result)
        self.work_done(context)

    def work_retry(self, context):
        self.n_trylater.increment()
        context.attempt += 1
        self.work_done(context)
        self.queue.put(context)

    def work_fail(self, context, exception):
        self.n_unhandled.increment()
        self.execute_duration_ms.add(context.elapsed * 1000.0)
        handled = context.set_exception(exception)
        self.work_done(context)
        if not handled:
            raise

    def work_done(self, context):
        pass