Skip to content

集群对端的元数据写入不失效本节点的 listCache / registry —— 收到广播的节点最长 30s 继续服务旧定义 #5109

Description

@os-zhuang

发现于 #5089(#5040 E2 端点匹配器)实现期,不在该单范围内,故单独立卡。

现象

packages/metadata/src/metadata-manager.ts attachClusterPubSub()(:1800-1833)在收到对端的 metadata.changed 广播时,只做一件事:

setImmediate(() => {
  this.notifyWatchersLocal(p.type, p.event);
});

notifyWatchersLocal(:1770-)只把事件派发给 watchCallbacks。它不碰 this.registry,也不碰 this.listCache

对比本节点自己的写入路径 —— register() / unregister() / registerInMemory() 都先 invalidateListCache(type)notifyWatchers(...);applyRepoEvent()(:1704-)更是显式地把 registry 条目删掉再 listCache.delete(type) 才通知。也就是说:本地写会失效缓存,对端写只会通知 watcher。

后果:节点 A 改了一条 view/permission/flow,节点 B 收到广播,B 的 watcher 们(ObjectQL SchemaRegistry 桥、HMR SSE)确实被叫醒了,但 B 上任何走 list(type) 的读在 LIST_CACHE_TTL_MS = 30_000 窗口内继续返回改动前的清单。被叫醒的 watcher 如果自己回头调 list() 重新拉取,拉到的还是旧的 —— 失效通知与失效数据自相矛盾。

为什么值得单独修

  • 这与 cluster-semantics.mdx §5 声称的语义不符:该通道的用途原文就是 "consumed by peers to invalidate their local caches"(见 ClusterMetadataChangedPayload 的注释),而实现只做了通知、没做失效;
  • 单机部署完全无感,只有多节点才暴露,属于「看着正常、其实供旧数据」那一类;
  • 修法看起来很小(在 notifyWatchersLocal 之前补 registry 条目删除 + listCache.delete(type),即复用 applyRepoEvent 已有的那几行),但要不要连 registry 一起删牵涉语义选择(applyRepoEvent 的注释解释了它为什么选择「删而不预填」),所以不该顺手在别的单里带一刀。

复现思路

两个 MetadataManager 接同一个 IPubSub(memory 驱动即可),各自 attachClusterPubSub(pubsub, nodeId);在 A 上 list('view') 预热 B 的缓存做不到 —— 直接在 B 上先 list('view') 预热,然后 A 上 register('view', ...),再在 B 上 list('view'):返回的是预热时的旧清单。

关联

cluster-semantics.mdx §5、#5089(发现处)、applyRepoEvent(同文件内已做对的那条路径,可作修法样板)。

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions