master
go 126 lines 3.23 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package dyncfg
4
5 import "sync"
6
7 // Config is the constraint interface for configs stored in handler caches.
8 type Config interface {
9 UID() string // SeenCache key (globally unique per source)
10 ExposedKey() string // ExposedCache key (one per logical name)
11 SourceType() string // "dyncfg", "user", "stock"
12 SourceTypePriority() int // dyncfg=16, user=8, stock=2
13 Source() string // source identifier
14 Hash() uint64 // content hash for change detection
15 }
16
17 // Entry pairs a config with its current dyncfg status.
18 type Entry[C Config] struct {
19 Cfg C
20 Status Status
21 }
22
23 // SeenCache stores all discovered configs keyed by UID().
24 // Thread-safe: SD dispatches read-only commands concurrently.
25 type SeenCache[C Config] struct {
26 mux sync.RWMutex
27 items map[string]C
28 }
29
30 func NewSeenCache[C Config]() *SeenCache[C] {
31 return &SeenCache[C]{items: make(map[string]C)}
32 }
33
34 func (c *SeenCache[C]) Add(cfg C) {
35 c.mux.Lock()
36 defer c.mux.Unlock()
37 c.items[cfg.UID()] = cfg
38 }
39
40 func (c *SeenCache[C]) Remove(cfg C) {
41 c.mux.Lock()
42 defer c.mux.Unlock()
43 delete(c.items, cfg.UID())
44 }
45
46 func (c *SeenCache[C]) Lookup(cfg C) (C, bool) {
47 return c.LookupByUID(cfg.UID())
48 }
49
50 func (c *SeenCache[C]) LookupByUID(uid string) (C, bool) {
51 c.mux.RLock()
52 defer c.mux.RUnlock()
53 v, ok := c.items[uid]
54 return v, ok
55 }
56
57 func (c *SeenCache[C]) Count() int {
58 c.mux.RLock()
59 defer c.mux.RUnlock()
60 return len(c.items)
61 }
62
63 // ForEach iterates over all entries. Return false to stop iteration.
64 func (c *SeenCache[C]) ForEach(fn func(uid string, cfg C) bool) {
65 c.mux.RLock()
66 defer c.mux.RUnlock()
67 for uid, cfg := range c.items {
68 if !fn(uid, cfg) {
69 return
70 }
71 }
72 }
73
74 // ExposedCache stores active config+status per logical name, keyed by ExposedKey().
75 // LookupByKey returns a pointer to the stored Entry — mutations to Status
76 // are visible through the pointer. The mutex protects map access only.
77 // Entry.Status is written exclusively by the serialized command goroutine
78 // (via Handler.Cmd*) and is not read by any concurrent code path in production.
79 // Concurrent read-only commands (schema/get/test/userconfig) access only
80 // Entry.Cfg, which is immutable after creation.
81 type ExposedCache[C Config] struct {
82 mux sync.RWMutex
83 items map[string]*Entry[C]
84 }
85
86 func NewExposedCache[C Config]() *ExposedCache[C] {
87 return &ExposedCache[C]{items: make(map[string]*Entry[C])}
88 }
89
90 // Add inserts or overwrites an entry by cfg.ExposedKey().
91 func (c *ExposedCache[C]) Add(entry *Entry[C]) {
92 c.mux.Lock()
93 defer c.mux.Unlock()
94 c.items[entry.Cfg.ExposedKey()] = entry
95 }
96
97 func (c *ExposedCache[C]) Remove(cfg C) {
98 c.mux.Lock()
99 defer c.mux.Unlock()
100 delete(c.items, cfg.ExposedKey())
101 }
102
103 // LookupByKey returns a pointer to the stored entry.
104 func (c *ExposedCache[C]) LookupByKey(key string) (*Entry[C], bool) {
105 c.mux.RLock()
106 defer c.mux.RUnlock()
107 v, ok := c.items[key]
108 return v, ok
109 }
110
111 // ForEach iterates over all entries. Return false to stop iteration.
112 func (c *ExposedCache[C]) ForEach(fn func(key string, entry *Entry[C]) bool) {
113 c.mux.RLock()
114 defer c.mux.RUnlock()
115 for k, e := range c.items {
116 if !fn(k, e) {
117 return
118 }
119 }
120 }
121
122 func (c *ExposedCache[C]) Count() int {
123 c.mux.RLock()
124 defer c.mux.RUnlock()
125 return len(c.items)
126 }