diff --git a/ExtCore.Tests/Atomic.fs b/ExtCore.Tests/Atomic.fs new file mode 100644 index 0000000..52fe433 --- /dev/null +++ b/ExtCore.Tests/Atomic.fs @@ -0,0 +1,264 @@ +(* + +Copyright 2019 Bartosz Sypytkowski + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*) + +namespace Tests.ExtCore + +module Atomic = + + open ExtCore + open ExtCore.Atomic.Operators + open NUnit.Framework + + (* AtomicRef<'a> tests *) + + [] + let ``Atomic ref returns init value`` () : unit = + let atom = Atomic.ref "hello" + !atom |> assertEqual "hello" + + [] + let ``Atomic ref swap returns old value`` () : unit = + let atom = Atomic.ref "hello" + let old = atom := "world" + old |> assertEqual "hello" + !atom |> assertEqual "world" + + [] + let ``Atomic ref cas swaps value if comparand is equal`` () : unit = + let value = "hello" + let atom = Atomic.ref value + let success = atom |> Atomic.cas value "world" + success |> assertTrue + !atom |> assertEqual "world" + + [] + let ``Atomic ref cas doesn't swap value if comparand is not equal`` () : unit = + let atom = Atomic.ref "hello" + let success = atom |> Atomic.cas "hi" "world" + success |> assertFalse + !atom |> assertEqual "hello" + + [] + let ``Atomic ref update replaces old value with modified one`` () : unit = + let atom = Atomic.ref "hello" + let old = atom |> Atomic.update (fun o -> o + o) + old |> assertEqual "hello" + !atom |> assertEqual "hellohello" + + (* AtomicInt tests *) + + [] + let ``Atomic int returns init value`` () : unit = + let atom = Atomic.int 1 + !atom |> assertEqual 1 + + [] + let ``Atomic int swap returns old value`` () : unit = + let atom = Atomic.int 1 + let old = atom := 2 + old |> assertEqual 1 + !atom |> assertEqual 2 + + [] + let ``Atomic int cas swaps value if comparand is equal`` () : unit = + let atom = Atomic.int 1 + let success = atom |> Atomic.cas 1 2 + success |> assertTrue + !atom |> assertEqual 2 + + [] + let ``Atomic int cas doesn't swap value if comparand is not equal`` () : unit = + let atom = Atomic.int 1 + let success = atom |> Atomic.cas 3 2 + success |> assertFalse + !atom |> assertEqual 1 + + [] + let ``Atomic int update replaces old value with modified one`` () : unit = + let atom = Atomic.int 1 + let old = atom |> Atomic.update (fun o -> o + o) + old |> assertEqual 1 + !atom |> assertEqual 2 + + [] + let ``Atomic int inc increments the counter value`` () : unit = + let atom = Atomic.int 2 + let current = Atomic.inc atom + current |> assertEqual 3 + !atom |> assertEqual 3 + + [] + let ``Atomic int dec decrements the counter value`` () : unit = + let atom = Atomic.int 2 + let current = Atomic.dec atom + current |> assertEqual 1 + !atom |> assertEqual 1 + + (* AtomicInt64 tests *) + + [] + let ``Atomic int64 returns init value`` () : unit = + let atom = Atomic.int64 1L + !atom |> assertEqual 1L + + [] + let ``Atomic int64 swap returns old value`` () : unit = + let atom = Atomic.int64 1L + let old = atom := 2L + old |> assertEqual 1L + !atom |> assertEqual 2L + + [] + let ``Atomic int64 cas swaps value if comparand is equal`` () : unit = + let atom = Atomic.int64 1L + let success = atom |> Atomic.cas 1L 2L + success |> assertTrue + !atom |> assertEqual 2L + + [] + let ``Atomic int64 cas doesn't swap value if comparand is not equal`` () : unit = + let atom = Atomic.int64 1L + let success = atom |> Atomic.cas 3L 2L + success |> assertFalse + !atom |> assertEqual 1L + + [] + let ``Atomic int64 update replaces old value with modified one`` () : unit = + let atom = Atomic.int64 1L + let old = atom |> Atomic.update (fun o -> o + o) + old |> assertEqual 1L + !atom |> assertEqual 2L + + [] + let ``Atomic int64 inc increments the counter value`` () : unit = + let atom = Atomic.int64 2L + let current = Atomic.inc atom + current |> assertEqual 3L + !atom |> assertEqual 3L + + [] + let ``Atomic int64 dec decrements the counter value`` () : unit = + let atom = Atomic.int64 2L + let current = Atomic.dec atom + current |> assertEqual 1L + !atom |> assertEqual 1L + + (* AtomicFloat tests *) + + [] + let ``Atomic float returns init value`` () : unit = + let atom = Atomic.float 1.0 + !atom |> assertEqual 1.0 + + [] + let ``Atomic float swap returns old value`` () : unit = + let atom = Atomic.float 1.0 + let old = atom := 2.0 + old |> assertEqual 1.0 + !atom |> assertEqual 2.0 + + [] + let ``Atomic float cas swaps value if comparand is equal`` () : unit = + let atom = Atomic.float 1.0 + let success = atom |> Atomic.cas 1.0 2.0 + success |> assertTrue + !atom |> assertEqual 2.0 + + [] + let ``Atomic float cas doesn't swap value if comparand is not equal`` () : unit = + let atom = Atomic.float 1.0 + let success = atom |> Atomic.cas 3.0 2.0 + success |> assertFalse + !atom |> assertEqual 1.0 + + [] + let ``Atomic float update replaces old value with modified one`` () : unit = + let atom = Atomic.float 1.0 + let old = atom |> Atomic.update (fun o -> o + o) + old |> assertEqual 1.0 + !atom |> assertEqual 2.0 + + (* AtomicFloat32 tests *) + + [] + let ``Atomic float32 returns init value`` () : unit = + let atom = Atomic.float32 1.0f + !atom |> assertEqual 1.0f + + [] + let ``Atomic float32 swap returns old value`` () : unit = + let atom = Atomic.float32 1.0f + let old = atom := 2.0f + old |> assertEqual 1.0f + !atom |> assertEqual 2.0f + + [] + let ``Atomic float32 cas swaps value if comparand is equal`` () : unit = + let atom = Atomic.float32 1.0f + let success = atom |> Atomic.cas 1.0f 2.0f + success |> assertTrue + !atom |> assertEqual 2.0f + + [] + let ``Atomic float32 cas doesn't swap value if comparand is not equal`` () : unit = + let atom = Atomic.float32 1.0f + let success = atom |> Atomic.cas 3.0f 2.0f + success |> assertFalse + !atom |> assertEqual 1.0f + + [] + let ``Atomic float32 update replaces old value with modified one`` () : unit = + let atom = Atomic.float32 1.0f + let old = atom |> Atomic.update (fun o -> o + o) + old |> assertEqual 1.0f + !atom |> assertEqual 2.0f + + (* AtomicBool tests *) + + [] + let ``Atomic bool returns init value`` () : unit = + let atom = Atomic.bool true + !atom |> assertEqual true + + [] + let ``Atomic bool swap returns old value`` () : unit = + let atom = Atomic.bool true + let old = atom := false + old |> assertEqual true + !atom |> assertEqual false + + [] + let ``Atomic bool cas swaps value if comparand is equal`` () : unit = + let atom = Atomic.bool true + let success = atom |> Atomic.cas true false + success |> assertTrue + !atom |> assertEqual false + + [] + let ``Atomic bool cas doesn't swap value if comparand is not equal`` () : unit = + let atom = Atomic.bool true + let success = atom |> Atomic.cas false true + success |> assertFalse + !atom |> assertEqual true + + [] + let ``Atomic bool update replaces old value with modified one`` () : unit = + let atom = Atomic.bool true + let old = atom |> Atomic.update (not) + old |> assertEqual true + !atom |> assertEqual false diff --git a/ExtCore.Tests/ExtCore.Tests.fsproj b/ExtCore.Tests/ExtCore.Tests.fsproj index 0987223..78de6a7 100644 --- a/ExtCore.Tests/ExtCore.Tests.fsproj +++ b/ExtCore.Tests/ExtCore.Tests.fsproj @@ -5,6 +5,7 @@ + diff --git a/ExtCore/Atomic.fs b/ExtCore/Atomic.fs new file mode 100644 index 0000000..42cd89d --- /dev/null +++ b/ExtCore/Atomic.fs @@ -0,0 +1,182 @@ +(* + +Copyright 2019 Bartosz Sypytkowski + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*) + +namespace ExtCore + +open System +open System.Threading +open System.Runtime.CompilerServices + +[] +type IAtomic<'a> = + abstract Value: unit -> 'a + abstract Swap: 'a -> 'a + abstract CompareAndSwap: 'a * 'a -> bool + +[] +type AtomicBool(initialValue: bool) = + let mutable value: int = if initialValue then 1 else 0 + interface IAtomic with + [] + member __.Value () = Volatile.Read(&value) = 1 + + [] + member __.Swap (nval: bool): bool = Interlocked.Exchange(&value, if nval then 1 else 0) = 1 + + [] + member __.CompareAndSwap (compared: bool, nval: bool): bool = + let v = if compared then 1 else 0 + Interlocked.CompareExchange(&value, (if nval then 1 else 0), v) = v + +[] +type AtomicInt(initialValue: int) = + let mutable value: int = initialValue + + [] + member __.Increment() = Interlocked.Increment(&value) + + [] + member __.Decrement() = Interlocked.Decrement(&value) + + interface IAtomic with + [] + member __.Value () = Volatile.Read(&value) + + [] + member __.Swap (nval: int): int = Interlocked.Exchange(&value, nval) + + [] + member __.CompareAndSwap (compared: int, nval: int): bool = Interlocked.CompareExchange(&value, nval, compared) = compared + +[] +type AtomicInt64(initialValue: int64) = + let mutable value: int64 = initialValue + + [] + member __.Increment() = Interlocked.Increment(&value) + + [] + member __.Decrement() = Interlocked.Decrement(&value) + + interface IAtomic with + [] + member __.Value () = Volatile.Read(&value) + + [] + member __.Swap (nval: int64): int64 = Interlocked.Exchange(&value, nval) + + [] + member __.CompareAndSwap (compared: int64, nval: int64): bool = Interlocked.CompareExchange(&value, nval, compared) = compared + +[] +type AtomicFloat(initialValue: float) = + let mutable value: float = initialValue + interface IAtomic with + [] + member __.Value () = Volatile.Read(&value) + + [] + member __.Swap (nval: float): float = Interlocked.Exchange(&value, nval) + + [] + member __.CompareAndSwap (compared: float, nval: float): bool = Interlocked.CompareExchange(&value, nval, compared) = compared + +[] +type AtomicFloat32(initialValue: float32) = + let mutable value: float32 = initialValue + interface IAtomic with + [] + member __.Value () = Volatile.Read(&value) + + [] + member __.Swap (nval: float32): float32 = Interlocked.Exchange(&value, nval) + + [] + member __.CompareAndSwap (compared: float32, nval: float32): bool = Interlocked.CompareExchange(&value, nval, compared) = compared + +[] +type AtomicRef<'a when 'a: not struct>(initialValue: 'a) = + let mutable value: 'a = initialValue + interface IAtomic<'a> with + [] + member __.Value () = Volatile.Read(&value) + + [] + member __.Swap (nval: 'a): 'a = Interlocked.Exchange<'a>(&value, nval) + + [] + member __.CompareAndSwap (compared: 'a, nval: 'a): bool = Object.ReferenceEquals(Interlocked.CompareExchange<'a>(&value, nval, compared), compared) + +[] +module Atomic = + + /// Creates an atomic cell reference for a given boolean value. + let inline bool (x: bool): AtomicBool = AtomicBool(x) + + /// Creates an atomic cell reference for a given integer number (32 bits). + let inline int (x: int): AtomicInt = AtomicInt(x) + + /// Creates an atomic cell reference for a given integer number (64 bits). + let inline int64 (x: int64): AtomicInt64 = AtomicInt64(x) + + /// Creates an atomic cell reference for a given floating point number (64 bits). + let inline float (x: float): AtomicFloat = AtomicFloat(x) + + /// Creates an atomic cell reference for a given floating point number (32 bits). + let inline float32 (x: float32): AtomicFloat32 = AtomicFloat32(x) + + /// Creates an atomic cell reference for a given object (instance of reference type). + let inline ref<'a when 'a: not struct> (x: 'a): AtomicRef<'a> = AtomicRef<'a>(x) + + /// Atomically replaces old value stored inside an atom with a new one, + /// but only if previously stored value is (referentially) equal to the + /// expected value. Returns true if managed to successfully replace the + /// stored value, false otherwise. + let inline cas (expected: 'a) (nval: 'a) (atom: #IAtomic<'a>) = + atom.CompareAndSwap(expected, nval) + + /// Atomically tries to update value stored inside an atom, by passing + /// current atom's value to modify function to get new result, which will + /// be stored instead. Returns the last value stored prior to an update. + let update (modify: 'a -> 'a) (atom: #IAtomic<'a>): 'a = + let rec loop (modify: 'a -> 'a) (atom: #IAtomic<'a>) = + let old = atom.Value () + let nval = modify old + if cas old nval atom + then old + else loop modify atom + loop modify atom + + /// Atomically increments counter stored internally inside of an atom. + /// Returns an incremented value. + let inline inc (atom: ^a ): ^b when ^a : (member Increment: unit -> ^b) = + ( ^a : (member Increment: unit -> ^b) (atom)) + + /// Atomically decrements counter stored internally inside of an atom. + /// Returns an incremented value. + let inline dec (atom: ^a ): ^b when ^a : (member Decrement: unit -> ^b) = + ( ^a : (member Decrement: unit -> ^b) (atom)) + + module Operators = + + /// Unwraps the value stored inside of an atom. + let inline (!) (atom: #IAtomic<'a>): 'a = atom.Value () + + /// Atomically swaps the value stored inside of an atom with provided one. + /// Returns previously stored value. + let inline (:=) (atom: #IAtomic<'a>) (value: 'a): 'a = atom.Swap value diff --git a/ExtCore/ExtCore.fsproj b/ExtCore/ExtCore.fsproj index 73fb972..6f5a578 100644 --- a/ExtCore/ExtCore.fsproj +++ b/ExtCore/ExtCore.fsproj @@ -21,66 +21,67 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +