Skip to content

Commit 60cfc64

Browse files
authored
classic arg (#348)
1 parent 8465588 commit 60cfc64

File tree

2 files changed

+95
-36
lines changed

2 files changed

+95
-36
lines changed

BitFaster.Caching.UnitTests/Lru/ClassicLruTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,17 @@ public void WhenKeyIsRequestedItIsCreatedAndCached()
181181
result1.Should().Be(result2);
182182
}
183183

184+
185+
[Fact]
186+
public void WhenKeyIsRequestedWithArgItIsCreatedAndCached()
187+
{
188+
var result1 = lru.GetOrAdd(1, valueFactory.Create, "x");
189+
var result2 = lru.GetOrAdd(1, valueFactory.Create, "y");
190+
191+
valueFactory.timesCalled.Should().Be(1);
192+
result1.Should().Be(result2);
193+
}
194+
184195
[Fact]
185196
public async Task WhenKeyIsRequesteItIsCreatedAndCachedAsync()
186197
{
@@ -191,6 +202,16 @@ public async Task WhenKeyIsRequesteItIsCreatedAndCachedAsync()
191202
result1.Should().Be(result2);
192203
}
193204

205+
[Fact]
206+
public async Task WhenKeyIsRequestedWithArgItIsCreatedAndCachedAsync()
207+
{
208+
var result1 = await lru.GetOrAddAsync(1, valueFactory.CreateAsync, "x").ConfigureAwait(false);
209+
var result2 = await lru.GetOrAddAsync(1, valueFactory.CreateAsync, "y").ConfigureAwait(false);
210+
211+
valueFactory.timesCalled.Should().Be(1);
212+
result1.Should().Be(result2);
213+
}
214+
194215
[Fact]
195216
public void WhenDifferentKeysAreRequestedValueIsCreatedForEach()
196217
{

BitFaster.Caching/Lru/ClassicLru.cs

Lines changed: 74 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -113,15 +113,9 @@ public bool TryGet(K key, out V value)
113113
return false;
114114
}
115115

116-
///<inheritdoc/>
117-
public V GetOrAdd(K key, Func<K, V> valueFactory)
116+
private bool TryAdd(K key, V value)
118117
{
119-
if (this.TryGet(key, out var value))
120-
{
121-
return value;
122-
}
123-
124-
var node = new LinkedListNode<LruItem>(new LruItem(key, valueFactory(key)));
118+
var node = new LinkedListNode<LruItem>(new LruItem(key, value));
125119

126120
if (this.dictionary.TryAdd(key, node))
127121
{
@@ -152,57 +146,101 @@ public V GetOrAdd(K key, Func<K, V> valueFactory)
152146
Disposer<V>.Dispose(removed.Value.Value);
153147
}
154148

155-
return node.Value.Value;
149+
return true;
156150
}
157151

158-
return this.GetOrAdd(key, valueFactory);
152+
return false;
159153
}
160154

161155
///<inheritdoc/>
162-
public async ValueTask<V> GetOrAddAsync(K key, Func<K, Task<V>> valueFactory)
156+
public V GetOrAdd(K key, Func<K, V> valueFactory)
163157
{
164158
if (this.TryGet(key, out var value))
165159
{
166160
return value;
167161
}
168162

169-
var node = new LinkedListNode<LruItem>(new LruItem(key, await valueFactory(key)));
163+
value = valueFactory(key);
170164

171-
if (this.dictionary.TryAdd(key, node))
165+
if (TryAdd(key, value))
172166
{
173-
LinkedListNode<LruItem> first = null;
167+
return value;
168+
}
174169

175-
lock (this.linkedList)
176-
{
177-
if (linkedList.Count >= capacity)
178-
{
179-
first = linkedList.First;
180-
linkedList.RemoveFirst();
181-
}
170+
return this.GetOrAdd(key, valueFactory);
171+
}
182172

183-
linkedList.AddLast(node);
184-
}
173+
/// <summary>
174+
/// Adds a key/value pair to the cache if the key does not already exist. Returns the new value, or the
175+
/// existing value if the key already exists.
176+
/// </summary>
177+
/// <typeparam name="TArg">The type of an argument to pass into valueFactory.</typeparam>
178+
/// <param name="key">The key of the element to add.</param>
179+
/// <param name="valueFactory">The factory function used to generate a value for the key.</param>
180+
/// <param name="factoryArgument">An argument value to pass into valueFactory.</param>
181+
/// <returns>The value for the key. This will be either the existing value for the key if the key is already
182+
/// in the cache, or the new value if the key was not in the cache.</returns>
183+
public V GetOrAdd<TArg>(K key, Func<K, TArg, V> valueFactory, TArg factoryArgument)
184+
{
185+
if (this.TryGet(key, out var value))
186+
{
187+
return value;
188+
}
185189

186-
// Remove from the dictionary outside the lock. This means that the dictionary at this moment
187-
// contains an item that is not in the linked list. If another thread fetches this item,
188-
// LockAndMoveToEnd will ignore it, since it is detached. This means we potentially 'lose' an
189-
// item just as it was about to move to the back of the LRU list and be preserved. The next request
190-
// for the same key will be a miss. Dictionary and list are eventually consistent.
191-
// However, all operations inside the lock are extremely fast, so contention is minimized.
192-
if (first != null)
193-
{
194-
dictionary.TryRemove(first.Value.Key, out var removed);
190+
value = valueFactory(key, factoryArgument);
195191

196-
Interlocked.Increment(ref this.metrics.evictedCount);
197-
Disposer<V>.Dispose(removed.Value.Value);
198-
}
192+
if (TryAdd(key, value))
193+
{
194+
return value;
195+
}
196+
197+
return this.GetOrAdd(key, valueFactory, factoryArgument);
198+
}
199+
200+
///<inheritdoc/>
201+
public async ValueTask<V> GetOrAddAsync(K key, Func<K, Task<V>> valueFactory)
202+
{
203+
if (this.TryGet(key, out var value))
204+
{
205+
return value;
206+
}
207+
208+
value = await valueFactory(key);
199209

200-
return node.Value.Value;
210+
if (TryAdd(key, value))
211+
{
212+
return value;
201213
}
202214

203215
return await this.GetOrAddAsync(key, valueFactory);
204216
}
205217

218+
/// <summary>
219+
/// Adds a key/value pair to the cache if the key does not already exist. Returns the new value, or the
220+
/// existing value if the key already exists.
221+
/// </summary>
222+
/// <typeparam name="TArg">The type of an argument to pass into valueFactory.</typeparam>
223+
/// <param name="key">The key of the element to add.</param>
224+
/// <param name="valueFactory">The factory function used to asynchronously generate a value for the key.</param>
225+
/// <param name="factoryArgument">An argument value to pass into valueFactory.</param>
226+
/// <returns>A task that represents the asynchronous GetOrAdd operation.</returns>
227+
public async ValueTask<V> GetOrAddAsync<TArg>(K key, Func<K, TArg, Task<V>> valueFactory, TArg factoryArgument)
228+
{
229+
if (this.TryGet(key, out var value))
230+
{
231+
return value;
232+
}
233+
234+
value = await valueFactory(key, factoryArgument);
235+
236+
if (TryAdd(key, value))
237+
{
238+
return value;
239+
}
240+
241+
return await this.GetOrAddAsync(key, valueFactory, factoryArgument);
242+
}
243+
206244
///<inheritdoc/>
207245
public bool TryRemove(K key)
208246
{

0 commit comments

Comments
 (0)