Split out cache
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/pr/build Pipeline failed
ci/woodpecker/push/all-checks-complete Pipeline was successful
ci/woodpecker/pr/all-checks-complete unknown status

This commit is contained in:
Smaug123
2024-01-29 23:01:46 +00:00
parent 5953e6efbb
commit 6e2cf29fc0
4 changed files with 87 additions and 83 deletions

View File

@@ -5,86 +5,6 @@ open System.Net.Http
open System.Threading open System.Threading
open System.Threading.Tasks open System.Threading.Tasks
type private CacheMessage<'a> =
| TriggerUpdate
| UpdateStored of 'a Task
| Get of AsyncReplyChannel<'a>
| Quit of AsyncReplyChannel<unit>
type Cache<'a> (obtainNew : CancellationToken -> 'a Task, expiry : 'a -> DateTime option) =
let cts = new CancellationTokenSource ()
let initialValue = obtainNew cts.Token
let rec handle (value : 'a Task) (mailbox : MailboxProcessor<CacheMessage<'a>>) : Async<unit> =
async {
let! message = mailbox.Receive ()
match message with
| Quit channel ->
channel.Reply ()
return ()
| CacheMessage.UpdateStored newValue -> return! handle newValue mailbox
| CacheMessage.TriggerUpdate ->
async {
let! a = Async.AwaitTask (obtainNew cts.Token)
let expiry = expiry a
match expiry with
| None -> return ()
| Some expiry ->
// a bit sloppy but :shrug:
do! Async.Sleep ((expiry - DateTime.Now) - TimeSpan.FromMinutes 1.0)
try
mailbox.Post CacheMessage.TriggerUpdate
with _ ->
// Post during shutdown sequence: drop it on the floor
()
return ()
}
|> fun a -> Async.Start (a, cancellationToken = cts.Token)
return! handle value mailbox
| CacheMessage.Get reply ->
let! valueAwaited = Async.AwaitTask value
reply.Reply valueAwaited
return! handle value mailbox
}
let mailbox = new MailboxProcessor<_> (handle initialValue)
do
mailbox.Start ()
mailbox.Post CacheMessage.TriggerUpdate
let isDisposing = ref 0
let hasDisposed = TaskCompletionSource<unit> ()
member this.GetCurrentValue () =
try
mailbox.PostAndAsyncReply CacheMessage.Get
with
// TODO I think this is the right exception...
| :? InvalidOperationException ->
raise (ObjectDisposedException (nameof (Cache)))
interface IDisposable with
member _.Dispose () =
if Interlocked.Increment isDisposing = 1 then
mailbox.PostAndReply CacheMessage.Quit
(mailbox :> IDisposable).Dispose ()
// We can't terminate the CTS until the mailbox has processed all client requests.
// Otherwise we terminate the mailbox's state Task before it has finished querying that
// task on behalf of clients.
cts.Cancel ()
cts.Dispose ()
hasDisposed.SetResult ()
else
hasDisposed.Task.Result
/// Methods for interacting with the PureGym REST API. /// Methods for interacting with the PureGym REST API.
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module Api = module Api =

86
PureGym/Cache.fs Normal file
View File

