理想-咸鱼

鱼翔浅底


  • Home

  • Archives

  • Tags

  • Categories

ubuntu12.04 上搭建 bochs2.3.5 调试环境

Posted on 2020-01-18
Words count in article: 834 | Reading time ≈ 4

ubuntu12.04 上搭建 bochs2.3.5 调试环境

在 ubuntu12.04 64 位环境下使用源码编译 bochs2.3.5 带 debug 带 gui 版本。

Read more »

Hello World

Posted on 2020-01-16
Words count in article: 73 | Reading time ≈ 1

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

hello,hexo

Posted on 2020-01-15
Words count in article: 2 | Reading time ≈ 1

hello, hexo!

uefi security boot 实现细节

Posted on 2020-01-08
Words count in article: 950 | Reading time ≈ 3

uefi security boot 实现细节

密钥体系

PKpub

Platform Key

  • 确定平台所有者和平台固件之间的信任关系
  • BIOS 厂商 / OEM 创建这个 KEY
  • PK 是自签名
  • PKpriv 的所有者必须保证私钥的安全

KEKpub

Key Exchange Key

  • 确定平台固件和操作系统之间的信任关系
  • KEY 用来对 db/dbx 进行签名
  • 操作系统厂商提供
  • OEM 也可提供自己的 KEY 对 shell app 进行签名

DB

合规数据库

  • 用来存放运行被执行的代码的签名,或者存储证书
  • OEM 可以把 IHV(独立硬件厂商)的签名放在 DB 里面,用来对第三方 OpROMs(UEFI CA) 进行校验
  • MS CA / UEFI CA 由 MSFT 提供.

DBX

禁止数据库

  • 用来存储禁止执行代码的签名
  • 或者被禁止的公司的证书

KEY 的生成过程

db 及 dbx 存放的是记录以及由某个 KEK 对该记录进行签名的数据,记录可以是:
1. efi 文件的 hash 值
2. efi 文件的签名数据
3. 对 efi 文件进行签名的证书

PK 由平台所有者持有,比如联想
KEK 由 PK 签名

db/dbx 数据进行更新时,bios 需要进行授权验证,验证该更新数据是否是由某个 KEK 进行签名,若验证通过,则将该条记录及其对应的签名数据存入 db/dbx。

MSCApri 以及 UEFICApri 可以对厂商提供的驱动进行签名。

驱动更新流程

比如某个显卡厂商 Card

  • 使用自己的私钥 (Cardpri) 对自己的驱动进行签名生成 CardSign,(其证书为 CardCert)。

  • 找 UEFICApri 对自己的数据(CardSign、CardCert)进行签名,生成 ((CardSign,UEFICApri_CardSign_Sign )(CardCert,UEFICApri_CardCert_Sign )) 。

  • 调用 bios 接口进行更新,更新数据为 ((CardSign,UEFICApri_CardSign_Sign )(CardCert,UEFICApri_CardCert_Sign ))

  • bios 中有 UEFICApub 对应的 KEY (UEFIKEKpub)

  • bios 遍历 KEK,使用 KEK 对 ((CardSign,UEFICApri_CardSign_Sign )(CardCert,UEFICApri_CardCert_Sign ))进行验签

  • 验签通过,将条目 (CardSign,UEFICApri_CardSign_Sign)、(CardCert,UEFICApri_CardCert_Sign)存入 db

efi 文件无签名数据的情况

按照上述校验流程,显卡厂商也可以不使用自己的私钥对驱动签名,而只是生成一个哈希值。
再使用 UEFICApri 对哈希值进行签名生成 (CardHash, UEFICApri_CardHash_Sign)。

要将该哈希及对应签名信息更新到 db 时,仍然要使用 KEK 验证 UEFICApri_CardHash_Sign。

使用 hash 值的 efi 文件在启动时,只需要验证该 efi 文件的 hash 是否匹配 db/dbx 中的某个 hash。

驱动校验流程

  • bios 验证显卡驱动
  • 显卡驱动中由对应的签名的证书(CardCert)信息。
  • 遍历 db 找到 CardCert
  • 使用 CardCert 对 db 中所有类型为签名值的条目进行验证
  • 如果某条验证通过,则校验通过

dbx

dbx 内容与 db 中数据格式一致,匹配 dbx 中数据则认为验证失败

