生命周期¶
阶段¶
一个阶段是一次遍历的名字。框架提供三个:
它们既是标记方法用的装饰器,也是 enter() 与 leave() 的参数。
class Database(Canary):
@init
def prepare(self) -> None: ...
@start
async def connect(self) -> None: ...
@stop
async def close(self) -> None: ...
同一个类的同一个阶段可以有多个钩子,按定义顺序执行;同一个方法可以同时属于多个阶段。
钩子可以是同步的,也可以是 async def——框架按返回值判断是否需要等待,因此返回协程的
同步函数同样成立。
进入:依赖在前¶
await unit.init() 在依赖图上进入一次 init:先进入依赖,再运行自身的钩子。
class Config(Canary): ...
class Database(Canary):
config = dep(Config)
class Service(Canary):
database = dep(Database)
await Service().init() # Config -> Database -> Service
三条性质:
- 同一个单元的同一个阶段只运行一次,无论有多少单元依赖它。
- 互不依赖的单元同时进入,因此耗时贴着依赖链的关键路径,而不是所有单元之和。
- 图检查通过之前不运行任何钩子。 依赖图先于一切构建,因此环、或
start()时某个单元的@init尚未运行,都在任何钩子运行之前报告。
栅栏¶
init() 的返回是一道栅栏:全部 @init 完成之后,才有任何 @start 运行。
这是 @init 与 @start 的实质区别。@init 不获取外部资源,所以它在整张图上跑一遍时,
失败不需要回收;@start 获取资源,所以它有配对的 @stop。
未调用 init() 就 start() 会抛出:
停止¶
单元从 @start 开始时就持有它获取的东西,而不是等它完成——因此启动到一半失败的单元
同样会被回收。
stop() 是唯一的回收路径。它停止本单元(除非仍被使用),再以同样的方式尝试它的每个依赖:
| 调用时机 | 行为 |
|---|---|
| 从未启动 | 本单元无可停止;仍会尝试它的依赖 |
只 init() 过 |
没有单元进入过 @start,空操作 |
| 正常启动之后 | 停止本单元,再停止不再被其他单元使用的依赖 |
| 仍被使用——有依赖者正在启动、运行或停止 | 跳过本单元,不报错;仍会尝试它的依赖 |
start() 仍在进行 |
等它结束,再作判断 |
| 重复调用 | 已无可回收,空操作 |
被多个单元共享的依赖,在最后一个使用者停止之后才停止。示例见
单元 › stop() 是单元的动作。
由于 stop() 会等待进行中的 start(),不要在同一张图的 @start 钩子里调用它:那会等待
自身。需要限时关闭时,用 asyncio.timeout 包住这次调用。
再次启动¶
回收时,被回收单元在 start 上的进入记录一并撤销,因此停止之后可以再次启动。@init
没有配对的回收阶段,它的记录保留,重启时不会再次运行:
以 after=start 声明的阶段随 start 一起撤销,重启之后需要重新进入。
失败的 start() 已经释放了它获取的东西(见下文),因此重试就是再调用一次。已经完成该阶段
的单元不会重复运行:
失败¶
启动失败。 start() 自行清理:
@start抛出的单元立即执行自己的@stop;- 依赖它的单元不启动,并释放它们正在等待的依赖;
- 与它同时启动的单元不会被取消——它们执行完毕,若不再被其他单元使用则随即释放。
start() 抛出时,它启动的一切都已停止,仍被其他运行中单元使用的除外。以 X → A、B、
A → C、B → C、B 失败为例:
回滚中 @stop 抛出的异常作为 note 附在最初那个异常上。调用方取消 start() 时,同样的回滚
在取消传播之前完成;取消无法携带 @stop 的异常,因此它们交给事件循环的异常处理器。
多个单元同时失败。 一个失败不会取消其他单元。多个单元失败时合并为一个
ExceptionGroup;只有一个失败时原样抛出,经多条路径到达的同一个失败只报告一次。
回收失败。 单个 @stop 抛出不中断整轮回收,其余单元照常回收,最后合并为一个
ExceptionGroup 抛出,即使只有一个:
自定义阶段¶
Phase 是公开的,加一个阶段不需要注册。Canary 的方法只是两个函数的薄包装——
unit.start() 即 enter(unit, start),unit.stop() 即 leave(unit, start)——自定义阶段
直接使用它们:
from canary_framework import Phase, enter, init, leave
rollback = Phase("rollback")
migrate = Phase("migrate", after=init, leave=rollback)
class Schema(Canary):
@migrate
async def apply(self) -> None: ...
@rollback
async def revert(self) -> None: ...
await unit.init()
await enter(unit, migrate) # 运行 @migrate,依赖在前
await leave(unit, migrate) # 运行 @rollback,本单元在前
after 声明前驱:尚未进入前驱时进入本阶段会抛 LifecycleError,而不是静默跳过。
leave 声明离开本阶段时运行的阶段,与 start 的 stop 完全相同:leave() 按与 stop()
相同的规则运行它们,@migrate 失败的单元也立即运行自己的 @rollback。
接入宿主¶
框架不认识任何外壳。宿主收异步上下文管理器时:
宿主收成对的启停回调时,直接给它 service.init / service.start / service.stop
三个方法。