From c4d88c46cdbe96d55db096d442242dcdd44bf0e3 Mon Sep 17 00:00:00 2001 From: bygu4 Date: Mon, 14 Apr 2025 13:41:45 +0300 Subject: [PATCH 1/6] add Lazy implementations --- Tasks/Lazy/Lazy.Tests/Lazy.Tests.fsproj | 24 +++++++++++++++++ Tasks/Lazy/Lazy.Tests/LazyTests.fs | 36 +++++++++++++++++++++++++ Tasks/Lazy/Lazy.Tests/Program.fs | 4 +++ Tasks/Lazy/Lazy.sln | 28 +++++++++++++++++++ Tasks/Lazy/Lazy/ILazy.fs | 4 +++ Tasks/Lazy/Lazy/Lazy.fsproj | 15 +++++++++++ Tasks/Lazy/Lazy/LockFreeLazy.fs | 34 +++++++++++++++++++++++ Tasks/Lazy/Lazy/SingleThreadLazy.fs | 28 +++++++++++++++++++ Tasks/Lazy/Lazy/ThreadSafeLazy.fs | 32 ++++++++++++++++++++++ 9 files changed, 205 insertions(+) create mode 100644 Tasks/Lazy/Lazy.Tests/Lazy.Tests.fsproj create mode 100644 Tasks/Lazy/Lazy.Tests/LazyTests.fs create mode 100644 Tasks/Lazy/Lazy.Tests/Program.fs create mode 100644 Tasks/Lazy/Lazy.sln create mode 100644 Tasks/Lazy/Lazy/ILazy.fs create mode 100644 Tasks/Lazy/Lazy/Lazy.fsproj create mode 100644 Tasks/Lazy/Lazy/LockFreeLazy.fs create mode 100644 Tasks/Lazy/Lazy/SingleThreadLazy.fs create mode 100644 Tasks/Lazy/Lazy/ThreadSafeLazy.fs diff --git a/Tasks/Lazy/Lazy.Tests/Lazy.Tests.fsproj b/Tasks/Lazy/Lazy.Tests/Lazy.Tests.fsproj new file mode 100644 index 0000000..6df6c56 --- /dev/null +++ b/Tasks/Lazy/Lazy.Tests/Lazy.Tests.fsproj @@ -0,0 +1,24 @@ + + + + net9.0 + latest + false + false + + + + + + + + + + + + + + + + + diff --git a/Tasks/Lazy/Lazy.Tests/LazyTests.fs b/Tasks/Lazy/Lazy.Tests/LazyTests.fs new file mode 100644 index 0000000..0df4744 --- /dev/null +++ b/Tasks/Lazy/Lazy.Tests/LazyTests.fs @@ -0,0 +1,36 @@ +module Lazy.Tests + +open NUnit.Framework +open FsUnit + +open System +open System.Threading + +let simpleSuppliers: (unit -> obj) list = [ + fun () -> 80808; + fun () -> "qwertyuiop".Substring (0, 6); + fun () -> Math.Round (782.192, 1); +] + +let suppliersWithDelay = + simpleSuppliers |> List.map (fun f -> + Thread.Sleep 100 + f () + ) + +let suppliersWithSideEffects = + simpleSuppliers |> List.map (fun f -> + count <- count + 1 + f () + ) + +let suppliersThrowingException: (unit -> obj) list = [ + fun () -> + raise (new InvalidOperationException ()) + 100; + fun () -> + if count > 1 then raise (new ArgumentException ()) + "abc"; +] + +let singleThreadTestCase (supplier: unit -> obj) = diff --git a/Tasks/Lazy/Lazy.Tests/Program.fs b/Tasks/Lazy/Lazy.Tests/Program.fs new file mode 100644 index 0000000..96b133f --- /dev/null +++ b/Tasks/Lazy/Lazy.Tests/Program.fs @@ -0,0 +1,4 @@ +module Program + +[] +let main _ = 0 diff --git a/Tasks/Lazy/Lazy.sln b/Tasks/Lazy/Lazy.sln new file mode 100644 index 0000000..e547247 --- /dev/null +++ b/Tasks/Lazy/Lazy.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Lazy", "Lazy\Lazy.fsproj", "{ACEDBB1D-CD4A-4C1E-B4AC-88257DD98315}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Lazy.Tests", "Lazy.Tests\Lazy.Tests.fsproj", "{7AE9BEB1-1946-4B5A-865A-2062DA8FCB00}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ACEDBB1D-CD4A-4C1E-B4AC-88257DD98315}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACEDBB1D-CD4A-4C1E-B4AC-88257DD98315}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACEDBB1D-CD4A-4C1E-B4AC-88257DD98315}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACEDBB1D-CD4A-4C1E-B4AC-88257DD98315}.Release|Any CPU.Build.0 = Release|Any CPU + {7AE9BEB1-1946-4B5A-865A-2062DA8FCB00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7AE9BEB1-1946-4B5A-865A-2062DA8FCB00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7AE9BEB1-1946-4B5A-865A-2062DA8FCB00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7AE9BEB1-1946-4B5A-865A-2062DA8FCB00}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Tasks/Lazy/Lazy/ILazy.fs b/Tasks/Lazy/Lazy/ILazy.fs new file mode 100644 index 0000000..1272ad6 --- /dev/null +++ b/Tasks/Lazy/Lazy/ILazy.fs @@ -0,0 +1,4 @@ +namespace Lazy + +type ILazy<'a> = + abstract member Get: unit -> 'a diff --git a/Tasks/Lazy/Lazy/Lazy.fsproj b/Tasks/Lazy/Lazy/Lazy.fsproj new file mode 100644 index 0000000..59ac8c0 --- /dev/null +++ b/Tasks/Lazy/Lazy/Lazy.fsproj @@ -0,0 +1,15 @@ + + + + net9.0 + true + + + + + + + + + + diff --git a/Tasks/Lazy/Lazy/LockFreeLazy.fs b/Tasks/Lazy/Lazy/LockFreeLazy.fs new file mode 100644 index 0000000..e9755d8 --- /dev/null +++ b/Tasks/Lazy/Lazy/LockFreeLazy.fs @@ -0,0 +1,34 @@ +namespace Lazy + +open System +open System.Threading + +type LockFreeLazy<'a when 'a: equality>(supplier: unit -> 'a) = + [] + let mutable supplier = Some supplier + let mutable result: 'a option * Exception option = None, None + + let tryEvaluate () = + let currentSupplier = supplier + if currentSupplier.IsNone then () + try + let localResult = Some (currentSupplier.Value ()) + Interlocked.CompareExchange (&result, (localResult, None), (None, None)) + |> ignore + with + | e -> + Interlocked.CompareExchange (&result, (None, Some e), (None, None)) + |> ignore + supplier <- None + + let getResult () = + tryEvaluate () + match result with + | Some value, _ -> value + | _, Some e -> raise e + | _ -> raise (new ArgumentException "unexpected match pattern") + + interface ILazy<'a> with + member _.Get (): 'a = getResult () + + member _.Get (): 'a = getResult () diff --git a/Tasks/Lazy/Lazy/SingleThreadLazy.fs b/Tasks/Lazy/Lazy/SingleThreadLazy.fs new file mode 100644 index 0000000..f71ed93 --- /dev/null +++ b/Tasks/Lazy/Lazy/SingleThreadLazy.fs @@ -0,0 +1,28 @@ +namespace Lazy + +open System + +type SingleThreadLazy<'a when 'a: equality>(supplier: unit -> 'a) = + let mutable supplier = Some supplier + let mutable result: 'a option = None + let mutable thrownException: Exception option = None + + let tryEvaluate () = + if supplier.IsNone then () + try + result <- Some (supplier.Value ()) + with + | e -> thrownException <- Some e + supplier <- None + + let getResult () = + tryEvaluate () + match result, thrownException with + | Some value, _ -> value + | _, Some e -> raise e + | _ -> raise (new ArgumentException "unexpected match pattern") + + interface ILazy<'a> with + member _.Get (): 'a = getResult () + + member _.Get (): 'a = getResult () diff --git a/Tasks/Lazy/Lazy/ThreadSafeLazy.fs b/Tasks/Lazy/Lazy/ThreadSafeLazy.fs new file mode 100644 index 0000000..44572bd --- /dev/null +++ b/Tasks/Lazy/Lazy/ThreadSafeLazy.fs @@ -0,0 +1,32 @@ +namespace Lazy + +open System + +type ThreadSafeLazy<'a when 'a: equality>(supplier: unit -> 'a) = + [] + let mutable supplier = Some supplier + let mutable result: 'a option = None + let mutable thrownException: Exception option = None + let lockObject = new Object () + + let tryEvaluate () = + lock lockObject (fun () -> + if supplier.IsNone then () + try + result <- Some (supplier.Value ()) + with + | e -> thrownException <- Some e + supplier <- None + ) + + let getResult () = + tryEvaluate () + match result, thrownException with + | Some value, _ -> value + | _, Some e -> raise e + | _ -> raise (new ArgumentException "unexpected match pattern") + + interface ILazy<'a> with + member _.Get (): 'a = getResult () + + member _.Get (): 'a = getResult () From 7f6e77a3bc735a3e6265c18740e8ee308d219551 Mon Sep 17 00:00:00 2001 From: bygu4 Date: Mon, 14 Apr 2025 21:40:32 +0300 Subject: [PATCH 2/6] add tests --- Tasks/Lazy/Lazy.Tests/Lazy.Tests.fsproj | 4 ++ Tasks/Lazy/Lazy.Tests/LazyTests.fs | 79 ++++++++++++++++++++----- 2 files changed, 67 insertions(+), 16 deletions(-) diff --git a/Tasks/Lazy/Lazy.Tests/Lazy.Tests.fsproj b/Tasks/Lazy/Lazy.Tests/Lazy.Tests.fsproj index 6df6c56..76fefe2 100644 --- a/Tasks/Lazy/Lazy.Tests/Lazy.Tests.fsproj +++ b/Tasks/Lazy/Lazy.Tests/Lazy.Tests.fsproj @@ -21,4 +21,8 @@ + + + + diff --git a/Tasks/Lazy/Lazy.Tests/LazyTests.fs b/Tasks/Lazy/Lazy.Tests/LazyTests.fs index 0df4744..7f8510c 100644 --- a/Tasks/Lazy/Lazy.Tests/LazyTests.fs +++ b/Tasks/Lazy/Lazy.Tests/LazyTests.fs @@ -5,32 +5,79 @@ open FsUnit open System open System.Threading +open System.Threading.Tasks -let simpleSuppliers: (unit -> obj) list = [ +let numberOfGets = 1000 +let numberOfThreads = 1000 + +let mutable evaluationCount = 0 + +let testSupplier supplier = fun () -> + Interlocked.Increment &evaluationCount |> ignore + supplier () + +let suppliersWithResult: (unit -> obj) list = [ fun () -> 80808; fun () -> "qwertyuiop".Substring (0, 6); fun () -> Math.Round (782.192, 1); ] -let suppliersWithDelay = - simpleSuppliers |> List.map (fun f -> - Thread.Sleep 100 - f () - ) - -let suppliersWithSideEffects = - simpleSuppliers |> List.map (fun f -> - count <- count + 1 - f () - ) - -let suppliersThrowingException: (unit -> obj) list = [ +let suppliersWithException: (unit -> obj) list = [ fun () -> raise (new InvalidOperationException ()) 100; fun () -> - if count > 1 then raise (new ArgumentException ()) + raise (new ArgumentException ()) "abc"; ] -let singleThreadTestCase (supplier: unit -> obj) = +let testSuppliersWithResult = suppliersWithResult |> List.map testSupplier +let testSuppliersWithException = suppliersWithException |> List.map testSupplier + +let results: obj list = [80808; "qwerty"; 782.2] +let exceptions = [typeof; typeof] + +let testCasesWithResult = Seq.zip testSuppliersWithResult results |> Seq.map TestCaseData +let testCasesWithException = Seq.zip testSuppliersWithException exceptions |> Seq.map TestCaseData + +let testForSingleThread_WithResult (result: 'a) (testLazy: ILazy<'a>) = + for _ in { 1 .. numberOfGets } do + testLazy.Get () |> should equal result + evaluationCount |> should equal 1 + +let testForSingleThread_WithException (exc: Type) (testLazy: ILazy<'a>) = + for _ in { 1 .. numberOfGets } do + testLazy.Get () |> should throw exc + evaluationCount |> should equal 1 + +[] +let resetEvaluationCount () = + evaluationCount <- 0 + +[] +let testSingleThreadLazy_WithResult (supplier: unit -> 'a, result: 'a) = + supplier + |> SingleThreadLazy + |> testForSingleThread_WithResult result + +[] +let testSingleThreadLazy_WithException (supplier: unit -> 'a, exc: Type) = + supplier + |> SingleThreadLazy + |> testForSingleThread_WithException exc + +[] +let testThreadSafeLazy_WithResult (supplier: unit -> 'a, result: 'a) = + let testLazy = supplier |> ThreadSafeLazy + { 1 .. numberOfThreads } + |> Seq.map (fun _ -> Task.Run ( + fun () -> testForSingleThread_WithResult result testLazy)) + |> Task.WaitAll + +[] +let testThreadSafeLazy_WithException (supplier: unit -> 'a, exc: Type) = + let testLazy = supplier |> ThreadSafeLazy + { 1 .. numberOfThreads } + |> Seq.map (fun _ -> Task.Run ( + fun () -> testForSingleThread_WithException exc testLazy)) + |> Task.WaitAll From b25de60b34755880a439bd97a889eeacd23ad95a Mon Sep 17 00:00:00 2001 From: bygu4 Date: Mon, 14 Apr 2025 22:29:09 +0300 Subject: [PATCH 3/6] debug --- Tasks/Lazy/Lazy.Tests/LazyTests.fs | 14 +++++++------- Tasks/Lazy/Lazy/LockFreeLazy.fs | 18 +++++++++--------- Tasks/Lazy/Lazy/SingleThreadLazy.fs | 12 ++++++------ Tasks/Lazy/Lazy/ThreadSafeLazy.fs | 12 ++++++------ 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Tasks/Lazy/Lazy.Tests/LazyTests.fs b/Tasks/Lazy/Lazy.Tests/LazyTests.fs index 7f8510c..7734e6f 100644 --- a/Tasks/Lazy/Lazy.Tests/LazyTests.fs +++ b/Tasks/Lazy/Lazy.Tests/LazyTests.fs @@ -7,8 +7,8 @@ open System open System.Threading open System.Threading.Tasks -let numberOfGets = 1000 -let numberOfThreads = 1000 +let numberOfGets = 500 +let numberOfThreads = 500 let mutable evaluationCount = 0 @@ -47,7 +47,7 @@ let testForSingleThread_WithResult (result: 'a) (testLazy: ILazy<'a>) = let testForSingleThread_WithException (exc: Type) (testLazy: ILazy<'a>) = for _ in { 1 .. numberOfGets } do - testLazy.Get () |> should throw exc + testLazy.Get |> should throw exc evaluationCount |> should equal 1 [] @@ -55,19 +55,19 @@ let resetEvaluationCount () = evaluationCount <- 0 [] -let testSingleThreadLazy_WithResult (supplier: unit -> 'a, result: 'a) = +let testSingleThreadLazy_WithResult (supplier: unit -> obj, result: obj) = supplier |> SingleThreadLazy |> testForSingleThread_WithResult result [] -let testSingleThreadLazy_WithException (supplier: unit -> 'a, exc: Type) = +let testSingleThreadLazy_WithException (supplier: unit -> obj, exc: Type) = supplier |> SingleThreadLazy |> testForSingleThread_WithException exc [] -let testThreadSafeLazy_WithResult (supplier: unit -> 'a, result: 'a) = +let testThreadSafeLazy_WithResult (supplier: unit -> obj, result: obj) = let testLazy = supplier |> ThreadSafeLazy { 1 .. numberOfThreads } |> Seq.map (fun _ -> Task.Run ( @@ -75,7 +75,7 @@ let testThreadSafeLazy_WithResult (supplier: unit -> 'a, result: 'a) = |> Task.WaitAll [] -let testThreadSafeLazy_WithException (supplier: unit -> 'a, exc: Type) = +let testThreadSafeLazy_WithException (supplier: unit -> obj, exc: Type) = let testLazy = supplier |> ThreadSafeLazy { 1 .. numberOfThreads } |> Seq.map (fun _ -> Task.Run ( diff --git a/Tasks/Lazy/Lazy/LockFreeLazy.fs b/Tasks/Lazy/Lazy/LockFreeLazy.fs index e9755d8..2e9bafb 100644 --- a/Tasks/Lazy/Lazy/LockFreeLazy.fs +++ b/Tasks/Lazy/Lazy/LockFreeLazy.fs @@ -10,16 +10,16 @@ type LockFreeLazy<'a when 'a: equality>(supplier: unit -> 'a) = let tryEvaluate () = let currentSupplier = supplier - if currentSupplier.IsNone then () - try - let localResult = Some (currentSupplier.Value ()) - Interlocked.CompareExchange (&result, (localResult, None), (None, None)) - |> ignore - with - | e -> - Interlocked.CompareExchange (&result, (None, Some e), (None, None)) + if currentSupplier.IsSome then + try + let localResult = Some (currentSupplier.Value ()) + Interlocked.CompareExchange (&result, (localResult, None), (None, None)) |> ignore - supplier <- None + with + | e -> + Interlocked.CompareExchange (&result, (None, Some e), (None, None)) + |> ignore + supplier <- None let getResult () = tryEvaluate () diff --git a/Tasks/Lazy/Lazy/SingleThreadLazy.fs b/Tasks/Lazy/Lazy/SingleThreadLazy.fs index f71ed93..de6bae8 100644 --- a/Tasks/Lazy/Lazy/SingleThreadLazy.fs +++ b/Tasks/Lazy/Lazy/SingleThreadLazy.fs @@ -8,12 +8,12 @@ type SingleThreadLazy<'a when 'a: equality>(supplier: unit -> 'a) = let mutable thrownException: Exception option = None let tryEvaluate () = - if supplier.IsNone then () - try - result <- Some (supplier.Value ()) - with - | e -> thrownException <- Some e - supplier <- None + if supplier.IsSome then + try + result <- Some (supplier.Value ()) + with + | e -> thrownException <- Some e + supplier <- None let getResult () = tryEvaluate () diff --git a/Tasks/Lazy/Lazy/ThreadSafeLazy.fs b/Tasks/Lazy/Lazy/ThreadSafeLazy.fs index 44572bd..6c4ed0d 100644 --- a/Tasks/Lazy/Lazy/ThreadSafeLazy.fs +++ b/Tasks/Lazy/Lazy/ThreadSafeLazy.fs @@ -11,12 +11,12 @@ type ThreadSafeLazy<'a when 'a: equality>(supplier: unit -> 'a) = let tryEvaluate () = lock lockObject (fun () -> - if supplier.IsNone then () - try - result <- Some (supplier.Value ()) - with - | e -> thrownException <- Some e - supplier <- None + if supplier.IsSome then + try + result <- Some (supplier.Value ()) + with + | e -> thrownException <- Some e + supplier <- None ) let getResult () = From c3f66724ea12a0b4f8cf6397ce3e34af0e91c3b4 Mon Sep 17 00:00:00 2001 From: bygu4 Date: Tue, 15 Apr 2025 00:13:40 +0300 Subject: [PATCH 4/6] add tests for lock free lazy, debug --- Tasks/Lazy/Lazy.Tests/LazyTests.fs | 46 +++++++++++++++++++++++------- Tasks/Lazy/Lazy/LockFreeLazy.fs | 7 +++-- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/Tasks/Lazy/Lazy.Tests/LazyTests.fs b/Tasks/Lazy/Lazy.Tests/LazyTests.fs index 7734e6f..1f78db2 100644 --- a/Tasks/Lazy/Lazy.Tests/LazyTests.fs +++ b/Tasks/Lazy/Lazy.Tests/LazyTests.fs @@ -7,8 +7,8 @@ open System open System.Threading open System.Threading.Tasks -let numberOfGets = 500 -let numberOfThreads = 500 +let numberOfGets = 256 +let numberOfThreads = 256 let mutable evaluationCount = 0 @@ -16,10 +16,18 @@ let testSupplier supplier = fun () -> Interlocked.Increment &evaluationCount |> ignore supplier () +let testObject1, testObject2 = new Object (), new Object () + let suppliersWithResult: (unit -> obj) list = [ fun () -> 80808; fun () -> "qwertyuiop".Substring (0, 6); fun () -> Math.Round (782.192, 1); + fun () -> + if evaluationCount < 10 then testObject1 + else testObject2; + fun () -> + if evaluationCount < 10 then testObject2 + else raise (new SystemException ()); ] let suppliersWithException: (unit -> obj) list = [ @@ -34,21 +42,21 @@ let suppliersWithException: (unit -> obj) list = [ let testSuppliersWithResult = suppliersWithResult |> List.map testSupplier let testSuppliersWithException = suppliersWithException |> List.map testSupplier -let results: obj list = [80808; "qwerty"; 782.2] +let results: obj list = [80808; "qwerty"; 782.2; testObject1; testObject2] let exceptions = [typeof; typeof] let testCasesWithResult = Seq.zip testSuppliersWithResult results |> Seq.map TestCaseData let testCasesWithException = Seq.zip testSuppliersWithException exceptions |> Seq.map TestCaseData -let testForSingleThread_WithResult (result: 'a) (testLazy: ILazy<'a>) = +let testForSingleThread_WithResult (result: 'a) (countConstraint: int -> bool) (testLazy: ILazy<'a>) = for _ in { 1 .. numberOfGets } do testLazy.Get () |> should equal result - evaluationCount |> should equal 1 + countConstraint evaluationCount |> should be True -let testForSingleThread_WithException (exc: Type) (testLazy: ILazy<'a>) = +let testForSingleThread_WithException (exc: Type) (countConstraint: int -> bool) (testLazy: ILazy<'a>) = for _ in { 1 .. numberOfGets } do testLazy.Get |> should throw exc - evaluationCount |> should equal 1 + countConstraint evaluationCount |> should be True [] let resetEvaluationCount () = @@ -58,20 +66,20 @@ let resetEvaluationCount () = let testSingleThreadLazy_WithResult (supplier: unit -> obj, result: obj) = supplier |> SingleThreadLazy - |> testForSingleThread_WithResult result + |> testForSingleThread_WithResult result (( = ) 1) [] let testSingleThreadLazy_WithException (supplier: unit -> obj, exc: Type) = supplier |> SingleThreadLazy - |> testForSingleThread_WithException exc + |> testForSingleThread_WithException exc (( = ) 1) [] let testThreadSafeLazy_WithResult (supplier: unit -> obj, result: obj) = let testLazy = supplier |> ThreadSafeLazy { 1 .. numberOfThreads } |> Seq.map (fun _ -> Task.Run ( - fun () -> testForSingleThread_WithResult result testLazy)) + fun () -> testForSingleThread_WithResult result (( = ) 1) testLazy)) |> Task.WaitAll [] @@ -79,5 +87,21 @@ let testThreadSafeLazy_WithException (supplier: unit -> obj, exc: Type) = let testLazy = supplier |> ThreadSafeLazy { 1 .. numberOfThreads } |> Seq.map (fun _ -> Task.Run ( - fun () -> testForSingleThread_WithException exc testLazy)) + fun () -> testForSingleThread_WithException exc (( = ) 1) testLazy)) + |> Task.WaitAll + +[] +let testLockFreeLazy_WithResult (supplier: unit -> obj, result: obj) = + let testLazy = supplier |> LockFreeLazy + { 1 .. numberOfThreads } + |> Seq.map (fun _ -> Task.Run ( + fun () -> testForSingleThread_WithResult result (( >= ) numberOfGets) testLazy)) + |> Task.WaitAll + +[] +let testLockFreeLazy_WithException (supplier: unit -> obj, exc: Type) = + let testLazy = supplier |> LockFreeLazy + { 1 .. numberOfThreads } + |> Seq.map (fun _ -> Task.Run ( + fun () -> testForSingleThread_WithException exc (( >= ) numberOfGets) testLazy)) |> Task.WaitAll diff --git a/Tasks/Lazy/Lazy/LockFreeLazy.fs b/Tasks/Lazy/Lazy/LockFreeLazy.fs index 2e9bafb..9119f76 100644 --- a/Tasks/Lazy/Lazy/LockFreeLazy.fs +++ b/Tasks/Lazy/Lazy/LockFreeLazy.fs @@ -6,18 +6,19 @@ open System.Threading type LockFreeLazy<'a when 'a: equality>(supplier: unit -> 'a) = [] let mutable supplier = Some supplier - let mutable result: 'a option * Exception option = None, None + let initialResult: 'a option * exn option = None, None + let mutable result = initialResult let tryEvaluate () = let currentSupplier = supplier if currentSupplier.IsSome then try let localResult = Some (currentSupplier.Value ()) - Interlocked.CompareExchange (&result, (localResult, None), (None, None)) + Interlocked.CompareExchange (&result, (localResult, None), initialResult) |> ignore with | e -> - Interlocked.CompareExchange (&result, (None, Some e), (None, None)) + Interlocked.CompareExchange (&result, (None, Some e), initialResult) |> ignore supplier <- None From 5bbd9312d200b5b336aaed796cda36a460f6d1dd Mon Sep 17 00:00:00 2001 From: bygu4 Date: Tue, 15 Apr 2025 00:31:47 +0300 Subject: [PATCH 5/6] add comments --- Tasks/Lazy/Lazy/ILazy.fs | 2 ++ Tasks/Lazy/Lazy/LockFreeLazy.fs | 5 +++++ Tasks/Lazy/Lazy/SingleThreadLazy.fs | 5 +++++ Tasks/Lazy/Lazy/ThreadSafeLazy.fs | 5 +++++ 4 files changed, 17 insertions(+) diff --git a/Tasks/Lazy/Lazy/ILazy.fs b/Tasks/Lazy/Lazy/ILazy.fs index 1272ad6..6fef851 100644 --- a/Tasks/Lazy/Lazy/ILazy.fs +++ b/Tasks/Lazy/Lazy/ILazy.fs @@ -1,4 +1,6 @@ namespace Lazy +/// An interface representing a lazy evaluation. type ILazy<'a> = + /// Get the result evaluated lazily. abstract member Get: unit -> 'a diff --git a/Tasks/Lazy/Lazy/LockFreeLazy.fs b/Tasks/Lazy/Lazy/LockFreeLazy.fs index 9119f76..840367e 100644 --- a/Tasks/Lazy/Lazy/LockFreeLazy.fs +++ b/Tasks/Lazy/Lazy/LockFreeLazy.fs @@ -3,12 +3,15 @@ namespace Lazy open System open System.Threading +/// Class representing thread safe lazy evaluation without locks. +/// Creates an instance with the given `supplier` to evaluate. type LockFreeLazy<'a when 'a: equality>(supplier: unit -> 'a) = [] let mutable supplier = Some supplier let initialResult: 'a option * exn option = None, None let mutable result = initialResult + /// Run supplier and set the result if it was not already set, then clear the supplier. let tryEvaluate () = let currentSupplier = supplier if currentSupplier.IsSome then @@ -22,6 +25,7 @@ type LockFreeLazy<'a when 'a: equality>(supplier: unit -> 'a) = |> ignore supplier <- None + /// Get the result evaluated lazily. let getResult () = tryEvaluate () match result with @@ -32,4 +36,5 @@ type LockFreeLazy<'a when 'a: equality>(supplier: unit -> 'a) = interface ILazy<'a> with member _.Get (): 'a = getResult () + /// Get the result evaluated lazily. member _.Get (): 'a = getResult () diff --git a/Tasks/Lazy/Lazy/SingleThreadLazy.fs b/Tasks/Lazy/Lazy/SingleThreadLazy.fs index de6bae8..9c97821 100644 --- a/Tasks/Lazy/Lazy/SingleThreadLazy.fs +++ b/Tasks/Lazy/Lazy/SingleThreadLazy.fs @@ -2,11 +2,14 @@ open System +/// Class representing a lazy evaluation for a single thread. +/// Creates an instance with the given `supplier` to evaluate. type SingleThreadLazy<'a when 'a: equality>(supplier: unit -> 'a) = let mutable supplier = Some supplier let mutable result: 'a option = None let mutable thrownException: Exception option = None + /// Run supplier if it was not already run, set result and clear the supplier. let tryEvaluate () = if supplier.IsSome then try @@ -15,6 +18,7 @@ type SingleThreadLazy<'a when 'a: equality>(supplier: unit -> 'a) = | e -> thrownException <- Some e supplier <- None + /// Get the result evaluated lazily. let getResult () = tryEvaluate () match result, thrownException with @@ -25,4 +29,5 @@ type SingleThreadLazy<'a when 'a: equality>(supplier: unit -> 'a) = interface ILazy<'a> with member _.Get (): 'a = getResult () + /// Get the result evaluated lazily. member _.Get (): 'a = getResult () diff --git a/Tasks/Lazy/Lazy/ThreadSafeLazy.fs b/Tasks/Lazy/Lazy/ThreadSafeLazy.fs index 6c4ed0d..6c9a83d 100644 --- a/Tasks/Lazy/Lazy/ThreadSafeLazy.fs +++ b/Tasks/Lazy/Lazy/ThreadSafeLazy.fs @@ -2,6 +2,8 @@ namespace Lazy open System +/// Class representing thread safe lazy evaluation. +/// Creates an instance with the given `supplier` to evaluate. type ThreadSafeLazy<'a when 'a: equality>(supplier: unit -> 'a) = [] let mutable supplier = Some supplier @@ -9,6 +11,7 @@ type ThreadSafeLazy<'a when 'a: equality>(supplier: unit -> 'a) = let mutable thrownException: Exception option = None let lockObject = new Object () + /// Run supplier if it was not already run, set result and clear the supplier. let tryEvaluate () = lock lockObject (fun () -> if supplier.IsSome then @@ -19,6 +22,7 @@ type ThreadSafeLazy<'a when 'a: equality>(supplier: unit -> 'a) = supplier <- None ) + /// Get the result evaluated lazily. let getResult () = tryEvaluate () match result, thrownException with @@ -29,4 +33,5 @@ type ThreadSafeLazy<'a when 'a: equality>(supplier: unit -> 'a) = interface ILazy<'a> with member _.Get (): 'a = getResult () + /// Get the result evaluated lazily. member _.Get (): 'a = getResult () From 2ed4f3c41d4d4aacc4db625ed669d4237786fa77 Mon Sep 17 00:00:00 2001 From: bygu4 Date: Fri, 6 Jun 2025 13:48:45 +0300 Subject: [PATCH 6/6] use match in tryEvaluate --- Tasks/Lazy/Lazy/LockFreeLazy.fs | 6 ++++-- Tasks/Lazy/Lazy/SingleThreadLazy.fs | 6 ++++-- Tasks/Lazy/Lazy/ThreadSafeLazy.fs | 8 +++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Tasks/Lazy/Lazy/LockFreeLazy.fs b/Tasks/Lazy/Lazy/LockFreeLazy.fs index 840367e..956b89d 100644 --- a/Tasks/Lazy/Lazy/LockFreeLazy.fs +++ b/Tasks/Lazy/Lazy/LockFreeLazy.fs @@ -14,9 +14,10 @@ type LockFreeLazy<'a when 'a: equality>(supplier: unit -> 'a) = /// Run supplier and set the result if it was not already set, then clear the supplier. let tryEvaluate () = let currentSupplier = supplier - if currentSupplier.IsSome then + match currentSupplier with + | Some func -> try - let localResult = Some (currentSupplier.Value ()) + let localResult = Some (func ()) Interlocked.CompareExchange (&result, (localResult, None), initialResult) |> ignore with @@ -24,6 +25,7 @@ type LockFreeLazy<'a when 'a: equality>(supplier: unit -> 'a) = Interlocked.CompareExchange (&result, (None, Some e), initialResult) |> ignore supplier <- None + | None -> () /// Get the result evaluated lazily. let getResult () = diff --git a/Tasks/Lazy/Lazy/SingleThreadLazy.fs b/Tasks/Lazy/Lazy/SingleThreadLazy.fs index 9c97821..7d3d0ce 100644 --- a/Tasks/Lazy/Lazy/SingleThreadLazy.fs +++ b/Tasks/Lazy/Lazy/SingleThreadLazy.fs @@ -11,12 +11,14 @@ type SingleThreadLazy<'a when 'a: equality>(supplier: unit -> 'a) = /// Run supplier if it was not already run, set result and clear the supplier. let tryEvaluate () = - if supplier.IsSome then + match supplier with + | Some func -> try - result <- Some (supplier.Value ()) + result <- Some (func ()) with | e -> thrownException <- Some e supplier <- None + | None -> () /// Get the result evaluated lazily. let getResult () = diff --git a/Tasks/Lazy/Lazy/ThreadSafeLazy.fs b/Tasks/Lazy/Lazy/ThreadSafeLazy.fs index 6c9a83d..44e43ba 100644 --- a/Tasks/Lazy/Lazy/ThreadSafeLazy.fs +++ b/Tasks/Lazy/Lazy/ThreadSafeLazy.fs @@ -5,7 +5,7 @@ open System /// Class representing thread safe lazy evaluation. /// Creates an instance with the given `supplier` to evaluate. type ThreadSafeLazy<'a when 'a: equality>(supplier: unit -> 'a) = - [] + [] let mutable supplier = Some supplier let mutable result: 'a option = None let mutable thrownException: Exception option = None @@ -14,12 +14,14 @@ type ThreadSafeLazy<'a when 'a: equality>(supplier: unit -> 'a) = /// Run supplier if it was not already run, set result and clear the supplier. let tryEvaluate () = lock lockObject (fun () -> - if supplier.IsSome then + match supplier with + | Some func -> try - result <- Some (supplier.Value ()) + result <- Some (func ()) with | e -> thrownException <- Some e supplier <- None + | None -> () ) /// Get the result evaluated lazily.