Skip to content

Tornado之stack_context #9

Open
@loadlj

Description

@loadlj

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:123b:1234的不同,需要将b中新增加元素dump出来,则可以用上面的方法。取出新的context后,完成回调。

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions