package web import ( "sync" "time" ) type dashboardCacheEntry struct { value map[string]any expiresAt time.Time building chan struct{} } type dashboardSnapshotCache struct { mu sync.Mutex ttl time.Duration generation uint64 entries map[string]*dashboardCacheEntry } func newDashboardSnapshotCache(ttl time.Duration) *dashboardSnapshotCache { if ttl <= 0 { ttl = 5 * time.Second } return &dashboardSnapshotCache{ttl: ttl, entries: map[string]*dashboardCacheEntry{}} } func (c *dashboardSnapshotCache) Get(key string, build func() (map[string]any, error)) (map[string]any, bool, error) { for { now := time.Now() c.mu.Lock() entry := c.entries[key] if entry != nil && entry.value != nil && now.Before(entry.expiresAt) { value := entry.value c.mu.Unlock() return value, true, nil } if entry != nil && entry.building != nil { ready := entry.building c.mu.Unlock() <-ready continue } generation := c.generation ready := make(chan struct{}) c.entries[key] = &dashboardCacheEntry{building: ready} c.mu.Unlock() value, err := build() c.mu.Lock() if generation != c.generation { if current := c.entries[key]; current != nil && current.building == ready { delete(c.entries, key) } close(ready) c.mu.Unlock() if err != nil { return nil, false, err } continue } if err != nil { delete(c.entries, key) close(ready) c.mu.Unlock() return nil, false, err } c.entries[key] = &dashboardCacheEntry{value: value, expiresAt: time.Now().Add(c.ttl)} close(ready) c.mu.Unlock() return value, false, nil } } func (c *dashboardSnapshotCache) Invalidate() { c.mu.Lock() c.generation++ for key, entry := range c.entries { if entry.building == nil { delete(c.entries, key) } } c.mu.Unlock() }