MSCApri 与 UEFICApri

市面上已经有大量使用 MSCApri 以及 UEFICApri 签名的 efi。

要加入该体系,只需要,使用 PKpri 对 MSCApub 及 UEFICApub 进行签名,生成对应的两个 KEK: MSKEKpub UEFIKEKpub。

将该两个 KEK 加入 bios , 即可以支持已签名的 efi。

OS Loader

比如 grub

OS Loader 使用 MSCApri 签名,对应的 MSCApub 存储于 db,同时 MSKEKpub 也内置于 bios。

db/dbx 防篡改

db/dbx 每条记录都是 数据以及使用 KEK 对该数据的签名。
bios 启动时,逐条遍历记录并验证签名。

概括

通过使用分级密钥体系

  • 平台所有者只持有 PKpri
  • 各级板卡厂商及操作系统厂商可以使用各自的公私钥体系对数据进行签名
  • 平台所有者通过签名各级厂商的公钥生成 KEK, 将平台所有者认为可信的厂商加入信任

Sync From: https://github.com/TheBigFish/blog/issues/15

python 类函数绑定方法的实现

Posted on 2019-12-18
Words count in article: 612 | Reading time ≈ 3

python 类函数绑定方法的实现

实现一个函数描述器

This means that all functions are non-data descriptors which return bound methods when they are invoked from an object.

所有的函数都是一个无数据的描述器。类实例调用函数即触发描述器语法,该描述器在类实例被调用时,返回一个绑定的普通方法。

下面实现了一个纯 python 的描述器 BindFunction, 用来绑定方法 f_normal 到函数 f。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class D:
def f(self, name):
print(self, name)
d=D()
d.f("hello")
D.f(d, "hello")


import types
from functools import wraps


def f_normal(self, name):
print(self, name)


class BindFunction(object):
def __init__(self, func):
wraps(func)(self)

def __call__(self, *args, **kwargs):
return self.__wrapped__(*args, **kwargs)

def __get__(self, obj, objtype=None):
"Simulate func_descr_get() in Objects/funcobject.c"
if obj is None:
return self
return types.MethodType(self, obj)


class D:
f = BindFunction(f_normal)

d = D()
d.f("world")
D.f(d, "world")

类 BindFunction 也可以实现如下:

1
2
3
4
5
6
7
8
9
class BindFunction(object):
def __init__(self, func):
self.func = func

def __get__(self, obj, objtype=None):
"Simulate func_descr_get() in Objects/funcobject.c"
if obj is None:
return self.func
return types.MethodType(self.func, obj)

Functions and Methods

Functions and Methods

Python’s object oriented features are built upon a function based environment. Using non-data descriptors, the two are merged seamlessly.
Class dictionaries store methods as functions. In a class definition, methods are written using def or lambda, the usual tools for creating functions. Methods only differ from regular functions in that the first argument is reserved for the object instance. By Python convention, the instance reference is called self but may be called this or any other variable name.

To support method calls, functions include the __get__() method for binding methods during attribute access. This means that all functions are non-data descriptors which return bound methods when they are invoked from an object. In pure Python, it works like this:

class Function(object):
    . . .
    def __get__(self, obj, objtype=None):
        "Simulate func_descr_get() in Objects/funcobject.c"
        if obj is None:
            return self
        return types.MethodType(self, obj)
Running the interpreter shows how the function descriptor works in practice:

>>>
>>> class D(object):
...     def f(self, x):
...         return x
...
>>> d = D()

# Access through the class dictionary does not invoke __get__.
# It just returns the underlying function object.
>>> D.__dict__['f']
<function D.f at 0x00C45070>

# Dotted access from a class calls __get__() which just returns
# the underlying function unchanged.
>>> D.f
<function D.f at 0x00C45070>

# The function has a __qualname__ attribute to support introspection
>>> D.f.__qualname__
'D.f'

# Dotted access from an instance calls __get__() which returns the
# function wrapped in a bound method object
>>> d.f
<bound method D.f of <__main__.D object at 0x00B18C90>>

# Internally, the bound method stores the underlying function,
# the bound instance, and the class of the bound instance.
>>> d.f.__func__
<function D.f at 0x1012e5ae8>
>>> d.f.__self__
<__main__.D object at 0x1012e1f98>
>>> d.f.__class__
<class 'method'>

