Skip to content

Commit d3aea5b

Browse files
authored
Remove ICache (#389)
1 parent ccd7f75 commit d3aea5b

File tree

5 files changed

+40
-69
lines changed

5 files changed

+40
-69
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ The `Unreleased` section name is replaced by the expected version of next releas
4949
### Removed
5050

5151
- Remove explicit `net461` handling; minimum target now `net6.0` / `FSharp.Core` v `6.0.0` [#310](https://github.com/jet/equinox/pull/310) [#323](https://github.com/jet/equinox/pull/323) [#354](https://github.com/jet/equinox/pull/354)
52+
- Remove `Equinox.Core.ICache` (there is/was only one impl, and the interface has changed as part of [#386](https://github.com/jet/equinox/pull/386)) [#389](https://github.com/jet/equinox/pull/389)
5253

5354
### Fixed
5455

src/Equinox.Core/Cache.fs

Lines changed: 27 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,10 @@
1-
namespace Equinox.Core
2-
3-
open System
4-
open System.Runtime.Caching
5-
open System.Threading.Tasks
6-
7-
type [<NoEquality; NoComparison; Struct>] CacheItemOptions =
8-
| AbsoluteExpiration of ae: DateTimeOffset
9-
| RelativeExpiration of re: TimeSpan
10-
module internal CacheItemOptions =
11-
let toPolicy = function
12-
| AbsoluteExpiration absolute -> CacheItemPolicy(AbsoluteExpiration = absolute)
13-
| RelativeExpiration relative -> CacheItemPolicy(SlidingExpiration = relative)
14-
15-
type ICache =
16-
abstract member Load: key: string
17-
* maxAge: TimeSpan
18-
* isStale: Func<StreamToken, StreamToken, bool>
19-
* options: CacheItemOptions
20-
* loadOrReload: (struct (StreamToken * 'state) voption -> Task<struct (StreamToken * 'state)>)
21-
-> Task<struct (StreamToken * 'state)>
22-
abstract member Save: key: string
23-
* isStale: Func<StreamToken, StreamToken, bool>
24-
* options: CacheItemOptions
25-
* timestamp: int64
26-
* token: StreamToken * state: 'state
27-
-> unit
28-
291
namespace Equinox
302

313
open Equinox.Core
324
open Equinox.Core.Tracing
335
open System
346

35-
type internal CacheEntry<'state>(initialToken: StreamToken, initialState: 'state, initialTimestamp: int64) =
7+
type private CacheEntry<'state>(initialToken: StreamToken, initialState: 'state, initialTimestamp: int64) =
368
let mutable currentToken = initialToken
379
let mutable currentState = initialState
3810
let mutable verifiedTimestamp = initialTimestamp
@@ -79,13 +51,13 @@ type Cache private (inner: System.Runtime.Caching.MemoryCache) =
7951
| null -> ValueNone
8052
| :? CacheEntry<'state> as existingEntry -> existingEntry.TryGetValue()
8153
| x -> failwith $"tryLoad Incompatible cache entry %A{x}"
82-
let addOrGet key options entry =
83-
match inner.AddOrGetExisting(key, entry, CacheItemOptions.toPolicy options) with
54+
let addOrGet key policy entry =
55+
match inner.AddOrGetExisting(key, entry, policy = policy) with
8456
| null -> Ok entry
8557
| :? CacheEntry<'state> as existingEntry -> Error existingEntry
8658
| x -> failwith $"addOrGet Incompatible cache entry %A{x}"
87-
let getElseAddEmptyEntry key options =
88-
match addOrGet key options (CacheEntry<'state>.CreateEmpty()) with
59+
let getElseAddEmptyEntry key policy =
60+
match addOrGet key policy (CacheEntry<'state>.CreateEmpty()) with
8961
| Ok fresh -> fresh
9062
| Error existingEntry -> existingEntry
9163
let addOrMergeCacheEntry isStale key options timestamp struct (token, state) =
@@ -97,37 +69,36 @@ type Cache private (inner: System.Runtime.Caching.MemoryCache) =
9769
let config = System.Collections.Specialized.NameValueCollection(1)
9870
config.Add("cacheMemoryLimitMegabytes", string sizeMb);
9971
Cache(new System.Runtime.Caching.MemoryCache(name, config))
100-
interface ICache with
101-
// if there's a non-zero maxAge, concurrent read attempts share the roundtrip (and its fate, if it throws)
102-
member _.Load(key, maxAge, isStale, options, loadOrReload) = task {
103-
let loadOrReload maybeBaseState () = task {
104-
let act = System.Diagnostics.Activity.Current
105-
if act <> null then act.AddCacheHit(ValueOption.isSome maybeBaseState) |> ignore
106-
let ts = System.Diagnostics.Stopwatch.GetTimestamp()
107-
let! res = loadOrReload maybeBaseState
108-
return struct (ts, res) }
109-
if maxAge = TimeSpan.Zero then // Boring algorithm that has each caller independently load/reload the data and then cache it
110-
let maybeBaseState = tryLoad key
111-
let! timestamp, res = loadOrReload maybeBaseState ()
112-
addOrMergeCacheEntry isStale key options timestamp res
113-
return res
114-
else // ensure we have an entry in the cache for this key; coordinate retrieval through that
115-
let cacheSlot = getElseAddEmptyEntry key options
116-
return! cacheSlot.ReadThrough(maxAge, isStale, loadOrReload) }
117-
// Newer values get saved; equal values update the last retrieval timestamp
118-
member _.Save(key, isStale, options, timestamp, token, state) =
119-
addOrMergeCacheEntry isStale key options timestamp (token, state)
72+
// if there's a non-zero maxAge, concurrent read attempts share the roundtrip (and its fate, if it throws)
73+
member _.Load(key, maxAge, isStale, policy, loadOrReload) = task {
74+
let loadOrReload maybeBaseState () = task {
75+
let act = System.Diagnostics.Activity.Current
76+
if act <> null then act.AddCacheHit(ValueOption.isSome maybeBaseState) |> ignore
77+
let ts = System.Diagnostics.Stopwatch.GetTimestamp()
78+
let! res = loadOrReload maybeBaseState
79+
return struct (ts, res) }
80+
if maxAge = TimeSpan.Zero then // Boring algorithm that has each caller independently load/reload the data and then cache it
81+
let maybeBaseState = tryLoad key
82+
let! timestamp, res = loadOrReload maybeBaseState ()
83+
addOrMergeCacheEntry isStale key policy timestamp res
84+
return res
85+
else // ensure we have an entry in the cache for this key; coordinate retrieval through that
86+
let cacheSlot = getElseAddEmptyEntry key policy
87+
return! cacheSlot.ReadThrough(maxAge, isStale, loadOrReload) }
88+
// Newer values get saved; equal values update the last retrieval timestamp
89+
member _.Save(key, isStale, policy, timestamp, token, state) =
90+
addOrMergeCacheEntry isStale key policy timestamp (token, state)
12091

12192
type [<NoComparison; NoEquality; RequireQualifiedAccess>] CachingStrategy =
12293
/// Retain a single 'state per streamName.
12394
/// Each cache hit for a stream renews the retention period for the defined <c>window</c>.
12495
/// Upon expiration of the defined <c>window</c> from the point at which the cache was entry was last used, a full reload is triggered.
12596
/// Unless a <c>LoadOption</c> is used, cache hits still incur a roundtrip to load any subsequently-added events.
126-
| SlidingWindow of ICache * window: TimeSpan
97+
| SlidingWindow of Cache * window: TimeSpan
12798
/// Retain a single 'state per streamName.
12899
/// Upon expiration of the defined <c>period</c>, a full reload is triggered.
129100
/// Unless a <c>LoadOption</c> is used, cache hits still incur a roundtrip to load any subsequently-added events.
130-
| FixedTimeSpan of ICache * period: TimeSpan
101+
| FixedTimeSpan of Cache * period: TimeSpan
131102
/// Prefix is used to segregate multiple folded states per stream when they are stored in the cache.
132103
/// Semantics are otherwise identical to <c>SlidingWindow</c>.
133-
| SlidingWindowPrefixed of ICache * window: TimeSpan * prefix: string
104+
| SlidingWindowPrefixed of Cache * window: TimeSpan * prefix: string

src/Equinox.Core/Caching.fs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ let private tee f (inner: CancellationToken -> Task<struct (StreamToken * 'state
1515
return tokenAndState }
1616

1717
type private Decorator<'event, 'state, 'context, 'cat when 'cat :> ICategory<'event, 'state, 'context> and 'cat :> IReloadable<'state> >
18-
(category: 'cat, cache: ICache, isStale, createKey, createOptions) =
18+
(category: 'cat, cache: Equinox.Cache, isStale, createKey, createOptions) =
1919
interface ICategory<'event, 'state, 'context> with
2020
member _.Load(log, categoryName, streamId, streamName, maxAge, requireLeader, ct) = task {
2121
let loadOrReload = function
@@ -35,16 +35,15 @@ type private Decorator<'event, 'state, 'context, 'cat when 'cat :> ICategory<'ev
3535
let private mkKey prefix streamName =
3636
prefix + streamName
3737

38-
let private optionsSlidingExpiration (slidingExpiration: TimeSpan) () =
39-
CacheItemOptions.RelativeExpiration slidingExpiration
40-
let private optionsFixedTimeSpan (period: TimeSpan) () =
38+
let private policySlidingExpiration (slidingExpiration: TimeSpan) () =
39+
System.Runtime.Caching.CacheItemPolicy(SlidingExpiration = slidingExpiration)
40+
let private policyFixedTimeSpan (period: TimeSpan) () =
4141
let expirationPoint = let creationDate = DateTimeOffset.UtcNow in creationDate.Add period
42-
CacheItemOptions.AbsoluteExpiration expirationPoint
43-
42+
System.Runtime.Caching.CacheItemPolicy(AbsoluteExpiration = expirationPoint)
4443
let private mapStrategy = function
45-
| Equinox.CachingStrategy.FixedTimeSpan (cache, period) -> struct ( cache, mkKey null, optionsFixedTimeSpan period)
46-
| Equinox.CachingStrategy.SlidingWindow (cache, window) -> cache, mkKey null, optionsSlidingExpiration window
47-
| Equinox.CachingStrategy.SlidingWindowPrefixed (cache, window, prefix) -> cache, mkKey prefix, optionsSlidingExpiration window
44+
| Equinox.CachingStrategy.FixedTimeSpan (cache, period) -> struct ( cache, mkKey null, policyFixedTimeSpan period)
45+
| Equinox.CachingStrategy.SlidingWindow (cache, window) -> cache, mkKey null, policySlidingExpiration window
46+
| Equinox.CachingStrategy.SlidingWindowPrefixed (cache, window, prefix) -> cache, mkKey prefix, policySlidingExpiration window
4847

4948
let apply isStale x (cat: 'cat when 'cat :> ICategory<'event, 'state, 'context> and 'cat :> IReloadable<'state>): ICategory<_, _, _> =
5049
match x with

src/Equinox.CosmosStore/CosmosStore.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,12 +1331,12 @@ type CachingStrategy =
13311331
/// Unless <c>LoadOption.AnyCachedValue</c> or <c>AllowStale</c> are used, cache hits still incurs an etag-contingent Tip read (at a cost of a roundtrip with a 1RU charge if unmodified).
13321332
// NB while a strategy like EventStore.Caching.SlidingWindowPrefixed is obviously easy to implement, the recommended approach is to
13331333
// track all relevant data in the state, and/or have the `unfold` function ensure _all_ relevant events get held in the `u`nfolds in Tip
1334-
| SlidingWindow of ICache * window: TimeSpan
1334+
| SlidingWindow of Equinox.Cache * window: TimeSpan
13351335
/// Retain a single 'state per streamName, together with the associated etag.
13361336
/// Upon expiration of the defined <c>period</c>, a full reload is triggered.
13371337
/// Typically combined with an `Equinox.LoadOption` to minimize loads.
13381338
/// Unless <c>LoadOption.AnyCachedValue</c> or <c>AllowStale</c> are used, cache hits still incurs an etag-contingent Tip read (at a cost of a roundtrip with a 1RU charge if unmodified).
1339-
| FixedTimeSpan of ICache * period: TimeSpan
1339+
| FixedTimeSpan of Equinox.Cache * period: TimeSpan
13401340

13411341
[<NoComparison; NoEquality; RequireQualifiedAccess>]
13421342
type AccessStrategy<'event, 'state> =

src/Equinox.DynamoStore/DynamoStore.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,12 +1287,12 @@ type CachingStrategy =
12871287
/// Unless a <c>LoadOption</c> is used, each cache hit still involves a read roundtrip (RU charges incurred, transport latency) though deserialization is skipped due to etag match
12881288
// NB while a strategy like EventStore.Caching.SlidingWindowPrefixed is obviously easy to implement, the recommended approach is to
12891289
// track all relevant data in the state, and/or have the `unfold` function ensure _all_ relevant events get held in the unfolds in Tip
1290-
| SlidingWindow of ICache * window: TimeSpan
1290+
| SlidingWindow of Equinox.Cache * window: TimeSpan
12911291
/// Retain a single 'state per streamName, together with the associated etag.
12921292
/// Upon expiration of the defined <c>period</c>, a full reload is triggered.
12931293
/// Typically combined with an `Equinox.LoadOption` to minimize loads.
12941294
/// Unless a <c>LoadOption</c> is used, each cache hit still involves a read roundtrip (RU charges incurred, transport latency) though deserialization is skipped due to etag match
1295-
| FixedTimeSpan of ICache * period: TimeSpan
1295+
| FixedTimeSpan of Equinox.Cache * period: TimeSpan
12961296

12971297
[<NoComparison; NoEquality; RequireQualifiedAccess>]
12981298
type AccessStrategy<'event, 'state> =

0 commit comments

Comments
 (0)