]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add gen.moment, a yieldable object that resumes after one IOLoop iteration.
authorBen Darnell <ben@bendarnell.com>
Sun, 11 May 2014 22:49:40 +0000 (18:49 -0400)
committerBen Darnell <ben@bendarnell.com>
Sun, 11 May 2014 22:49:40 +0000 (18:49 -0400)
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
tornado/gen.py
tornado/http1connection.py
tornado/test/gen_test.py

index 0aafa11f70d66d587a5836408fb66d051acb5f7c..da3366d4b9c026d9efd385a730de7e8d8a12803f 100644 (file)
@@ -37,6 +37,9 @@
 
    .. autofunction:: maybe_future
 
+   .. autodata:: moment
+      :annotation:
+
    Other classes
    -------------
 
index b54bbed488d82850da5642fee36b25a38281df13..bff8b75cac2fd9ae091054a6434875fd33e63783 100644 (file)
@@ -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
index 0eda8b2dda6af8c7b74499eecac9bf25b20506f2..a0c96a2f28dd44e8b4ff06782cdd66a439767f8f 100644 (file)
@@ -600,5 +600,6 @@ class HTTP1ServerConnection(object):
                     return
                 if not ret:
                     return
+                yield gen.moment
         finally:
             delegate.on_close(self)
index ffe1c7d3c30380baf75c76dfb2f1f75c10c40ad9..20750cd18ddf29cf4194375cd54473988f81f42e 100644 (file)
@@ -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