notify/record_runtime.go
starainrt 7ed3dd5b37
feat: 完善 RecordStream 的协议协商、运行观测与文档说明
- 将 RecordStream 出站路径收敛为单 writer loop
  - 支持在 batch header 中 piggyback AckSeq,保留独立 ack 作为兼容回退
  - 增加 record stream 打开阶段能力协商,支持 mixed-version peer 自动降级
  - 补充 RecordSnapshot 与 diagnostics summary 的 record-plane 观测项
  - 增加 batch/ack/error frame、piggyback ack、barrier 等待拆分与 apply backlog 指标
  - 收紧 TransportConn detach 后的 runtime snapshot 语义
  - 补充 README 中的 RecordStream 语义、兼容行为与诊断快照说明
  - 补充相关单测与 race 回归验证
2026-04-15 19:52:45 +08:00

168 lines
3.0 KiB
Go

package notify
import (
"strconv"
"sync"
)
type recordRuntime struct {
mu sync.RWMutex
handler func(RecordAcceptInfo) error
records map[string]*recordStream
}
func newRecordRuntime() *recordRuntime {
return &recordRuntime{
records: make(map[string]*recordStream),
}
}
func (r *recordRuntime) setHandler(fn func(RecordAcceptInfo) error) {
if r == nil {
return
}
r.mu.Lock()
r.handler = fn
r.mu.Unlock()
}
func (r *recordRuntime) handlerSnapshot() func(RecordAcceptInfo) error {
if r == nil {
return nil
}
r.mu.RLock()
defer r.mu.RUnlock()
return r.handler
}
func (c *ClientCommon) getRecordRuntime() *recordRuntime {
if c == nil {
return nil
}
return c.recordRuntime
}
func (s *ServerCommon) getRecordRuntime() *recordRuntime {
if s == nil {
return nil
}
return s.recordRuntime
}
func (r *recordRuntime) register(record *recordStream) {
if r == nil || record == nil {
return
}
key := record.runtimeRegistryKey()
if key == "" {
return
}
r.mu.Lock()
r.records[key] = record
r.mu.Unlock()
}
func (r *recordRuntime) remove(key string) {
if r == nil || key == "" {
return
}
r.mu.Lock()
delete(r.records, key)
r.mu.Unlock()
}
func (r *recordRuntime) snapshots() []RecordSnapshot {
if r == nil {
return nil
}
r.mu.RLock()
records := make([]*recordStream, 0, len(r.records))
for _, record := range r.records {
if record == nil {
continue
}
records = append(records, record)
}
r.mu.RUnlock()
snapshots := make([]RecordSnapshot, 0, len(records))
for _, record := range records {
snapshots = append(snapshots, record.snapshot())
}
sortRecordSnapshots(snapshots)
return snapshots
}
func bindRecordRuntime(record RecordStream, runtime *recordRuntime) {
if runtime == nil || record == nil {
return
}
rs, ok := record.(*recordStream)
if !ok {
return
}
rs.bindRuntime(runtime)
}
func (r *recordStream) bindRuntime(runtime *recordRuntime) {
if r == nil || runtime == nil {
return
}
key := r.runtimeRegistryKey()
if key == "" {
return
}
r.mu.Lock()
r.runtime = runtime
r.runtimeKey = key
r.mu.Unlock()
runtime.register(r)
r.runtimeWatchOnce.Do(func() {
go func() {
streamCtx := r.stream.Context()
if streamCtx == nil {
<-r.ctx.Done()
} else {
select {
case <-r.ctx.Done():
case <-streamCtx.Done():
}
}
r.detachRuntime()
}()
})
}
func (r *recordStream) detachRuntime() {
if r == nil {
return
}
r.runtimeDetachOnce.Do(func() {
r.mu.Lock()
runtime := r.runtime
key := r.runtimeKey
r.runtime = nil
r.runtimeKey = ""
r.mu.Unlock()
if runtime != nil {
runtime.remove(key)
}
})
}
func (r *recordStream) runtimeRegistryKey() string {
if r == nil || r.stream == nil {
return ""
}
scope := ""
dataID := uint64(0)
if stream, ok := r.stream.(*streamHandle); ok {
scope = normalizeFileScope(stream.runtimeScope)
dataID = stream.dataID
}
key := scope + "\x00" + r.stream.ID()
if dataID != 0 {
key += "\x01" + strconv.FormatUint(dataID, 10)
}
return key
}