Skip to content

perf: cache sorted Radios list in FlexDiscoveryService - #91

Open
g4dpz wants to merge 1 commit into
magicbug:mainfrom
g4dpz:optimise/flex-discovery-cached-radios
Open

perf: cache sorted Radios list in FlexDiscoveryService#91
g4dpz wants to merge 1 commit into
magicbug:mainfrom
g4dpz:optimise/flex-discovery-cached-radios

Conversation

@g4dpz

@g4dpz g4dpz commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Summary

The Radios property previously sorted and allocated a new List<> on every access via LINQ OrderBy/ToList. Since the settings UI may poll this property on layout passes, this caused repeated allocations even when no radios had changed.

Changes

  • Cache the sorted list as a volatile field
  • Only rebuild when Upsert detects an actual data change or Clear() is called
  • Lock-free reads on the happy path (cached list exists); lock only on rebuild

Tests

2 unit tests added:

  • Radios_returns_cached_list_until_change — verifies Assert.Same on repeated access, cache survives duplicate datagrams, invalidates on actual change with correct re-sort
  • Clear_invalidates_cached_radios — verifies Clear empties and rebuilds

All 5 FlexDiscoveryServiceTests pass.

@magicbug magicbug left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — direction looks good and the tests are welcome. Happy to merge once the cache invalidation race is tightened up.

Issue: rebuild of _cachedRadios happens under _gate (??=), but invalidation in Upsert / Clear sets _cachedRadios = null without that lock. A concurrent reader can rebuild a snapshot, then lose a later invalidation via ??=, leaving a stale sorted list until the next real change.

Please fix so invalidate and rebuild share the same locking (e.g. null the cache under _gate in Upsert/Clear, and keep the double-checked rebuild under _gate only). Happy path can stay lock-free for reads of a non-null cache if you like.

Once that is in, this looks good to merge.

@g4dpz
g4dpz force-pushed the optimise/flex-discovery-cached-radios branch from 61d370c to 3bb3860 Compare August 3, 2026 08:04
@g4dpz

g4dpz commented Aug 3, 2026 via email

Copy link
Copy Markdown
Contributor Author

@magicbug

magicbug commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Thanks for the update, Dave.

I had another look at 3bb3860 and the cache invalidation race from the earlier review still looks present. In Upsert / Clear, _cachedRadios is still set to null outside _gate, while rebuild uses _cachedRadios ??= ... under the lock. A concurrent reader can still rebuild a snapshot and then keep a stale sorted list via ??= until the next real change.

Could you please null the cache under _gate in both Upsert and Clear, and keep the double-checked rebuild under _gate only? Happy-path reads of a non-null cache can stay lock-free.

Happy to merge once that is in. CI is already green.

@g4dpz
g4dpz force-pushed the optimise/flex-discovery-cached-radios branch from 3bb3860 to dcb8a92 Compare August 3, 2026 16:13
The Radios property previously sorted and allocated a new List on
every access. Since the UI settings panel may poll this property
on layout passes, this caused repeated LINQ OrderBy + ToList
allocations even when no radios had changed.

Now caches the sorted list and only rebuilds when Upsert detects
an actual change or Clear is called. Uses volatile for lock-free
reads on the happy path.

Includes 2 unit tests verifying cache identity (Assert.Same) on
repeated access and proper invalidation on change/clear.
@g4dpz
g4dpz force-pushed the optimise/flex-discovery-cached-radios branch from dcb8a92 to 1385eb9 Compare August 3, 2026 16:16
@g4dpz

g4dpz commented Aug 3, 2026 via email

Copy link
Copy Markdown
Contributor Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants