From cb7c8c0196ed70665b0382909141ac743d7633a2 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 15 Mar 2010 21:14:41 -0700 Subject: [PATCH] Added multiple-reader prevention code because it seems to be a fairly common pitfall. Also added defaults to the debug methods so you can call them no-args to reset them. --- eventlet/debug.py | 12 ++++++++---- eventlet/hubs/hub.py | 12 ++++++++++++ tests/greenio_test.py | 24 ++++++++++++++++++++++++ tests/stdlib/all.py | 2 ++ 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/eventlet/debug.py b/eventlet/debug.py index fd15a49..cca5862 100644 --- a/eventlet/debug.py +++ b/eventlet/debug.py @@ -93,7 +93,7 @@ def format_hub_timers(): result.append(repr(l)) return os.linesep.join(result) -def hub_listener_stacks(state): +def hub_listener_stacks(state = False): """Toggles whether or not the hub records the stack when clients register listeners on file descriptors. This can be useful when trying to figure out what the hub is up to at any given moment. To inspect the stacks @@ -103,15 +103,19 @@ def hub_listener_stacks(state): from eventlet import hubs hubs.get_hub().set_debug_listeners(state) -def hub_timer_stacks(state): +def hub_timer_stacks(state = False): """Toggles whether or not the hub records the stack when timers are set. To inspect the stacks of the current timers, call :func:`format_hub_timers` at critical junctures in the application logic. """ from eventlet.hubs import timer timer._g_debug = state + +def hub_prevent_multiple_readers(state = True): + from eventlet.hubs import hub + hub.g_prevent_multiple_readers = state -def hub_exceptions(state): +def hub_exceptions(state = True): """Toggles whether the hub prints exceptions that are raised from its timers. This can be useful to see how greenthreads are terminating. """ @@ -120,7 +124,7 @@ def hub_exceptions(state): from eventlet import greenpool greenpool.DEBUG = state -def tpool_exceptions(state): +def tpool_exceptions(state = False): """Toggles whether tpool itself prints exceptions that are raised from functions that are executed in it, in addition to raising them like it normally does.""" diff --git a/eventlet/hubs/hub.py b/eventlet/hubs/hub.py index d07468e..4c635a4 100644 --- a/eventlet/hubs/hub.py +++ b/eventlet/hubs/hub.py @@ -1,12 +1,15 @@ import heapq import sys import traceback +import warnings from eventlet.support import greenlets as greenlet, clear_sys_exc_info from eventlet.hubs import timer from eventlet import patcher time = patcher.original('time') +g_prevent_multiple_readers = True + READ="read" WRITE="write" @@ -74,6 +77,15 @@ class BaseHub(object): listener = self.lclass(evtype, fileno, cb) bucket = self.listeners[evtype] if fileno in bucket: + if g_prevent_multiple_readers: + raise RuntimeError("Second simultaneous %s on fileno %s "\ + "detected. Unless you really know what you're doing, "\ + "make sure that only one greenthread can %s any "\ + "particular socket. Consider using a pools.Pool. "\ + "If you do know what you're doing and want to disable "\ + "this error, call "\ + "eventlet.debug.hub_multiple_reader_prevention(False)" % ( + evtype, fileno, evtype)) # store off the second listener in another structure self.secondaries[evtype].setdefault(fileno, []).append(listener) else: diff --git a/tests/greenio_test.py b/tests/greenio_test.py index 663eaba..05e839c 100644 --- a/tests/greenio_test.py +++ b/tests/greenio_test.py @@ -483,11 +483,34 @@ class TestGreenIo(LimitedTestCase): gt.wait() + @skip_with_pyevent + def test_raised_multiple_readers(self): + debug.hub_prevent_multiple_readers(True) + + def handle(sock, addr): + sock.recv(1) + sock.sendall("a") + raise eventlet.StopServe() + listener = eventlet.listen(('127.0.0.1', 0)) + server = eventlet.spawn(eventlet.serve, + listener, + handle) + def reader(s): + s.recv(1) + + s = eventlet.connect(('127.0.0.1', listener.getsockname()[1])) + a = eventlet.spawn(reader, s) + eventlet.sleep(0) + self.assertRaises(RuntimeError, s.recv, 1) + s.sendall('b') + a.wait() + class TestGreenIoLong(LimitedTestCase): TEST_TIMEOUT=10 # the test here might take a while depending on the OS @skip_with_pyevent def test_multiple_readers(self, clibufsize=False): + debug.hub_prevent_multiple_readers(False) recvsize = 2 * min_buf_size() sendsize = 10 * recvsize # test that we can have multiple coroutines reading @@ -534,6 +557,7 @@ class TestGreenIoLong(LimitedTestCase): listener.close() self.assert_(len(results1) > 0) self.assert_(len(results2) > 0) + debug.hub_prevent_multiple_readers() @skipped # by rdw because it fails but it's not clear how to make it pass @skip_with_pyevent diff --git a/tests/stdlib/all.py b/tests/stdlib/all.py index ca46ff0..9b47ee1 100644 --- a/tests/stdlib/all.py +++ b/tests/stdlib/all.py @@ -3,6 +3,8 @@ Many of these tests make connections to external servers, and all.py tries to skip these tests rather than failing them, so you can get some work done on a plane. """ +from eventlet import debug +debug.hub_prevent_multiple_readers(False) def restart_hub(): from eventlet import hubs