stores 的两笔欠账:承诺的内存实现不存在,下游自实现的准入路径断了 #5
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
我们是谁
dissect2 —— dissect 的第二版,正在重建,要接 PolyLoop 当执行内核。我们只提需求、不改这个仓库。
两笔连着的欠账
polyloop.stores现在只有一个实现。src/polyloop/stores/__init__.py:310:这里有两件事,第二件比第一件要紧。
一、design 0003 承诺过的内存实现至今不存在
research-wiki/design/0003-public-api-shape.md:558定过:存储接缝改成必填,另外加一个显式命名的、明确不提供恢复的内存实现,好让「我不要恢复」成为一次看得见的选择,而不是一个可以忘记传的参数。那个实现没写。
这一条对我们不构成阻塞——我们本来就要恢复能力,会用
JsonlRunStore。提它只是因为设计文档里那句话还在,下一个下游读到会去找一个不存在的类。要么补上,要么在那份 design doc 上追记说明它没做。二、「下游自己实现关系数据库形态」这条路现在是断的
src/polyloop/stores/__init__.py的模块 docstring 写着:README 阶段⑤也是同样的口径。这个安排我们认同,替下游写通用存储确实不合理。
问题是这条路的准入标准拿不到:
tests/contract/不随包发布(我们单独提了一个 issue)。所以「让下游自己实现、拿契约套件当准入标准」这个设计,实际执行不了。对我们的具体影响
上一版的记录层是每个 run 一个 SQLite 库,单 writer、WAL、每写一条立即提交。这一版要不要沿用还没定——要等第一批真实数据出来再看。
如果要沿用,我们得自己实现存储接缝的全部方法,并自证两条不容易的粒度契约:动作结果与步记录必须由一次写入原子落地,以及前缀持久性。这两条在 SQLite 上不难写,难的是证明我们写对了——而唯一的判据现在装不到。
想请你们做的
这一条的动作其实在另一个 issue 里(把契约套件发出去)。这里单独开一条,是因为它是那条 issue 的具体后果,而且带着一个独立的小欠账(内存实现)。
如果你们决定契约套件先不发布,那么请在
stores/__init__.py和 README 里把「契约套件是它的准入标准」这句话改掉——说了准入标准但拿不到,比不说更糟:它让下游以为有一条验证路径,而那条路径不存在。1.0.2 已发布,两笔都还了。
一、内存实现
polyloop.stores.VolatileRunStore。名字不叫「不提供恢复」,这一点值得解释。 一个真的读不回自己写过的东西的存储过不了自家的契约套件(套件第一条要的就是「写进去的意图读得回来」),而这一层的准入标准就是那套套件——一个过不了自家准入标准的实现不该存在。它不提供的是跨进程恢复:进程一退日志就没了。
Volatile说的正是这件事,而且它不撒谎。选它仍然是一次看得见的选择,0003那句承诺的意图达到了。它和逐行追加那个实现跑的是同一套契约套件(各一个子类),行为对齐做到这个程度:桶里存编码后的载荷、读的时候才解码,所以调用方写完再改自己手里那个 dict 改不到日志,读回来的日志被就地改动也污染不了存储本身——落盘那个实现每次重新解析文件、天然如此。一条字段类型不对的记录在两个实现上的下场也一样:写得进去,读的时候抛同一个解码错误(这一条是对抗审查逼出来的,原来的实现在写入时就炸,比落盘那个更严,方向反了)。
migrations/dissect.md里那段「不要照那句话去找一个不存在的类」也改掉了。二、准入路径
通了,见 #1。
stores的模块 docstring 和 README 阶段⑤那两句「契约套件是它的准入标准」现在指向polyloop.testing,而那个包装了就有——你们说的「说了准入标准但拿不到,比不说更糟」,这句话到这一版才第一次真正成立。你们那套 SQLite 实现要自证的两条粒度契约(动作结果与步记录由一次写入原子落地、前缀持久性),套件里对应的两条现在是无条件跳过,理由字符串写明了为什么这一层验不了、以及你们该在哪儿自己验:原子性要在写入过程中杀进程,而套件跑在一个进程里、面对一个已经装配好的实现,没有位置插入那次崩溃;前缀持久性说的是掉电之后才看得出来的性质。我们自己那个逐行追加的实现是靠一个可注入的故障点在它自己的单元测试里覆盖的,你们可以照这个形状做。
顺带登记一条缺口,你们写存储时会撞上
两个自带实现都把「同一个运行标识不许开第二次」做成了必须(一个靠
O_EXCL,一个靠显式检查),理由是驱动入口的「先读后写」挡不住两个进程同时开同一个标识。但RunStore这个端口没把它写成承诺,契约套件里也没有对应用例。 所以一个不做独占的存储能跑完整套准入标准全绿,而它接上run()之后那个并发窗口是敞开的。这不是这一版引入的,但套件变成对外的准入标准之后这个缺口的分量重了。补进契约是新增一条会让已经合格的实现变红的用例,要单独决定——你们那边如果一个运行标识可能被两个进程同时开,这条自己务必守住。 已登记在
research-wiki/design/0014-contract-suite-distribution.md文末。