Sync From: https://github.com/TheBigFish/blog/issues/14

codeblock 链接 openssl 库文件

Posted on 2019-07-17
Words count in article: 138 | Reading time ≈ 1

codeblock 链接 openssl 库文件

安装 codeblock

版本为: codeblocks-17.12mingw-setup.exe
gcc: 5.1.0

安装 MSYS-1.0.10.exe

将 `CodeBlocks\MinGW` 下文件拷贝至 `msys\1.0\mingw`
gcc -v 输出版本号即成功

安装 perl 5

strawberry-perl-5.30.0.1-64bit.msi

编译 openssl

- 版本 openssl-1.0.2j.tar.gz
- 编译
    - ./config
    - make
    - make test
    - make install
编译后文件位于 `msys\1.0\local\ssl`

配置 codeblock

- project -> build options -> linker settings 增加:
    - libcrypto.a
    - libssl.a
    - libgdi32.a
- project -> build options -> search directories 增加:
    - msys\1.0\local\ssl\include

完成


Sync From: https://github.com/TheBigFish/blog/issues/13

tornado源码之coroutine分析

Posted on 2019-01-16
Words count in article: 2.4k | Reading time ≈ 10

tornado 源码之 coroutine 分析

tornado 的协程原理分析
版本:4.3.0

为支持异步,tornado 实现了一个协程库。

tornado 实现的协程框架有下面几个特点:

  1. 支持 python 2.7,没有使用 yield from
    特性,纯粹使用 yield 实现
  2. 使用抛出异常的方式从协程返回值
  3. 采用 Future 类代理协程(保存协程的执行结果,当携程执行结束时,调用注册的回调函数)
  4. 使用 IOLoop 事件循环,当事件发生时在循环中调用注册的回调,驱动协程向前执行

由此可见,这是 python 协程的一个经典的实现。

本文将实现一个类似 tornado 实现的基础协程框架,并阐述相应的原理。

外部库

使用 time 来实现定时器回调的时间计算。
bisect 的 insort 方法维护一个时间有限的定时器队列。
functools 的 partial 方法绑定函数部分参数。
使用 backports_abc 导入 Generator 来判断函数是否是生成器。

1
2
3
4
import time
import bisect
import functools
from backports_abc import Generator as GeneratorType

Future

是一个穿梭于协程和调度器之间的信使。
提供了回调函数注册 (当异步事件完成后,调用注册的回调)、中间结果保存、结束结果返回等功能

add_done_callback 注册回调函数,当 Future 被解决时,改回调函数被调用。
set_result 设置最终的状态,并且调用已注册的回调函数

协程中的每一个 yield 对应一个协程,相应的对应一个 Future 对象,譬如:

1
2
3
4
5
@coroutine
def routine_main():
yield routine_simple()

yield sleep(1)

这里的 routine_simple() 和 sleep(1) 分别对应一个协程,同时有一个 Future 对应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Future(object):
def __init__(self):
self._done = False
self._callbacks = []
self._result = None

def _set_done(self):
self._done = True
for cb in self._callbacks:
cb(self)
self._callbacks = None

def done(self):
return self._done

def add_done_callback(self, fn):
if self._done:
fn(self)
else:
self._callbacks.append(fn)

def set_result(self, result):
self._result = result
self._set_done()

def result(self):
return self._result

IOLoop

这里的 IOLoop 去掉了 tornado 源代码中 IO 相关部分,只保留了基本需要的功能,如果命名为 CoroutineLoop 更贴切。

这里的 IOLoop 提供基本的回调功能。它是一个线程循环,在循环中完成两件事:

  1. 检测有没有注册的回调并执行
  2. 检测有没有到期的定时器回调并执行

程序中注册的回调事件,最终都会在此处执行。
可以认为,协程程序本身、协程的驱动程序 都会在此处执行。
协程本身使用 wrapper 包装,并最后注册到 IOLoop 的事件回调,所以它的从预激到结束的代码全部在 IOLoop 回调中执行。
而协程预激后,会把 Runner.run() 函数注册到 IOLoop 的事件回调,以驱动协程向前运行。

理解这一点对于理解协程的运行原理至关重要。

这就是单线程异步的基本原理。因为都在一个线程循环中执行,我们可以不用处理多线程需要面对的各种繁琐的事情。

IOLoop.start

