diff --git a/src/Paket.Core/RemoteDownload.fs b/src/Paket.Core/RemoteDownload.fs index 6f8ca623e1..899d60d8c1 100644 --- a/src/Paket.Core/RemoteDownload.fs +++ b/src/Paket.Core/RemoteDownload.fs @@ -11,17 +11,11 @@ open Paket.Domain open Paket.Git.CommandHelper open Paket.Git.Handling -let private githubCache = System.Collections.Concurrent.ConcurrentDictionary<_, _>() -let private lookupDocument (auth,url : string) = async { - let key = auth,url - match githubCache.TryGetValue key with - | true, document -> return document - | _ -> - let! document = safeGetFromUrl(auth, url, null) - githubCache.TryAdd(key, document) |> ignore - return document - } +let private safeGetFromUrlCached = memoizeAsync <| safeGetFromUrl + +let private lookupDocument (auth,url : string) = + safeGetFromUrlCached(auth,url,null) let private auth key url = key diff --git a/src/Paket.Core/Utils.fs b/src/Paket.Core/Utils.fs index 1f0ec14d12..baa245d0ae 100644 --- a/src/Paket.Core/Utils.fs +++ b/src/Paket.Core/Utils.fs @@ -28,6 +28,11 @@ let internal memoize (f: 'a -> 'b) : 'a -> 'b = fun (x: 'a) -> cache.GetOrAdd(x, f) +let internal memoizeAsync f = + let cache = System.Collections.Concurrent.ConcurrentDictionary<'a, System.Threading.Tasks.Task<'b>>() + fun (x: 'a) -> // task.Result serialization to sync after done. + cache.GetOrAdd(x, fun x -> f(x) |> Async.StartAsTask) |> Async.AwaitTask + type Auth = | Credentials of Username : string * Password : string | Token of string diff --git a/tests/Paket.Tests/DependenciesFile/ParserSpecs.fs b/tests/Paket.Tests/DependenciesFile/ParserSpecs.fs index 8de8f7c516..3ac7351f3e 100644 --- a/tests/Paket.Tests/DependenciesFile/ParserSpecs.fs +++ b/tests/Paket.Tests/DependenciesFile/ParserSpecs.fs @@ -1343,4 +1343,34 @@ let ``should read config with caches``() = (main.Sources |> List.item 0) |> shouldEqual PackageSources.DefaultNuGetSource (main.Sources |> List.item 1).Url |> shouldEqual "./dependencies" - (main.Sources |> List.item 2).Url |> shouldEqual "//hive/dependencies" \ No newline at end of file + (main.Sources |> List.item 2).Url |> shouldEqual "//hive/dependencies" + +[] +let ``async cache should work``() = + let mutable x = 0 + let someSlowFunc mykey = async { + Console.WriteLine "Simulated downloading..." + do! Async.Sleep 400 + Console.WriteLine "Simulated downloading Done." + x <- x + 1 // Side effect! + return "" } + let memFunc = memoizeAsync <| someSlowFunc + async { + do! memFunc "a" |> Async.Ignore + do! memFunc "a" |> Async.Ignore + do! memFunc "a" |> Async.Ignore + do! [|1 .. 30|] |> Seq.map(fun _ -> (memFunc "a")) + |> Async.Parallel |> Async.Ignore + for i = 1 to 30 do + Async.Start( memFunc "a" |> Async.Ignore ) + Async.Start( memFunc "a" |> Async.Ignore ) + do! Async.Sleep 500 + do! memFunc "a" |> Async.Ignore + do! memFunc "a" |> Async.Ignore + for i = 1 to 30 do + Async.Start( memFunc "a" |> Async.Ignore ) + do! [|1 .. 30|] |> Seq.map(fun _ -> (memFunc "a")) + |> Async.Parallel |> Async.Ignore + } |> Async.RunSynchronously + x |> shouldEqual 1 +