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 回归验证
This commit is contained in:
+125
-2
@@ -1,14 +1,20 @@
|
||||
package notify
|
||||
|
||||
import "sync"
|
||||
import (
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type recordRuntime struct {
|
||||
mu sync.RWMutex
|
||||
handler func(RecordAcceptInfo) error
|
||||
records map[string]*recordStream
|
||||
}
|
||||
|
||||
func newRecordRuntime() *recordRuntime {
|
||||
return &recordRuntime{}
|
||||
return &recordRuntime{
|
||||
records: make(map[string]*recordStream),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *recordRuntime) setHandler(fn func(RecordAcceptInfo) error) {
|
||||
@@ -42,3 +48,120 @@ func (s *ServerCommon) getRecordRuntime() *recordRuntime {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user