Alternate-key cache support for ConcurrentLru#739
Conversation
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/766875ac-21b6-44fa-a3cb-3b38b9beed94
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/766875ac-21b6-44fa-a3cb-3b38b9beed94
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/766875ac-21b6-44fa-a3cb-3b38b9beed94
|
@copilot similar to |
|
@copilot extract the interface |
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/ccdc20c6-3b27-4e41-b068-265e05d1dc33
|
@copilot in the positive unit tests. use |
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/04f2a992-fbe7-4afa-83a1-cb7a7759a43b
Updated in 51324ab — the positive tests now use |
|
@copilot implement this feedback: go with "AlternateLookup" naming as opposed to "AlternateCache" as it is an adapter providing an alternative lookup over an existing cache, not an alternative cache as such. Also, "AlernativeLookup" is the established name in the .NET ecosystem. Also, the alternate lookup on the underlying ConcurrentDictionary<K, V> should be retrieved and cached in a private field of the AlternateLookup struct, as opposed to calling this.Lru.dictionary.GetAlternateLookup() on each TryGet/TryRemove/GetOrAdd invocation. |
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/49e070c4-dba5-4443-8b0a-829987bd0f73
Updated in b303547 — the API and tests now use |
|
|
||
| public bool TryRemove(TAlternateKey key, [MaybeNullWhen(false)] out K actualKey, [MaybeNullWhen(false)] out V value) | ||
| { | ||
| var alternate = this.Lru.dictionary.GetAlternateLookup<TAlternateKey>(); |
There was a problem hiding this comment.
@copilot can you make a new iteration where the AlternateLookup returned on this line is cached as a member variable?
There was a problem hiding this comment.
Done in 75d5105 — the AlternateLookup<TAlternateKey> adapter now caches the underlying ConcurrentDictionary<K, I>.AlternateLookup<TAlternateKey> as a member and reuses it for TryGet/TryRemove.
| /// <param name="factoryArgument">The factory argument.</param> | ||
| /// <returns>The cached value.</returns> | ||
| TValue GetOrAdd<TArg>(TAlternateKey key, Func<TAlternateKey, TArg, TValue> valueFactory, TArg factoryArgument); | ||
| } |
There was a problem hiding this comment.
@copilot add TryAdd and AddOrUpdate and implement them for ConcurrentLruCore. Add tests to verify they function correctly.
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/2840c7a1-cc33-48ef-987a-6c8c3adadd09
| /// <param name="key">The alternate key.</param> | ||
| /// <param name="value">The value to add.</param> | ||
| /// <returns><see langword="true" /> when the key was added; otherwise, <see langword="false" />.</returns> | ||
| bool TryAdd(TAlternateKey key, TValue value); |
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/392d5c7a-4968-4d2b-9145-28de72f2c39b
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/5d9beacc-a1e1-45c2-a22d-0f002f9deb35
| return false; | ||
| } | ||
|
|
||
| public bool TryAdd(TAlternateKey key, V value) |
There was a problem hiding this comment.
@copilot delete the TryAdd method because it is not part of ICache. Remove it from IAlternateLookup and also remove the associated tests.
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/8911400c-2c01-4911-8e85-8bc70df76ef5
| return false; | ||
| } | ||
|
|
||
| internal readonly struct AlternateLookup<TAlternateKey> : IAlternateLookup<TAlternateKey, K, V> |
There was a problem hiding this comment.
@copilot implement AddOrUpdate as part of AlternateLookup. Add this new method to the IAlternateLookup interface and provide unit tests.
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/43665a27-197b-4c1d-9b27-87101805f538
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/43665a27-197b-4c1d-9b27-87101805f538
…s://github.com/bitfaster/BitFaster.Caching into copilot/add-alternate-cache-from-concurrentlru
This pulls the alternate-key cache work for
ConcurrentLruout of PR #638 into its own reviewable change. It adds a net9-only API for alternate lookups, removals, updates, and add-or-get/update flows onConcurrentLruwhen the configured comparer implementsIAlternateEqualityComparer<TAlternateKey, TKey>.Alternate lookup API
GetAlternateLookup<TAlternateKey>()andTryGetAlternateLookup<TAlternateKey>(...)toConcurrentLruCore.IAlternateLookup<TAlternateKey, TKey, TValue>in theBitFaster.Cachingnamespace with:TryGetTryRemoveTryUpdateAddOrUpdateGetOrAddGetOrAdd<TArg>ConcurrentDictionary alternate lookup integration
ConcurrentDictionary<TKey, TValue>.GetAlternateLookup<TAlternateKey>()on net9 to perform alternate-key operations without first materializing the primary key on cache hits.ConcurrentDictionaryalternate lookup adapter for repeated alternate-key operations.TryUpdatepaths preserve the same update semantics.AddOrUpdateretries to avoid repeated alternate-key materialization under contention.IAlternateEqualityComparer<TAlternateKey, TKey>.Create(...)before falling back to the existing add/update paths.Compatibility guardrails
Throw.IncompatibleComparer().NET9_0_OR_GREATERso the public surface remains unchanged on earlier targets.Targeted coverage
TryUpdatebehavior with alternate keysAddOrUpdatebehavior with alternate keysGetOrAddbehavior with alternate keysReadOnlySpan<char>as the positive alternate key in tests withStringComparer.Ordinal.Example:
📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.