@@ -29,29 +29,27 @@ type CacheOptions =
29
29
[<Sealed; NoComparison; NoEquality>]
30
30
[<DebuggerDisplay( " {ToString()}" ) >]
31
31
type 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
36
34
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 >>
44
37
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
50
49
51
50
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
55
53
this
56
54
57
55
override this.ToString () = $" {this.Key}"
@@ -142,21 +140,15 @@ type CacheMetrics(cacheId) =
142
140
// More than totalCapacity can be created, but it will hold for reuse at most totalCapacity.
143
141
type EntityPool < 'Key , 'Value >( totalCapacity , cacheId ) =
144
142
let pool = ConcurrentBag< CachedEntity< 'Key, 'Value>>()
145
- let mutable created = 0
146
143
147
- let overCapacity =
148
- CacheMetrics.Meter.CreateCounter< int64>( " over-capacity" , " count" , cacheId)
144
+ let created = CacheMetrics.Meter.CreateCounter< int64>( " created" , " count" , cacheId)
149
145
150
146
member _.Acquire ( key , value ) =
151
147
match pool.TryTake() with
152
148
| true , entity -> entity.ReUse( key, value)
153
149
| _ ->
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)
160
152
161
153
member _.Reclaim ( entity : CachedEntity < 'Key , 'Value >) =
162
154
if pool.Count < totalCapacity then
@@ -223,13 +215,10 @@ type Cache<'Key, 'Value when 'Key: not null and 'Key: equality> internal (totalC
223
215
async {
224
216
match ! mb.Receive() with
225
217
| EvictionQueueMessage.Add entity ->
226
-
227
- assert entity.Node.IsSome
228
-
229
- evictionQueue.AddLast( entity.Node.Value)
218
+ evictionQueue.AddLast( entity.Node)
230
219
231
220
// Evict one immediately if necessary.
232
- while evictionQueue.Count > capacity do
221
+ if evictionQueue.Count > capacity then
233
222
let first = nonNull evictionQueue.First
234
223
235
224
match store.TryRemove( first.Value.Key) with
@@ -240,16 +229,13 @@ type Cache<'Key, 'Value when 'Key: not null and 'Key: equality> internal (totalC
240
229
evicted.Trigger()
241
230
| _ -> evictionFails.Add 1 L
242
231
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 -> ()
247
234
248
- let node = entity.Node.Value
249
- assert ( node.List = evictionQueue)
235
+ | EvictionQueueMessage.Update entity ->
250
236
// 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 )
253
239
254
240
return ! processNext ()
255
241
}
@@ -264,25 +250,27 @@ type Cache<'Key, 'Value when 'Key: not null and 'Key: equality> internal (totalC
264
250
265
251
member _.TryGetValue ( key : 'Key , value : outref < 'Value >) =
266
252
match store.TryGetValue( key) with
267
- | true , cachedEntity ->
253
+ | true , entity ->
268
254
hits.Add 1 L
269
- evictionProcessor.Post( EvictionQueueMessage.Update cachedEntity )
270
- value <- cachedEntity .Value
255
+ evictionProcessor.Post( EvictionQueueMessage.Update entity )
256
+ value <- entity .Value
271
257
true
272
258
| _ ->
273
259
misses.Add 1 L
274
260
value <- Unchecked.defaultof< 'Value>
275
261
false
276
262
277
263
member _.TryAdd ( key : 'Key , value : 'Value ) =
278
- let cachedEntity = pool.Acquire( key, value)
264
+ let entity = pool.Acquire( key, value)
279
265
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)
283
270
else
284
- pool.Reclaim( cachedEntity)
285
- false
271
+ pool.Reclaim( entity)
272
+
273
+ added
286
274
287
275
interface IDisposable with
288
276
member this.Dispose () =
0 commit comments