事件循环,回调事件和定时器事件在循环中调用。

IOLoop.run_sync

执行一个协程。

将 run 注册进全局回调,在 run 中调用 func() 启动协程。
注册协程结束回调 stop, 退出 run_sync 的 start 循环,事件循环随之结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class IOLoop(object):,
def __init__(self):
self._callbacks = []
self._timers = []
self._running = False

@classmethod
def instance(cls):
if not hasattr(cls, "_instance"):
cls._instance = cls()
return cls._instance

def add_future(self, future, callback):
future.add_done_callback(
lambda future: self.add_callback(functools.partial(callback, future)))

def add_timeout(self, when, callback):
bisect.insort(self._timers, (when, callback))

def call_later(self, delay, callback):
return self.add_timeout(time.time() + delay, callback)

def add_callback(self, call_back):
self._callbacks.append(call_back)

def start(self):
self._running = True
while self._running:

# 回调任务
callbacks = self._callbacks
self._callbacks = []
for call_back in callbacks:
call_back()

# 定时器任务
while self._timers and self._timers[0][0] < time.time():
task = self._timers[0][1]
del self._timers[0]
task()

def stop(self):
self._running = False

def run_sync(self, func):
future_cell = [None]

def run():
try:
future_cell[0] = func()
except Exception:
pass

self.add_future(future_cell[0], lambda future: self.stop())

self.add_callback(run)

self.start()
return future_cell[0].result()

coroutine

协程装饰器。
协程由 coroutine 装饰,分为两类:

  1. 含 yield 的生成器函数
  2. 无 yield 语句的普通函数

装饰协程,并通过注册回调驱动协程运行。
程序中通过 yield coroutine_func() 方式调用协程。
此时,wrapper 函数被调用:

  1. 获取协程生成器
  2. 如果是生成器,则
    1. 调用 next() 预激协程
    2. 实例化 Runner(),驱动协程
  3. 如果是普通函数,则
    1. 调用 set_result() 结束协程

协程返回 Future 对象,供外层的协程处理。外部通过操作该 Future 控制协程的运行。
每个 yield 对应一个协程,每个协程拥有一个 Future 对象。

外部协程获取到内部协程的 Future 对象,如果内部协程尚未结束,将 Runner.run() 方法注册到 内部协程的 Future 的结束回调。
这样,在内部协程结束时,会调用注册的 run() 方法,从而驱动外部协程向前执行。

各个协程通过 Future 形成一个链式回调关系。

Runner 类在下面单独小节描述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def coroutine(func):
return _make_coroutine_wrapper(func)

