Skip to content

Add GetAsyncAlternateLookup and TryGetAsyncAlternateLookup methods to IAsyncCache#751

Merged
bitfaster merged 11 commits intomainfrom
copilot/add-getasyncalternatelookup-methods
Apr 13, 2026
Merged

Add GetAsyncAlternateLookup and TryGetAsyncAlternateLookup methods to IAsyncCache#751
bitfaster merged 11 commits intomainfrom
copilot/add-getasyncalternatelookup-methods

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 7, 2026

Summary

Adds GetAsyncAlternateLookup<TAlternateKey>() and TryGetAsyncAlternateLookup<TAlternateKey>() methods to the IAsyncCache<K, V> interface, gated behind #if NET9_0_OR_GREATER. This mirrors the existing GetAlternateLookup/TryGetAlternateLookup pattern already present on ICache<K, V>.

Changes

  • IAsyncCache.cs: Added default interface methods GetAsyncAlternateLookup and TryGetAsyncAlternateLookup with throw new NotSupportedException() defaults, matching the ICache pattern including CS8714 pragma suppression
  • ConcurrentLruCore.cs, ClassicLru.cs, ConcurrentLfu.cs, ConcurrentTLfu.cs, ConcurrentLfuCore.cs: Updated existing method XML doc comments to use ///<inheritdoc/> since they now implement the interface methods
  • AtomicFactoryAsyncCache.cs: Implemented GetAsyncAlternateLookup and TryGetAsyncAlternateLookup with an internal AlternateLookup<TAlternateKey> struct that wraps the inner cache's IAlternateLookup<TAlternateKey, K, AsyncAtomicFactory<K, V>> and translates values. The struct stores an IAlternateEqualityComparer<TAlternateKey, K> obtained from cache.Comparer (added in Add net9 Comparer property to cache interfaces and implementations #754) and uses comparer.Create(key) in GetOrAddAsync to produce the actual K from TAlternateKey, then delegates to AsyncAtomicFactory.GetValueAsync(actualKey, valueFactory) which ensures exactly-once initialization through its DoubleCheck mechanism. A TryGet fast path avoids the slow path allocation on cache hits.

Testing

All 1486 unit tests pass on net9.0, including 9 new tests in AtomicFactoryAsyncCacheAsyncAlternateLookupTests covering compatible/incompatible comparer handling, TryGet miss, TryRemove hit/miss, TryUpdate, AddOrUpdate, and both GetOrAddAsync overloads verifying miss+hit semantics. Builds succeed across all target frameworks (netstandard2.0, netcoreapp3.1, net6.0, net9.0).

Comment thread BitFaster.Caching/Atomic/AtomicFactoryAsyncCache.cs
…cFactoryAsyncCache with unit tests

Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/ecb8a7bd-bccd-4ac6-a5ac-05912949a409

Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds async alternate-key lookup discovery to IAsyncCache<K,V> (net9+) to mirror the existing ICache alternate-lookup pattern, and wires implementations/docs across LRU/LFU caches plus the atomic async decorator.

Changes:

  • Add GetAsyncAlternateLookup<TAlternateKey>() / TryGetAsyncAlternateLookup<TAlternateKey>() default interface methods to IAsyncCache<K,V> (net9+).
  • Update LRU/LFU implementations’ XML docs to ///<inheritdoc/> now that these methods are interface members.
  • Implement async alternate lookup in AtomicFactoryAsyncCache and add unit tests for basic lookup/update/remove and GetOrAddAsync miss/hit behavior.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
BitFaster.Caching/IAsyncCache.cs Adds net9+ async alternate-lookup methods as default interface members (mirrors ICache).
BitFaster.Caching/Lru/ConcurrentLruCore.cs Switches async alternate-lookup method docs to inherit interface docs.
BitFaster.Caching/Lru/ClassicLru.cs Switches async alternate-lookup method docs to inherit interface docs.
BitFaster.Caching/Lfu/ConcurrentLfuCore.cs Switches async alternate-lookup method docs to inherit interface docs.
BitFaster.Caching/Lfu/ConcurrentLfu.cs Switches async alternate-lookup method docs to inherit interface docs.
BitFaster.Caching/Lfu/ConcurrentTLfu.cs Switches async alternate-lookup method docs to inherit interface docs.
BitFaster.Caching/Atomic/AtomicFactoryAsyncCache.cs Adds IAsyncCache async alternate-lookup implementation by wrapping inner IAlternateLookup and translating AsyncAtomicFactory values.
BitFaster.Caching.UnitTests/Atomic/AtomicFactoryAsyncCacheAsyncAlternateLookupTests.cs Adds tests covering comparer compatibility and core async alternate-lookup operations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread BitFaster.Caching/Atomic/AtomicFactoryAsyncCache.cs Outdated
Comment on lines +239 to +241
Task<V> task = valueFactory(key, factoryArgument);
return factory.GetValueAsync(default(K)!, static (_, t) => t, task);
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

