11// Copyright (c) Six Labors.
22// Licensed under the Six Labors Split License.
3- #nullable disable
43
54using System . Collections . Concurrent ;
5+ using System . Diagnostics . CodeAnalysis ;
66using System . Runtime . CompilerServices ;
77
88namespace SixLabors . ImageSharp . Web . Caching ;
@@ -26,6 +26,7 @@ namespace SixLabors.ImageSharp.Web.Caching;
2626/// 6. When cold is full, cold tail is moved to warm head or removed from dictionary on depending on WasAccessed.
2727/// </remarks>
2828internal class ConcurrentTLruCache < TKey , TValue >
29+ where TKey : notnull
2930{
3031 private readonly ConcurrentDictionary < TKey , LongTickCountLruItem < TKey , TValue > > dictionary ;
3132
@@ -97,9 +98,9 @@ public ConcurrentTLruCache(int concurrencyLevel, int capacity, IEqualityComparer
9798 /// <param name="key">The key of the value to get.</param>
9899 /// <param name="value">When this method returns, contains the object from the cache that has the specified key, or the default value of the type if the operation failed.</param>
99100 /// <returns><see langword="true"/> if the key was found in the cache; otherwise, <see langword="false"/>.</returns>
100- public bool TryGet ( TKey key , out TValue value )
101+ public bool TryGet ( TKey key , [ NotNullWhen ( true ) ] out TValue ? value )
101102 {
102- if ( this . dictionary . TryGetValue ( key , out LongTickCountLruItem < TKey , TValue > item ) )
103+ if ( this . dictionary . TryGetValue ( key , out LongTickCountLruItem < TKey , TValue > ? item ) )
103104 {
104105 return this . GetOrDiscard ( item , out value ) ;
105106 }
@@ -111,7 +112,7 @@ public bool TryGet(TKey key, out TValue value)
111112 // AggressiveInlining forces the JIT to inline policy.ShouldDiscard(). For LRU policy
112113 // the first branch is completely eliminated due to JIT time constant propogation.
113114 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
114- private bool GetOrDiscard ( LongTickCountLruItem < TKey , TValue > item , out TValue value )
115+ private bool GetOrDiscard ( LongTickCountLruItem < TKey , TValue > item , out TValue ? value )
115116 {
116117 if ( this . policy . ShouldDiscard ( item ) )
117118 {
@@ -135,7 +136,7 @@ private bool GetOrDiscard(LongTickCountLruItem<TKey, TValue> item, out TValue va
135136 /// in the cache, or the new value if the key was not in the dictionary.</returns>
136137 public TValue GetOrAdd ( TKey key , Func < TKey , TValue > valueFactory )
137138 {
138- if ( this . TryGet ( key , out TValue value ) )
139+ if ( this . TryGet ( key , out TValue ? value ) )
139140 {
140141 return value ;
141142 }
@@ -164,7 +165,7 @@ public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
164165 /// <returns>A task that represents the asynchronous <see cref="GetOrAdd(TKey, Func{TKey, TValue})"/> operation.</returns>
165166 public async Task < TValue > GetOrAddAsync ( TKey key , Func < TKey , Task < TValue > > valueFactory )
166167 {
167- if ( this . TryGet ( key , out TValue value ) )
168+ if ( this . TryGet ( key , out TValue ? value ) )
168169 {
169170 return value ;
170171 }
@@ -202,7 +203,7 @@ public bool TryRemove(TKey key)
202203 // and it will not be marked as removed. If key 1 is fetched while LruItem1* is still in the queue, there will
203204 // be two queue entries for key 1, and neither is marked as removed. Thus when LruItem1 * ages out, it will
204205 // incorrectly remove 1 from the dictionary, and this cycle can repeat.
205- if ( this . dictionary . TryGetValue ( key , out LongTickCountLruItem < TKey , TValue > existing ) )
206+ if ( this . dictionary . TryGetValue ( key , out LongTickCountLruItem < TKey , TValue > ? existing ) )
206207 {
207208 if ( existing . WasRemoved )
208209 {
@@ -219,7 +220,7 @@ public bool TryRemove(TKey key)
219220 existing . WasRemoved = true ;
220221 }
221222
222- if ( this . dictionary . TryRemove ( key , out LongTickCountLruItem < TKey , TValue > removedItem ) )
223+ if ( this . dictionary . TryRemove ( key , out LongTickCountLruItem < TKey , TValue > ? removedItem ) )
223224 {
224225 // Mark as not accessed, it will later be cycled out of the queues because it can never be fetched
225226 // from the dictionary. Note: Hot/Warm/Cold count will reflect the removed item until it is cycled
@@ -261,7 +262,7 @@ private void CycleHot()
261262 {
262263 Interlocked . Decrement ( ref this . hotCount ) ;
263264
264- if ( this . hotQueue . TryDequeue ( out LongTickCountLruItem < TKey , TValue > item ) )
265+ if ( this . hotQueue . TryDequeue ( out LongTickCountLruItem < TKey , TValue > ? item ) )
265266 {
266267 ItemDestination where = this . policy . RouteHot ( item ) ;
267268 this . Move ( item , where ) ;
@@ -279,7 +280,7 @@ private void CycleWarm()
279280 {
280281 Interlocked . Decrement ( ref this . warmCount ) ;
281282
282- if ( this . warmQueue . TryDequeue ( out LongTickCountLruItem < TKey , TValue > item ) )
283+ if ( this . warmQueue . TryDequeue ( out LongTickCountLruItem < TKey , TValue > ? item ) )
283284 {
284285 ItemDestination where = this . policy . RouteWarm ( item ) ;
285286
@@ -308,7 +309,7 @@ private void CycleCold()
308309 {
309310 Interlocked . Decrement ( ref this . coldCount ) ;
310311
311- if ( this . coldQueue . TryDequeue ( out LongTickCountLruItem < TKey , TValue > item ) )
312+ if ( this . coldQueue . TryDequeue ( out LongTickCountLruItem < TKey , TValue > ? item ) )
312313 {
313314 ItemDestination where = this . policy . RouteCold ( item ) ;
314315
@@ -354,7 +355,7 @@ private void Move(LongTickCountLruItem<TKey, TValue> item, ItemDestination where
354355 break ;
355356 }
356357
357- if ( this . dictionary . TryRemove ( item . Key , out LongTickCountLruItem < TKey , TValue > removedItem ) )
358+ if ( this . dictionary . TryRemove ( item . Key , out LongTickCountLruItem < TKey , TValue > ? removedItem ) )
358359 {
359360 item . WasRemoved = true ;
360361 if ( removedItem . Value is IDisposable d )
0 commit comments