Skip to content

沙箱三件套(__txBegin)同样不 join ambient 事务 —— #6168 修好了回调面,VM 侧 ctx.api.transaction 仍在事务中再开事务 #6406

Description

@baozhoutao

发现于 #6168 的实施(PD #10 范围外发现,未在该 PR 内修改)。未认领。

事实

#6168 / PR #6403ScopedContext.transaction()(回调式)补上了 ADR-0067 D2 的 ambient join。实施中实读发现:QuickJS 沙箱面的 ctx.api.transaction(fn) 根本不走那个方法,因此不在该修复的覆盖范围内。

packages/runtime/src/sandbox/quickjs-runner.ts:639-660 里,VM 侧的 ctx.api.transaction 是一段 JS 糖,底下驱动三个 host leaf:__txBegin / __txCommit / __txRollback__txBegin(:566-579)调的是离散三件套:

const begin = apiTx?.beginTransaction;
if (typeof begin === 'function') {
  const r = await begin.call(apiTx) ?? null;
  if (r) { txState.api = r.ctx; txState.handle = r.handle; }
}

ScopedContext.beginTransaction()(packages/objectql/src/engine.ts,三件套区)与修复前的回调面一样,没有 ambient 判定:直接 txDriver()driver.beginTransaction()

于是 #6168 描述的两条后果,在 VM 侧 body 上原样存在:

  1. 再要一条连接 —— 单连接池(knex/SQLite)上就是死锁;
  2. 内层由 __txCommit 自行 commit,写入存活过外层回滚 —— 外层 engine.transaction() 回滚后,VM 里写的行仍在,无报错无日志。

为什么不能顺手在 #6168 里一起修

三件套刻意不用 ALS:body 跨很多 host event-loop turn 执行(deferred promise + pump),setImmediate 边界上 AsyncLocalStorage 不存活 —— 这是三件套存在的全部理由,engine.ts 的三件套 TSDoc 与 ADR-0119 的「declared LIMIT」段都写明了。所以 __txBegin 里直接 txStore.getStore() 未必读得到外层 ambient(leaf 在 deferred 里跑,多半已不在那个 ALS 上下文里),不是加一个 if 就能了事。

至少需要决定:

  • ambient 句柄在哪一刻捕获并显式穿进 txState(建 sandbox api 时?还是 hook dispatch 时?);
  • join 之后 __txCommit / __txRollback 必须弃权(不能 commit 自己不拥有的事务),txState 得带 owned 位;
  • body 显式 rollback 一个 joined 事务时的语义(标记外层必回滚?还是报错?)—— 这一条是契约级决定,不是实现细节。

这些都超出 #6168 的范围(该单锁定回调面),故另开。

可达性(诚实说明)

#6168 同款,且更窄一层:需要「沙箱 hook/action body 显式调 ctx.api.transaction()」+「该 body 由一次 engine.transaction() 触发」+「该 body 走 QuickJS 而非进程内」三者同时成立。示例 app 里没有可指的用例。方向上是持久性一类,不是功能一类:失败时看起来一切正常,回滚后留下不该留的行。

注:quickjs-runner.ts:567 已挡住 body 自己的嵌套(nested ctx.api.transaction is not supported),但那只看 txState.open,看不见宿主侧的外层事务。

#6167 的关系

不是同一件事,别合并。#6167同源校验对不可归属句柄弃权(transactionCoversDriverFor 判不了引擎没开过的句柄);本单是嵌套时不 join。两者都落在三件套这片面上,#6167 的收口方向(让句柄属主在 IDataDriver 上可查)不解决本单,反之亦然。

Refs:#6168(回调面已修,PR #6403)、#6167、ADR-0067 D2、ADR-0119 D1 的 declared-LIMIT 段、#5696(owned 信号)。

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions