83 lines
1.7 KiB
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()
|
|
}
|
|
}
|