# 每个协程都有一个 future, 代表当前协程的运行状态
def _make_coroutine_wrapper(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
future = Future()

try:
result = func(*args, **kwargs)
except (Return, StopIteration) as e:
result = _value_from_stopiteration(e)
except Exception:
return future
else:
if isinstance(result, GeneratorType):
try:
yielded = next(result)
except (StopIteration, Return) as e:
future.set_result(_value_from_stopiteration(e))
except Exception:
pass
else:
Runner(result, future, yielded)
try:
return future
finally:
future = None
future.set_result(result)
return future
return wrapper

协程返回值

因为没有使用 yield from,协程无法直接返回值,所以使用抛出异常的方式返回。

python 2 无法在生成器中使用 return 语句。但是生成器中抛出的异常可以在外部 send() 语句中捕获。
所以,使用抛出异常的方式,将返回值存储在异常的 value 属性中,抛出。外部使用诸如:

1
2
3
try:
yielded = gen.send(value)
except Return as e:

这样的方式获取协程的返回值。

1
2
3
4
5
class Return(Exception):
def __init__(self, value=None):
super(Return, self).__init__()
self.value = value
self.args = (value,)

Runner

Runner 是协程的驱动器类。

self.result_future 保存当前协程的状态。
self.future 保存 yield 子协程传递回来的协程状态。
从子协程的 future 获取协程运行结果 send 给当前协程,以驱动协程向前执行。

注意,会判断子协程返回的 future
如果 future 已经 set_result,代表子协程运行结束,回到 while Ture 循环,继续往下执行下一个 send;
如果 future 未 set_result,代表子协程运行未结束,将 self.run 注册到子协程结束的回调,这样,子协程结束时会调用 self.run,重新驱动协程执行。

如果本协程 send() 执行过程中,捕获到 StopIteration 或者 Return 异常,说明本协程执行结束,设置 result_future 的协程返回值,此时,注册的回调函数被执行。这里的回调函数为本协程的父协程所注册的 run()。
相当于唤醒已经处于 yiled 状态的父协程,通过 IOLoop 回调 run 函数,再执行 send()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class Runner(object):
def __init__(self, gen, result_future, first_yielded):
self.gen = gen
self.result_future = result_future
self.io_loop = IOLoop.instance()
self.running = False
self.future = None

if self.handle_yield(first_yielded):
self.run()

def run(self):
try:
self.running = True
while True:

try:
# 每一个 yield 处看做一个协程,对应一个 Future
# 将该协程的结果 send 出去
# 这样外层形如 ret = yiled coroutine_func() 能够获取到协程的返回数据
value = self.future.result()
yielded = self.gen.send(value)
except (StopIteration, Return) as e:
# 协程执行完成,不再注册回调
self.result_future.set_result(_value_from_stopiteration(e))
self.result_future = None
return
except Exception:
return
# 协程未执行结束,继续使用 self.run() 进行驱动
if not self.handle_yield(yielded):
return
finally:
self.running = False

def handle_yield(self, yielded):
self.future = yielded
if not self.future.done():
# 给 future 增加执行结束回调函数,这样,外部使用 future.set_result 时会调用该回调
# 而该回调是把 self.run() 注册到 IOLoop 的事件循环
# 所以,future.set_result 会把 self.run() 注册到 IOLoop 的事件循环,从而在下一个事件循环中调用
self.io_loop.add_future(
self.future, lambda f: self.run())
return False
return True

sleep

sleep 是一个延时协程,充分展示了协程的标准实现。

  • 创建一个 Future,并返回给外部协程;
  • 外部协程发现是一个未完的状态,将 run() 注册到 Future 的完成回调,同时外部协程被挂起;
  • 在设置的延时后,IOLoop 会回调 set_result 结束协程;
  • IOLoop 调用 run() 函数;
  • IOLoop 调用 send(),唤醒挂起的外部协程。

流程如下图:

1
2
3
4
def sleep(duration):
f = Future()
IOLoop.instance().call_later(duration, lambda: f.set_result(None))
return f

运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@coroutine
def routine_ur(url, wait):
yield sleep(wait)
print('routine_ur {} took {}s to get!'.format(url, wait))


@coroutine
def routine_url_with_return(url, wait):
yield sleep(wait)
print('routine_url_with_return {} took {}s to get!'.format(url, wait))
raise Return((url, wait))

# 非生成器协程,不会为之生成单独的 Runner()
# coroutine 运行结束后,直接返回一个已经执行结束的 future
@coroutine
def routine_simple():
print("it is simple routine")

@coroutine
def routine_simple_return():
print("it is simple routine with return")
raise Return("value from routine_simple_return")

@coroutine
def routine_main():
yield routine_simple()

yield routine_ur("url0", 1)

ret = yield routine_simple_return()
print(ret)

ret = yield routine_url_with_return("url1", 1)
print(ret)

ret = yield routine_url_with_return("url2", 2)
print(ret)


if __name__ == '__main__':
IOLoop.instance().run_sync(routine_main)

运行输出为:

it is simple routine
routine_ur url0 took 1s to get!
it is simple routine with return
value from routine_simple_return
routine_url_with_return url1 took 1s to get!
('url1', 1)
routine_url_with_return url2 took 2s to get!
('url2', 2)

可以观察到协程 sleep 已经生效。

源码

simple_coroutine.py

copyright

author:bigfish
copyright: 许可协议 知识共享署名 - 非商业性使用 4.0 国际许可协议


Sync From: https://github.com/TheBigFish/blog/issues/12

rust 安装及配置

Posted on 2019-01-03
Words count in article: 115 | Reading time ≈ 1

rust 安装及配置

配置参数

1
2
3
4
5
6
7
8
vi ~/.bashrc

#add
export CARGO_HOME="~/.cargo/"
export RUSTBINPATH="~/.cargo/bin"
export RUST="~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu"
export RUST_SRC_PATH="$RUST/lib/rustlib/src/rust/src"
export PATH=$PATH:$RUSTBINPATH

修改源

.basrc

1
2
3
4
vi ~/.bashrc
#add
export RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static
export RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup

config

1
2
3
4
5
6
7
vi ~/.cargo/config
#add
[source.crates-io]
replace-with = 'ustc'

[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"

ch02-00-guessing-game-tutorial.md 编译错误

1
2
3
// main.rst 文件顶部增加

extern crate rand;

Sync From: https://github.com/TheBigFish/blog/issues/11

tornado源码之StackContext(二)

Posted on 2018-12-21
Words count in article: 1k | Reading time ≈ 4

tornado 源码之 StackContext(二)

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

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.

一个栈结构的上下文处理类
异常处理也是一个栈结构的上下文应用

contents

  • tornado 源码之 StackContext(二)
    • contents
    • example usage
    • head
    • _State
    • StackContext
    • ExceptionStackContext
      • example
    • NullContext
    • wrap
    • copyright

example usage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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

1
2
3
4
5
6
7
from __future__ import with_statement

import contextlib
import functools
import itertools
import logging
import threading

_State

1
2
3
4
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) 上执行
执行完成后恢复全局上下文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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, 表明异常已经被处理,不会再抛出

example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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,则异常不会蔓延

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

临时构造一个空的全局上下文

1
2
3
4
5
6
7
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 的执行环境与当时保存时的一模一样

之所以进行这样复杂的操作,是为了对某些前面执行环境相同的情况省略前面的构造,节省时间,否则,可以用一行代替:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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 国际许可协议


Sync From: https://github.com/TheBigFish/blog/issues/10

tornado源码之StackContext(一)

Posted on 2018-12-21
Words count in article: 1.1k | Reading time ≈ 5

tornado 源码之 StackContext(一)

tornado 的异步上下文机制分析

contents

  • tornado 源码之 StackContext(一)
    • contents
    • MyIOLoop
    • 异步回调异常的捕获
    • 使用 wrap
    • 使用 contextlib
    • inspired by
    • copyright

我们实现一个简单的 MyIOLoop 类,模仿 tornado 的 IOLoop,实现异步回调
实现一个简单的 MyStackContext 类,模仿 tornado 的 StackContext,实现上下文

MyIOLoop

模拟 tornado IOLoop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MyIOLoop:
def __init__(self):
self._callbacks = []

@classmethod
def instance(cls):
if not hasattr(cls, "_instance"):
cls._instance = cls()
return cls._instance

def add_callback(self, call_back):
self._callbacks.append(call_back)

def start(self):
callbacks = self._callbacks
self._callbacks = []
for call_back in callbacks:
call_back()

异步回调异常的捕获

由输出可以看到,回调函数 call_func 中抛出的异常,在 main 函数中无法被捕获
main 函数只能捕获当时运行的 async_task 中抛出的异常, async_task 只是向 MyIOLoop 注册了一个回调,并没有当场调用回调
call_func 函数最终在 MyIOLoop.start 中调用,其异常没有被捕获

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
my_io_loop = MyIOLoop.instance()
times = 0


def call_func():
print 'run call_func'
raise ValueError('except in call_func')


def async_task():
global times
times += 1
print 'run async task {}'.format(times)
my_io_loop.add_callback(call_back=call_func)


def main():
try:
async_task()
except Exception as e:
print 'main exception {}'.format(e)
print 'end'


if __name__ == '__main__':
main()
my_io_loop.start()

# run async task 1
# Traceback (most recent call last):
# run call_func
# File "E:/learn/python/simple-python/stack_context_example.py", line 56, in <module>
# my_io_loop.start()
# File "E:/learn/python/simple-python/stack_context_example.py", line 26, in start
# call_back()
# File "E:/learn/python/simple-python/stack_context_example.py", line 36, in call_func
# raise ValueError('except in call_func')
# ValueError: except in call_func

使用 wrap

可以使用 wrap 的方式,把函数调用和异常捕捉写在一起,回调实际调用的是带异常捕捉的函数 wrapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
my_io_loop = MyIOLoop.instance()
times = 0


def call_func():
print 'run call_func'
raise ValueError('except in call_func')


def wrapper(func):
try:
func()
except Exception as e:
print 'wrapper exception {}'.format(e)


def async_task():
global times
times += 1
print 'run async task {}'.format(times)
my_io_loop.add_callback(call_back=functools.partial(wrapper, call_func))


def main():
try:
async_task()
except Exception as e:
print 'main exception {}'.format(e)
print 'end'


if __name__ == '__main__':
main()
my_io_loop.start()

# run async task 1
# run call_func
# wrapper exception except in call_func

由此,可以想到,构造一个上下文环境,使用全局变量保存这个执行环境,等回调函数执行的时候,构造出这个环境

使用 contextlib

下面模仿了 tornado 异步上下文实现机制

  1. MyStackContext 使用 __enter__ __exit__ 支持上下文
  2. MyStackContext 构造函数参数为一个上下文对象
  3. with MyStackContext(context) 进行如下动作:
    在 MyStackContext(context) 构造时,把 context 注册进全局工厂 MyStackContext.context_factory
    1. 进入 MyStackContext 的__enter
    2. 构造一个 context 对象
    3. 调用 context 对象的 __enter,进入真正 context 上下文
    4. 执行 context 上下文,my_context yield 语句前的部分
    5. 执行上下文包裹的语句,async_task
    6. async_task 中 add_callback,实际保存的 wrap, wrap 将此时的全局上下文环境 MyStackContext.context_factory 保存,以方便 call_back 调用
    7. 调用 context 对象的 __exit, 退出 context 上下文
    8. 进入 MyStackContext 的__exit
  4. my_io_loop.start() 执行, 调用注册的 _call_back
  5. 实际调用 wrapped 函数
    1. 获取保存的 context 环境
    2. with context
    3. 调用真正的 callback

这样,在 main 函数中执行

1
2
with MyStackContext(my_context):
async_task()

构造一个执行上下文 my_context,异步函数将在这个上下文中调用
效果上相当于在 my_context 这个上下文环境中调用 async_task
类似:

1
2
3
4
5
6
7
8
def my_context():
print '---enter my_context--->>'
try:
async_task()
except Exception as e:
print 'handler except: {}'.format(e)
finally:
print '<<---exit my_context ---'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import contextlib
import functools


class MyIOLoop:
def __init__(self):
self._callbacks = []

@classmethod
def instance(cls):
if not hasattr(cls, "_instance"):
cls._instance = cls()
return cls._instance

def add_callback(self, call_back):
self._callbacks.append(wrap(call_back))

def start(self):
callbacks = self._callbacks
self._callbacks = []
for call_back in callbacks:
self._call_back(call_back)

@staticmethod
def _call_back(func):
func()


class MyStackContext(object):
context_factory = []

def __init__(self, context):
if context:
MyStackContext.context_factory.append(context)

def __enter__(self):
try:
self.context = self.context_factory[0]()
self.context.__enter__()
except Exception:
raise

def __exit__(self, type, value, traceback):
try:
return self.context.__exit__(type, value, traceback)
finally:
pass


def wrap(fn):
def wrapped(callback, contexts, *args, **kwargs):
context = contexts[0]()
with context:
callback(*args, **kwargs)

contexts = MyStackContext.context_factory
result = functools.partial(wrapped, fn, contexts)
return result


my_io_loop = MyIOLoop.instance()

times = 0


def call_func():
print 'run call_func'
raise ValueError('except in call_func')


def async_task():
global times
times += 1
print 'run async task {}'.format(times)
my_io_loop.add_callback(call_back=call_func)


@contextlib.contextmanager
def my_context():
print '---enter my_context--->>'
try:
yield
except Exception as e:
print 'handler except: {}'.format(e)
finally:
print '<<---exit my_context ---'


def main():
with MyStackContext(my_context):
async_task()
print 'end main'


if __name__ == '__main__':
main()
my_io_loop.start()

# ---enter my_context--->>
# run async task 1
# <<---exit my_context ---
# end main
# ---enter my_context--->>
# run call_func
# handler except: except in call_func
# <<---exit my_context ---

inspired by

Tornado 源码分析(二)异步上下文管理(StackContext)

copyright

author:bigfish
copyright: 许可协议 知识共享署名 - 非商业性使用 4.0 国际许可协议


Sync From: https://github.com/TheBigFish/blog/issues/9

12<i class="fa fa-angle-right"></i>

bigfish

一些简单的想法

18 posts
5 tags
RSS
© 2020 bigfish
Powered by Hexo
|
Theme — NexT.Pisces v5.1.3
访客数 人 总访问量 次