From 1de2db29270db9eb6f7492fa3d450afcc0dfcd78 Mon Sep 17 00:00:00 2001 From: Petr Pokorny Date: Tue, 30 Jan 2024 14:26:38 +0100 Subject: [PATCH 1/2] Improve AsyncMemoize tests --- .../CompilerService/AsyncMemoize.fs | 78 ++++++++++--------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs index 694b9c580e9..44938273d6d 100644 --- a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs +++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs @@ -18,6 +18,12 @@ let waitFor (mre: ManualResetEvent) = if not <| mre.WaitOne timeout then failwith "waitFor timed out" +let waitUntil condition value = + let sw = Stopwatch.StartNew() + while not <| condition value do + if sw.Elapsed > timeout then + failwith "waitUntil timed out" + Thread.Sleep 10 let rec internal spinFor (duration: TimeSpan) = node { @@ -29,6 +35,24 @@ let rec internal spinFor (duration: TimeSpan) = } +type internal EventRecorder<'a, 'b, 'c when 'a : equality and 'b : equality>(memoize: AsyncMemoize<'a,'b,'c>) as self = + + let events = ConcurrentQueue() + + do memoize.OnEvent self.Add + + member _.Add (e, (_label, k, _version)) = events.Enqueue (e, k) + + member _.Received value = events |> Seq.exists (fst >> (=) value) + + member _.CountOf value count = events |> Seq.filter (fst >> (=) value) |> Seq.length |> (=) count + + member _.ShouldBe (expected) = + let expected = expected |> Seq.toArray + let actual = events |> Seq.toArray + Assert.Equal<_ array>(expected, actual) + + [] let ``Basics``() = @@ -69,21 +93,14 @@ let ``We can cancel a job`` () = let jobStarted = new ManualResetEvent(false) - let jobCanceled = new ManualResetEvent(false) - let computation action = node { action() |> ignore do! spinFor timeout failwith "Should be canceled before it gets here" } - let eventLog = ConcurrentQueue() - let memoize = AsyncMemoize() - memoize.OnEvent(fun (e, (_label, k, _version)) -> - eventLog.Enqueue (e, k) - if e = Canceled then - jobCanceled.Set() |> ignore - ) + let memoize = AsyncMemoize<_, int, _>() + let events = EventRecorder(memoize) use cts1 = new CancellationTokenSource() use cts2 = new CancellationTokenSource() @@ -96,13 +113,10 @@ let ``We can cancel a job`` () = waitFor jobStarted jobStarted.Reset() |> ignore - let jobRequested = new ManualResetEvent(false) - memoize.OnEvent(fun (e, _) -> if e = Requested then jobRequested.Set() |> ignore) - let _task2 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation ignore), ct = cts2.Token) let _task3 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation ignore), ct = cts3.Token) - waitFor jobRequested + waitUntil (events.CountOf Requested) 3 cts1.Cancel() cts2.Cancel() @@ -111,16 +125,16 @@ let ``We can cancel a job`` () = cts3.Cancel() - waitFor jobCanceled + waitUntil events.Received Canceled - Assert.Equal<(JobEvent * int) array>([| + events.ShouldBe [ Requested, key Started, key Requested, key Requested, key Restarted, key Canceled, key - |], eventLog |> Seq.toArray ) + ] } [] @@ -136,9 +150,9 @@ let ``Job is restarted if first requestor cancels`` () = return key * 2 } - let eventLog = ConcurrentStack() - let memoize = AsyncMemoize() - memoize.OnEvent(fun (e, (_, k, _version)) -> eventLog.Push (e, k)) + let memoize = AsyncMemoize<_, int, _>() + let events = EventRecorder(memoize) + use cts1 = new CancellationTokenSource() use cts2 = new CancellationTokenSource() @@ -151,13 +165,10 @@ let ``Job is restarted if first requestor cancels`` () = waitFor jobStarted jobStarted.Reset() |> ignore - let jobRequested = new ManualResetEvent(false) - memoize.OnEvent(fun (e, _) -> if e = Requested then jobRequested.Set() |> ignore) - let _task2 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts2.Token) let _task3 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts3.Token) - waitFor jobRequested + waitUntil (events.CountOf Requested) 3 cts1.Cancel() @@ -168,16 +179,13 @@ let ``Job is restarted if first requestor cancels`` () = let! result = _task2 Assert.Equal(2, result) - let orderedLog = eventLog |> Seq.rev |> Seq.toList - let expected = [ + events.ShouldBe [ Requested, key Started, key Requested, key Requested, key Restarted, key Finished, key ] - - Assert.Equal<_ list>(expected, orderedLog) } [] @@ -192,10 +200,10 @@ let ``Job is restarted if first requestor cancels but keeps running if second re waitFor jobCanComplete return key * 2 } + + let memoize = AsyncMemoize<_, int, _>() + let events = EventRecorder(memoize) - let eventLog = ConcurrentStack() - let memoize = AsyncMemoize() - memoize.OnEvent(fun (e, (_label, k, _version)) -> eventLog.Push (e, k)) use cts1 = new CancellationTokenSource() use cts2 = new CancellationTokenSource() @@ -208,13 +216,10 @@ let ``Job is restarted if first requestor cancels but keeps running if second re waitFor jobStarted jobStarted.Reset() |> ignore - let jobRequested = new ManualResetEvent(false) - memoize.OnEvent(fun (e, _) -> if e = Requested then jobRequested.Set() |> ignore) - let _task2 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts2.Token) let _task3 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts3.Token) - waitFor jobRequested + waitUntil (events.CountOf Requested) 3 cts1.Cancel() @@ -227,16 +232,13 @@ let ``Job is restarted if first requestor cancels but keeps running if second re let! result = _task3 Assert.Equal(2, result) - let orderedLog = eventLog |> Seq.rev |> Seq.toList - let expected = [ + events.ShouldBe [ Requested, key Started, key Requested, key Requested, key Restarted, key Finished, key ] - - Assert.Equal<_ list>(expected, orderedLog) } From 1e93f2ced3e48d1540059e8a502aced807fef33c Mon Sep 17 00:00:00 2001 From: Petr Pokorny Date: Tue, 30 Jan 2024 15:04:41 +0100 Subject: [PATCH 2/2] async wait --- .../CompilerService/AsyncMemoize.fs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs index 44938273d6d..e442335f940 100644 --- a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs +++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs @@ -19,11 +19,13 @@ let waitFor (mre: ManualResetEvent) = failwith "waitFor timed out" let waitUntil condition value = - let sw = Stopwatch.StartNew() - while not <| condition value do - if sw.Elapsed > timeout then - failwith "waitUntil timed out" - Thread.Sleep 10 + task { + let sw = Stopwatch.StartNew() + while not <| condition value do + if sw.Elapsed > timeout then + failwith "waitUntil timed out" + do! Task.Delay 10 + } let rec internal spinFor (duration: TimeSpan) = node { @@ -116,7 +118,7 @@ let ``We can cancel a job`` () = let _task2 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation ignore), ct = cts2.Token) let _task3 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation ignore), ct = cts3.Token) - waitUntil (events.CountOf Requested) 3 + do! waitUntil (events.CountOf Requested) 3 cts1.Cancel() cts2.Cancel() @@ -125,7 +127,7 @@ let ``We can cancel a job`` () = cts3.Cancel() - waitUntil events.Received Canceled + do! waitUntil events.Received Canceled events.ShouldBe [ Requested, key @@ -168,7 +170,7 @@ let ``Job is restarted if first requestor cancels`` () = let _task2 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts2.Token) let _task3 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts3.Token) - waitUntil (events.CountOf Requested) 3 + do! waitUntil (events.CountOf Requested) 3 cts1.Cancel() @@ -219,7 +221,7 @@ let ``Job is restarted if first requestor cancels but keeps running if second re let _task2 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts2.Token) let _task3 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts3.Token) - waitUntil (events.CountOf Requested) 3 + do! waitUntil (events.CountOf Requested) 3 cts1.Cancel()