Example #1
0
    def __init__(self, hub):
        self._hub_wref = wref(hub, self._on_hub_gc)
        self.should_run = True

        # Must be installed in the thread that the hub is running in;
        # the trace function is threadlocal
        assert get_thread_ident() == hub.thread_ident
        self._greenlet_tracer = GreenletTracer()

        self._monitoring_functions = [
            _MonitorEntry(self.monitor_blocking,
                          GEVENT_CONFIG.max_blocking_time)
        ]
        self._calculated_sleep_time = GEVENT_CONFIG.max_blocking_time
        # Create the actual monitoring thread. This is effectively a "daemon"
        # thread.
        self.monitor_thread_ident = start_new_thread(self, ())

        # We must track the PID to know if your thread has died after a fork
        self.pid = os.getpid()
Example #2
0
def limit_thread_cpu_usage_by_time() -> None:
    """This will enable Gevent's monitoring thread, and if a Greenlet uses the
    CPU for longer than `max_blocking_time` it will be killed.

    This will result in the whole process being killed, since exceptions are
    propagate to the top-level. The goal here is to detect slow functions that
    have to be optimized.
    """
    gevent.config.monitor_thread = True
    gevent.config.max_blocking_time = 10.0

    # The monitoring thread will use the trace api just like the TraceSampler
    # and the SwitchMonitoring. Sadly there is no API to uninstall the thread,
    # but this should not be a problem.
    monitor_thread = gevent.get_hub().start_periodic_monitoring_thread()

    # This code must not use the tracer from the monitor_thread because calls
    # to `did_block_hub` will reset its internal state. If two threads use the
    # same underlying tracer false positives will happen, because the switch
    # counter will be artifically reset.
    greenlet_tracer = GreenletTracer()

    def kill_offender(hub: Hub) -> None:
        if greenlet_tracer.did_block_hub(hub):
            active_greenlet = greenlet_tracer.active_greenlet

            msg = ""
            if monitor_thread._tracer.active_greenlet != active_greenlet:
                msg = (
                    f"Mismatch values for the active_greenlet among the "
                    f"monitor_thread and deubgging tracer, this either means "
                    f"there is a bug in the trace chain (the wrong values are "
                    f"forwarded), or that one of the trace functions was wrongly "
                    f"uninstalled. Active greenlets "
                    f"monitor_thread={monitor_thread._tracer.active_greenlet} "
                    f"debug_tracer={active_greenlet}.")

            hub.loop.run_callback(lambda: active_greenlet.throw(
                RaidenUnrecoverableError(
                    f"A greenlet used the CPU for longer than "
                    f"{gevent.config.max_blocking_time} seconds, killing it.{msg}"
                )))

    monitor_thread.add_monitoring_function(kill_offender,
                                           gevent.config.max_blocking_time)
Example #3
0
    def __init__(self, hub):
        self._hub_wref = wref(hub, self._on_hub_gc)
        self.should_run = True

        # Must be installed in the thread that the hub is running in;
        # the trace function is threadlocal
        assert get_thread_ident() == hub.thread_ident
        self._greenlet_tracer = GreenletTracer()

        self._monitoring_functions = [_MonitorEntry(self.monitor_blocking,
                                                    GEVENT_CONFIG.max_blocking_time)]
        self._calculated_sleep_time = GEVENT_CONFIG.max_blocking_time
        # Create the actual monitoring thread. This is effectively a "daemon"
        # thread.
        self.monitor_thread_ident = start_new_thread(self, ())

        # We must track the PID to know if your thread has died after a fork
        self.pid = os.getpid()
