fix(objectql): 区分「同一实例的幂等重入」与「真正的 driver 名字冲突」(#4773) - #5007
Merged
xuyushun441-sys merged 2 commits intoAug 4, 2026
Conversation
…real name collision (#4773) Every showcase boot logged `WARN Driver already registered, skipping {"driverName":"com.objectstack.driver.sql"}`. Traced end to end, the default datasource is registered twice on two legs of one round trip, with the SAME object instance both times: 1. DatasourceConnectionService.attemptConnect() registers the driver it built (isDefault: true), driven by DefaultDatasourcePlugin.init(); 2. that plugin republishes the instance it read back out of the engine as the `driver.<name>` kernel service, and ObjectQLPlugin.start()'s `driver.*` discovery loop bridges it back in (isDefault: false). Nothing is decided and nothing is discarded, so that re-entry now logs at `debug`. It is not a blanket downgrade: the same warn also covered two DIFFERENT instances claiming one name, where "skipping" silently drops one of two configurations. Those are now split by object identity — identical re-entry is quiet, a collision still warns and names which config was kept and which was discarded, and a dropped `isDefault` request warns rather than vanishing. Registration behaviour is unchanged: first registration still wins. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NrmBxj8rK2uGCnh9aipjwX
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 13 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
xuyushun441-sys
marked this pull request as ready for review
August 3, 2026 23:58
xuyushun441-sys
deleted the
claude/issue-4773-driver-double-registration
branch
August 4, 2026 00:04
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4773
逐条回答 issue 的三个问题
1. 谁注册了第一次、谁注册了第二次?
在真实 boot 上打桩
ObjectQL.prototype.registerDriver抓调用栈,得到确定答案 —— 两次,不多不少:对应源码位置:
isDefaultDatasourceConnectionService.attemptConnect(),由DefaultDatasourcePlugin.init()驱动packages/services/service-datasource/src/datasource-connection-service.ts:542(经packages/runtime/src/default-datasource-plugin.ts:142)trueObjectQLPlugin.start()的driver.*服务发现循环packages/objectql/src/plugin.ts:436false中间那一跳是关键:
DefaultDatasourcePlugin.init()在连上之后,把它刚从 engine 里读回来的那个对象(packages/runtime/src/default-datasource-plugin.ts:163-172,engine.getDriverByName(engine.getDefaultDriverName()))重新注册成driver.{name}kernel service —— 那是os migrate(schema-migrate.ts的SQL_DRIVER_SERVICES)和 serve 存储探测定位主库用的面。于是 Phase 2 里ObjectQLPlugin.start()的发现循环把它又交回给 engine。这是一趟往返的两条腿,不是两次独立注册。OS_DATABASE_URL推断和defineStack的 datasource 声明这两个候选都排除了:前者只产出定义(standalone-stack.ts只做 URL→config 翻译,不建 driver),后者的非 default datasource 在attemptConnect里会被engineDriver.name = name打上自己的名字,天生不撞名。2. ⭐ 两次注册的配置是否一致?—— 一致到对象同一性这一级
这是本单的承重问题,答案比「配置等价」更强:
两次传进
registerDriver的是同一个 JS 对象引用。没有第二份连接串、没有第二份能力集、没有任何一份配置被静默采纳或丢弃 —— 第二次调用在语义上是纯 no-op。(supports两次读出来不是同一个对象,只因为它是get supports()每次现造一个,sql-driver.ts:563;实例本身同一。)所以这不是 issue 第 2 点担心的「静默采用其中一份配置」,是第 3 点的「设计内的幂等重入」。
3. 那就该降级日志 —— 但不是一刀切降级
按 #4632 的降级日志级别约定,一条每次启动都出现、且不代表任何异常的
warn只会训练人忽略 warn。但直接把这行降成 debug 会连带吞掉真正要命的那一类:同一个名字下来了两个不同的实例时,「skipping」确实就是静默丢掉了两份配置中的一份 —— 而在今天的代码里,这两种情况打的是同一行、同一级别的日志,读日志的人无法分辨。所以按对象同一性把它劈成三支:
debug,并在日志点注明这趟往返为什么存在;warn,而且现在说清楚哪一份配置生效了、哪一份被丢了(带双方version),并给出修法(给第二个 datasource 起自己的名字);isDefault、而 default 已被别人占着 → 也warn:调用方的意图正在被无声丢弃。注册行为零变化,先到先得不变;变的只是哪一种值得运维看一眼。
复现前后对照(issue 里给的 repro)
rm -rf examples/app-showcase/.objectstack && pnpm dev(实跑在随机端口--fresh -p 38773/38774,不占 3000):before
after
剩下那条是已在跟踪的 #4968,不在本单范围内。
文件面(申报)
packages/objectql/src/engine.ts——registerDriver的守卫按对象同一性分三支 + 文档;packages/objectql/src/plugin.ts—— 仅注释:在第二个调用方(driver.*发现循环)处交叉引用这趟往返,让两端都能查到;packages/objectql/src/engine-driver-registration.test.ts(新)、packages/runtime/src/default-datasource-plugin.test.ts—— 回归测试;.changeset/driver-double-registration-log.md。packages/spec/**、protocol.ts、content/docs/releases/零改动;showcase 只读复现。测试
新增 5 条 engine 单测(安静重入 / 重入不改变既有注册 / 同名不同实例仍然响且点名双方 /
isDefault意图被丢弃仍然响 / 重申已持有的 default 保持安静),外加 1 条真 kernel boot 的回归钉(default-datasource-plugin.test.ts)。那条 boot 钉两半都断言,避免空转:(a) 这趟往返确实发生了 ——
registerDriver对 default 名字恰好被调 2 次、isDefault依次为true/false、第二次的实例toBe第一次的实例,且driver.{name}kernel servicetoBeengine 里那一个;(b) 整个 boot 的 stdout 里没有对应的 WARN(拦process.stdout.write而不是console.warn—— Node 下ObjectLogger直写 stdout,serve的 boot-quiet 窗口拦的也是这一路;第一版拦console.warn就是空转的,负向对照当场抓出来了)。负向对照:把
engine.ts换回origin/main重跑该钉 →AssertionError: expected [ Array(1) ] to deeply equal [],Tests 1 failed;换回本 PR → 通过。门禁链(合并
origin/main后重跑):check:engine-double-contractOK(9 pinned / 30 DEBT / 1 exempt)、check:durability-log-levelOK(10 seams)、check:startup-registry-verdictOK(47 seams)、check:driver-conformanceOK(20 cells)、check:adr-anchorsOK、check:init-service-contractOK、check:service-providersOK、check:type-check-coverageOK、check:nul-bytes/check:release-notes/check:doc-authoring/check:error-code-casing/check:role-wordOK,改动文件 eslint 无输出。🤖 Generated with Claude Code
https://claude.ai/code/session_01NrmBxj8rK2uGCnh9aipjwX