Documentation
¶
Overview ¶
Package subhub 是「快照式订阅表」的**唯一实现**(本仓内部件,不进公开 API)。
生命周期: Experimental(v0.33.0 定级)——本包仍会改、删、拆,不承诺任何 兼容,升级风险自负;定级理由见 api-lifecycle.yaml,分级规则见 docs/api-compat.md。
它替掉了什么 ¶
这段机制此前在本仓有三份**近乎逐字相同**的实现:`netguard/subscribe.go`、 `pii/subscribe.go`、`guardset/assembly_subscribe.go`。三份的包头各自写着 「与 netguard 的 notifyPolicy 同款」——**交叉引用本身就是重复的自白**。
重复的代价不在于多写了百来行,而在于:这是一段**并发正确性代码** (copy-on-write 发布、句柄单调、通知在锁外、回调 panic 隔离)。 只要有一份先被修(比如「注销时 CAS 失败要重试」),另外两份不会跟着改, 于是同一个不变量在不同包里有三种成色,而失效形态是**偶发漏通知 / 偶发误留订阅** ——不会红、不会报错,只会在长跑进程里慢慢显形。
三条不变量(收在这里,改就改这一处) ¶
- **整体替换,永不原地改**:订阅表用 `atomic.Pointer` 挂一个切片,写侧复制出 新切片再换回去。读侧一次 Load 拿到的就是某一时点的完整名单——遍历期间并发 增删不会把名单撕裂。不用读写锁:锁只能让「取名单」不阻塞,「遍历并逐个调用」 仍落在锁里。
- **通知必须在锁外**:订阅者要做 I/O(重算派生视图、写审计、打日志)。 本包根本没有锁,这条由结构保证而不是靠纪律。
- **回调 panic 不得影响发布面**:一次成功的交换**已经落地**,此时让某个订阅者 抛异常就把已发生的事实改写成「没发生」。故逐个 recover(见 Invoke / Do)。
句柄 ¶
句柄由本包铸造(单调递增,0 表示无效),注销只认句柄、不认内容——「这次注销是不是 我登记的那一次」由句柄回答,而不是靠回调指针或序号比对认人(业务字段允许重复时, 那种认法必然误放)。
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Registry ¶
type Registry[T any] struct { // contains filtered or unexported fields }
Registry 快照式订阅表。**零值可用**:atomic.Pointer 与 atomic.Uint64 的零值 就是「空表 + 0 号已占用」,故使用方不必构造函数(全局变量与结构体字段都能直接声明)。
func (*Registry[T]) Add ¶
Add 登记一个订阅者并返回句柄。
fn 为 nil 的**语义判定归调用方**:三个使用方各有自己的公开哨兵 (netguard.ErrNilSubscriber 等),报错文案也各有语境。本包不替它们判, 也不替它们编一个共用哨兵——共用哨兵会让 `errors.Is(err, netguard.ErrNilSubscriber)` 这类既有调用点判定链断掉。