diff -r 4fc575d55e2b Lib/asyncio/base_events.py --- a/Lib/asyncio/base_events.py Thu Aug 28 11:19:46 2014 +0200 +++ b/Lib/asyncio/base_events.py Thu Aug 28 12:07:37 2014 -0700 @@ -267,6 +267,39 @@ return future.result() + def run_nested_until_complete(self, future): + """Run an event loop from within an executing task. + + This method will execute a nested event loop, and will not + return until the passed future has completed execution. The + nested loop shares the data structures of the main event loop, + so tasks and events scheduled on the main loop will still + execute while the nested loop is running. + + Semantically, this method is very similar to `yield from + asyncio.wait_for(future)`, and where possible, that is the + preferred way to block until a future is complete. The + difference is that this method can be called from a + non-coroutine function, even if that function was itself + invoked from within a coroutine. + """ + + self._check_closed() + if not self._running: + raise RuntimeError('Event loop is not running.') + new_task = not isinstance(future, futures.Future) + task = tasks.async(future, loop=self) + if new_task: + # An exception is raised if the future didn't complete, so there + # is no need to log the "destroy pending task" message + task._log_destroy_pending = False + while not task.done(): + try: + self._run_once() + except _StopError: + break + return task.result() + def stop(self): """Stop running the event loop. diff -r 4fc575d55e2b Lib/asyncio/tasks.py --- a/Lib/asyncio/tasks.py Thu Aug 28 11:19:46 2014 +0200 +++ b/Lib/asyncio/tasks.py Thu Aug 28 12:07:37 2014 -0700 @@ -225,6 +225,7 @@ coro = self._coro self._fut_waiter = None + containing_task = self.__class__._current_tasks.get(self._loop, None) self.__class__._current_tasks[self._loop] = self # Call either coro.throw(exc) or coro.send(value). try: @@ -277,7 +278,10 @@ RuntimeError( 'Task got bad yield: {!r}'.format(result))) finally: - self.__class__._current_tasks.pop(self._loop) + if containing_task is None: + self.__class__._current_tasks.pop(self._loop) + else: + self.__class__._current_tasks[self._loop] = containing_task self = None # Needed to break cycles when an exception occurs. def _wakeup(self, future): diff -r 4fc575d55e2b Lib/test/test_asyncio/test_tasks.py --- a/Lib/test/test_asyncio/test_tasks.py Thu Aug 28 11:19:46 2014 +0200 +++ b/Lib/test/test_asyncio/test_tasks.py Thu Aug 28 12:07:37 2014 -0700 @@ -1696,6 +1696,64 @@ 'test_task_source_traceback')) self.loop.run_until_complete(task) + def test_task_invokes_nested_event_loop(self): + from asyncio.base_events import _StopError + + loop = asyncio.new_event_loop() + + @asyncio.coroutine + def nop(): + pass + + @asyncio.coroutine + def nesting(depth): + if depth == 0: + return 'ok' + yield from nop() + task = asyncio.async(nesting(depth - 1), loop=loop) + while not task.done(): + try: + loop._run_once() + except _StopError: + break + return task.result() + + t = asyncio.async(nesting(5), loop=loop) + loop.run_until_complete(t) + loop.close() + + self.assertEqual(t.result(), 'ok') + + def test_task_invokes_run_nested_until_complete(self): + from asyncio.base_events import _StopError + + loop = asyncio.new_event_loop() + + @asyncio.coroutine + def nop(): + pass + + @asyncio.coroutine + def nesting_coro(depth): + if depth == 0: + return 'ok' + yield from nop() + return nesting_func(depth) + + def nesting_func(depth): + task = asyncio.async(nesting_coro(depth - 1), loop=loop) + loop.run_nested_until_complete(task) + return 'o' + task.result() + + @asyncio.coroutine + def outer(): + yield from nop() + return nesting_func(5) + + task = asyncio.async(outer(), loop=loop) + loop.run_until_complete(task) + + self.assertEqual(task.result(), 'ooooook') class GatherTestsBase: