3
3
4
4
namespace FSharp.Data.GraphQL
5
5
6
- open FSharp. Data . GraphQL . Client
6
+ open System
7
7
open System.Collections .Concurrent
8
+ open System.Collections .Generic
9
+ open System.Timers
10
+ open FSharp.Data .GraphQL .Client
8
11
open ProviderImplementation.ProvidedTypes
9
12
13
+ // see: cache implementation http://www.fssnip.net/7UT/title/Threadsafe-Generic-MemoryCache-and-Memoize-Function
14
+
15
+
16
+ type CacheExpirationPolicy =
17
+ | NoExpiration
18
+ | AbsoluteExpiration of TimeSpan
19
+ | SlidingExpiration of TimeSpan
20
+
21
+ type CacheEntryExpiration =
22
+ | NeverExpires
23
+ | ExpiresAt of DateTime
24
+ | ExpiresAfter of TimeSpan
25
+
26
+ type CacheEntry < 'key , 'value > =
27
+ { Key: 'key
28
+ Value: 'value
29
+ Expiration: CacheEntryExpiration
30
+ LastUsage: DateTime }
31
+
32
+ module CacheExpiration =
33
+ let isExpired ( entry : CacheEntry < _ , _ >) =
34
+ match entry.Expiration with
35
+ | NeverExpires -> false
36
+ | ExpiresAt date -> DateTime.UtcNow > date
37
+ | ExpiresAfter window -> ( DateTime.UtcNow - entry.LastUsage) > window
38
+
39
+ type private IMemoryCacheStore < 'key , 'value > =
40
+ inherit IEnumerable< CacheEntry< 'key, 'value>>
41
+ abstract member Add: CacheEntry < 'key , 'value > -> unit
42
+ abstract member GetOrAdd: 'key -> ( 'key -> CacheEntry < 'key , 'value >) -> CacheEntry < 'key , 'value >
43
+ abstract member Remove: 'key -> unit
44
+ abstract member Contains: 'key -> bool
45
+ abstract member Update: 'key -> ( CacheEntry < 'key , 'value > -> CacheEntry < 'key , 'value >) -> unit
46
+ abstract member TryFind: 'key -> CacheEntry < 'key , 'value > option
47
+
48
+ type MemoryCache < 'key , 'value > (? cacheExpirationPolicy ) =
49
+ let policy = defaultArg cacheExpirationPolicy NoExpiration
50
+ let store =
51
+ let entries = ConcurrentDictionary< 'key, CacheEntry< 'key, 'value>>()
52
+ let get , getEnumerator =
53
+ let values = entries |> Seq.map ( fun kvp -> kvp.Value)
54
+ ( fun () -> values), ( fun () -> values.GetEnumerator())
55
+ { new IMemoryCacheStore< 'key, 'value> with
56
+ member __.Add entry = entries.AddOrUpdate( entry.Key, entry, fun _ _ -> entry) |> ignore
57
+ member __.GetOrAdd key getValue = entries.GetOrAdd( key, getValue)
58
+ member __.Remove key = entries.TryRemove key |> ignore
59
+ member __.Contains key = entries.ContainsKey key
60
+ member __.Update key update =
61
+ match entries.TryGetValue( key) with
62
+ | ( true , entry) -> entries.AddOrUpdate( key, entry, fun _ entry -> update entry) |> ignore
63
+ | _ -> ()
64
+ member __.TryFind key =
65
+ match entries.TryGetValue( key) with
66
+ | ( true , entry) -> Some entry
67
+ | _ -> None
68
+ member __.GetEnumerator () = getEnumerator ()
69
+ member __.GetEnumerator () = getEnumerator () :> Collections .IEnumerator
70
+ }
71
+
72
+ let checkExpiration () =
73
+ store
74
+ |> Seq.filter CacheExpiration.isExpired
75
+ |> Seq.map ( fun entry -> entry.Key)
76
+ |> Seq.iter store.Remove
77
+
78
+ let newCacheEntry key value =
79
+ { Key = key
80
+ Value = value
81
+ Expiration = match policy with
82
+ | NoExpiration -> NeverExpires
83
+ | AbsoluteExpiration time -> ExpiresAt ( DateTime.UtcNow + time)
84
+ | SlidingExpiration window -> ExpiresAfter window
85
+ LastUsage = DateTime.UtcNow
86
+ }
87
+
88
+ let add key value =
89
+ if key |> store.Contains
90
+ then store.Update key ( fun entry -> { entry with Value = value; LastUsage = DateTime.UtcNow})
91
+ else store.Add <| newCacheEntry key value
92
+
93
+ let remove key =
94
+ store.Remove key
95
+
96
+ let get key =
97
+ store.TryFind key |> Option.bind ( fun entry -> Some entry.Value)
98
+
99
+ let getOrAdd key value =
100
+ store.GetOrAdd key ( fun _ -> newCacheEntry key value)
101
+ |> fun entry -> entry.Value
102
+
103
+ let getOrAddResult key f =
104
+ store.GetOrAdd key ( fun _ -> newCacheEntry key <| f())
105
+ |> fun entry -> entry.Value
106
+
107
+ let getTimer ( expiration : TimeSpan ) =
108
+ if expiration.TotalSeconds < 1.0
109
+ then TimeSpan.FromMilliseconds 100.0
110
+ elif expiration.TotalMinutes < 1.0
111
+ then TimeSpan.FromSeconds 1.0
112
+ else TimeSpan.FromMinutes 1.0
113
+ |> fun interval -> new Timer( interval.TotalMilliseconds)
114
+
115
+ let timer =
116
+ match policy with
117
+ | NoExpiration -> None
118
+ | AbsoluteExpiration time -> time |> getTimer |> Some
119
+ | SlidingExpiration time -> time |> getTimer |> Some
120
+
121
+ let observer =
122
+ match timer with
123
+ | Some t ->
124
+ let disposable = t.Elapsed |> Observable.subscribe ( fun _ -> checkExpiration())
125
+ t.Start()
126
+ Some disposable
127
+ | None -> None
128
+
129
+ member __.Add key value = add key value
130
+ member __.Remove key = remove key
131
+ member __.Get key = get key
132
+ member __.GetOrAdd key value = getOrAdd key value
133
+ member __.GetOrAddResult key f = getOrAddResult key f
134
+
135
+
10
136
type internal ProviderKey =
11
137
{ IntrospectionLocation : IntrospectionLocation
12
138
CustomHttpHeadersLocation : StringLocation }
13
139
14
- type internal CacheInvalidator ( key : ProviderKey , invalidateFn : ProviderKey -> unit ) =
15
- let lockObj = obj()
16
- let mutable remainingTime = 30000
17
- do
18
- async {
19
- while remainingTime > 0 do
20
- do ! Async.Sleep( 1000 )
21
- lock lockObj ( fun _ -> remainingTime <- remainingTime - 1000 )
22
- invalidateFn key
23
- } |> Async.Start
24
- member __.Reset () = lock lockObj ( fun _ -> remainingTime <- 30000 )
25
-
26
140
module internal DesignTimeCache =
27
- let private cache = ConcurrentDictionary < ProviderKey , CacheInvalidator * ProvidedTypeDefinition >( )
28
-
141
+ let private expiration = CacheExpirationPolicy.SlidingExpiration ( TimeSpan.FromSeconds 30.0 )
142
+ let private cache = MemoryCache < ProviderKey , ProvidedTypeDefinition >( expiration )
29
143
let getOrAdd ( key : ProviderKey ) ( defMaker : unit -> ProvidedTypeDefinition ) =
30
- if not ( cache.ContainsKey( key))
31
- then
32
- let def = defMaker()
33
- let invalidateFn location = cache.TryRemove( location) |> ignore
34
- let invalidator = CacheInvalidator( key, invalidateFn)
35
- cache.TryAdd( key, ( invalidator, def)) |> ignore
36
- def
37
- else
38
- let invalidator , def = cache.[ key]
39
- invalidator.Reset()
40
- def
144
+ cache.GetOrAddResult key defMaker
0 commit comments