Same issue as the non-TArg overload: invoking valueFactory(key, factoryArgument) eagerly means concurrent callers on a cache miss will start multiple tasks, but only one will be consumed by AsyncAtomicFactory initialization; the remainder can cause duplicated side effects and unobserved exceptions. Consider moving the valueFactory invocation into the inner.GetOrAdd add-path so it executes once, then have all callers await the shared initialization result.

Copilot uses AI. Check for mistakes.
@coveralls
Copy link
Copy Markdown

coveralls commented Apr 9, 2026

Coverage Status

coverage: 99.048% (-0.04%) from 99.083% — copilot/add-getasyncalternatelookup-methods into main

@bitfaster
Copy link
Copy Markdown
Owner

@copilot merge with main and fix merge conflicts. Ensure comments in the PR are addressed.

Copilot AI and others added 2 commits April 11, 2026 22:23
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com>
…omicFactory double-check

Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/172d9137-e999-4a1e-b572-963a2f919311

Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 11, 2026

@copilot merge with main and fix merge conflicts. Ensure comments in the PR are addressed.

Merged with main in 2ef7671 (no conflicts). Addressed the reviewer comments about GetOrAddAsync duplicate factory invocations in 92725a6.

After merging #752, the factory signature changed from Func<TAlternateKey, Task<V>> to Func<K, Task<V>>, which allows passing the factory directly into AsyncAtomicFactory.GetValueAsync for exactly-once initialization via its DoubleCheck mechanism. The implementation uses inner.GetOrAdd<TArg> to capture the actual K key (via a KeyBox<K> reference) from the factory lambda, then delegates to GetValueAsync(actualKey, valueFactory) — matching the non-alternate GetOrAddAsync pattern. A TryGet fast path avoids the KeyBox allocation on cache hits.


private ValueTask<V> GetOrAddAsyncSlow(TAlternateKey key, Func<K, Task<V>> valueFactory)
{
var box = new KeyBox<K>();
Copy link
Copy Markdown
Owner

@bitfaster bitfaster Apr 12, 2026

Choose a reason for hiding this comment

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

This appears to be introduced because ICache does not expose comparer, without it there is no way to create an instance of K from TAlternateKey. The LLM thinks it can capture K by invoking the value factory.

This means the cache must expose the comparer somehow.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 88f9d4e. The AlternateLookup struct now stores IAlternateEqualityComparer<TAlternateKey, K> (obtained from cache.Comparer added in #754) and uses comparer.Create(key) to produce K from TAlternateKey, eliminating the KeyBox workaround entirely.

@bitfaster
Copy link
Copy Markdown
Owner

@copilot merge the latest from main, then address the review comment in AtomicFactoryAsyncCache.cs. Instead of capturing the key, use the cache comparer property to create a key instance from the alternate key.

Copilot AI and others added 2 commits April 13, 2026 07:02
…into copilot/add-getasyncalternatelookup-methods

Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 13, 2026

@copilot merge the latest from main, then address the review comment in AtomicFactoryAsyncCache.cs. Instead of capturing the key, use the cache comparer property to create a key instance from the alte...

Merged latest main in 7af9ef6 (includes #754 Comparer property). Replaced the KeyBox-based key capture with comparer.Create(key) in 88f9d4e. The AlternateLookup struct now stores an IAlternateEqualityComparer<TAlternateKey, K> obtained from cache.Comparer and uses comparer.Create(key) to produce the actual K before delegating to AsyncAtomicFactory.GetValueAsync(actualKey, valueFactory). All 1486 tests pass.

Copilot AI requested a review from bitfaster April 13, 2026 07:14
@bitfaster bitfaster marked this pull request as ready for review April 13, 2026 21:06
@bitfaster bitfaster merged commit ec9df93 into main Apr 13, 2026
22 of 23 checks passed
@bitfaster bitfaster deleted the copilot/add-getasyncalternatelookup-methods branch April 13, 2026 21:55
@bitfaster bitfaster linked an issue Apr 13, 2026 that may be closed by this pull request
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.

[Feature request] Add alternate lookup support

4 participants