55using System . Diagnostics ;
66using System . Diagnostics . CodeAnalysis ;
77using System . Linq ;
8+ using System . Runtime . CompilerServices ;
89using System . Runtime . InteropServices ;
910using System . Threading ;
1011using System . Threading . Tasks ;
@@ -70,7 +71,7 @@ public sealed class ConcurrentLfu<K, V> : ICache<K, V>, IAsyncCache<K, V>, IBoun
7071
7172 private readonly IScheduler scheduler ;
7273
73- private readonly LfuNode < K , V > [ ] drainBuffer ;
74+ private readonly LfuNode < K , V > [ ] drainBuffer ;
7475
7576 /// <summary>
7677 /// Initializes a new instance of the ConcurrentLfu class with the specified capacity.
@@ -478,9 +479,10 @@ private void AfterWrite(LfuNode<K, V> node)
478479 private void ScheduleAfterWrite ( )
479480 {
480481 var spinner = new SpinWait ( ) ;
482+ int status = this . drainStatus . NonVolatileRead ( ) ;
481483 while ( true )
482484 {
483- switch ( this . drainStatus . Status ( ) )
485+ switch ( status )
484486 {
485487 case DrainStatus . Idle :
486488 this . drainStatus . Cas ( DrainStatus . Idle , DrainStatus . Required ) ;
@@ -494,6 +496,7 @@ private void ScheduleAfterWrite()
494496 {
495497 return ;
496498 }
499+ status = this . drainStatus . VolatileRead ( ) ;
497500 break ;
498501 case DrainStatus . ProcessingToRequired :
499502 return ;
@@ -509,8 +512,8 @@ IEnumerator IEnumerable.GetEnumerator()
509512
510513 private void TryScheduleDrain ( )
511514 {
512- if ( this . drainStatus . Status ( ) >= DrainStatus . ProcessingToIdle )
513- {
515+ if ( this . drainStatus . NonVolatileRead ( ) >= DrainStatus . ProcessingToIdle )
516+ {
514517 return ;
515518 }
516519
@@ -521,15 +524,15 @@ private void TryScheduleDrain()
521524
522525 if ( lockTaken )
523526 {
524- int status = this . drainStatus . Status ( ) ;
527+ int status = this . drainStatus . NonVolatileRead ( ) ;
525528
526529 if ( status >= DrainStatus . ProcessingToIdle )
527530 {
528531 return ;
529532 }
530533
531- this . drainStatus . Set ( DrainStatus . ProcessingToIdle ) ;
532- scheduler . Run ( ( ) => DrainBuffers ( ) ) ;
534+ this . drainStatus . VolatileWrite ( DrainStatus . ProcessingToIdle ) ;
535+ scheduler . Run ( ( ) => this . DrainBuffers ( ) ) ;
533536 }
534537 }
535538 finally
@@ -559,15 +562,15 @@ private void DrainBuffers()
559562 }
560563 }
561564
562- if ( this . drainStatus . Status ( ) == DrainStatus . Required )
565+ if ( this . drainStatus . VolatileRead ( ) == DrainStatus . Required )
563566 {
564567 TryScheduleDrain ( ) ;
565568 }
566569 }
567570
568571 private bool Maintenance ( LfuNode < K , V > droppedWrite = null )
569572 {
570- this . drainStatus . Set ( DrainStatus . ProcessingToIdle ) ;
573+ this . drainStatus . VolatileWrite ( DrainStatus . ProcessingToIdle ) ;
571574
572575 // Note: this is only Span on .NET Core 3.1+, else this is no-op and it is still an array
573576 var buffer = this . drainBuffer . AsSpanOrArray ( ) ;
@@ -609,10 +612,10 @@ private bool Maintenance(LfuNode<K, V> droppedWrite = null)
609612 // 1. We drained both input buffers (all work done)
610613 // 2. or scheduler is foreground (since don't run continuously on the foreground)
611614 if ( ( done || ! scheduler . IsBackground ) &&
612- ( this . drainStatus . Status ( ) != DrainStatus . ProcessingToIdle ||
615+ ( this . drainStatus . NonVolatileRead ( ) != DrainStatus . ProcessingToIdle ||
613616 ! this . drainStatus . Cas ( DrainStatus . ProcessingToIdle , DrainStatus . Idle ) ) )
614617 {
615- this . drainStatus . Set ( DrainStatus . Required ) ;
618+ this . drainStatus . NonVolatileWrite ( DrainStatus . Required ) ;
616619 }
617620
618621 return done ;
@@ -743,13 +746,15 @@ private ref struct EvictIterator
743746 public LfuNode < K , V > node ;
744747 public int freq ;
745748
749+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
746750 public EvictIterator ( CmSketch < K > sketch , LfuNode < K , V > node )
747751 {
748752 this . sketch = sketch ;
749753 this . node = node ;
750754 freq = node == null ? - 1 : sketch . EstimateFrequency ( node . Key ) ;
751755 }
752756
757+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
753758 public void Next ( )
754759 {
755760 node = node . Next ;
@@ -863,9 +868,10 @@ private class DrainStatus
863868
864869 private PaddedInt drainStatus ; // mutable struct, don't mark readonly
865870
871+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
866872 public bool ShouldDrain ( bool delayable )
867873 {
868- int status = Volatile . Read ( ref this . drainStatus . Value ) ;
874+ int status = this . NonVolatileRead ( ) ;
869875 return status switch
870876 {
871877 Idle => ! delayable ,
@@ -875,19 +881,34 @@ public bool ShouldDrain(bool delayable)
875881 } ;
876882 }
877883
878- public void Set ( int newStatus )
884+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
885+ public void VolatileWrite ( int newStatus )
879886 {
880887 Volatile . Write ( ref this . drainStatus . Value , newStatus ) ;
888+ }
889+
890+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
891+ public void NonVolatileWrite ( int newStatus )
892+ {
893+ this . drainStatus . Value = newStatus ;
881894 }
882895
896+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
883897 public bool Cas ( int oldStatus , int newStatus )
884898 {
885899 return Interlocked . CompareExchange ( ref this . drainStatus . Value , newStatus , oldStatus ) == oldStatus ;
886900 }
887901
888- public int Status ( )
902+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
903+ public int VolatileRead ( )
889904 {
890905 return Volatile . Read ( ref this . drainStatus . Value ) ;
906+ }
907+
908+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
909+ public int NonVolatileRead ( )
910+ {
911+ return this . drainStatus . Value ;
891912 }
892913
893914 [ ExcludeFromCodeCoverage ]
0 commit comments