Permalink
Browse files

Improve isolation of stack contexts.

Previously, a chain of callbacks started without any contexts could
accidentally pick up other contexts based on where the callbacks
were run.  This was due to a mistaken optimization in the no-context
case; now the behavior is the same whether the initial context was empty
or not.
  • Loading branch information...
bdarnell committed Sep 29, 2012
1 parent 9817791 commit 69e449201c5235b3502107b15df213d62255cc30
Showing with 45 additions and 6 deletions.
  1. +2 −5 tornado/stack_context.py
  2. +43 −1 tornado/test/stack_context_test.py
View
@@ -198,7 +198,7 @@ def wrap(fn):
def wrapped(*args, **kwargs):
callback, contexts, args = args[0], args[1], args[2:]
- if contexts is _state.contexts or not contexts:
+ if contexts is _state.contexts:
callback(*args, **kwargs)
return
if not _state.contexts:
@@ -231,10 +231,7 @@ def wrapped(*args, **kwargs):
callback(*args, **kwargs)
else:
callback(*args, **kwargs)
- if _state.contexts:
- return _StackContextWrapper(wrapped, fn, _state.contexts)
- else:
- return _StackContextWrapper(fn)
+ return _StackContextWrapper(wrapped, fn, _state.contexts)
@contextlib.contextmanager
@@ -2,7 +2,7 @@
from __future__ import absolute_import, division, with_statement
from tornado.log import app_log
-from tornado.stack_context import StackContext, wrap
+from tornado.stack_context import StackContext, wrap, NullContext
from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, ExpectLog
from tornado.test.util import unittest
from tornado.util import b
@@ -127,5 +127,47 @@ def f5():
self.io_loop.add_callback(f1)
self.wait()
+ def test_isolation_nonempty(self):
+ # f2 and f3 are a chain of operations started in context c1.
+ # f2 is incidentally run under context c2, but that context should
+ # not be passed along to f3.
+ def f1():
+ with StackContext(functools.partial(self.context, 'c1')):
+ wrapped = wrap(f2)
+ with StackContext(functools.partial(self.context, 'c2')):
+ wrapped()
+
+ def f2():
+ self.assertIn('c1', self.active_contexts)
+ self.io_loop.add_callback(f3)
+
+ def f3():
+ self.assertIn('c1', self.active_contexts)
+ self.assertNotIn('c2', self.active_contexts)
+ self.stop()
+
+ self.io_loop.add_callback(f1)
+ self.wait()
+
+ def test_isolation_empty(self):
+ # Similar to test_isolation_nonempty, but here the f2/f3 chain
+ # is started without any context. Behavior should be equivalent
+ # to the nonempty case (although historically it was not)
+ def f1():
+ with NullContext():
+ wrapped = wrap(f2)
+ with StackContext(functools.partial(self.context, 'c2')):
+ wrapped()
+
+ def f2():
+ self.io_loop.add_callback(f3)
+
+ def f3():
+ self.assertNotIn('c2', self.active_contexts)
+ self.stop()
+
+ self.io_loop.add_callback(f1)
+ self.wait()
+
if __name__ == '__main__':
unittest.main()

0 comments on commit 69e4492

Please sign in to comment.