import sys
import textwrap
import types
+import weakref
from tornado.concurrent import Future, TracebackFuture, is_future, chain_future
from tornado.ioloop import IOLoop
"""
return _make_coroutine_wrapper(func, replace_callback=True)
+# Ties lifetime of runners to their result futures. Github Issue #1769
+# Generators, like any object in Python, must be strong referenced
+# in order to not be cleaned up by the garbage collector. When using
+# coroutines, the Runner object is what strong-refs the inner
+# generator. However, the only item that strong-reffed the Runner
+# was the last Future that the inner generator yielded (via the
+# Future's internal done_callback list). Usually this is enough, but
+# it is also possible for this Future to not have any strong references
+# other than other objects referenced by the Runner object (usually
+# when using other callback patterns and/or weakrefs). In this
+# situation, if a garbage collection ran, a cycle would be detected and
+# Runner objects could be destroyed along with their inner generators
+# and everything in their local scope.
+# This map provides strong references to Runner objects as long as
+# their result future objects also have strong references (typically
+# from the parent coroutine's Runner). This keeps the coroutine's
+# Runner alive.
+_futures_to_runners = weakref.WeakKeyDictionary()
def _make_coroutine_wrapper(func, replace_callback):
"""The inner workings of ``@gen.coroutine`` and ``@gen.engine``.
except Exception:
future.set_exc_info(sys.exc_info())
else:
- Runner(result, future, yielded)
+ _futures_to_runners[future] = Runner(result, future, yielded)
try:
return future
finally:
from __future__ import absolute_import, division, print_function, with_statement
+import gc
import contextlib
import datetime
import functools
except ImportError:
futures = None
-
class GenEngineTest(AsyncTestCase):
def setUp(self):
super(GenEngineTest, self).setUp()
gen.WaitIterator(gen.sleep(0)).next())
+class RunnerGCTest(AsyncTestCase):
+ """Github issue 1769: Runner objects can get GCed unexpectedly"""
+ @gen_test
+ def test_gc(self):
+ """Runners shouldn't GC if future is alive"""
+ # Create the weakref
+ weakref_scope = [None]
+ def callback():
+ gc.collect(2)
+ weakref_scope[0]().set_result(123)
+
+ @gen.coroutine
+ def tester():
+ fut = Future()
+ weakref_scope[0] = weakref.ref(fut)
+ self.io_loop.add_callback(callback)
+ yield fut
+
+ yield gen.with_timeout(
+ datetime.timedelta(seconds=0.2),
+ tester()
+ )
+
if __name__ == '__main__':
unittest.main()