Open
Description
Tornado之stack_context
stack_context是Tornado1.1.0新增的一个类, 主要是用来处理异步函数的异常的,主要有这么几个部分:
- _State
- StackContext
- NullContext
- wrap
_State
Torndo是单线程的,这里的_State用来保存Tornado的上下文内容。
class _State(threading.local):
def __init__(self):
self.contexts = ()
_state = _State()
StackContext
先看代码:
@contextlib.contextmanager
def StackContext(context_factory):
old_contexts = _state.contexts
try:
_state.contexts = old_contexts + (context_factory,)
with context_factory():
yield
finally:
_state.contexts = old_contexts
结合之前提到的contextmanager,这里的context_factory必须是一个上下文管理器。_state.contexts存储的是主线程中其他还没有异步回调的事件管理器。当回调完成后则将其从主线程中删除。
NullContext
@contextlib.contextmanager
def NullContext():
'''Resets the StackContext.
Useful when creating a shared resource on demand (e.g. an AsyncHTTPClient)
where the stack that caused the creating is not relevant to future
operations.
'''
old_contexts = _state.contexts
try:
_state.contexts = ()
yield
finally:
_state.contexts = old_contexts
当我们写异步回调共享同一个资源的时候,比如说一个连接池,当我们其中一个回调的时候,我们需要对资源进行加锁,用with stack_context.NullContext():
防止连接泄漏。
wrap
def wrap(fn):
'''Returns a callable object that will resore the current StackContext
when executed.
Use this whenever saving a callback to be executed later in a
different execution context (either in a different thread or
asynchronously in the same thread).
'''
# functools.wraps doesn't appear to work on functools.partial objects
#@functools.wraps(fn)
def wrapped(callback, contexts, *args, **kwargs):
# _state.contexts and contexts may share a common prefix.
# For each element of contexts not in that prefix, create a new
# StackContext object.
# TODO(bdarnell): do we want to be strict about the order,
# or is what we really want just set(contexts) - set(_state.contexts)?
# I think we do want to be strict about using identity comparison,
# so a set may not be quite right. Conversely, it's not very stack-like
# to have new contexts pop up in the middle, so would we want to
# ensure there are no existing contexts not in the stack being restored?
# That feels right, but given the difficulty of handling errors at this
# level I'm not going to check for it now.
pairs = itertools.izip(itertools.chain(_state.contexts,
itertools.repeat(None)),
contexts)
new_contexts = []
for old, new in itertools.dropwhile(lambda x: x[0] is x[1], pairs):
new_contexts.append(StackContext(new))
if new_contexts:
with contextlib.nested(*new_contexts):
callback(*args, **kwargs)
else:
callback(*args, **kwargs)
if getattr(fn, 'stack_context_wrapped', False):
return fn
contexts = _state.contexts
result = functools.partial(wrapped, fn, contexts)
result.stack_context_wrapped = True
return result
分析一下代码, wrap接受一个函数,获取当前线程的上下文(contexts),将wrapped和fn还有contexts封装成一个函数并返回。
再看一下wrapped里边的内容
先是将旧的context和新传进来的context打包,然后再解包,取出新的context。类似于set(contexts) - set(_state.contexts)
, 但是我们需要明确的对比不同之处,因为我们存放context的时候是有顺序的,所以不能用set去区分。例如我们要比较a:123
与b:1234
的不同,需要将b中新增加元素dump出来,则可以用上面的方法。取出新的context后,完成回调。