Skip to content

fix(objectql): 区分「同一实例的幂等重入」与「真正的 driver 名字冲突」(#4773) - #5007

Merged
xuyushun441-sys merged 2 commits into
mainfrom
claude/issue-4773-driver-double-registration
Aug 4, 2026
Merged

fix(objectql): 区分「同一实例的幂等重入」与「真正的 driver 名字冲突」(#4773)#5007
xuyushun441-sys merged 2 commits into
mainfrom
claude/issue-4773-driver-double-registration

Conversation

@xuyushun441-sys

@xuyushun441-sys xuyushun441-sys commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #4773

逐条回答 issue 的三个问题

1. 谁注册了第一次、谁注册了第二次?

在真实 boot 上打桩 ObjectQL.prototype.registerDriver 抓调用栈,得到确定答案 —— 两次,不多不少:

--- call #1  name=com.objectstack.driver.sqlite-wasm  isDefault=true
    at DatasourceConnectionService.attemptConnect (services/service-datasource/dist/index.js:605)
    at async DatasourceConnectionService.connect
    at async DefaultDatasourcePlugin.init (runtime/dist/index.js:142)
    at async ObjectKernel.initPluginWithTimeout

--- call #2  name=com.objectstack.driver.sqlite-wasm  isDefault=false
    at ObjectQLPlugin.start (objectql/dist/index.mjs:8527)
    at ObjectKernel.startPluginWithTimeout
    at ObjectKernel.bootstrap

对应源码位置:

调用方 源码 isDefault
第一次() DatasourceConnectionService.attemptConnect(),由 DefaultDatasourcePlugin.init() 驱动 packages/services/service-datasource/src/datasource-connection-service.ts:542(经 packages/runtime/src/default-datasource-plugin.ts:142) true
第二次(被 skip) ObjectQLPlugin.start()driver.* 服务发现循环 packages/objectql/src/plugin.ts:436 false

中间那一跳是关键:DefaultDatasourcePlugin.init() 在连上之后,把它刚从 engine 里读回来的那个对象(packages/runtime/src/default-datasource-plugin.ts:163-172,engine.getDriverByName(engine.getDefaultDriverName()))重新注册成 driver.{name} kernel service —— 那是 os migrate(schema-migrate.tsSQL_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. ⭐ 两次注册的配置是否一致?—— 一致到对象同一性这一级

这是本单的承重问题,答案比「配置等价」更强:

SAME INSTANCE (call1.driver === call2.driver):  true
same name:  true
same version:  1.0.0 1.0.0

两次传进 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

  ⚠ Boot diagnostics — 2 warnings logged during startup:
    2026-08-03T19:53:14.434Z WARN Driver already registered, skipping {"driverName":"com.objectstack.driver.sql"}
    2026-08-03T19:53:20.099Z WARN StorageServicePlugin: storage adapter swapped (LocalStorageAdapter → LocalStorageAdapter). ...

after

  ⚠ Boot diagnostics — 1 warning logged during startup:
    2026-08-03T20:01:35.312Z WARN StorageServicePlugin: storage adapter swapped (LocalStorageAdapter → LocalStorageAdapter). ...

剩下那条是已在跟踪的 #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.tscontent/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 service toBe engine 里那一个;(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 → 通过。

pnpm --filter @objectstack/objectql test    → Test Files 113 passed (113) | Tests 1793 passed (1793)
pnpm --filter @objectstack/runtime  test    → Test Files  80 passed  (80) | Tests 1095 passed (1095)
pnpm --filter @objectstack/objectql typecheck → tsc --noEmit clean

门禁链(合并 origin/main 后重跑):check:engine-double-contract OK(9 pinned / 30 DEBT / 1 exempt)、check:durability-log-level OK(10 seams)、check:startup-registry-verdict OK(47 seams)、check:driver-conformance OK(20 cells)、check:adr-anchors OK、check:init-service-contract OK、check:service-providers OK、check:type-check-coverage OK、check:nul-bytes / check:release-notes / check:doc-authoring / check:error-code-casing / check:role-word OK,改动文件 eslint 无输出。


🤖 Generated with Claude Code

https://claude.ai/code/session_01NrmBxj8rK2uGCnh9aipjwX

claude added 2 commits August 3, 2026 20:06
…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
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 3, 2026 8:13pm

Request Review

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 3, 2026
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/objectql.

13 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/objectql)
  • content/docs/data-modeling/formulas.mdx (via packages/objectql)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/objectql)
  • content/docs/kernel/services.mdx (via @objectstack/objectql)
  • content/docs/permissions/authentication.mdx (via @objectstack/objectql)
  • content/docs/plugins/index.mdx (via @objectstack/objectql)
  • content/docs/plugins/packages.mdx (via @objectstack/objectql)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/objectql)
  • content/docs/protocol/objectql/query-syntax.mdx (via packages/objectql)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql)
  • content/docs/releases/implementation-status.mdx (via @objectstack/objectql)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: 每次启动都报 Driver already registered, skipping {"driverName":"com.objectstack.driver.sql"} —— SQL driver 被注册了两次

2 participants