Skip to content

Commit d9981a2

Browse files
authored
loops (#84)
1 parent 6f63d76 commit d9981a2

File tree

1 file changed

+84
-76
lines changed

1 file changed

+84
-76
lines changed

BitFaster.Caching/Lru/TemplateConcurrentLru.cs

Lines changed: 84 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -131,93 +131,99 @@ private bool GetOrDiscard(I item, out V value)
131131
///<inheritdoc/>
132132
public V GetOrAdd(K key, Func<K, V> valueFactory)
133133
{
134-
if (this.TryGet(key, out var value))
135-
{
136-
return value;
137-
}
134+
while (true)
135+
{
136+
if (this.TryGet(key, out var value))
137+
{
138+
return value;
139+
}
138140

139-
// The value factory may be called concurrently for the same key, but the first write to the dictionary wins.
140-
// This is identical logic in ConcurrentDictionary.GetOrAdd method.
141-
var newItem = this.policy.CreateItem(key, valueFactory(key));
141+
// The value factory may be called concurrently for the same key, but the first write to the dictionary wins.
142+
// This is identical logic in ConcurrentDictionary.GetOrAdd method.
143+
var newItem = this.policy.CreateItem(key, valueFactory(key));
142144

143-
if (this.dictionary.TryAdd(key, newItem))
144-
{
145-
this.hotQueue.Enqueue(newItem);
146-
Interlocked.Increment(ref hotCount);
147-
Cycle();
148-
return newItem.Value;
149-
}
145+
if (this.dictionary.TryAdd(key, newItem))
146+
{
147+
this.hotQueue.Enqueue(newItem);
148+
Interlocked.Increment(ref hotCount);
149+
Cycle();
150+
return newItem.Value;
151+
}
150152

151-
if (newItem.Value is IDisposable d)
152-
{
153-
d.Dispose();
153+
if (newItem.Value is IDisposable d)
154+
{
155+
d.Dispose();
156+
}
154157
}
155-
156-
return this.GetOrAdd(key, valueFactory);
157158
}
158159

159160
///<inheritdoc/>
160161
public async Task<V> GetOrAddAsync(K key, Func<K, Task<V>> valueFactory)
161162
{
162-
if (this.TryGet(key, out var value))
163-
{
164-
return value;
165-
}
163+
while (true)
164+
{
165+
if (this.TryGet(key, out var value))
166+
{
167+
return value;
168+
}
166169

167-
// The value factory may be called concurrently for the same key, but the first write to the dictionary wins.
168-
// This is identical logic in ConcurrentDictionary.GetOrAdd method.
169-
var newItem = this.policy.CreateItem(key, await valueFactory(key).ConfigureAwait(false));
170+
// The value factory may be called concurrently for the same key, but the first write to the dictionary wins.
171+
// This is identical logic in ConcurrentDictionary.GetOrAdd method.
172+
var newItem = this.policy.CreateItem(key, await valueFactory(key).ConfigureAwait(false));
170173

171-
if (this.dictionary.TryAdd(key, newItem))
172-
{
173-
this.hotQueue.Enqueue(newItem);
174-
Interlocked.Increment(ref hotCount);
175-
Cycle();
176-
return newItem.Value;
177-
}
174+
if (this.dictionary.TryAdd(key, newItem))
175+
{
176+
this.hotQueue.Enqueue(newItem);
177+
Interlocked.Increment(ref hotCount);
178+
Cycle();
179+
return newItem.Value;
180+
}
178181

179-
if (newItem.Value is IDisposable d)
180-
{
181-
d.Dispose();
182+
if (newItem.Value is IDisposable d)
183+
{
184+
d.Dispose();
185+
}
182186
}
183-
184-
return await this.GetOrAddAsync(key, valueFactory).ConfigureAwait(false);
185187
}
186188

187189
///<inheritdoc/>
188190
public bool TryRemove(K key)
189191
{
190-
if (this.dictionary.TryGetValue(key, out var existing))
191-
{
192-
var kvp = new KeyValuePair<K, I>(key, existing);
193-
194-
// hidden atomic remove
195-
// https://devblogs.microsoft.com/pfxteam/little-known-gems-atomic-conditional-removals-from-concurrentdictionary/
196-
if (((ICollection<KeyValuePair<K, I>>)this.dictionary).Remove(kvp))
192+
while (true)
193+
{
194+
if (this.dictionary.TryGetValue(key, out var existing))
197195
{
198-
// Mark as not accessed, it will later be cycled out of the queues because it can never be fetched
199-
// from the dictionary. Note: Hot/Warm/Cold count will reflect the removed item until it is cycled
200-
// from the queue.
201-
existing.WasAccessed = false;
202-
existing.WasRemoved = true;
203-
204-
// serialize dispose (common case dispose not thread safe)
205-
lock (existing)
196+
var kvp = new KeyValuePair<K, I>(key, existing);
197+
198+
// hidden atomic remove
199+
// https://devblogs.microsoft.com/pfxteam/little-known-gems-atomic-conditional-removals-from-concurrentdictionary/
200+
if (((ICollection<KeyValuePair<K, I>>)this.dictionary).Remove(kvp))
206201
{
207-
if (existing.Value is IDisposable d)
202+
// Mark as not accessed, it will later be cycled out of the queues because it can never be fetched
203+
// from the dictionary. Note: Hot/Warm/Cold count will reflect the removed item until it is cycled
204+
// from the queue.
205+
existing.WasAccessed = false;
206+
existing.WasRemoved = true;
207+
208+
// serialize dispose (common case dispose not thread safe)
209+
lock (existing)
208210
{
209-
d.Dispose();
211+
if (existing.Value is IDisposable d)
212+
{
213+
d.Dispose();
214+
}
210215
}
216+
217+
return true;
211218
}
212219

213-
return true;
220+
// it existed, but we couldn't remove - this means value was replaced afer the TryGetValue (a race), try again
221+
}
222+
else
223+
{
224+
return false;
214225
}
215-
216-
// it existed, but we couldn't remove - this means value was replaced afer the TryGetValue (a race), try again
217-
return TryRemove(key);
218226
}
219-
220-
return false;
221227
}
222228

223229
///<inheritdoc/>
@@ -250,25 +256,27 @@ public bool TryUpdate(K key, V value)
250256
///<remarks>Note: Updates to existing items do not affect LRU order. Added items are at the top of the LRU.</remarks>
251257
public void AddOrUpdate(K key, V value)
252258
{
253-
// first, try to update
254-
if (this.TryUpdate(key, value))
259+
while (true)
255260
{
256-
return;
257-
}
261+
// first, try to update
262+
if (this.TryUpdate(key, value))
263+
{
264+
return;
265+
}
258266

259-
// then try add
260-
var newItem = this.policy.CreateItem(key, value);
267+
// then try add
268+
var newItem = this.policy.CreateItem(key, value);
261269

262-
if (this.dictionary.TryAdd(key, newItem))
263-
{
264-
this.hotQueue.Enqueue(newItem);
265-
Interlocked.Increment(ref hotCount);
266-
Cycle();
267-
return;
268-
}
270+
if (this.dictionary.TryAdd(key, newItem))
271+
{
272+
this.hotQueue.Enqueue(newItem);
273+
Interlocked.Increment(ref hotCount);
274+
Cycle();
275+
return;
276+
}
269277

270-
// if both update and add failed there was a race, try again
271-
AddOrUpdate(key, value);
278+
// if both update and add failed there was a race, try again
279+
}
272280
}
273281

274282
///<inheritdoc/>

0 commit comments

Comments
 (0)