Esempio n. 1
0
    def dedicated_worker(self, message, **settings):
        """
        Run workflow in a dedicated thread.
        """

        # Check if we need to dequeue message from an existing worker thread
        if "dequeue" in settings:
            threads = self._worker_threads.modify_all_for(
                settings["dequeue"],
                lambda t: t.context["messages"].remove(message))

            return {"dequeued": [t.name for t in threads]}

        # Check if we need to enqueue message to an existing worker thread
        if "enqueue" in settings:
            threads = self._worker_threads.modify_all_for(
                settings["enqueue"],
                lambda t: t.context["messages"].append(message))

            return {"enqueued": [t.name for t in threads]}

        # Exceptions will NOT kill worker thread as default behavior
        suppress_exceptions = settings.pop("suppress_exceptions", True)

        # Terminates worker thread after a successful run without warnings nor exceptions
        kill_upon_success = settings.pop("kill_upon_success", False)

        # Perform entire job iteration transactionally
        transactional = settings.pop("transactional", False)

        # Prepare function that performs actual work
        def do_work(thread, context):
            success = True

            # Loop through all messages found in thread context
            for message in list(context["messages"]
                                ):  # Copy list to allow changes while looping
                try:

                    # Run workflow
                    self._call_hook_for(message, "workflow", message)

                except Warning as wa:
                    success = False

                    # Register time of last warning in context
                    context["last_warning"] = datetime.datetime.utcnow(
                    ).isoformat()

                    # Also register all distinct warning messages in context
                    msg = str(wa)
                    context.setdefault("distinct_warnings",
                                       {}).setdefault(msg, 0)
                    context["distinct_warnings"][msg] += 1

                    # Only allow recurring warnings to be logged every minute
                    if context["distinct_warnings"][msg] > 3 \
                        and timer() - getattr(thread, "warning_log_timer", 0) < 60:
                        return
                    setattr(thread, "warning_log_timer", timer())

                    # Go ahead and log the warning
                    if context["distinct_warnings"][msg] > 1:
                        log.info(
                            "Recurring warning ({:} times) in worker thread '{:}': {:}"
                            .format(context["distinct_warnings"][msg],
                                    thread.name, wa))
                    else:
                        log.info("Warning in worker thread '{:}': {:}".format(
                            thread.name, wa))

                except Exception as ex:
                    success = False

                    # Register time of last error in context
                    context["last_error"] = datetime.datetime.utcnow(
                    ).isoformat()

                    # Also register all distinct error messages in context
                    msg = str(ex)
                    context.setdefault("distinct_errors",
                                       {}).setdefault(msg, 0)
                    context["distinct_errors"][msg] += 1

                    # Only allow recurring exceptions to be logged every minute
                    if suppress_exceptions and context["distinct_errors"][msg] > 3 \
                        and timer() - getattr(thread, "exception_log_timer", 0) < 60:
                        return
                    setattr(thread, "exception_log_timer", timer())

                    # Go ahead and log the exception
                    if context["distinct_errors"][msg] > 1:
                        log.exception(
                            "Recurring exception ({:} times) in worker thread '{:}' while running workflow for message: {:}"
                            .format(context["distinct_errors"][msg],
                                    thread.name, message))
                    else:
                        log.exception(
                            "Exception in worker thread '{:}' while running workflow for message: {:}"
                            .format(thread.name, message))

                    # Finally suppress or propagate the exception
                    if suppress_exceptions:
                        if transactional:
                            log.info(
                                "Suppressing prior exception in worker thread '{:}' and skips any following work"
                                .format(thread.name))

                            break
                        else:
                            log.info(
                                "Suppressing prior exception in worker thread '{:}' and continues as normal"
                                .format(thread.name))
                    else:
                        raise

            # Clear any warnings and errors on success
            if success:
                context.pop("distinct_warnings", None)
                context.pop("distinct_errors", None)

            if kill_upon_success and success:
                thread.kill()

                if log.isEnabledFor(logging.DEBUG):
                    log.debug("Killed worker thread '{:}' upon successful run".
                              format(thread.name))

        # Auto start is default
        auto_start = settings.pop("start", True)

        # Add new worker thread
        thread = threading_more.WorkerThread(
            target=self._synchronize_wrapper(self._hook_lock, do_work)
            if transactional else do_work,
            context={"messages": [message] if message else []},
            registry=self._worker_threads,  # Registers thread in registry
            **settings)  # Pass additional settings

        if auto_start:
            thread.start()

            return {"started": thread.name}

        return {"created": thread.name}
Esempio n. 2
0
    def dedicated_worker(self, message, **settings):
        """
        Run workflow in a dedicated thread.
        """

        # Check if we need to dequeue message from an existing worker thread
        if "dequeue" in settings:
            threads = self._worker_threads.modify_all_for(settings["dequeue"],
                lambda t: t.context["messages"].remove(message))

            return {
                "dequeued": [t.name for t in threads]
            }

        # Check if we need to enqueue message to an existing worker thread
        if "enqueue" in settings:
            threads = self._worker_threads.modify_all_for(settings["enqueue"],
                lambda t: t.context["messages"].append(message))

            return {
                "enqueued": [t.name for t in threads]
            }

        # Exceptions will kill worker thread as default behavior
        suppress_exceptions = settings.pop("suppress_exceptions", False)

        # Terminates worker thread after a successful run without warnings nor exceptions
        kill_upon_success = settings.pop("kill_upon_success", False)

        # Prepare function that performs actual work
        def do_work(thread, context):
            success = True

            # Loop through all messages found in thread context
            for message in list(context["messages"]):  # Copy list to allow changes while looping
                try:

                    # Run workflow
                    self._call_hook_for(message, "workflow", message)

                except Warning as w:
                    log.info("Warning in worker thread '{:}': {:}".format(thread.name, w))
                    success = False

                except Exception:
                    log.exception("Exception in worker thread '{:}' while running workflow for message: {:}".format(thread.name, message))
                    success = False

                    if suppress_exceptions:
                        log.warn("Suppressing above exception and continues as nothing happened")
                    else:
                        raise

            if kill_upon_success and success:
                thread.kill()

                if log.isEnabledFor(logging.DEBUG):
                    log.debug("Killed worker thread '{:}' upon successful run".format(thread.name))

        # Auto start is default
        auto_start = settings.pop("start", True)

        # Add new worker thread
        thread = threading_more.WorkerThread(
            target=do_work,
            context={"messages": [message] if message else []},
            registry=self._worker_threads,  # Registers thread in registry
            **settings)  # Pass additional settings

        if auto_start:
            thread.start()

            return {
                "started": thread.name
            }

        return {
            "created": thread.name
        }