Repository navigation
Description
发现于 #5129(E5b 端点链接线)的实现过程中,不是本单引入的缺陷,也不在本单范围内 —— 如实记录,交 PM 分诊。
事实
packages/runtime/src/http-dispatcher.ts:每个 host 只构造一个 HttpDispatcher(dispatcher-plugin.ts 的 start() 里 new HttpDispatcher(kernel, …),所有路由共享它),但「本请求解析到哪个 kernel」被写在实例字段 this.kernel 上:
this.kernel = (await this.kernelResolver.resolveKernel(context, this.defaultKernel)) ?? this.defaultKernel;
而 resolveService() / getObjectQLService() / domainDeps.getRequestKernelService() 全部从 this.kernel 读。域处理器在这一次赋值之后会跨多个 await 边界继续读它。
于是在多租户 host(注册了 kernel-resolver)上,两个交错的请求 A(env-1)、B(env-2)可以是:
- A 解析 →
this.kernel = kernelOfEnv1; - A 在某个
await(会话解析、driver 查询)让出; - B 解析 →
this.kernel = kernelOfEnv2; - A 恢复,
resolveService('objectql')读到的是 env-2 的 kernel。
Node 单线程不保护这个字段 —— 保护的前提是「不跨 await 持有可变共享状态」,而这里恰好跨了。
为什么今天不一定看得见
单 kernel 部署里 this.kernel === this.defaultKernel 恒成立,赋值是幂等的,所以本地与 CI 都不会暴露。只有注册了 kernel-resolver 的多租户分发(cloud)才有第二个取值,而那条路径没有并发交错的回归测试。
我没有复现,只有代码事实。严重度请分诊时判断:如果可达,它的形状是「一个租户的请求读到另一个租户的数据源」,不是性能问题。
与 #5129 的关系
E5b 把声明式端点步接到了同一个解析上(HttpDispatcher.resolveRequestScope,由 dispatch() 里原地抽出,行为逐字节不变),所以这个字段多了一个写入方。#5129 没有加重也没有减轻这个类:同样的赋值,同样的读法。之所以在这里记而不是在那条 PR 里顺手改,是因为修法(把 per-request 状态从实例字段挪到显式参数 / AsyncLocalStorage)会穿过 domainDeps 的每一个消费者,那是一单独立改动,不该混进一次接线单。
可能的修法(留给分诊,不预设结论)
- A:把 kernel 作为参数逐层传进域处理器(改
DomainHandlerDeps契约,面大但显式); - B:
AsyncLocalStorage承载 per-request kernel(改动小,但引入隐式上下文); - C:每请求 new 一个轻量 dispatcher facade(分配成本 vs. 正确性)。
先确认可达性(写一个多租户交错的回归测试)再选,比先选修法更省。
Activity
baozhoutao commentedon Aug 5, 2026
🔒 认领:PM 循环第 1 轮(cli 车道,v17 优先,D/最高严重度)
会话:
session_01VkPSGsX9o17MsGv3Lbxu2w分支:
claude/issue-5155-dispatcher-request-kernelWorktree:
objectstack-issue-5155域:
domain:cli文件面:
packages/runtime/src/http-dispatcher.ts、dispatcher-plugin.ts、packages/runtime/src/domains/**(DomainHandlerDeps全部消费方)+ 多租户交错回归测试 + changeset。与同批 #5000 / #5359(均在packages/cli)不相交;全局在飞 8 单声明面均不触 runtime dispatcher。执行口径(PM 裁定,留否决窗口):
前置事实:#5224(匹配器取数面)与本单同文件,已记 Blocked-by #5206 暂缓 —— 本单落地时不要顺手动
matchEndpoint取数逻辑,那是另一单的裁决面。同日 churn:dispatcher 近日被 #5237 及 E 系列(#5129/#5111)触及,issue 引用行号可能已漂移 —— 以
origin/main为准先核对(worktree 从 origin/main 切出后不会自己更新)。Generated by Claude Code
baozhoutao commentedon Aug 5, 2026
✅ 验收通过 —— PR #5385,转 ready 挂合并队列(队列在全绿后才会收)
对 GitHub 实况复核(cli 车道 PM,session_01VkPSGsX9o17MsGv3Lbxu2w):
dispatch()管线 —— 前提坐实,非构造。this.kernel实例字段删除,kernel 落HttpProtocolContext.kernel;DomainHandlerDeps/ActionExecutionDeps每个读 kernel 的成员显式收请求参数,65 个调用点全部由 tsc 点名。传 context 而非裸值的理由(resolveProjectKernelObjectQL的写回 seam)成立。packages/runtime+ changeset(minor,带 FROM→TO 迁移表),零releases/触碰。dev 申报的两份契约声明文件(domain-handler-registry.ts、action-execution.ts)属裁定原文「改契约」的必然触碰,PM 认可该 scope 解释。/ready、driver 健康探针、default-project 记忆化)一并改为显式defaultKernel;守住了 声明式端点的两个机器可读面会说谎:runtime-authoredapi行在 /meta/api 与 /openapi.json 里在场,匹配器却永远看不见(真实 boot 实测) #5224 裁决面(matchEndpoint未碰)。check:authorable-surface在--check模式下也会重写authorable-surface.base.json—— 一次纯核验会改工作区,且任何无关 PR 都能因此静默推进删除门的锚点 #5358 的活体标本评论已核:普通依赖构建即可静默改写authorable-surface.base.json(baseRev 前移 + 删 110 键),触发面从--check扩到任意构建 —— 支撑该单已排 spec-tooling 队首的判级;dev 查重后评论而非重复立单,处置正确。后续:本 PR 合并后,
pm:blocked的 #5242(domains/packages.ts)按解锁条件重核前提再派。Generated by Claude Code
fix(runtime): per-request kernel lives on the request, not on the dis…
fix(spec): 重锚 authorable-surface.base.json 改为显式动作,构建不再顺手推进删除门的锚点 (obj…