tornado 源碼之 StackContext(二)

StackContext allows applications to maintain threadlocal-like state that follows execution as it moves to other execution contexts.python

an exception handler is a kind of stack-local state and when that stack is suspended and resumed in a new context that state needs to be preserved.閉包

一個棧結構的上下文處理類
異常處理也是一個棧結構的上下文應用app

contents

example usage

@contextlib.contextmanager
def die_on_error():
    try:
        yield
    except:
        logging.error("exception in asynchronous operation",exc_info=True)
        sys.exit(1)

with StackContext(die_on_error):
    # Any exception thrown here *or in callback and its desendents*
    # will cause the process to exit instead of spinning endlessly
    # in the ioloop.
    http_client.fetch(url, callback)
ioloop.start()
複製代碼

head

from __future__ import with_statement

import contextlib
import functools
import itertools
import logging
import threading
複製代碼

_State

class _State(threading.local):
    def __init__(self):
        self.contexts = ()
_state = _State()
複製代碼

StackContext

  1. 初始化時保持傳入的上下文對象
  2. __enter__
    1. 保存當前的全局上下文
    2. append 新的上下文到全局上下文
    3. 構造新的上下文
    4. 進入新的上下文 __enter__
  3. __exit__
    1. 調用新的上下文 context __exit__
    2. 回覆全局上下文

全局上下文保存整個執行程序的上下文(棧)
with StackContext(context) 使程序包裹在 (global_context, context)上執行
執行完成後恢復全局上下文less

class StackContext(object):
    def __init__(self, context_factory):
        self.context_factory = context_factory


    def __enter__(self):
        self.old_contexts = _state.contexts
        _state.contexts = (self.old_contexts +
                           ((StackContext, self.context_factory),))
        try:
            self.context = self.context_factory()
            self.context.__enter__()
        except Exception:
            _state.contexts = self.old_contexts
            raise

    def __exit__(self, type, value, traceback):
        try:
            return self.context.__exit__(type, value, traceback)
        finally:
            _state.contexts = self.old_contexts
複製代碼

ExceptionStackContext

捕獲上下文執行中拋出而又未被捕獲的異常
做用相似 finally
用於執行在程序拋出異常後記錄日誌、關閉 socket 這些現場清理工做
若是 exception_handler 中返回 True,代表異常已經被處理,不會再拋出socket

example

from tornado import ioloop
import tornado.stack_context
import contextlib

ioloop = tornado.ioloop.IOLoop.instance()

@contextlib.contextmanager
def context_without_catch():
    print("enter context")
    yield
    print("exit context")


def exception_handler(type, value, traceback):
    print "catch uncaught exception:", type, value, traceback
    return True

def main():
    with tornado.stack_context.ExceptionStackContext(exception_handler):
        with tornado.stack_context.StackContext(context_without_catch):
            print 0 / 0

main()
# enter context
# catch uncaught exception: <type 'exceptions.ZeroDivisionError'> integer division or modulo by zero <traceback object at 0x0000000003321FC8>

複製代碼

__exit__ 中捕獲 with 語句所包裹的程序執行中所拋出的異常,調用註冊的 exception_handler 進行處理
exception_handler 返回 True,則異常不會蔓延async

class ExceptionStackContext(object):
    def __init__(self, exception_handler):
        self.exception_handler = exception_handler

    def __enter__(self):
        self.old_contexts = _state.contexts
        _state.contexts = (self.old_contexts +
                           ((ExceptionStackContext, self.exception_handler),))

    def __exit__(self, type, value, traceback):
        try:
            if type is not None:
                return self.exception_handler(type, value, traceback)
        finally:
            _state.contexts = self.old_contexts
複製代碼

NullContext

臨時構造一個空的全局上下文函數

class NullContext(object):
    def __enter__(self):
        self.old_contexts = _state.contexts
        _state.contexts = ()

    def __exit__(self, type, value, traceback):
        _state.contexts = self.old_contexts
複製代碼

wrap

  1. 比較當時的全局上下文(_state.contexts)和閉包中保存的上下文(contexts)
    1. 若是當前上下文長度長,表面執行環境須要從新構造
    2. 若是有二者有任何一個上下文不一樣,執行環境也要從新構造
      1. 新建一個 NullContext(),清除當前的_state.contexts(保存原來的,提出時復原)
      2. 以此爲基礎構造一個 contexts 上下文鏈
    3. 若是 contexts 是 當前上下文的一個 prefix,則將當前上下文的後續部分做爲上下文鏈,前面共有的無需再構造
  2. 在新的上下文鏈(new_contexts)上執行 with 操做,保證 callback 的執行環境與當時保存時的如出一轍

之因此進行這樣複雜的操做,是爲了對某些前面執行環境相同的狀況省略前面的構造,節省時間,不然,能夠用一行代替:tornado

new_contexts = ([NullContext()] + [cls(arg) for (cls,arg) in contexts])oop

def wrap(fn):
    if fn is None:
      return None

    def wrapped(callback, contexts, *args, **kwargs):
        # 函數實際調用時,上下文環境發生了變化,與`contexts = _state.contexts`已經有所不一樣
        if (len(_state.contexts) > len(contexts) or
            any(a[1] is not b[1]
                for a, b in itertools.izip(_state.contexts, contexts))):
            # contexts have been removed or changed, so start over
            new_contexts = ([NullContext()] +
                            [cls(arg) for (cls,arg) in contexts])
        else:
            new_contexts = [cls(arg)
                            for (cls, arg) in contexts[len(_state.contexts):]]
        if len(new_contexts) > 1:
            with contextlib.nested(*new_contexts):
                callback(*args, **kwargs)
        elif new_contexts:
            with new_contexts[0]:
                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

複製代碼

copyright

author:bigfish
copyright: 許可協議 知識共享署名-非商業性使用 4.0 國際許可協議fetch

相關文章
相關標籤/搜索