You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

387 lines
12 KiB
Python

# Copyright 2018 gevent contributors. See LICENSE for details.
import gc
import unittest
from greenlet import gettrace
from greenlet import settrace
from gevent.monkey import get_original
from gevent._compat import thread_mod_name
from gevent._compat import NativeStrIO
from gevent.testing import verify
from gevent.testing.skipping import skipWithoutPSUtil
from gevent import _monitor as monitor
from gevent import config as GEVENT_CONFIG
get_ident = get_original(thread_mod_name, 'get_ident')
class MockHub(object):
_threadpool = None
_resolver = None
def __init__(self):
self.thread_ident = get_ident()
self.exception_stream = NativeStrIO()
self.dead = False
def __bool__(self):
return not self.dead
__nonzero__ = __bool__
def handle_error(self, *args): # pylint:disable=unused-argument
raise # pylint:disable=misplaced-bare-raise
@property
def loop(self):
return self
def reinit(self):
"mock loop.reinit"
class _AbstractTestPeriodicMonitoringThread(object):
# Makes sure we don't actually spin up a new monitoring thread.
# pylint:disable=no-member
def setUp(self):
super(_AbstractTestPeriodicMonitoringThread, self).setUp()
self._orig_start_new_thread = monitor.start_new_thread
self._orig_thread_sleep = monitor.thread_sleep
monitor.thread_sleep = lambda _s: gc.collect() # For PyPy
self.tid = 0xDEADBEEF
def start_new_thread(_f, _a):
r = self.tid
self.tid += 1
return r
monitor.start_new_thread = start_new_thread
self.hub = MockHub()
self.pmt = monitor.PeriodicMonitoringThread(self.hub)
self.hub.periodic_monitoring_thread = self.pmt
self.pmt_default_funcs = self.pmt.monitoring_functions()[:]
self.len_pmt_default_funcs = len(self.pmt_default_funcs)
def tearDown(self):
monitor.start_new_thread = self._orig_start_new_thread
monitor.thread_sleep = self._orig_thread_sleep
prev = self.pmt._greenlet_tracer.previous_trace_function
self.pmt.kill()
assert gettrace() is prev, (gettrace(), prev)
settrace(None)
super(_AbstractTestPeriodicMonitoringThread, self).tearDown()
class TestPeriodicMonitoringThread(_AbstractTestPeriodicMonitoringThread,
unittest.TestCase):
def test_constructor(self):
self.assertEqual(0xDEADBEEF, self.pmt.monitor_thread_ident)
self.assertEqual(gettrace(), self.pmt._greenlet_tracer)
@skipWithoutPSUtil("Verifies the process")
def test_get_process(self):
proc = self.pmt._get_process()
self.assertIsNotNone(proc)
# Same object is returned each time.
self.assertIs(proc, self.pmt._get_process())
def test_hub_wref(self):
self.assertIs(self.hub, self.pmt.hub)
del self.hub
gc.collect()
self.assertIsNone(self.pmt.hub)
# And it killed itself.
self.assertFalse(self.pmt.should_run)
self.assertIsNone(gettrace())
def test_add_monitoring_function(self):
self.assertRaises(ValueError, self.pmt.add_monitoring_function, None, 1)
self.assertRaises(ValueError, self.pmt.add_monitoring_function, lambda: None, -1)
def f():
"Does nothing"
# Add
self.pmt.add_monitoring_function(f, 1)
self.assertEqual(self.len_pmt_default_funcs + 1, len(self.pmt.monitoring_functions()))
self.assertEqual(1, self.pmt.monitoring_functions()[1].period)
# Update
self.pmt.add_monitoring_function(f, 2)
self.assertEqual(self.len_pmt_default_funcs + 1, len(self.pmt.monitoring_functions()))
self.assertEqual(2, self.pmt.monitoring_functions()[1].period)
# Remove
self.pmt.add_monitoring_function(f, None)
self.assertEqual(self.len_pmt_default_funcs, len(self.pmt.monitoring_functions()))
def test_calculate_sleep_time(self):
self.assertEqual(
self.pmt.monitoring_functions()[0].period,
self.pmt.calculate_sleep_time())
# Pretend that GEVENT_CONFIG.max_blocking_time was set to 0,
# to disable this monitor.
self.pmt._calculated_sleep_time = 0
self.assertEqual(
self.pmt.inactive_sleep_time,
self.pmt.calculate_sleep_time()
)
# Getting the list of monitoring functions will also
# do this, if it looks like it has changed
self.pmt.monitoring_functions()[0].period = -1
self.pmt._calculated_sleep_time = 0
self.pmt.monitoring_functions()
self.assertEqual(
self.pmt.monitoring_functions()[0].period,
self.pmt.calculate_sleep_time())
self.assertEqual(
self.pmt.monitoring_functions()[0].period,
self.pmt._calculated_sleep_time)
def test_call_destroyed_hub(self):
# Add a function that destroys the hub so we break out (eventually)
# This clears the wref, which eventually calls kill()
def f(_hub):
_hub = None
self.hub = None
gc.collect()
self.pmt.add_monitoring_function(f, 0.1)
self.pmt()
self.assertFalse(self.pmt.should_run)
def test_call_dead_hub(self):
# Add a function that makes the hub go false (e.g., it quit)
# This causes the function to kill itself.
def f(hub):
hub.dead = True
self.pmt.add_monitoring_function(f, 0.1)
self.pmt()
self.assertFalse(self.pmt.should_run)
def test_call_SystemExit(self):
# breaks the loop
def f(_hub):
raise SystemExit()
self.pmt.add_monitoring_function(f, 0.1)
self.pmt()
def test_call_other_error(self):
class MyException(Exception):
pass
def f(_hub):
raise MyException()
self.pmt.add_monitoring_function(f, 0.1)
with self.assertRaises(MyException):
self.pmt()
def test_hub_reinit(self):
import os
from gevent.hub import reinit
self.pmt.pid = -1
old_tid = self.pmt.monitor_thread_ident
reinit(self.hub)
self.assertEqual(os.getpid(), self.pmt.pid)
self.assertEqual(old_tid + 1, self.pmt.monitor_thread_ident)
class TestPeriodicMonitorBlocking(_AbstractTestPeriodicMonitoringThread,
unittest.TestCase):
def test_previous_trace(self):
self.pmt.kill()
self.assertIsNone(gettrace())
called = []
def f(*args):
called.append(args)
settrace(f)
self.pmt = monitor.PeriodicMonitoringThread(self.hub)
self.assertEqual(gettrace(), self.pmt._greenlet_tracer)
self.assertIs(self.pmt._greenlet_tracer.previous_trace_function, f)
self.pmt._greenlet_tracer('event', ('args',))
self.assertEqual([('event', ('args',))], called)
def test__greenlet_tracer(self):
self.assertEqual(0, self.pmt._greenlet_tracer.greenlet_switch_counter)
# Unknown event still counts as a switch (should it?)
self.pmt._greenlet_tracer('unknown', None)
self.assertEqual(1, self.pmt._greenlet_tracer.greenlet_switch_counter)
self.assertIsNone(self.pmt._greenlet_tracer.active_greenlet)
origin = object()
target = object()
self.pmt._greenlet_tracer('switch', (origin, target))
self.assertEqual(2, self.pmt._greenlet_tracer.greenlet_switch_counter)
self.assertIs(target, self.pmt._greenlet_tracer.active_greenlet)
# Unknown event removes active greenlet
self.pmt._greenlet_tracer('unknown', ())
self.assertEqual(3, self.pmt._greenlet_tracer.greenlet_switch_counter)
self.assertIsNone(self.pmt._greenlet_tracer.active_greenlet)
def test_monitor_blocking(self):
# Initially there's no active greenlet and no switches,
# so nothing is considered blocked
from gevent.events import subscribers
from gevent.events import IEventLoopBlocked
events = []
subscribers.append(events.append)
self.assertFalse(self.pmt.monitor_blocking(self.hub))
# Give it an active greenlet
origin = object()
target = object()
self.pmt._greenlet_tracer('switch', (origin, target))
# We've switched, so we're not blocked
self.assertFalse(self.pmt.monitor_blocking(self.hub))
self.assertFalse(events)
# Again without switching is a problem.
self.assertTrue(self.pmt.monitor_blocking(self.hub))
self.assertTrue(events)
verify.verifyObject(IEventLoopBlocked, events[0])
del events[:]
# But we can order it not to be a problem
self.pmt.ignore_current_greenlet_blocking()
self.assertFalse(self.pmt.monitor_blocking(self.hub))
self.assertFalse(events)
# And back again
self.pmt.monitor_current_greenlet_blocking()
self.assertTrue(self.pmt.monitor_blocking(self.hub))
# A bad thread_ident in the hub doesn't mess things up
self.hub.thread_ident = -1
self.assertTrue(self.pmt.monitor_blocking(self.hub))
class MockProcess(object):
def __init__(self, rss):
self.rss = rss
def memory_full_info(self):
return self
@skipWithoutPSUtil("Accessess memory info")
class TestPeriodicMonitorMemory(_AbstractTestPeriodicMonitoringThread,
unittest.TestCase):
rss = 0
def setUp(self):
_AbstractTestPeriodicMonitoringThread.setUp(self)
self._old_max = GEVENT_CONFIG.max_memory_usage
GEVENT_CONFIG.max_memory_usage = None
self.pmt._get_process = lambda: MockProcess(self.rss)
def tearDown(self):
GEVENT_CONFIG.max_memory_usage = self._old_max
_AbstractTestPeriodicMonitoringThread.tearDown(self)
def test_can_monitor_and_install(self):
# We run tests with psutil installed, and we have access to our
# process.
self.assertTrue(self.pmt.can_monitor_memory_usage())
# No warning, adds a function
self.pmt.install_monitor_memory_usage()
self.assertEqual(self.len_pmt_default_funcs + 1, len(self.pmt.monitoring_functions()))
def test_cannot_monitor_and_install(self):
import warnings
self.pmt._get_process = lambda: None
self.assertFalse(self.pmt.can_monitor_memory_usage())
# This emits a warning, visible by default
with warnings.catch_warnings(record=True) as ws:
self.pmt.install_monitor_memory_usage()
self.assertEqual(1, len(ws))
self.assertIs(monitor.MonitorWarning, ws[0].category)
def test_monitor_no_allowed(self):
self.assertEqual(-1, self.pmt.monitor_memory_usage(None))
def test_monitor_greater(self):
from gevent import events
self.rss = 2
GEVENT_CONFIG.max_memory_usage = 1
# Initial event
event = self.pmt.monitor_memory_usage(None)
self.assertIsInstance(event, events.MemoryUsageThresholdExceeded)
self.assertEqual(2, event.mem_usage)
self.assertEqual(1, event.max_allowed)
self.assertIsInstance(event.memory_info, MockProcess)
# No growth, no event
event = self.pmt.monitor_memory_usage(None)
self.assertIsNone(event)
# Growth, event
self.rss = 3
event = self.pmt.monitor_memory_usage(None)
self.assertIsInstance(event, events.MemoryUsageThresholdExceeded)
self.assertEqual(3, event.mem_usage)
# Shrinking below gets us back
self.rss = 1
event = self.pmt.monitor_memory_usage(None)
self.assertIsInstance(event, events.MemoryUsageUnderThreshold)
self.assertEqual(1, event.mem_usage)
# coverage
repr(event)
# No change, no event
event = self.pmt.monitor_memory_usage(None)
self.assertIsNone(event)
# Growth, event
self.rss = 3
event = self.pmt.monitor_memory_usage(None)
self.assertIsInstance(event, events.MemoryUsageThresholdExceeded)
self.assertEqual(3, event.mem_usage)
def test_monitor_initial_below(self):
self.rss = 1
GEVENT_CONFIG.max_memory_usage = 10
event = self.pmt.monitor_memory_usage(None)
self.assertIsNone(event)
if __name__ == '__main__':
unittest.main()