diff --git a/Documentation/Sections/Defining Locker Behavior.md b/Documentation/Lock Wait State & Grant Behavior.md similarity index 66% rename from Documentation/Sections/Defining Locker Behavior.md rename to Documentation/Lock Wait State & Grant Behavior.md index 0bb4a6e..113f882 100644 --- a/Documentation/Sections/Defining Locker Behavior.md +++ b/Documentation/Lock Wait State & Grant Behavior.md @@ -1,89 +1,71 @@ - -# Defining Locker Behavior ---- -The `Locker`s behaviour is defined by the `LockMode`, `CompatibilityMatrix`, and `GroupModeMatrix`. These types and -structures define how the Locker will grant requests for lock modes. - -A `LockMode` is an enum entry that defines a specific mode of the lock. These modes are user defined - -### Lock Modes - -Lock modes determine the symbols used to define the modes a lock can be in. - -Here is an example of a simple lock mode definition: -```swift - enum MyLockMode: LockMode { - case S /// Shared - case X /// Exclusive - } -``` -The mode on it's own only defines the symbols that can be used. You must define a `CompatibilitMatrix` -and `GroupModeMatrix` to describe how the various modes interact with each other. - -> Note: Sticky Locking defines two built in `LockMode` enums along with corresponding compatibility and -> group mode matrix's. A simple readers-writer type named `SharedExclusiveLockMode` and an extended mode named -> `ExtendedLockMode` which is suitable for advanced data storage applications. - -### Lock Mode Compatibility - -The ability to share a lock mode with another request/thread is determined by the lock mode compatibility matrix -supplied for the lock mode. For every new lock request, the matrix is checked and if the value at the index of the -current lock mode and the requested lock mode is `true` the lock will be granted concurrently, otherwise the request -will queue and wait. -```swift - let compatibilityMatrix: CompatibilityMatrix - = [ - /* Shared, Exclusive */ - /* Shared */ [true, false], - /* Exclusive */ [false, false], - ] -``` - -### Lock Group Mode - -When multiple requests are compatible and granted concurrently the lock mode of the group must be calculated. This -is called the group mode. A new request is compatible with the members of the group if it is compatible with the -group mode. - -Sticky Locking uses the `GroupModeMatrix` to determine the group mode when a new request joins the group. -```swift - let groupModeMatrix: GroupModeMatrix - = [ - /* Requested Shared, Exclusive */ - /* Shared */ [S, X], - /* Exclusive */ [X, X], - ] -``` # Lock Wait State & Grant Behavior ---- -Locks can be in one of several states: + +Locks in the Locker can be in one of several states depending contention for the locks. + +The three states are: - **Granted** - The lock has been granted to the thread requesting the lock. If this is a shared lock, many threads can be in the granted state simultaneously. - **Waiting** - The lock is currently waiting to be granted. - **Converting** - The lock is currently converting from one mode to another by a thread (e.g. Shared converting to Exclusive). Many threads can be converting at the same time. A thread that is converting must be in the granted state before conversion. +Lock grants are given based on the `LockMode`, `CompatibilityMatrix`, and `GroupModeMatrix` in use at +the time. Below are a series of examples based on the built in `ExtendedLockMode`. The `ExtendedLockMode` +has the following definition. + +The defined modes are: + + * IS - Intention Shared + * IX - Intention Exclusive + * S - Shared + * SIX - Shared Intention Exclusive + * U - Update + * X - Exclusive + +The `CompatibilityMatrix` is defined as: + +| Requested | IS | IX | S | SIX | U | X | +|:---------:|:------:|:------:|:------:|:------:|:------:|:------:| +| **IS** |✔|✔|✔|✔|✔|✘| +| **IX** |✔|✔|✘|✘|✘|✘| +| **S** |✔|✘|✔|✘|✔|✘| +| **SIX** |✔|✘|✘|✘|✘|✘| +| **U** |✔|✘|✔|✘|✘|✘| +| **X** |✘|✘|✘|✘|✘|✘| + +The `GroupModeMatrix` is defined as: + +| Requested | IS | IX | S | SIX | U | X | +|:---------:|:------:|:------:|:------:|:------:|:------:|:------:| +| **IS** | IS | IX | S | SIX | U | X | +| **IX** | IX | IX | SIX | SIX | X | X | +| **S** | S | SIX | S | SIX | U | X | +| **SIX** | SIX | SIX | SIX | SIX | SIX | X | +| **U** | U | X | U | SIX | U | X | +| **X** | X | X | X | X | X | X | + + ## Lock Grants -Lock requests are granted immediately under the following conditions: +Using the above definitions, lock requests are granted immediately under the following conditions: -1) There are no requests waiting for conversion to a different mode. -2) There are no requests waiting for a new lock. +1) There are no requests waiting for conversion to a different mode. +2) There are no requests waiting for a new lock. Given the following empty lock queue: lock - | + | | queue -> If thread 1 (`T1`) requests an `S` lock, it is immediately granted because there are no conversion or waiters. lock (S) - | - | queue -> (T1, S, granted) + | + | queue -> (T1, S, granted) ### Lock Wait State -Locks will be put into waiting state if any of the following conditions are true. +Locks will be put into waiting state if any of the following conditions are true. 1) The requested lock mode is not compatible with the current group mode. 2) There are existing waiting locks in the queue. @@ -94,43 +76,43 @@ Locks will be put into waiting state if any of the following conditions are true Given the following lock queue of granted requests: lock (S) - | - | granted -> (T1, S, granted) + | + | granted -> (T1, S, granted) If thread 2 (`T2`) requests an X lock (exclusive), it must wait because it is not compatible with the existing granted group of S resulting in the following queue. lock (S) - | - | queue -> (T1, S, granted) --- (T2, X, waiting) + | + | queue -> (T1, S, granted) --- (T2, X, waiting) #### Scenario 2 (existing waiting requests) Given the following lock queue: lock (S) - | - | queue -> (T1, S, granted) --- (T2, X, waiting) - + | + | queue -> (T1, S, granted) --- (T2, X, waiting) + If thread 3 (`T3`) requests an S lock (shared), even though it is compatible with the existing group mode (S), it must wait because there are other waiters in the queue. lock (S) - | - | queue -> (T1, S, granted) --- (T2, X, waiting) --- (T3, S, waiting) - + | + | queue -> (T1, S, granted) --- (T2, X, waiting) --- (T3, S, waiting) + #### Scenario 3 (existing conversion requests) Given the following lock queue: lock (S) - | - | queue -> (T1, S, granted) --- (T2, S, granted) --- (T1, X, converting) - + | + | queue -> (T1, S, granted) --- (T2, S, granted) --- (T1, X, converting) + If thread 3 (`T3`) requests an S lock (shared), even though it is compatible with the existing group mode (S), it must wait because there are conversion requests in the queue. - + lock (S) - | - | queue -> (T1, S, granted) --- (T2, S, granted) --- (T1, X, converting) --- (T3, S, waiting) - + | + | queue -> (T1, S, granted) --- (T2, S, granted) --- (T1, X, converting) --- (T3, S, waiting) + ## Lock Conversion State Threads can request conversion from one lock mode to another. Either up-conversion, converting from a less strict to a more strict, or down-conversion, converting from a more strict to a less strict mode. `StickyLocking` uses a FIFO lock allocation scheme with the exception of conversion which is always granted before new waiting requests. Even though they take precedence over waiting new request, multiple conversion requests are granted in FIFO order. @@ -140,35 +122,35 @@ Threads can request conversion from one lock mode to another. Either up-convers Immediate conversion of a request can be granted if the following conditions are true. 1) The requested conversion is compatible with the lock group mode. -2) There are no requests waiting for conversion to a different mode. +2) There are no requests waiting for conversion to a different mode. #### Example 1 (no waiters) Given the following lock queue of granted requests: lock (S) - | + | | queue -> (T1, S, granted) --- (T2, S, granted) --- (T3, S, granted) - + If thread 1 (`T1`) requests conversion to `IS`, it is immediately granted, upgrading the group mode to `IS`. lock (IS) | - | queue -> (T1, IS, granted) --- (T2, S, granted) --- (T3, S, granted) - + | queue -> (T1, IS, granted) --- (T2, S, granted) --- (T3, S, granted) + #### Example 2 (with waiters) Given the following lock queue of granted and waiting requests: lock (S) - | + | | queue -> (T1, S, granted) --- (T2, S, granted) --- (T3, S, granted) --- (T4, X, waiting) - + If thread 1 (`T1`) requests conversion to `IS`, it is immediately granted, upgrading the group mode to `IS`. lock (IS) | - | queue -> (T1, IS, granted) --- (T2, S, granted) --- (T3, S, granted) --- (T4, X, waiting) + | queue -> (T1, IS, granted) --- (T2, S, granted) --- (T3, S, granted) --- (T4, X, waiting) ### Lock Scenario 2 (Wait on conversion) @@ -183,40 +165,40 @@ Given the following lock queue of granted requests: lock (U) | - | queue -> (T1, U, granted) --- (T2, IS, granted) --- (T3, IS, granted) + | queue -> (T1, U, granted) --- (T2, IS, granted) --- (T3, IS, granted) If thread 1 (`T1`) requests conversion to `X` the request will wait on the queue because the lock mode `X` is incompatible with the group lock mode `U`. lock (U) - | + | | queue -> (T1, U, granted) --- (T2, S, granted) --- (T3, IS, granted) --- (T1, X, converting) - + Once request `T2` and `T3` unlock, `T1` will convert to `X` given the following queue. lock (X) | | queue -> (T1, X, granted) - + #### Example 2 (with conversion queue) Given the following lock queue of granted requests with a waiting on conversion queue: lock (U) - | + | | queue -> (T1, U, granted) --- (T2, IS, granted) --- (T3, IS, granted) --- (T2, IX, converting) - + `T3` requests up-conversion to `IX` and since `T2` is already waiting, `T3` must also wait resulting in the following queue. lock (U) | | queue -> (T1, U, granted) --- (T2, IS, granted) --- (T3, IS, granted) --- (T2, IX, converting) --- (T3, IX, converting) - + `T1` unlocks resulting in `T2` and `T3` being granted the up-conversion since `IS` and `IX` are compatible. lock (IX) | - | queue -> (T2, IX, granted) --- (T3, IX, granted) - + | queue -> (T2, IX, granted) --- (T3, IX, granted) + ### Lock scenario 3 (with waiting queue) Conversion requests that can't be immediately granted will be placed in the queue before requests waiting for new locks. @@ -224,21 +206,21 @@ Conversion requests that can't be immediately granted will be placed in the queu Given the following lock queue of granted and waiting requests: lock (S) - | + | | queue -> (T1, S, granted) --- (T2, S, granted) --- (T3, IX, waiting) --- (T4, IX, waiting) - + If `T1` then requests an up-conversion from `S` to `X`, it will wait and be placed before requests waiting for new locks. lock (S) - | + | | queue -> (T1, S, granted) --- (T2, S, granted) --- (T1, X, converting) --- (T3, IX, waiting) --- (T4, IX, waiting) Once `T2` unlocks, `T1` will be granted it's conversion request before the request waiting for new locks are granted. lock (X) - | + | | queue -> (T1, X, granted) --- (T3, IX, waiting) --- (T4, IX, waiting) - + ### Conversion Deadlock Certain situations can cause lock request to go into a deadlock state. The most common cause it lock conversion as in the example below. @@ -248,8 +230,8 @@ Certain situations can cause lock request to go into a deadlock state. The most Given the lock queue: lock (S) - | - | queue -> (T1, S, granted) --- (T2, S, granted) + | + | queue -> (T1, S, granted) --- (T2, S, granted) `T1` and `T2` requests conversion to `X` causing a deadlock. diff --git a/Documentation/Sections/Built-in Lock Modes.md b/Documentation/Sections/Built-in Lock Modes.md deleted file mode 100644 index 044a04a..0000000 --- a/Documentation/Sections/Built-in Lock Modes.md +++ /dev/null @@ -1,68 +0,0 @@ -# Built-in Lock Modes - -Sticky Locking contains two pre-defined `LockMode` enums for use with various use cases. - -## Shared-Exclusive Lock Mode ---- -A Shared Exclusive lock (a.k.a. readers-writer or multi-reader) allows concurrent access for read-only -operations, while write operations gain exclusive access. - -This allows multiple readers to gain shared access to a resource blocking all writers until no more -readers are reading. Writers gain exclusive access to the resource blocking all other readers and writers -until the operation is complete. - -The defined modes are: - - * S - Shared (Read) - * X - Exclusive (Write) - -The default `CompatibilityMatrix` is defined as: - -| Requested | S | X | -|:---------:|:------:|:------:| -| **S** |✔|✘| -| **X** |✘|✘| - -The default `GroupModeMatrix` is defined as: - -| Requested | S | X | -|:---------:|:------:|:------:| -| **S** | S | X | -| **X** | X | X | - - -## Extended Lock Mode ---- -The `ExrendedLockMode` is a predefined `LockMode` implementation that can be used for complex database type applications. It defines -an extended set of lock modes including Update and Intention modes. - -The defined modes are: - - * IS - Intention Shared - * IX - Intention Exclusive - * S - Shared - * SIX - Shared Intention Exclusive - * U - Update - * X - Exclusive - -The default `CompatibilityMatrix` is defined as: - -| Requested | IS | IX | S | SIX | U | X | -|:---------:|:------:|:------:|:------:|:------:|:------:|:------:| -| **IS** |✔|✔|✔|✔|✔|✘| -| **IX** |✔|✔|✘|✘|✘|✘| -| **S** |✔|✘|✔|✘|✔|✘| -| **SIX** |✔|✘|✘|✘|✘|✘| -| **U** |✔|✘|✔|✘|✘|✘| -| **X** |✘|✘|✘|✘|✘|✘| - -The default `GroupModeMatrix` is defined as: - -| Requested | IS | IX | S | SIX | U | X | -|:---------:|:------:|:------:|:------:|:------:|:------:|:------:| -| **IS** | IS | IX | S | SIX | U | X | -| **IX** | IX | IX | SIX | SIX | X | X | -| **S** | S | SIX | S | SIX | U | X | -| **SIX** | SIX | SIX | SIX | SIX | SIX | X | -| **U** | U | X | U | SIX | U | X | -| **X** | X | X | X | X | X | X | \ No newline at end of file diff --git a/Documentation/Sections/Resources & Hashing.md b/Documentation/Sections/Resources & Hashing.md deleted file mode 100644 index a03b483..0000000 --- a/Documentation/Sections/Resources & Hashing.md +++ /dev/null @@ -1,9 +0,0 @@ -# Resources & Hashing ---- - -The `Locker` will lock and unlock any `Hashable` resource and it distinguishes the lock resources by the hash value, -therefore care must be taken to create a hashing algorithm that ensure uniqueness between individual objects of the -same type as well as the hash values between different types. - -If two resources hash to the same hash value, and the two requested modes are incompatible, then the collision may -cause spurious waits. diff --git a/README.md b/README.md index c957ee1..3b7484f 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,23 @@   Codecov -## Overview +**StickyLocking** is a general purpose embedded lock manager which allows for locking any resource hierarchy. Installable Lock modes allow for customization of the locking system that can meet the needs of almost any locking scenario. -**StickyLocking** is a general purpose embedded lock manager which allows for locking any resource hierarchy. Installable Lock modes allow for custimization of the locking system that can meet the needs of almost any locking scenario. +## Documentation -It offers multiple levels of locking for various use cases. +Sticky Locking provides the `Locker` class which is a high-level locking system designed to facilitate many different concurrency use cases including simple readers-writer locks which provide shared access for read operations and +exclusive access for write operations to more complex hierarchical locking schemes used to power database file, +database, page, and row level locking. -### **Hierarchical Locker** +Sticky Locking also provides a low-level mutual exclusion lock through the `Mutex` class to protect critical sections of +your code. In addition, wait conditions (`Condition`) are provided to allow for threads to wait for a mutex to +become available. + +The mutual exclusion lock is provided through the `Mutex` class while wait conditions can be created with the +`Condition` class. Internally, the higher level components are implemented using these two primitives, and other +modules in the Sticky Tools suite of libraries also use the mutex for protecting various critical sections of code. + +### Hierarchical Locker Sticky Locking provides the `Locker` class which is a high-level locking system designed to facilitate many different concurrency use cases including simple readers-writer locks which provide shared access for read operations and @@ -50,15 +60,151 @@ type use cases. This LockMode set was designed to be used by other models in th You are free to define your own LockMode set depending on your use case, from simpler mode structures to more complex, Sticky Locking will adapt to the mode given. -### **Mutexes & Conditions** +#### Defining Locker Behavior -Sticky Locking provides a low-level mutual exclusion lock through the `Mutex` class to protect critical sections of -your code. In addition, wait conditions (`Condition`) are provided to allow for threads to wait for a mutex to -become available. +The `Locker`s behavior is defined by the `LockMode`, `CompatibilityMatrix`, and `GroupModeMatrix`. These types and +structures define how the Locker will grant requests for lock modes. -The mutual exclusion lock is provided through the `Mutex` class while wait conditions can be created with the -`Condition` class. Internally, the higher level components are implemented using these two primitives, and other -modules in the Sticky Tools suite of libraries also use the mutex for protecting various critical sections of code. +A `LockMode` is an enum entry that defines a specific mode of the lock. These modes are user defined + +##### Lock Modes + +Lock modes determine the symbols used to define the modes a lock can be in. + +Here is an example of a simple lock mode definition: +```swift + enum MyLockMode: LockMode { + case S /// Shared + case X /// Exclusive + } +``` +The mode on it's own only defines the symbols that can be used. You must define a `CompatibilityMatrix` and `GroupModeMatrix` to describe how the various modes interact with each other. + +> Note: Sticky Locking defines two built in `LockMode` enums along with corresponding compatibility and group mode matrix's. A simple readers-writer type named `SharedExclusiveLockMode` and an extended mode named `ExtendedLockMode` which is suitable for advanced data storage applications. + +##### Lock Mode Compatibility + +The ability to share a lock mode with another request/thread is determined by the lock mode compatibility matrix +supplied for the lock mode. For every new lock request, the matrix is checked and if the value at the index of the +current lock mode and the requested lock mode is `true` the lock will be granted concurrently, otherwise the request +will queue and wait. +```swift + let compatibilityMatrix: CompatibilityMatrix + = [ + /* Shared, Exclusive */ + /* Shared */ [true, false], + /* Exclusive */ [false, false], + ] +``` + +##### Lock Group Mode + +When multiple requests are compatible and granted concurrently the lock mode of the group must be calculated. This +is called the group mode. A new request is compatible with the members of the group if it is compatible with the +group mode. + +Sticky Locking uses the `GroupModeMatrix` to determine the group mode when a new request joins the group. +```swift + let groupModeMatrix: GroupModeMatrix + = [ + /* Requested Shared, Exclusive */ + /* Shared */ [S, X], + /* Exclusive */ [X, X], + ] +``` + +> See [Lock Wait State & Grant Behavior](Documentation/Lock Wait State & Grant Behavior.md) for a more detailed description and examples of how the locker behaves during locking. + +#### Built-in Lock Modes + +Sticky Locking contains two pre-defined `LockMode` enums and associated matrix's for use with various use cases. + +##### Shared-Exclusive Lock Mode + +A Shared Exclusive lock (a.k.a. readers-writer or multi-reader) allows concurrent access for read-only +operations, while write operations gain exclusive access. + +This allows multiple readers to gain shared access to a resource blocking all writers until no more +readers are reading. Writers gain exclusive access to the resource blocking all other readers and writers +until the operation is complete. + +The defined modes are: + + * S - Shared (Read) + * X - Exclusive (Write) + +The default `CompatibilityMatrix` is defined as: + +| Requested | S | X | +|:---------:|:------:|:------:| +| **S** |✔|✘| +| **X** |✘|✘| + +The default `GroupModeMatrix` is defined as: + +| Requested | S | X | +|:---------:|:------:|:------:| +| **S** | S | X | +| **X** | X | X | + + +##### Extended Lock Mode + +The `ExtendedLockMode` is a predefined `LockMode` implementation that can be used for complex database type applications. It defines +an extended set of lock modes including Update and Intention modes. + +The defined modes are: + + * IS - Intention Shared + * IX - Intention Exclusive + * S - Shared + * SIX - Shared Intention Exclusive + * U - Update + * X - Exclusive + +The default `CompatibilityMatrix` is defined as: + +| Requested | IS | IX | S | SIX | U | X | +|:---------:|:------:|:------:|:------:|:------:|:------:|:------:| +| **IS** |✔|✔|✔|✔|✔|✘| +| **IX** |✔|✔|✘|✘|✘|✘| +| **S** |✔|✘|✔|✘|✔|✘| +| **SIX** |✔|✘|✘|✘|✘|✘| +| **U** |✔|✘|✔|✘|✘|✘| +| **X** |✘|✘|✘|✘|✘|✘| + +The default `GroupModeMatrix` is defined as: + +| Requested | IS | IX | S | SIX | U | X | +|:---------:|:------:|:------:|:------:|:------:|:------:|:------:| +| **IS** | IS | IX | S | SIX | U | X | +| **IX** | IX | IX | SIX | SIX | X | X | +| **S** | S | SIX | S | SIX | U | X | +| **SIX** | SIX | SIX | SIX | SIX | SIX | X | +| **U** | U | X | U | SIX | U | X | +| **X** | X | X | X | X | X | X | + + +#### Resources & Hashing + +The `Locker` will lock and unlock any `Hashable` resource and it distinguishes the lock resources by the hash value, +therefore care must be taken to create a hashing algorithm that ensure uniqueness between individual objects of the +same type as well as the hash values between different types. + +If two resources hash to the same hash value, and the two requested modes are incompatible, then the collision may +cause spurious waits. + +Also keep in mind that the hashValue should never change for the resource. If the hashValue changes over the life of the +resource, the locker will consider it a different resource each time the hashValue changes. For instance, an Array<> hashValue +changes with each element that is added or removed from the array, therefore the Array<> instance itself could not be used +as a lock resource on its own. You would have to use a surrogate Resource such as a fixed String or integer as the Resource +identifier. + +### Mutexes & Conditions + +Sticky Locking also provides a low-level mutual exclusion lock through the `Mutex` class to protect critical sections of your code. In addition, wait conditions (`Condition`) are provided to allow for threads to wait for a mutex to become available. + +The mutual exclusion lock is provided through the `Mutex` class while wait conditions can be created with the `Condition` class. Internally, the higher level components are implemented using these two primitives, and other modules in the Sticky Tools suite of libraries also use the mutex for protecting various critical sections of code. ## Sources and Binaries diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 3cc48c2..2ef4a6f 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -25,21 +25,3 @@ add_custom_target(tests ALL add_custom_target(generate-tests) add_dependencies(tests generate-tests) - -# -# Build the LinuxMain file. -# -# Note: We do this for all platforms so that we are assured that it is maintained whenever a change is made to the tests. -# -add_custom_command( - DEPENDS build-tools - OUTPUT "${LINUX_MAIN}" - COMMAND "${BUILD_TOOLS_BIN}/xctest_tool.rb" - ARGS --package-path "${CMAKE_SOURCE_DIR}" - VERBATIM -) - -add_custom_target(linux-main - DEPENDS "${LINUX_MAIN}" generate-tests - ) -add_dependencies(tests linux-main) diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 7d8dea4..89aede3 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -1,213 +1,8 @@ - -/// build-tools: auto-generated - -#if os(Linux) || os(FreeBSD) - import XCTest -@testable import StickyLockingTests - -XCTMain([ - testCase(Lock_ModeTests.allTests), - testCase(MutexTests.allTests), - testCase(ConditionTests.allTests), - testCase(LockGroupModeMatrixTests.allTests), - testCase(LockerUnrestrictedCompatibilityMatrixTests.allTests), - testCase(WaitTimeTests.allTests), - testCase(LockerRequesterTests.allTests), - testCase(LockerRequestQueueTests.allTests), - testCase(LockerSharedExclusiveLockModeTests.allTests), - testCase(LockerRequestTests.allTests), - testCase(Lock_CompatibilityMatrixTests.allTests), - testCase(ExtendedLockModeTests.allTests), - testCase(LockerExtendedLockModeTests.allTests) -]) - -extension Lock_ModeTests { - static var allTests: [(String, (Lock_ModeTests) -> () throws -> Void)] { - return [ - ("testInit", testInit), - ("testEqualTrue", testEqualTrue), - ("testEqualFalse", testEqualFalse), - ("testDescription", testDescription), - ("testDebugDescription", testDebugDescription) - ] - } -} - -extension MutexTests { - static var allTests: [(String, (MutexTests) -> () throws -> Void)] { - return [ - ("testLockUnlock", testLockUnlock), - ("testLockUnlockRecursive", testLockUnlockRecursive), - ("testLockUnlockNonRecursiveBlocked", testLockUnlockNonRecursiveBlocked), - ("testLockBlocked", testLockBlocked), - ("testTryLockBlocked", testTryLockBlocked) - ] - } -} - -extension ConditionTests { - static var allTests: [(String, (ConditionTests) -> () throws -> Void)] { - return [ - ("testWait", testWait), - ("testWaitWithTimeout", testWaitWithTimeout), - ("testWaitWithTimeOutSignaled", testWaitWithTimeOutSignaled) - ] - } -} - -extension LockGroupModeMatrixTests { - static var allTests: [(String, (LockGroupModeMatrixTests) -> () throws -> Void)] { - return [ - ("testInitAndCompatible", testInitAndCompatible), - ("testDescription", testDescription), - ("testDebugDescription", testDebugDescription) - ] - } -} - -extension LockerUnrestrictedCompatibilityMatrixTests { - static var allTests: [(String, (LockerUnrestrictedCompatibilityMatrixTests) -> () throws -> Void)] { - return [ - ("testLockWhenExistingLock", testLockWhenExistingLock) - ] - } -} - -extension WaitTimeTests { - static var allTests: [(String, (WaitTimeTests) -> () throws -> Void)] { - return [ - ("testNow", testNow), - ("testAdd1Second", testAdd1Second), - ("testAdd1NanoSecondRollingTo1Second", testAdd1NanoSecondRollingTo1Second), - ("testAdd1Second1NanoSecondRollingTo2Seconds", testAdd1Second1NanoSecondRollingTo2Seconds), - ("testAddNegative1NanoSecondRollingTo0NanoSeconds", testAddNegative1NanoSecondRollingTo0NanoSeconds), - ("testAddNegative1Second1NanoSecondRollingTo2Seconds", testAddNegative1Second1NanoSecondRollingTo2Seconds), - ("testEqualTrue", testEqualTrue), - ("testEqualFalse", testEqualFalse) - ] - } -} - -extension LockerRequesterTests { - static var allTests: [(String, (LockerRequesterTests) -> () throws -> Void)] { - return [ - ("testInit", testInit), - ("testEqualTrue", testEqualTrue), - ("testEqualFalse", testEqualFalse), - ("testDescription", testDescription), - ("testDebugDescription", testDebugDescription) - ] - } -} - -extension LockerRequestQueueTests { - static var allTests: [(String, (LockerRequestQueueTests) -> () throws -> Void)] { - return [ - ("testCount", testCount), - ("testCountAfterRemoval", testCountAfterRemoval), - ("testFindTrue", testFindTrue), - ("testFindFalse", testFindFalse), - ("testAdd", testAdd), - ("testRemove", testRemove), - ("testRemoveNonExisting", testRemoveNonExisting) - ] - } -} - -extension LockerSharedExclusiveLockModeTests { - static var allTests: [(String, (LockerSharedExclusiveLockModeTests) -> () throws -> Void)] { - return [ - ("testInit", testInit), - ("testLockWhenNoOtherLocks", testLockWhenNoOtherLocks), - ("testLockWhenExistingLockThatThreadOwns", testLockWhenExistingLockThatThreadOwns), - ("testLockWhenExistingLockThatThreadDoesNotOwnButIsCompatible", testLockWhenExistingLockThatThreadDoesNotOwnButIsCompatible), - ("testLockWhenExistingIncompatibleLockForcesWait", testLockWhenExistingIncompatibleLockForcesWait), - ("testLockWhenExistingIncompatibleLockForcesWaitWithTimeout", testLockWhenExistingIncompatibleLockForcesWaitWithTimeout), - ("testLockWhenExistingIncompatibleLockAllowingTimeout", testLockWhenExistingIncompatibleLockAllowingTimeout), - ("testLockMultipleResourcesOnSameThread", testLockMultipleResourcesOnSameThread), - ("testLock", testLock), - ("testLockUnlockCycle", testLockUnlockCycle), - ("testLockUnlockCycleMultipleLocks", testLockUnlockCycleMultipleLocks), - ("testLockUnlockCycleRecursiveLocks", testLockUnlockCycleRecursiveLocks), - ("testLockUnlockCycleMultipleLocksNonConflicting", testLockUnlockCycleMultipleLocksNonConflicting), - ("testLockUnlockCycleCompatibleLockMultipleLockers", testLockUnlockCycleCompatibleLockMultipleLockers), - ("testUnlockWhenNothingLocked", testUnlockWhenNothingLocked), - ("testUnlockWhenLockNotOwnedByRequester", testUnlockWhenLockNotOwnedByRequester) - ] - } -} - -extension LockerRequestTests { - static var allTests: [(String, (LockerRequestTests) -> () throws -> Void)] { - return [ - ("testInit", testInit), - ("testInitWithDefaultLockerValue", testInitWithDefaultLockerValue), - ("testInitWithLocker", testInitWithLocker), - ("testStatusDefaultValue", testStatusDefaultValue), - ("testCountDefaultValue", testCountDefaultValue), - ("testMode", testMode), - ("testCountIncrementAssign", testCountIncrementAssign), - ("testWaitSignal", testWaitSignal), - ("testDescription", testDescription), - ("testDebugDescription", testDebugDescription) - ] - } -} - -extension Lock_CompatibilityMatrixTests { - static var allTests: [(String, (Lock_CompatibilityMatrixTests) -> () throws -> Void)] { - return [ - ("testInitAndCompatible", testInitAndCompatible), - ("testDescription", testDescription), - ("testDebugDescription", testDebugDescription) - ] - } -} - -extension ExtendedLockModeTests { - static var allTests: [(String, (ExtendedLockModeTests) -> () throws -> Void)] { - return [ - ("testInitArrayLiteral", testInitArrayLiteral), - ("testDefaultMatrix", testDefaultMatrix), - ("testDescription", testDescription), - ("testDebugDescription", testDebugDescription) - ] - } -} +import StickyLockingTests -extension LockerExtendedLockModeTests { - static var allTests: [(String, (LockerExtendedLockModeTests) -> () throws -> Void)] { - return [ - ("testInit", testInit), - ("testLockGrantWhenNoOtherLocks", testLockGrantWhenNoOtherLocks), - ("testLockWaitScenario1NotCompatibleWithGroupMode", testLockWaitScenario1NotCompatibleWithGroupMode), - ("testLockWaitScenario2ExistingWaitingRequests", testLockWaitScenario2ExistingWaitingRequests), - ("testLockWaitScenario3ExistingConversionRequests", testLockWaitScenario3ExistingConversionRequests), - ("testLockConversionScenario1Example1ImmediateConversion", testLockConversionScenario1Example1ImmediateConversion), - ("testLockConversionScenario1Example2ImmediateConversionWithWaitQueue", testLockConversionScenario1Example2ImmediateConversionWithWaitQueue), - ("testLockConversionScenario2Example1WaitOnConversionNoQueue", testLockConversionScenario2Example1WaitOnConversionNoQueue), - ("testLockConversionScenario2Example2WaitOnConversionWithConversionQueue", testLockConversionScenario2Example2WaitOnConversionWithConversionQueue), - ("testLockConversionScenario2Example3WaitOnConversionWithWaitingQueue", testLockConversionScenario2Example3WaitOnConversionWithWaitingQueue), - ("testLockConversionScenario3Example1Deadlock", testLockConversionScenario3Example1Deadlock), - ("testLockWhenExistingLockThatThreadOwns", testLockWhenExistingLockThatThreadOwns), - ("testLockPromotionNoContention", testLockPromotionNoContention), - ("testLockWhenExistingLockThatThreadDoesNotOwnButIsCompatible", testLockWhenExistingLockThatThreadDoesNotOwnButIsCompatible), - ("testLockWhenExistingIncompatibleLockForcesWait", testLockWhenExistingIncompatibleLockForcesWait), - ("testLockWhenExistingIncompatibleLockForcesWaitWithTimeout", testLockWhenExistingIncompatibleLockForcesWaitWithTimeout), - ("testLockWhenExistingIncompatibleLockAllowingTimeout", testLockWhenExistingIncompatibleLockAllowingTimeout), - ("testLockMultipleResourcesOnSameThread", testLockMultipleResourcesOnSameThread), - ("testLock", testLock), - ("testLockUnlockCycle", testLockUnlockCycle), - ("testLockUnlockCycleMultipleLocks", testLockUnlockCycleMultipleLocks), - ("testLockUnlockCycleRecursiveLocks", testLockUnlockCycleRecursiveLocks), - ("testLockUnlockCycleMultipleLocksNonConflicting", testLockUnlockCycleMultipleLocksNonConflicting), - ("testLockUnlockCycleCompatibleLockMultipleLockers", testLockUnlockCycleCompatibleLockMultipleLockers), - ("testUnlockWhenNothingLocked", testUnlockWhenNothingLocked), - ("testUnlockWhenLockNotOwnedByRequester", testUnlockWhenLockNotOwnedByRequester) - ] - } -} +var tests = [XCTestCaseEntry]() +tests += StickyLockingTests.__allTests() -#endif +XCTMain(tests) diff --git a/Tests/StickyLockingTests/DocumentationExampleTests.swift b/Tests/StickyLockingTests/DocumentationExampleTests.swift new file mode 100644 index 0000000..0df971b --- /dev/null +++ b/Tests/StickyLockingTests/DocumentationExampleTests.swift @@ -0,0 +1,78 @@ +/// +/// DocumentationExampleTests.swift +/// +/// Copyright 2019 Tony Stone +/// +/// 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. +/// +/// Created by Tony Stone on 2/22/19. +/// +import XCTest + +import StickyLocking + +class DocumentationExampleTests: XCTestCase { + + /// Documentation Examples + /// + /// These test should represent the documentations examples exactly + /// with no XCTAsserts added. These tests are to make sure that the + /// examples compile and run. + /// + + func testSimpleLockerCreation() throws { + + // Create a locker using the built-in `SharedExclusiveLockMode`, and it's `CompatibilityMatrix` & `GroupModeMatrix` + let _ = Locker(compatibilityMatrix: SharedExclusiveLockMode.compatibilityMatrix, groupModeMatrix: SharedExclusiveLockMode.groupModeMatrix) + } + + func testSimpleLockerCreationExplicitelySpecialized() throws { + + // Create a locker using the built-in `SharedExclusiveLockMode`, and it's `CompatibilityMatrix` & `GroupModeMatrix` + let _ = Locker(compatibilityMatrix: SharedExclusiveLockMode.compatibilityMatrix, groupModeMatrix: SharedExclusiveLockMode.groupModeMatrix) + } + + func testLockAndUnlockSimpleResource() throws { + + struct ProtectedArray: ExpressibleByArrayLiteral { + private var storage: [T] = [] + private let locker = Locker(compatibilityMatrix: SharedExclusiveLockMode.compatibilityMatrix, groupModeMatrix: SharedExclusiveLockMode.groupModeMatrix) + private let resource = "storage" + + init(arrayLiteral elements: T...) { + self.storage = elements.map({ $0 }) + } + + subscript(index: Int) -> T { + get { + locker.lock(self.resource, mode: .S) + defer { locker.unlock(self.resource) } + + return storage[index] + } + set { + locker.lock(self.resource, mode: .X) + defer { locker.unlock(self.resource) } + + storage[index] = newValue + } + } + } + + var array: ProtectedArray = ["String 1", "String 2", "String 3"] + + array[0] = "New String" + + print(array[0]) + } +} diff --git a/Tests/StickyLockingTests/Locker+SharedExclusiveLockModeTests.swift b/Tests/StickyLockingTests/Locker+SharedExclusiveLockModeTests.swift index b7e4482..6bbb1d5 100644 --- a/Tests/StickyLockingTests/Locker+SharedExclusiveLockModeTests.swift +++ b/Tests/StickyLockingTests/Locker+SharedExclusiveLockModeTests.swift @@ -57,12 +57,12 @@ class LockerSharedExclusiveLockModeTests: XCTestCase { let input = "database1" let group = DispatchGroup() - /// Aquire a lock on the main thread. + /// Acquire a lock on the main thread. XCTAssertEqual(self.locker.lock(input, mode: .S), .granted) /// Get a shared lock on a background thread DispatchQueue.global().async(group: group) { - /// Now lock with a compatible lock in the backgroun. + /// Now lock with a compatible lock in the background. XCTAssertEqual(self.locker.lock(input, mode: .S), .granted) XCTAssertEqual(self.locker.unlock(input), true) } diff --git a/Tests/StickyLockingTests/XCTestManifests.swift b/Tests/StickyLockingTests/XCTestManifests.swift new file mode 100644 index 0000000..2f01b19 --- /dev/null +++ b/Tests/StickyLockingTests/XCTestManifests.swift @@ -0,0 +1,189 @@ +import XCTest + +extension ConditionTests { + static let __allTests = [ + ("testWait", testWait), + ("testWaitWithTimeout", testWaitWithTimeout), + ("testWaitWithTimeOutSignaled", testWaitWithTimeOutSignaled), + ] +} + +extension DocumentationExampleTests { + static let __allTests = [ + ("testSimpleLockerCreation", testSimpleLockerCreation), + ] +} + +extension ExtendedLockModeTests { + static let __allTests = [ + ("testDebugDescription", testDebugDescription), + ("testDefaultMatrix", testDefaultMatrix), + ("testDescription", testDescription), + ("testInitArrayLiteral", testInitArrayLiteral), + ] +} + +extension LockGroupModeMatrixTests { + static let __allTests = [ + ("testDebugDescription", testDebugDescription), + ("testDescription", testDescription), + ("testInitAndCompatible", testInitAndCompatible), + ] +} + +extension Lock_CompatibilityMatrixTests { + static let __allTests = [ + ("testDebugDescription", testDebugDescription), + ("testDescription", testDescription), + ("testInitAndCompatible", testInitAndCompatible), + ] +} + +extension Lock_ModeTests { + static let __allTests = [ + ("testDebugDescription", testDebugDescription), + ("testDescription", testDescription), + ("testEqualFalse", testEqualFalse), + ("testEqualTrue", testEqualTrue), + ("testInit", testInit), + ] +} + +extension LockerExtendedLockModeTests { + static let __allTests = [ + ("testInit", testInit), + ("testLock", testLock), + ("testLockConversionScenario1Example1ImmediateConversion", testLockConversionScenario1Example1ImmediateConversion), + ("testLockConversionScenario1Example2ImmediateConversionWithWaitQueue", testLockConversionScenario1Example2ImmediateConversionWithWaitQueue), + ("testLockConversionScenario2Example1WaitOnConversionNoQueue", testLockConversionScenario2Example1WaitOnConversionNoQueue), + ("testLockConversionScenario2Example2WaitOnConversionWithConversionQueue", testLockConversionScenario2Example2WaitOnConversionWithConversionQueue), + ("testLockConversionScenario2Example3WaitOnConversionWithWaitingQueue", testLockConversionScenario2Example3WaitOnConversionWithWaitingQueue), + ("testLockConversionScenario3Example1Deadlock", testLockConversionScenario3Example1Deadlock), + ("testLockGrantWhenNoOtherLocks", testLockGrantWhenNoOtherLocks), + ("testLockMultipleResourcesOnSameThread", testLockMultipleResourcesOnSameThread), + ("testLockPromotionNoContention", testLockPromotionNoContention), + ("testLockUnlockCycle", testLockUnlockCycle), + ("testLockUnlockCycleCompatibleLockMultipleLockers", testLockUnlockCycleCompatibleLockMultipleLockers), + ("testLockUnlockCycleMultipleLocks", testLockUnlockCycleMultipleLocks), + ("testLockUnlockCycleMultipleLocksNonConflicting", testLockUnlockCycleMultipleLocksNonConflicting), + ("testLockUnlockCycleRecursiveLocks", testLockUnlockCycleRecursiveLocks), + ("testLockWaitScenario1NotCompatibleWithGroupMode", testLockWaitScenario1NotCompatibleWithGroupMode), + ("testLockWaitScenario2ExistingWaitingRequests", testLockWaitScenario2ExistingWaitingRequests), + ("testLockWaitScenario3ExistingConversionRequests", testLockWaitScenario3ExistingConversionRequests), + ("testLockWhenExistingIncompatibleLockAllowingTimeout", testLockWhenExistingIncompatibleLockAllowingTimeout), + ("testLockWhenExistingIncompatibleLockForcesWait", testLockWhenExistingIncompatibleLockForcesWait), + ("testLockWhenExistingIncompatibleLockForcesWaitWithTimeout", testLockWhenExistingIncompatibleLockForcesWaitWithTimeout), + ("testLockWhenExistingLockThatThreadDoesNotOwnButIsCompatible", testLockWhenExistingLockThatThreadDoesNotOwnButIsCompatible), + ("testLockWhenExistingLockThatThreadOwns", testLockWhenExistingLockThatThreadOwns), + ("testUnlockWhenLockNotOwnedByRequester", testUnlockWhenLockNotOwnedByRequester), + ("testUnlockWhenNothingLocked", testUnlockWhenNothingLocked), + ] +} + +extension LockerRequestQueueTests { + static let __allTests = [ + ("testAdd", testAdd), + ("testCount", testCount), + ("testCountAfterRemoval", testCountAfterRemoval), + ("testFindFalse", testFindFalse), + ("testFindTrue", testFindTrue), + ("testRemove", testRemove), + ("testRemoveNonExisting", testRemoveNonExisting), + ] +} + +extension LockerRequestTests { + static let __allTests = [ + ("testCountDefaultValue", testCountDefaultValue), + ("testCountIncrementAssign", testCountIncrementAssign), + ("testDebugDescription", testDebugDescription), + ("testDescription", testDescription), + ("testInit", testInit), + ("testInitWithDefaultLockerValue", testInitWithDefaultLockerValue), + ("testInitWithLocker", testInitWithLocker), + ("testMode", testMode), + ("testStatusDefaultValue", testStatusDefaultValue), + ("testWaitSignal", testWaitSignal), + ] +} + +extension LockerRequesterTests { + static let __allTests = [ + ("testDebugDescription", testDebugDescription), + ("testDescription", testDescription), + ("testEqualFalse", testEqualFalse), + ("testEqualTrue", testEqualTrue), + ("testInit", testInit), + ] +} + +extension LockerSharedExclusiveLockModeTests { + static let __allTests = [ + ("testInit", testInit), + ("testLock", testLock), + ("testLockMultipleResourcesOnSameThread", testLockMultipleResourcesOnSameThread), + ("testLockUnlockCycle", testLockUnlockCycle), + ("testLockUnlockCycleCompatibleLockMultipleLockers", testLockUnlockCycleCompatibleLockMultipleLockers), + ("testLockUnlockCycleMultipleLocks", testLockUnlockCycleMultipleLocks), + ("testLockUnlockCycleMultipleLocksNonConflicting", testLockUnlockCycleMultipleLocksNonConflicting), + ("testLockUnlockCycleRecursiveLocks", testLockUnlockCycleRecursiveLocks), + ("testLockWhenExistingIncompatibleLockAllowingTimeout", testLockWhenExistingIncompatibleLockAllowingTimeout), + ("testLockWhenExistingIncompatibleLockForcesWait", testLockWhenExistingIncompatibleLockForcesWait), + ("testLockWhenExistingIncompatibleLockForcesWaitWithTimeout", testLockWhenExistingIncompatibleLockForcesWaitWithTimeout), + ("testLockWhenExistingLockThatThreadDoesNotOwnButIsCompatible", testLockWhenExistingLockThatThreadDoesNotOwnButIsCompatible), + ("testLockWhenExistingLockThatThreadOwns", testLockWhenExistingLockThatThreadOwns), + ("testLockWhenNoOtherLocks", testLockWhenNoOtherLocks), + ("testUnlockWhenLockNotOwnedByRequester", testUnlockWhenLockNotOwnedByRequester), + ("testUnlockWhenNothingLocked", testUnlockWhenNothingLocked), + ] +} + +extension LockerUnrestrictedCompatibilityMatrixTests { + static let __allTests = [ + ("testLockWhenExistingLock", testLockWhenExistingLock), + ] +} + +extension MutexTests { + static let __allTests = [ + ("testLockBlocked", testLockBlocked), + ("testLockUnlock", testLockUnlock), + ("testLockUnlockNonRecursiveBlocked", testLockUnlockNonRecursiveBlocked), + ("testLockUnlockRecursive", testLockUnlockRecursive), + ("testTryLockBlocked", testTryLockBlocked), + ] +} + +extension WaitTimeTests { + static let __allTests = [ + ("testAdd1NanoSecondRollingTo1Second", testAdd1NanoSecondRollingTo1Second), + ("testAdd1Second", testAdd1Second), + ("testAdd1Second1NanoSecondRollingTo2Seconds", testAdd1Second1NanoSecondRollingTo2Seconds), + ("testAddNegative1NanoSecondRollingTo0NanoSeconds", testAddNegative1NanoSecondRollingTo0NanoSeconds), + ("testAddNegative1Second1NanoSecondRollingTo2Seconds", testAddNegative1Second1NanoSecondRollingTo2Seconds), + ("testEqualFalse", testEqualFalse), + ("testEqualTrue", testEqualTrue), + ("testNow", testNow), + ] +} + +#if !canImport(ObjectiveC) +public func __allTests() -> [XCTestCaseEntry] { + return [ + testCase(ConditionTests.__allTests), + testCase(DocumentationExampleTests.__allTests), + testCase(ExtendedLockModeTests.__allTests), + testCase(LockGroupModeMatrixTests.__allTests), + testCase(Lock_CompatibilityMatrixTests.__allTests), + testCase(Lock_ModeTests.__allTests), + testCase(LockerExtendedLockModeTests.__allTests), + testCase(LockerRequestQueueTests.__allTests), + testCase(LockerRequestTests.__allTests), + testCase(LockerRequesterTests.__allTests), + testCase(LockerSharedExclusiveLockModeTests.__allTests), + testCase(LockerUnrestrictedCompatibilityMatrixTests.__allTests), + testCase(MutexTests.__allTests), + testCase(WaitTimeTests.__allTests), + ] +} +#endif