Example #4
0
class PeriodicMonitoringThread(object):
    # This doesn't extend threading.Thread because that gets monkey-patched.
    # We use the low-level 'start_new_thread' primitive instead.

    # The amount of seconds we will sleep when we think we have nothing
    # to do.
    inactive_sleep_time = 2.0

    # The absolute minimum we will sleep, regardless of
    # what particular monitoring functions want to say.
    min_sleep_time = 0.005

    # The minimum period in seconds at which we will check memory usage.
    # Getting memory usage is fairly expensive.
    min_memory_monitor_period = 2

    # A list of _MonitorEntry objects: [(function(hub), period, last_run_time))]
    # The first entry is always our entry for self.monitor_blocking
    _monitoring_functions = None

    # The calculated min sleep time for the monitoring functions list.
    _calculated_sleep_time = None

    # A boolean value that also happens to capture the
    # memory usage at the time we exceeded the threshold. Reset
    # to 0 when we go back below.
    _memory_exceeded = 0

    # The instance of GreenletTracer we're using
    _greenlet_tracer = None

    def __init__(self, hub):
        self._hub_wref = wref(hub, self._on_hub_gc)
        self.should_run = True

        # Must be installed in the thread that the hub is running in;
        # the trace function is threadlocal
        assert get_thread_ident() == hub.thread_ident
        self._greenlet_tracer = GreenletTracer()

        self._monitoring_functions = [
            _MonitorEntry(self.monitor_blocking,
                          GEVENT_CONFIG.max_blocking_time)
        ]
        self._calculated_sleep_time = GEVENT_CONFIG.max_blocking_time
        # Create the actual monitoring thread. This is effectively a "daemon"
        # thread.
        self.monitor_thread_ident = start_new_thread(self, ())

        # We must track the PID to know if your thread has died after a fork
        self.pid = os.getpid()

    def _on_fork(self):
        # Pseudo-standard method that resolver_ares and threadpool
        # also have, called by hub.reinit()
        pid = os.getpid()
        if pid != self.pid:
            self.pid = pid
            self.monitor_thread_ident = start_new_thread(self, ())

    @property
    def hub(self):
        return self._hub_wref()

    def monitoring_functions(self):
        # Return a list of _MonitorEntry objects

        # Update max_blocking_time each time.
        mbt = GEVENT_CONFIG.max_blocking_time  # XXX: Events so we know when this changes.
        if mbt != self._monitoring_functions[0].period:
            self._monitoring_functions[0].period = mbt
            self._calculated_sleep_time = min(
                x.period for x in self._monitoring_functions)
        return self._monitoring_functions

    def add_monitoring_function(self, function, period):
        if not callable(function):
            raise ValueError("function must be callable")

        if period is None:
            # Remove.
            self._monitoring_functions = [
                x for x in self._monitoring_functions if x.function != function
            ]
        elif period <= 0:
            raise ValueError("Period must be positive.")
        else:
            # Add or update period
            entry = _MonitorEntry(function, period)
            self._monitoring_functions = [
                x if x.function != function else entry
                for x in self._monitoring_functions
            ]
            if entry not in self._monitoring_functions:
                self._monitoring_functions.append(entry)
        self._calculated_sleep_time = min(x.period
                                          for x in self._monitoring_functions)

    def calculate_sleep_time(self):
        min_sleep = self._calculated_sleep_time
        if min_sleep <= 0:
            # Everyone wants to be disabled. Sleep for a longer period of
            # time than usual so we don't spin unnecessarily. We might be
            # enabled again in the future.
            return self.inactive_sleep_time
        return max((min_sleep, self.min_sleep_time))

    def kill(self):
        if not self.should_run:
            # Prevent overwriting trace functions.
            return
        # Stop this monitoring thread from running.
        self.should_run = False
        # Uninstall our tracing hook
        self._greenlet_tracer.kill()

    def _on_hub_gc(self, _):
        self.kill()

    def __call__(self):
        # The function that runs in the monitoring thread.
        # We cannot use threading.current_thread because it would
        # create an immortal DummyThread object.
        getcurrent().gevent_monitoring_thread = wref(self)

        try:
            while self.should_run:
                functions = self.monitoring_functions()
                assert functions
                sleep_time = self.calculate_sleep_time()

                thread_sleep(sleep_time)

                # Make sure the hub is still around, and still active,
                # and keep it around while we are here.
                hub = self.hub
                if not hub:
                    self.kill()

                if self.should_run:
                    this_run = perf_counter()
                    for entry in functions:
                        f = entry.function
                        period = entry.period
                        last_run = entry.last_run_time
                        if period and last_run + period <= this_run:
                            entry.last_run_time = this_run
                            f(hub)
                del hub  # break our reference to hub while we sleep

        except SystemExit:
            pass
        except:  # pylint:disable=bare-except
            # We're a daemon thread, so swallow any exceptions that get here
            # during interpreter shutdown.
            if not sys or not sys.stderr:  # pragma: no cover
                # Interpreter is shutting down
                pass
            else:
                hub = self.hub
                if hub is not None:
                    # XXX: This tends to do bad things like end the process, because we
                    # try to switch *threads*, which can't happen. Need something better.
                    hub.handle_error(self, *sys.exc_info())

    def monitor_blocking(self, hub):
        # Called periodically to see if the trace function has
        # fired to switch greenlets. If not, we will print
        # the greenlet tree.

        # For tests, we return a true value when we think we found something
        # blocking

        did_block = self._greenlet_tracer.did_block_hub(hub)
        if not did_block:
            return

        active_greenlet = did_block[1]
        report = self._greenlet_tracer.did_block_hub_report(
            hub, active_greenlet,
            dict(greenlet_stacks=False,
                 current_thread_ident=self.monitor_thread_ident))

        stream = hub.exception_stream
        for line in report:
            # Printing line by line may interleave with other things,
            # but it should also prevent a "reentrant call to print"
            # when the report is large.
            print(line, file=stream)

        notify(
            EventLoopBlocked(active_greenlet, GEVENT_CONFIG.max_blocking_time,
                             report))
        return (active_greenlet, report)

    def ignore_current_greenlet_blocking(self):
        self._greenlet_tracer.ignore_current_greenlet_blocking()

    def monitor_current_greenlet_blocking(self):
        self._greenlet_tracer.monitor_current_greenlet_blocking()

    def _get_process(self):  # pylint:disable=method-hidden
        proc = get_this_psutil_process()
        self._get_process = lambda: proc
        return proc

    def can_monitor_memory_usage(self):
        return self._get_process() is not None

    def install_monitor_memory_usage(self):
        # Start monitoring memory usage, if possible.
        # If not possible, emit a warning.
        if not self.can_monitor_memory_usage():
            import warnings
            warnings.warn("Unable to monitor memory usage. Install psutil.",
                          MonitorWarning)
            return

        self.add_monitoring_function(
            self.monitor_memory_usage,
            max(GEVENT_CONFIG.memory_monitor_period,
                self.min_memory_monitor_period))

    def monitor_memory_usage(self, _hub):
        max_allowed = GEVENT_CONFIG.max_memory_usage
        if not max_allowed:
            # They disabled it.
            return -1  # value for tests

        rusage = self._get_process().memory_full_info()
        # uss only documented available on Windows, Linux, and OS X.
        # If not available, fall back to rss as an aproximation.
        mem_usage = getattr(rusage, 'uss', 0) or rusage.rss

        event = None  # Return value for tests

        if mem_usage > max_allowed:
            if mem_usage > self._memory_exceeded:
                # We're still growing
                event = MemoryUsageThresholdExceeded(mem_usage, max_allowed,
                                                     rusage)
                notify(event)
            self._memory_exceeded = mem_usage
        else:
            # we're below. Were we above it last time?
            if self._memory_exceeded:
                event = MemoryUsageUnderThreshold(mem_usage, max_allowed,
                                                  rusage,
                                                  self._memory_exceeded)
                notify(event)
            self._memory_exceeded = 0

        return event

    def __repr__(self):
        return '<%s at %s in thread %s greenlet %r for %r>' % (
            self.__class__.__name__, hex(id(self)),
            hex(self.monitor_thread_ident), getcurrent(), self._hub_wref())
