diff --git a/eventlet/debug.py b/eventlet/debug.py index cca5862..2920fd9 100644 --- a/eventlet/debug.py +++ b/eventlet/debug.py @@ -130,3 +130,14 @@ def tpool_exceptions(state = False): it normally does.""" from eventlet import tpool tpool.QUIET = not state + +def hub_blocking_detection(state = False): + """Toggles whether Eventlet makes an effort to detect blocking + behavior in other code. It does this by setting a SIGALARM with a short + timeout. + """ + from eventlet import hubs + hubs.get_hub().debug_blocking = state + if(not state): + hubs.get_hub().block_detect_post() + diff --git a/eventlet/hubs/hub.py b/eventlet/hubs/hub.py index 74efcaa..4aaf49a 100644 --- a/eventlet/hubs/hub.py +++ b/eventlet/hubs/hub.py @@ -2,6 +2,7 @@ import heapq import sys import traceback import warnings +import signal from eventlet.support import greenlets as greenlet, clear_sys_exc_info from eventlet.hubs import timer @@ -42,6 +43,11 @@ class DebugListener(FdListener): __str__ = __repr__ +def alarm_handler(signum, frame): + import inspect + raise RuntimeError("ALARMED at" + str(inspect.getframeinfo(frame))) + + class BaseHub(object): """ Base hub class for easing the implementation of subclasses that are specific to a particular underlying event architecture. """ @@ -62,8 +68,21 @@ class BaseHub(object): self.timers = [] self.next_timers = [] self.lclass = FdListener - self.debug_exceptions = True self.timers_canceled = 0 + self.debug_exceptions = True + self.debug_blocking = False + + def block_detect_pre(self): + # shortest alarm we can possibly raise is one second + tmp = signal.signal(signal.SIGALRM, alarm_handler) + if tmp != alarm_handler: + self._old_signal_handler = tmp + signal.alarm(1) + def block_detect_post(self): + if (hasattr(self, "_old_signal_handler") and + self._old_signal_handler): + signal.signal(signal.SIGALRM, self._old_signal_handler) + signal.alarm(0) def add(self, evtype, fileno, cb): """ Signals an intent to or write a particular file descriptor. @@ -166,7 +185,11 @@ class BaseHub(object): self.stopping = False while not self.stopping: self.prepare_timers() + if self.debug_blocking: + self.block_detect_pre() self.fire_timers(self.clock()) + if self.debug_blocking: + self.block_detect_post() self.prepare_timers() wakeup_when = self.sleep_until() if wakeup_when is None: diff --git a/eventlet/hubs/poll.py b/eventlet/hubs/poll.py index 890ff50..76f5747 100644 --- a/eventlet/hubs/poll.py +++ b/eventlet/hubs/poll.py @@ -1,12 +1,13 @@ import sys import errno +import signal from eventlet import patcher select = patcher.original('select') time = patcher.original('time') sleep = time.sleep from eventlet.support import get_errno, clear_sys_exc_info -from eventlet.hubs.hub import BaseHub, READ, WRITE, noop +from eventlet.hubs.hub import BaseHub, READ, WRITE, noop, alarm_handler EXC_MASK = select.POLLERR | select.POLLHUP READ_MASK = select.POLLIN | select.POLLPRI @@ -82,6 +83,11 @@ class Hub(BaseHub): raise SYSTEM_EXCEPTIONS = self.SYSTEM_EXCEPTIONS + if self.debug_blocking: + # shortest alarm we can possibly set is one second + signal.signal(signal.SIGALRM, alarm_handler) + signal.alarm(1) + for fileno, event in presult: try: if event & READ_MASK: @@ -99,3 +105,7 @@ class Hub(BaseHub): except: self.squelch_exception(fileno, sys.exc_info()) clear_sys_exc_info() + + if self.debug_blocking: + signal.alarm(0) + diff --git a/tests/hub_test.py b/tests/hub_test.py index 22526f3..8eac85d 100644 --- a/tests/hub_test.py +++ b/tests/hub_test.py @@ -142,6 +142,17 @@ class TestHubSelection(LimitedTestCase): hubs._threadlocal.hub = oldhub +class TestHubBlockingDetector(LimitedTestCase): + TEST_TIMEOUT = 10 + def test_block_detect(self): + def look_im_blocking(): + import time + time.sleep(2) + from eventlet import debug + debug.hub_blocking_detection(True) + gt = eventlet.spawn(look_im_blocking) + self.assertRaises(RuntimeError, gt.wait) + debug.hub_blocking_detection(False) class Foo(object): pass