diff --git a/internal/hub/expirymap/expirymap.go b/internal/hub/expirymap/expirymap.go index dcfc962c..0a3436bd 100644 --- a/internal/hub/expirymap/expirymap.go +++ b/internal/hub/expirymap/expirymap.go @@ -16,7 +16,7 @@ type val[T comparable] struct { } type ExpiryMap[T comparable] struct { - store *store.Store[string, *val[T]] + store store.Store[string, val[T]] stopChan chan struct{} stopOnce sync.Once } @@ -24,7 +24,7 @@ type ExpiryMap[T comparable] struct { // New creates a new expiry map with custom cleanup interval func New[T comparable](cleanupInterval time.Duration) *ExpiryMap[T] { m := &ExpiryMap[T]{ - store: store.New(map[string]*val[T]{}), + store: *store.New(map[string]val[T]{}), stopChan: make(chan struct{}), } go m.startCleaner(cleanupInterval) @@ -33,7 +33,7 @@ func New[T comparable](cleanupInterval time.Duration) *ExpiryMap[T] { // Set stores a value with the given TTL func (m *ExpiryMap[T]) Set(key string, value T, ttl time.Duration) { - m.store.Set(key, &val[T]{ + m.store.Set(key, val[T]{ value: value, expires: time.Now().Add(ttl), }) @@ -116,3 +116,12 @@ func (m *ExpiryMap[T]) cleanup() { } } } + +// UpdateExpiration updates the expiration time of a key +func (m *ExpiryMap[T]) UpdateExpiration(key string, ttl time.Duration) { + value, ok := m.store.GetOk(key) + if ok { + value.expires = time.Now().Add(ttl) + m.store.Set(key, value) + } +} diff --git a/internal/hub/expirymap/expirymap_test.go b/internal/hub/expirymap/expirymap_test.go index 9fec2012..b7b39c35 100644 --- a/internal/hub/expirymap/expirymap_test.go +++ b/internal/hub/expirymap/expirymap_test.go @@ -178,6 +178,33 @@ func TestExpiryMap_GenericTypes(t *testing.T) { }) } +func TestExpiryMap_UpdateExpiration(t *testing.T) { + em := New[string](time.Hour) + + // Set a value with short TTL + em.Set("key1", "value1", time.Millisecond*50) + + // Verify it exists + assert.True(t, em.Has("key1")) + + // Update expiration to a longer TTL + em.UpdateExpiration("key1", time.Hour) + + // Wait for the original TTL to pass + time.Sleep(time.Millisecond * 100) + + // Should still exist because expiration was updated + assert.True(t, em.Has("key1")) + value, ok := em.GetOk("key1") + assert.True(t, ok) + assert.Equal(t, "value1", value) + + // Try updating non-existent key (should not panic) + assert.NotPanics(t, func() { + em.UpdateExpiration("nonexistent", time.Hour) + }) +} + func TestExpiryMap_ZeroValues(t *testing.T) { em := New[string](time.Hour) diff --git a/internal/hub/systems/system.go b/internal/hub/systems/system.go index 1da3706e..564d008b 100644 --- a/internal/hub/systems/system.go +++ b/internal/hub/systems/system.go @@ -139,13 +139,25 @@ func (sys *System) update() error { // create system records _, err = sys.createRecords(data) + // if details were included and fetched successfully, mark details as fetched and update smart interval if set by agent + if err == nil && data.Details != nil { + sys.detailsFetched.Store(true) + // update smart interval if it's set on the agent side + if data.Details.SmartInterval > 0 { + sys.smartInterval = data.Details.SmartInterval + // make sure we reset expiration of lastFetch to remain as long as the new smart interval + // to prevent premature expiration leading to new fetch if interval is different. + sys.manager.smartFetchMap.UpdateExpiration(sys.Id, sys.smartInterval+time.Minute) + } + } + // Fetch and save SMART devices when system first comes online or at intervals - if backgroundSmartFetchEnabled() { + if backgroundSmartFetchEnabled() && sys.detailsFetched.Load() { if sys.smartInterval <= 0 { sys.smartInterval = time.Hour } lastFetch, _ := sys.manager.smartFetchMap.GetOk(sys.Id) - if time.Since(time.UnixMilli(lastFetch)) >= sys.smartInterval && sys.smartFetching.CompareAndSwap(false, true) { + if time.Since(time.UnixMilli(lastFetch-1e4)) >= sys.smartInterval && sys.smartFetching.CompareAndSwap(false, true) { go func() { defer sys.smartFetching.Store(false) sys.manager.smartFetchMap.Set(sys.Id, time.Now().UnixMilli(), sys.smartInterval+time.Minute) @@ -223,11 +235,6 @@ func (sys *System) createRecords(data *system.CombinedData) (*core.Record, error if err := createSystemDetailsRecord(txApp, data.Details, sys.Id); err != nil { return err } - sys.detailsFetched.Store(true) - // update smart interval if it's set on the agent side - if data.Details.SmartInterval > 0 { - sys.smartInterval = data.Details.SmartInterval - } } // update system record (do this last because it triggers alerts and we need above records to be inserted first)