@@ -0,0 +1,86 @@
namespace PureGym
open System
open System.Threading
open System.Threading.Tasks
type private CacheMessage<'a> =
| TriggerUpdate
| UpdateStored of 'a Task
| Get of AsyncReplyChannel<'a>
| Quit of AsyncReplyChannel<unit>
type internal Cache<'a> (obtainNew : CancellationToken -> 'a Task, expiry : 'a -> DateTime option) =
let cts = new CancellationTokenSource ()
let initialValue = obtainNew cts.Token
let rec handle (value : 'a Task) (mailbox : MailboxProcessor<CacheMessage<'a>>) : Async<unit> =
async {
let! message = mailbox.Receive ()
match message with
| Quit channel ->
channel.Reply ()
return ()
| CacheMessage.UpdateStored newValue -> return! handle newValue mailbox
| CacheMessage.TriggerUpdate ->
async {
let! a = Async.AwaitTask (obtainNew cts.Token)
let expiry = expiry a
match expiry with
| None -> return ()
| Some expiry ->
// a bit sloppy but :shrug:
do! Async.Sleep ((expiry - DateTime.Now) - TimeSpan.FromMinutes 1.0)
try
mailbox.Post CacheMessage.TriggerUpdate
with _ ->
// Post during shutdown sequence: drop it on the floor
()
return ()
}
|> fun a -> Async.Start (a, cancellationToken = cts.Token)
return! handle value mailbox
| CacheMessage.Get reply ->
let! valueAwaited = Async.AwaitTask value
reply.Reply valueAwaited
return! handle value mailbox
}
let mailbox = new MailboxProcessor<_> (handle initialValue)
do
mailbox.Start ()
mailbox.Post CacheMessage.TriggerUpdate
let isDisposing = ref 0
let hasDisposed = TaskCompletionSource<unit> ()
member this.GetCurrentValue () =
try
mailbox.PostAndAsyncReply CacheMessage.Get
with
// TODO I think this is the right exception...
| :? InvalidOperationException ->
raise (ObjectDisposedException (nameof (Cache)))
interface IDisposable with
member _.Dispose () =
if Interlocked.Increment isDisposing = 1 then
mailbox.PostAndReply CacheMessage.Quit
(mailbox :> IDisposable).Dispose ()
// We can't terminate the CTS until the mailbox has processed all client requests.
// Otherwise we terminate the mailbox's state Task before it has finished querying that
// task on behalf of clients.
cts.Cancel ()
cts.Dispose ()
hasDisposed.SetResult ()
else
hasDisposed.Task.Result

View File

@@ -20,6 +20,7 @@
<Compile Include="GeneratedClient.fs"> <Compile Include="GeneratedClient.fs">
<MyriadFile>Client.fs</MyriadFile> <MyriadFile>Client.fs</MyriadFile>
</Compile> </Compile>
<Compile Include="Cache.fs" />
<Compile Include="Api.fs" /> <Compile Include="Api.fs" />
<Compile Include="GymSelector.fs" /> <Compile Include="GymSelector.fs" />
<EmbeddedResource Include="SurfaceBaseline.txt" /> <EmbeddedResource Include="SurfaceBaseline.txt" />

View File

@@ -38,9 +38,6 @@ PureGym.AuthToken.get_ExpiryTime [method]: unit -> System.DateTime option
PureGym.AuthTokenModule inherit obj PureGym.AuthTokenModule inherit obj
PureGym.AuthTokenModule.get [static method]: PureGym.UsernamePin -> System.Threading.CancellationToken -> PureGym.AuthToken System.Threading.Tasks.Task PureGym.AuthTokenModule.get [static method]: PureGym.UsernamePin -> System.Threading.CancellationToken -> PureGym.AuthToken System.Threading.Tasks.Task
PureGym.AuthTokenModule.ofBearerToken [static method]: string -> PureGym.AuthToken PureGym.AuthTokenModule.ofBearerToken [static method]: string -> PureGym.AuthToken
PureGym.Cache`1 inherit obj, implements IDisposable
PureGym.Cache`1..ctor [constructor]: (System.Threading.CancellationToken -> 'a System.Threading.Tasks.Task, 'a -> System.DateTime option)
PureGym.Cache`1.GetCurrentValue [method]: unit -> 'a Microsoft.FSharp.Control.FSharpAsync
PureGym.Gym inherit obj, implements PureGym.Gym System.IEquatable, System.Collections.IStructuralEquatable, PureGym.Gym System.IComparable, System.IComparable, System.Collections.IStructuralComparable PureGym.Gym inherit obj, implements PureGym.Gym System.IEquatable, System.Collections.IStructuralEquatable, PureGym.Gym System.IComparable, System.IComparable, System.Collections.IStructuralComparable
PureGym.Gym..ctor [constructor]: (string, int, int, PureGym.GymAddress, string, string, PureGym.GymOpeningHours, PureGym.GymAccessOptions, PureGym.GymLocation, string, string) PureGym.Gym..ctor [constructor]: (string, int, int, PureGym.GymAddress, string, string, PureGym.GymOpeningHours, PureGym.GymAccessOptions, PureGym.GymLocation, string, string)
PureGym.Gym.AccessOptions [property]: [read-only] PureGym.GymAccessOptions PureGym.Gym.AccessOptions [property]: [read-only] PureGym.GymAccessOptions