Files
configcenter/internal/watch/hub.go

83 lines
1.7 KiB
Go

package watch
import (
"context"
"sync"
"github.com/longpeng/configcenter/internal/domain"
runtimepkg "github.com/longpeng/configcenter/internal/runtime"
)
type Hub struct {
ctx context.Context
runtime runtimepkg.Store
mu sync.Mutex
keys map[string]*keyWatch
}
type keyWatch struct {
cancel context.CancelFunc
nextID int64
subs map[int64]chan domain.ConfigEvent
}
func New(ctx context.Context, runtimeStore runtimepkg.Store) *Hub {
return &Hub{ctx: ctx, runtime: runtimeStore, keys: make(map[string]*keyWatch)}
}
func (h *Hub) Subscribe(key string) (<-chan domain.ConfigEvent, func()) {
h.mu.Lock()
watch, exists := h.keys[key]
if !exists {
ctx, cancel := context.WithCancel(h.ctx)
watch = &keyWatch{cancel: cancel, subs: make(map[int64]chan domain.ConfigEvent)}
h.keys[key] = watch
go h.run(ctx, key, watch)
}
watch.nextID++
id := watch.nextID
channel := make(chan domain.ConfigEvent, 16)
watch.subs[id] = channel
h.mu.Unlock()
var once sync.Once
return channel, func() {
once.Do(func() {
h.mu.Lock()
current, ok := h.keys[key]
if ok {
if ch, found := current.subs[id]; found {
delete(current.subs, id)
close(ch)
}
if len(current.subs) == 0 {
current.cancel()
delete(h.keys, key)
}
}
h.mu.Unlock()
})
}
}
func (h *Hub) run(ctx context.Context, key string, target *keyWatch) {
for result := range h.runtime.Watch(ctx, key, 0) {
if result.Err != nil {
return
}
h.mu.Lock()
current, exists := h.keys[key]
if !exists || current != target {
h.mu.Unlock()
return
}
for _, channel := range current.subs {
select {
case channel <- result.Event:
default:
}
}
h.mu.Unlock()
}
}