]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add support for lists of YieldPoints in the gen framework.
authorBen Darnell <ben@bendarnell.com>
Sun, 11 Sep 2011 22:09:25 +0000 (15:09 -0700)
committerBen Darnell <ben@bendarnell.com>
Sun, 11 Sep 2011 22:09:25 +0000 (15:09 -0700)
tornado/gen.py
tornado/test/gen_test.py
website/sphinx/conf.py

index 6571e5ed81d660ee7cc230f08a0a67da95d9c7ff..900f61a7d035751fdfb532ac4f96e5f98f8647fa 100644 (file)
@@ -28,8 +28,17 @@ could be written with ``gen`` as::
             self.render("template.html")
 
 `Task` works with any function that takes a ``callback`` keyword argument
-(and runs that callback with zero or one arguments).  For more complicated
-interfaces, `Task` can be split into two parts: `Callback` and `Wait`::
+(and runs that callback with zero or one arguments).  You can also yield
+a list of ``Tasks``, which will be started at the same time and run in parallel;
+a list of results will be returned when they are all finished::
+
+    def get(self):
+        http_client = AsyncHTTPClient()
+        response1, response2 = yield [gen.Task(http_client.fetch, url1),
+                                      gen.Task(http_client.fetch, url2)]
+
+For more complicated interfaces, `Task` can be split into two parts:
+`Callback` and `Wait`::
 
     class GenAsyncHandler2(RequestHandler):
         @asynchronous
@@ -43,9 +52,9 @@ interfaces, `Task` can be split into two parts: `Callback` and `Wait`::
             self.render("template.html")
 
 The ``key`` argument to `Callback` and `Wait` allows for multiple
-asynchronous operations to proceed in parallel: yield several
-callbacks with different keys, then wait for them once all the async
-operations have started.
+asynchronous operations to be started at different times and proceed
+in parallel: yield several callbacks with different keys, then wait
+for them once all the async operations have started.
 """
 
 import functools
@@ -149,6 +158,8 @@ class WaitAll(YieldPoint):
 
     The argument is a sequence of `Callback` keys, and the result is
     a list of results in the same order.
+
+    `WaitAll` is equivalent to yielding a list of `Wait` objects.
     """
     def __init__(self, keys):
         self.keys = keys
@@ -198,6 +209,28 @@ class Task(YieldPoint):
     def callback(self, arg=None):
         self.runner.set_result(self.key, arg)
 
+class Multi(YieldPoint):
+    """Runs multiple asynchronous operations in parallel.
+
+    Takes a list of ``Tasks`` or other ``YieldPoints`` and returns a list of
+    their responses.  It is not necessary to call `Multi` explicitly,
+    since the engine will do so automatically when the generator yields
+    a list of ``YieldPoints``.
+    """
+    def __init__(self, children):
+        assert all(isinstance(i, YieldPoint) for i in children)
+        self.children = children
+    
+    def start(self, runner):
+        for i in self.children:
+            i.start(runner)
+
+    def is_ready(self):
+        return all(i.is_ready() for i in self.children)
+
+    def get_result(self):
+        return [i.get_result() for i in self.children]
+
 class _NullYieldPoint(YieldPoint):
     def start(self, runner):
         pass
@@ -275,6 +308,8 @@ class Runner(object):
                 except Exception:
                     self.finished = True
                     raise
+                if isinstance(yielded, list):
+                    yielded = Multi(yielded)
                 if isinstance(yielded, YieldPoint):
                     self.yield_point = yielded
                     self.yield_point.start(self)
index 49cf978a20ecd01c962f0b905521528cf9876fca..bdfe97decc731321bb507365a039af8cb68399da 100644 (file)
@@ -1,3 +1,4 @@
+import functools
 from tornado.escape import url_escape
 from tornado.httpclient import AsyncHTTPClient
 from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, LogTrapTestCase
@@ -11,6 +12,14 @@ class GenTest(AsyncTestCase):
         f()
         self.wait()
 
+    def delay_callback(self, iterations, callback, arg):
+        """Runs callback(arg) after a number of IOLoop iterations."""
+        if iterations == 0:
+            callback(arg)
+        else:
+            self.io_loop.add_callback(functools.partial(
+                    self.delay_callback, iterations - 1, callback, arg))
+
     def test_no_yield(self):
         @gen.engine
         def f():
@@ -160,6 +169,28 @@ class GenTest(AsyncTestCase):
             pass
         self.orphaned_callback()
 
+    def test_multi(self):
+        @gen.engine
+        def f():
+            (yield gen.Callback("k1"))("v1")
+            (yield gen.Callback("k2"))("v2")
+            results = yield [gen.Wait("k1"), gen.Wait("k2")]
+            self.assertEqual(results, ["v1", "v2"])
+            self.stop()
+        self.run_gen(f)
+
+    def test_multi_delayed(self):
+        @gen.engine
+        def f():
+            # callbacks run at different times
+            responses = yield [
+                gen.Task(self.delay_callback, 3, arg="v1"),
+                gen.Task(self.delay_callback, 1, arg="v2"),
+                ]
+            self.assertEqual(responses, ["v1", "v2"])
+            self.stop()
+        self.run_gen(f)
+
 
 class GenSequenceHandler(RequestHandler):
     @asynchronous
index ac0b61dc0c623893f73e4ad0709beec41bf8ce90..905ab4102aa2db3db135f58091c4e13eea4b8bbb 100644 (file)
@@ -30,6 +30,7 @@ coverage_ignore_modules = [
 # I wish this could go in a per-module file...
 coverage_ignore_classes = [
     # tornado.gen
+    "Multi",
     "Runner",
     "YieldPoint",