From: Ben Darnell Date: Sun, 11 May 2014 22:49:40 +0000 (-0400) Subject: Add gen.moment, a yieldable object that resumes after one IOLoop iteration. X-Git-Tag: v4.0.0b1~79 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4f0d837ed2776eddaeb8c80b3224daf97ff35c54;p=thirdparty%2Ftornado.git 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). --- 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