fix(channel): 热重载把新适配器写回能力注册表
面板保存渠道配置后 Bridge.Reload 只重建了桥接器自己的适配器表,能力注册表里的条目仍指向保存前的旧实例;而管理面的渠道列表、每账号连接状态、open/close 的目标解析全部读注册表 —— 于是卡片永远「未连接」,点「连接」驱动的是旧实例(按旧 URL 拨号)。 生产日志实证:保存后 bridge 侧新实例连上 ws://localhost:3001 且心跳正常,而点连接时被驱动的适配器拨的是旧地址 ws://localhost:10301。 改法:注册表新增 upsert 语义的 ReplaceChannel(覆盖 Value、保留运行期 Policy;Register 遇重名会 ErrDuplicate,热重载必然同名);桥接器经窄端口在适配器上线后同步,账号被移除时删除条目;组合根注入 a.caps 包装。同步失败只记日志、不中断 Reload;未接线时保持旧行为并只 WARN 一次。 验收:go build ./... + go vet 全绿;registry/channelbridge/gateway 三包 test 全绿;主会话亲自变异(摘掉 Policy 沿用 ⇒ TestReplaceChannelSwapsValueAndKeepsPolicy 红)。linux/darwin 交叉编译通过。
This commit is contained in:
1 parent
23b6e3fe1e
commit
d81635672e
6 files changed
+560
-6
No files matched your search
@@ -14,6 +14,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/pkg/plugins/sdk"
|
||||
)
|
||||
|
||||
// PlatformFactory 依据平台当前生效的账号配置
|
||||
@@ -27,6 +28,20 @@ type PlatformFactory interface {
|
||||
Build(platform string) ([]adapter.PlatformAdapter, error)
|
||||
}
|
||||
|
||||
// ChannelRegistry 是能力注册表的**窄端口**,只暴露热重载需要同步的两件事:
|
||||
// 把某个名字的渠道条目换成新适配器、把已消失的账号条目删掉。桥接器用它让管理面板
|
||||
// (渠道列表 / 在线状态 / open·close 解析)读到的永远是**当前**适配器实例,而不必
|
||||
// 依赖 capability/registry 的具体类型 —— 注册表实现与桥接器因此保持解耦。
|
||||
//
|
||||
// 未接线(Options.Channels 为 nil)时桥接器照常热重载,但注册表条目会停在被保存前
|
||||
// 的那个实例上:面板显示「未连接」、点「连接」驱动的是旧实例。此时只记一次 WARN。
|
||||
type ChannelRegistry interface {
|
||||
// ReplaceChannel 用同名的新适配器覆盖(或新增)渠道条目。
|
||||
ReplaceChannel(ch sdk.Channel) error
|
||||
// RemoveChannel 删除该名字的渠道条目。
|
||||
RemoveChannel(name string) error
|
||||
}
|
||||
|
||||
// ErrNilFactory 表示请求重载时未注入平台工厂
|
||||
// (没有热重载配置来源)。
|
||||
var ErrNilFactory = errors.New("channelbridge: nil platform factory")
|
||||
@@ -112,16 +127,19 @@ func (b *Bridge) Reload(platform string) error {
|
||||
}
|
||||
for _, a := range newAdapters {
|
||||
b.connectAndRun(a)
|
||||
// 紧跟上线之后同步能力注册表:新实例先连上,面板才改为读它,
|
||||
// 避免「注册表已指向新实例、它却还没连」的窗口。
|
||||
b.replaceChannelEntry(a)
|
||||
}
|
||||
for _, o := range old {
|
||||
if newNames[o.Name()] {
|
||||
// 同名账号被重建后的适配器替换:停止旧的读取
|
||||
// 循环,但保留注册表条目——它现在指向新适配器。
|
||||
// 同名账号被重建后的适配器替换:只停旧实例的读取循环,
|
||||
// 注册表条目已由 replaceChannelEntry 指向新实例。
|
||||
b.cancelAndDisconnect(o, oldCancels[o.Name()])
|
||||
continue
|
||||
}
|
||||
// 账号被彻底移除(或更名):停止它并删除其注册表
|
||||
// 条目,后续 snapshot 便不再看到它。
|
||||
// 条目,后续 snapshot 与面板便不再看到它。
|
||||
b.removeAdapter(o, oldCancels[o.Name()])
|
||||
}
|
||||
b.log.Info("channel reloaded", "platform", platform, "adapters", len(newAdapters))
|
||||
@@ -148,10 +166,55 @@ func (b *Bridge) adapterSnap(platform string) ([]adapter.PlatformAdapter, map[st
|
||||
return list, cancels
|
||||
}
|
||||
|
||||
// replaceChannelEntry 把刚上线的适配器写回能力注册表。注册表里的渠道条目只在启动
|
||||
// 时写过一次,而热重载重建出来的是同名的新实例:不同步的话,管理面板(渠道列表、
|
||||
// 账号在线状态、open/close 的目标解析)读到的仍是保存前的**旧实例** —— 卡片显示
|
||||
// 「未连接」,点「连接」拨的是旧配置里的地址(design/04 §3.1)。
|
||||
//
|
||||
// 同步失败只记日志、绝不返回错误:面板保存不该被注册表同步拖垮。
|
||||
func (b *Bridge) replaceChannelEntry(a adapter.PlatformAdapter) {
|
||||
if a == nil {
|
||||
return
|
||||
}
|
||||
name := a.Name()
|
||||
if !b.IsConnected(name) {
|
||||
// 没上线就不动注册表:换成连不上的实例只会让面板的在线状态更失真。
|
||||
b.log.Warn("channel registry left unchanged: adapter not connected", "channel", name)
|
||||
return
|
||||
}
|
||||
if b.channels == nil {
|
||||
b.warnChannelRegistryUnwired()
|
||||
return
|
||||
}
|
||||
if err := b.channels.ReplaceChannel(a); err != nil {
|
||||
b.log.Warn("channel registry replace failed", "channel", name, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// dropChannelEntry 在账号被移除(或更名)后删掉它的注册表条目,使管理面板不再列出
|
||||
// 这条已经不存在的账号。同 replaceChannelEntry:失败只记日志。
|
||||
func (b *Bridge) dropChannelEntry(name string) {
|
||||
if b.channels == nil {
|
||||
b.warnChannelRegistryUnwired()
|
||||
return
|
||||
}
|
||||
if err := b.channels.RemoveChannel(name); err != nil {
|
||||
b.log.Warn("channel registry remove failed", "channel", name, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// warnChannelRegistryUnwired 只在首次同步时提醒一次:未注入 Channels 端口时热重载
|
||||
// 照常进行(适配器确实换了),只是面板读到的是保存前的旧实例。
|
||||
func (b *Bridge) warnChannelRegistryUnwired() {
|
||||
b.channelsWarnOnce.Do(func() {
|
||||
b.log.Warn("channel registry not wired: admin panel keeps the pre-reload adapter instance")
|
||||
})
|
||||
}
|
||||
|
||||
// cancelAndDisconnect 停止适配器的读取循环并拆除其会话。
|
||||
// 它绝不改动注册表 map:替换同名适配器的调用方
|
||||
// 希望(新)条目保留,而移除账号的调用方使用
|
||||
// removeAdapter。
|
||||
// 它绝不改动桥接器自己的适配器表与能力注册表:替换同名适配器
|
||||
// 的调用方希望(新)条目保留,而移除账号的调用方使用
|
||||
// removeAdapter(它会连注册表条目一起清掉)。
|
||||
func (b *Bridge) cancelAndDisconnect(o adapter.PlatformAdapter, c context.CancelFunc) {
|
||||
if c != nil {
|
||||
c()
|
||||
@@ -174,5 +237,6 @@ func (b *Bridge) removeAdapter(o adapter.PlatformAdapter, c context.CancelFunc)
|
||||
delete(b.readCancels, name)
|
||||
b.mu.Unlock()
|
||||
b.forgetAdapterPersona(name)
|
||||
b.dropChannelEntry(name)
|
||||
b.log.Info("channel (reload) removed", "channel", name)
|
||||
}
|
||||
Reference in new issue
Block a user