@@ -29,29 +29,27 @@ type CacheOptions =
2929[<Sealed; NoComparison; NoEquality>]
3030[<DebuggerDisplay( " {ToString()}" ) >]
3131type CachedEntity < 'Key , 'Value > =
32- val mutable Key : 'Key
33- val mutable Value : 'Value
34- val mutable AccessCount : int64
35- val mutable Node : LinkedListNode < CachedEntity < 'Key , 'Value >> voption
32+ val mutable private key : 'Key
33+ val mutable private value : 'Value
3634
37- new ( key, value) =
38- {
39- Key = key
40- Value = value
41- AccessCount = 0 L
42- Node = ValueNone
43- }
35+ [<DefaultValue( false ) >]
36+ val mutable private node : LinkedListNode < CachedEntity < 'Key , 'Value >>
4437
45- // This is one time initialization, outside of the constructor because of circular reference.
46- // The contract is that each CachedEntity that the EntityPool produces, has Node assigned.
47- member this.WithNode () =
48- this.Node <- ValueSome( LinkedListNode this)
49- this
38+ private new ( key, value) = { key = key; value = value }
39+
40+ member this.Node = this.node
41+ member this.Key = this.key
42+ member this.Value = this.value
43+
44+ static member Create ( key : 'Key , value : 'Value ) =
45+ let entity = CachedEntity( key, value)
46+ // The contract is that each CachedEntity produced by the EntityPool always has Node referencing itself.
47+ entity.node <- LinkedListNode( entity)
48+ entity
5049
5150 member this.ReUse ( key , value ) =
52- this.Key <- key
53- this.Value <- value
54- this.AccessCount <- 0 L
51+ this.key <- key
52+ this.value <- value
5553 this
5654
5755 override this.ToString () = $" {this.Key}"
@@ -142,21 +140,15 @@ type CacheMetrics(cacheId) =
142140// More than totalCapacity can be created, but it will hold for reuse at most totalCapacity.
143141type EntityPool < 'Key , 'Value >( totalCapacity , cacheId ) =
144142 let pool = ConcurrentBag< CachedEntity< 'Key, 'Value>>()
145- let mutable created = 0
146143
147- let overCapacity =
148- CacheMetrics.Meter.CreateCounter< int64>( " over-capacity" , " count" , cacheId)
144+ let created = CacheMetrics.Meter.CreateCounter< int64>( " created" , " count" , cacheId)
149145
150146 member _.Acquire ( key , value ) =
151147 match pool.TryTake() with
152148 | true , entity -> entity.ReUse( key, value)
153149 | _ ->
154- if Interlocked.Increment & created > totalCapacity then
155- overCapacity.Add 1 L
156-
157- // Associate a LinkedListNode with freshly created entity.
158- // This is a one time initialization.
159- CachedEntity( key, value) .WithNode()
150+ created.Add 1 L
151+ CachedEntity.Create( key, value)
160152
161153 member _.Reclaim ( entity : CachedEntity < 'Key , 'Value >) =
162154 if pool.Count < totalCapacity then
@@ -223,13 +215,10 @@ type Cache<'Key, 'Value when 'Key: not null and 'Key: equality> internal (totalC
223215 async {
224216 match ! mb.Receive() with
225217 | EvictionQueueMessage.Add entity ->
226-
227- assert entity.Node.IsSome
228-
229- evictionQueue.AddLast( entity.Node.Value)
218+ evictionQueue.AddLast( entity.Node)
230219
231220 // Evict one immediately if necessary.
232- while evictionQueue.Count > capacity do
221+ if evictionQueue.Count > capacity then
233222 let first = nonNull evictionQueue.First
234223
235224 match store.TryRemove( first.Value.Key) with
@@ -240,16 +229,13 @@ type Cache<'Key, 'Value when 'Key: not null and 'Key: equality> internal (totalC
240229 evicted.Trigger()
241230 | _ -> evictionFails.Add 1 L
242231
243- | EvictionQueueMessage.Update entity ->
244- entity.AccessCount <- entity.AccessCount + 1 L
245-
246- assert entity.Node.IsSome
232+ // Store updates are not synchronized. It is possible the entity is no longer in the queue.
233+ | EvictionQueueMessage.Update entity when isNull entity.Node.List -> ()
247234
248- let node = entity.Node.Value
249- assert ( node.List = evictionQueue)
235+ | EvictionQueueMessage.Update entity ->
250236 // Just move this node to the end of the list.
251- evictionQueue.Remove( node )
252- evictionQueue.AddLast( node )
237+ evictionQueue.Remove( entity.Node )
238+ evictionQueue.AddLast( entity.Node )
253239
254240 return ! processNext ()
255241 }
@@ -264,25 +250,27 @@ type Cache<'Key, 'Value when 'Key: not null and 'Key: equality> internal (totalC
264250
265251 member _.TryGetValue ( key : 'Key , value : outref < 'Value >) =
266252 match store.TryGetValue( key) with
267- | true , cachedEntity ->
253+ | true , entity ->
268254 hits.Add 1 L
269- evictionProcessor.Post( EvictionQueueMessage.Update cachedEntity )
270- value <- cachedEntity .Value
255+ evictionProcessor.Post( EvictionQueueMessage.Update entity )
256+ value <- entity .Value
271257 true
272258 | _ ->
273259 misses.Add 1 L
274260 value <- Unchecked.defaultof< 'Value>
275261 false
276262
277263 member _.TryAdd ( key : 'Key , value : 'Value ) =
278- let cachedEntity = pool.Acquire( key, value)
264+ let entity = pool.Acquire( key, value)
279265
280- if store.TryAdd( key, cachedEntity) then
281- evictionProcessor.Post( EvictionQueueMessage.Add cachedEntity)
282- true
266+ let added = store.TryAdd( key, entity)
267+
268+ if added then
269+ evictionProcessor.Post( EvictionQueueMessage.Add entity)
283270 else
284- pool.Reclaim( cachedEntity)
285- false
271+ pool.Reclaim( entity)
272+
273+ added
286274
287275 interface IDisposable with
288276 member this.Dispose () =
0 commit comments