From 4f0d837ed2776eddaeb8c80b3224daf97ff35c54 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sun, 11 May 2014 18:49:40 -0400 Subject: [PATCH] Add gen.moment, a yieldable object that resumes after one IOLoop iteration. Use this between requests in HTTP1ServerConnection to keep one connection from monopolizing the IOLoop when there are pipelined (or just fast) requests available. This was causing increased variance in benchmarks using "ab -k" (it also increased throughput, for reasons that are not yet clear, so this change reduces observed speed in these benchmarks). --- docs/gen.rst | 3 +++ tornado/gen.py | 15 ++++++++++++++- tornado/http1connection.py | 1 + tornado/test/gen_test.py | 25 +++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/docs/gen.rst b/docs/gen.rst index 0aafa11f7..da3366d4b 100644 --- a/docs/gen.rst +++ b/docs/gen.rst @@ -37,6 +37,9 @@ .. autofunction:: maybe_future + .. autodata:: moment + :annotation: + Other classes ------------- diff --git a/tornado/gen.py b/tornado/gen.py index b54bbed48..bff8b75ca 100644 --- a/tornado/gen.py +++ b/tornado/gen.py @@ -502,6 +502,19 @@ def with_timeout(timeout, future, io_loop=None): _null_future = Future() _null_future.set_result(None) +moment = Future() +moment.__doc__ = \ +"""A special object which may be yielded to allow the IOLoop to run for +one iteration. + +This is not needed in normal use but it can be helpful in long-running +coroutines that are likely to yield Futures that are ready instantly. + +Usage: ``yield gen.moment`` + +.. versionadded:: 3.3 +""" +moment.set_result(None) class Runner(object): """Internal implementation of `tornado.gen.engine`. @@ -648,7 +661,7 @@ class Runner(object): start_yield_point() elif is_future(yielded): self.future = yielded - if not self.future.done(): + if not self.future.done() or self.future is moment: self.io_loop.add_future( self.future, lambda f: self.run()) return False diff --git a/tornado/http1connection.py b/tornado/http1connection.py index 0eda8b2dd..a0c96a2f2 100644 --- a/tornado/http1connection.py +++ b/tornado/http1connection.py @@ -600,5 +600,6 @@ class HTTP1ServerConnection(object): return if not ret: return + yield gen.moment finally: delegate.on_close(self) diff --git a/tornado/test/gen_test.py b/tornado/test/gen_test.py index ffe1c7d3c..20750cd18 100644 --- a/tornado/test/gen_test.py +++ b/tornado/test/gen_test.py @@ -775,6 +775,31 @@ class GenCoroutineTest(AsyncTestCase): self.assertEqual(result, 42) self.finished = True + @gen_test + def test_moment(self): + calls = [] + @gen.coroutine + def f(name, yieldable): + for i in range(5): + calls.append(name) + yield yieldable + # First, confirm the behavior without moment: each coroutine + # monopolizes the event loop until it finishes. + immediate = Future() + immediate.set_result(None) + yield [f('a', immediate), f('b', immediate)] + self.assertEqual(''.join(calls), 'aaaaabbbbb') + + # With moment, they take turns. + calls = [] + yield [f('a', gen.moment), f('b', gen.moment)] + self.assertEqual(''.join(calls), 'ababababab') + self.finished = True + + calls = [] + yield [f('a', gen.moment), f('b', immediate)] + self.assertEqual(''.join(calls), 'abbbbbaaaa') + class GenSequenceHandler(RequestHandler): @asynchronous -- 2.47.2