Example #5
0
class PeriodicMonitoringThread(object):
    # This doesn't extend threading.Thread because that gets monkey-patched.
    # We use the low-level 'start_new_thread' primitive instead.

    # The amount of seconds we will sleep when we think we have nothing
    # to do.
    inactive_sleep_time = 2.0

    # The absolute minimum we will sleep, regardless of
    # what particular monitoring functions want to say.
    min_sleep_time = 0.005

    # The minimum period in seconds at which we will check memory usage.
    # Getting memory usage is fairly expensive.
    min_memory_monitor_period = 2

    # A list of _MonitorEntry objects: [(function(hub), period, last_run_time))]
    # The first entry is always our entry for self.monitor_blocking
    _monitoring_functions = None

    # The calculated min sleep time for the monitoring functions list.
    _calculated_sleep_time = None

    # A boolean value that also happens to capture the
    # memory usage at the time we exceeded the threshold. Reset
    # to 0 when we go back below.
    _memory_exceeded = 0

    # The instance of GreenletTracer we're using
    _greenlet_tracer = None

    def __init__(self, hub):
        self._hub_wref = wref(hub, self._on_hub_gc)
        self.should_run = True

        # Must be installed in the thread that the hub is running in;
        # the trace function is threadlocal
        assert get_thread_ident() == hub.thread_ident
        self._greenlet_tracer = GreenletTracer()

        self._monitoring_functions = [_MonitorEntry(self.monitor_blocking,
                                                    GEVENT_CONFIG.max_blocking_time)]
        self._calculated_sleep_time = GEVENT_CONFIG.max_blocking_time
        # Create the actual monitoring thread. This is effectively a "daemon"
        # thread.
        self.monitor_thread_ident = start_new_thread(self, ())

        # We must track the PID to know if your thread has died after a fork
        self.pid = os.getpid()

    def _on_fork(self):
        # Pseudo-standard method that resolver_ares and threadpool
        # also have, called by hub.reinit()
        pid = os.getpid()
        if pid != self.pid:
            self.pid = pid
            self.monitor_thread_ident = start_new_thread(self, ())

    @property
    def hub(self):
        return self._hub_wref()


    def monitoring_functions(self):
        # Return a list of _MonitorEntry objects

        # Update max_blocking_time each time.
        mbt = GEVENT_CONFIG.max_blocking_time # XXX: Events so we know when this changes.
        if mbt != self._monitoring_functions[0].period:
            self._monitoring_functions[0].period = mbt
            self._calculated_sleep_time = min(x.period for x in self._monitoring_functions)
        return self._monitoring_functions

    def add_monitoring_function(self, function, period):
        if not callable(function):
            raise ValueError("function must be callable")

        if period is None:
            # Remove.
            self._monitoring_functions = [
                x for x in self._monitoring_functions
                if x.function != function
            ]
        elif period <= 0:
            raise ValueError("Period must be positive.")
        else:
            # Add or update period
            entry = _MonitorEntry(function, period)
            self._monitoring_functions = [
                x if x.function != function else entry
                for x in self._monitoring_functions
            ]
            if entry not in self._monitoring_functions:
                self._monitoring_functions.append(entry)
        self._calculated_sleep_time = min(x.period for x in self._monitoring_functions)

    def calculate_sleep_time(self):
        min_sleep = self._calculated_sleep_time
        if min_sleep <= 0:
            # Everyone wants to be disabled. Sleep for a longer period of
            # time than usual so we don't spin unnecessarily. We might be
            # enabled again in the future.
            return self.inactive_sleep_time
        return max((min_sleep, self.min_sleep_time))

    def kill(self):
        if not self.should_run:
            # Prevent overwriting trace functions.
            return
        # Stop this monitoring thread from running.
        self.should_run = False
        # Uninstall our tracing hook
        self._greenlet_tracer.kill()

    def _on_hub_gc(self, _):
        self.kill()

    def __call__(self):
        # The function that runs in the monitoring thread.
        # We cannot use threading.current_thread because it would
        # create an immortal DummyThread object.
        getcurrent().gevent_monitoring_thread = wref(self)

        try:
            while self.should_run:
                functions = self.monitoring_functions()
                assert functions
                sleep_time = self.calculate_sleep_time()

                thread_sleep(sleep_time)

                # Make sure the hub is still around, and still active,
                # and keep it around while we are here.
                hub = self.hub
                if not hub:
                    self.kill()

                if self.should_run:
                    this_run = perf_counter()
                    for entry in functions:
                        f = entry.function
                        period = entry.period
                        last_run = entry.last_run_time
                        if period and last_run + period <= this_run:
                            entry.last_run_time = this_run
                            f(hub)
                del hub # break our reference to hub while we sleep

        except SystemExit:
            pass
        except: # pylint:disable=bare-except
            # We're a daemon thread, so swallow any exceptions that get here
            # during interpreter shutdown.
            if not sys or not sys.stderr: # pragma: no cover
                # Interpreter is shutting down
                pass
            else:
                hub = self.hub
                if hub is not None:
                    # XXX: This tends to do bad things like end the process, because we
                    # try to switch *threads*, which can't happen. Need something better.
                    hub.handle_error(self, *sys.exc_info())

    def monitor_blocking(self, hub):
        # Called periodically to see if the trace function has
        # fired to switch greenlets. If not, we will print
        # the greenlet tree.

        # For tests, we return a true value when we think we found something
        # blocking

        did_block = self._greenlet_tracer.did_block_hub(hub)
        if not did_block:
            return

        active_greenlet = did_block[1]
        report = self._greenlet_tracer.did_block_hub_report(
            hub, active_greenlet,
            dict(greenlet_stacks=False, current_thread_ident=self.monitor_thread_ident))

        stream = hub.exception_stream
        for line in report:
            # Printing line by line may interleave with other things,
            # but it should also prevent a "reentrant call to print"
            # when the report is large.
            print(line, file=stream)

        notify(EventLoopBlocked(active_greenlet, GEVENT_CONFIG.max_blocking_time, report))
        return (active_greenlet, report)

    def ignore_current_greenlet_blocking(self):
        self._greenlet_tracer.ignore_current_greenlet_blocking()

    def monitor_current_greenlet_blocking(self):
        self._greenlet_tracer.monitor_current_greenlet_blocking()

    def _get_process(self): # pylint:disable=method-hidden
        proc = get_this_psutil_process()
        self._get_process = lambda: proc
        return proc

    def can_monitor_memory_usage(self):
        return self._get_process() is not None

    def install_monitor_memory_usage(self):
        # Start monitoring memory usage, if possible.
        # If not possible, emit a warning.
        if not self.can_monitor_memory_usage():
            import warnings
            warnings.warn("Unable to monitor memory usage. Install psutil.",
                          MonitorWarning)
            return

        self.add_monitoring_function(self.monitor_memory_usage,
                                     max(GEVENT_CONFIG.memory_monitor_period,
                                         self.min_memory_monitor_period))

    def monitor_memory_usage(self, _hub):
        max_allowed = GEVENT_CONFIG.max_memory_usage
        if not max_allowed:
            # They disabled it.
            return -1 # value for tests

        rusage = self._get_process().memory_full_info()
        # uss only documented available on Windows, Linux, and OS X.
        # If not available, fall back to rss as an aproximation.
        mem_usage = getattr(rusage, 'uss', 0) or rusage.rss

        event = None # Return value for tests

        if mem_usage > max_allowed:
            if mem_usage > self._memory_exceeded:
                # We're still growing
                event = MemoryUsageThresholdExceeded(
                    mem_usage, max_allowed, rusage)
                notify(event)
            self._memory_exceeded = mem_usage
        else:
            # we're below. Were we above it last time?
            if self._memory_exceeded:
                event = MemoryUsageUnderThreshold(
                    mem_usage, max_allowed, rusage, self._memory_exceeded)
                notify(event)
            self._memory_exceeded = 0

        return event

    def __repr__(self):
        return '<%s at %s in thread %s greenlet %r for %r>' % (
            self.__class__.__name__,
            hex(id(self)),
            hex(self.monitor_thread_ident),
            getcurrent(),
            self._hub_wref())