Skip to content

Commit

Permalink
added convenience templates and setFlags
Browse files Browse the repository at this point in the history
  • Loading branch information
shayanhabibi committed Dec 21, 2021
1 parent 6a04887 commit 76cb192
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 19 deletions.
40 changes: 37 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
# WRFLock
<div id="top"></div>

<br />

<div align="center">
<a href="https://github.com/nim-works/nimskull">
<img src="papers/assets/logo.png" height="180"/>
</a>

<p align="center">
Multi-threaded synchronisation state machine.
<br />
<br />
</p>
</div>

<br />

> Pure nim implementation of the synchronisation object proposed by Mariusz Orlikowski in 'Single Producer - Multiple Consumers Ring Buffer Data Distribution System with Memory Management'
Expand All @@ -12,14 +28,25 @@ use case.

[The documentation is kept up to date and is generated from the source.](https://shayanhabibi.github.io/wrflock/wrflock.html)

<p align="right">(<a href="#top">back to top</a>)</p>

## Principle

A thread will acquire the capability to perform an action (write, read, free)
and then **wait** for that action to be allowed. Once the thread has completed
its action, it then releases the capability which will then allow the following
action to be completed.

**Principally, the wrflock is a state machine.**
<details><summary><b>Principally, the wrflock is a state machine.</b></summary><br />
<div align="center">

![Figure 5 from Mariusz Orlikowskis paper](papers/assets/2021-12-21-14-17-31.png "Figure 5 from Mariusz Orlikowskis paper showing the WRFLock as a state machine")

</div>
</details>

<p align="right">(<a href="#top">back to top</a>)</p>


## Use Cases

Expand All @@ -30,6 +57,8 @@ which is allowed when all reads have released the lock to deallocate/cleanse the
memory (or simply allow the write to proceed) before allowing the next write to
execute.

<p align="right">(<a href="#top">back to top</a>)</p>

## Usage

> **Note: the api has not been finalised and is subject to change**
Expand Down Expand Up @@ -79,4 +108,9 @@ to the scheduler for any of the actions.

```nim
let lock = initWRFLock([wWaitYield, rWaitYield, fWaitYield])
```
```

Convenience templates and extra procedures such as setFlags can be found in the
documentation.

<p align="right">(<a href="#top">back to top</a>)</p>
Binary file not shown.
Binary file added papers/assets/2021-12-21-14-17-31.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added papers/assets/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 9 additions & 12 deletions tests/test.nim
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,15 @@ proc writeLock() {.thread.} =

proc readLock() {.thread.} =
sleep(200)
doassert lock.rAcquire()
lock.rWait()
try:
check counter.load == 1
except Exception:
checkpoint " Lock allowed read before the writer released"
checkpoint " counter: ", load counter
checkpoint "expected: ", 1
raise

doassert lock.rRelease()

lock.withRLock:
try:
check counter.load == 1
except Exception:
checkpoint " Lock allowed read before the writer released"
checkpoint " counter: ", load counter
checkpoint "expected: ", 1
raise

proc freeLock() {.thread.} =
sleep(500)
doassert lock.fAcquire()
Expand Down
133 changes: 130 additions & 3 deletions wrflock.nim
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ proc `[]`(lock: WRFLock, idx: int): var uint32 {.inline.} =
# ============================================================================ #
proc initWRFLockObj(lock: var WRFLockObj; waitType: openArray[int]; pshared: bool) =
if pshared:
lock.data = 0u or nextStateWriteMask64
else:
lock.data = privateMask64 or nextStateWriteMask64
else:
lock.data = 0u or nextStateWriteMask64

if wWaitYield in waitType:
lock.data = lock.data or wWaitYieldMask64
Expand Down Expand Up @@ -427,4 +427,131 @@ proc fTryWait*(lock: WRFLock): bool {.discardable.} =
if (data and currStateFreeMask32) == 0u:
result = false
else:
result = true
result = true

proc setFlags*(lock: WRFLock, flags: openArray[int]): bool {.discardable.} =
## EXPERIMENTAL - non blocking change of flags on a lock. Any change from
## a blocking wait to a schedule yield will result in all waiters being awoken.
## Operations that are blocking will return to sleep after checking their condition
## while the schedule yield operations will yield after checking their condition.
var newData: uint32
var data = lock.loadState
var mustWake: bool

while true:
mustWake = false
newData = data

if wWaitYield in flags and (data and wWaitYieldMask32) == 0u:
mustWake = true
newData = newData or wWaitYieldMask32
elif wWaitBlock in flags and (data and wWaitYieldMask32) != 0u:
newData = newData xor wWaitYieldMask32

if rWaitYield in flags and (data and rWaitYieldMask32) == 0u:
mustWake = true
newData = newData or rWaitYieldMask32
elif rWaitBlock in flags and (data and rWaitYieldMask32) != 0u:
newData = newData xor rWaitYieldMask32

if fWaitYield in flags and (data and fWaitYieldMask32) == 0u:
mustWake = true
newData = newData or fWaitYieldMask32
elif fWaitBlock in flags and (data and fWaitYieldMask32) != 0u:
newData = newData xor fWaitYieldMask32
if lock[stateOffset].addr.atomicCompareExchange(data.addr, newdata.addr, true, ATOMIC_RELEASE, ATOMIC_RELAXED):
if mustWake:
wakeAll(lock[stateOffset].addr)
break
else:
data = lock.loadState
result = true

type
CurrState* = enum
Uninit, Write, Read, Free

proc getCurrState*(lock: WRFLock): CurrState =
## For debugging purposes; checks what state the lock is currently in.
##
## Returns Uninit if no valid state is found.
let data = lock.loadState
if (data and currStateReadMask32) != 0u:
result = CurrState.Read
elif (data and currStateWriteMask32) != 0u:
result = CurrState.Write
elif (data and currStateFreeMask32) != 0u:
result = CurrState.Free
else:
result = CurrState.Uninit

template withWLock*(lock: WRFLock; body: untyped): untyped =
## Convenience template; raises OverFlow error if there is already a writer.
##
## Blocks until the lock allows writing
if not lock.wAcquire():
raise newException(OverflowError, "Tried to acquire write status to a WRFLock that already has a writer")
else:
lock.wWait()
body
doAssert lock.wRelease(), "Releasing write status of the WRFLock was unsuccesful"

template withRLock*(lock: WRFLock; body: untyped): untyped =
## Convenience template; raises OverFlow error if there is already too many readers.
##
## Blocks until the lock allows reading
if not lock.rAcquire():
raise newException(OverflowError, "Tried to acquire read status to a WRFLock that has no tokens remaining. Ensure you release reads with rRelease()")
else:
lock.rWait()
body
doAssert lock.rRelease(), "Releasing read status of the WRFLock was unsuccesful"

template withFLock*(lock: WRFLock; body: untyped): untyped =
## Convenience template; raises OverFlow error if there is already a free/deallocater.
##
## Blocks until the lock allows free/deallocating
if not lock.fAcquire():
raise newException(OverflowError, "Tried to acquire free status to a WRFLock that already has a free/deallocator")
else:
lock.fWait()
body
doAssert lock.fRelease(), "Releasing free status of the WRFLock was unsuccesful"

template whileTryingWLock*(lock: WRFLock; body: untyped; succ: untyped): untyped =
## Convenience template; raises OverFlow error if there is already a writer.
##
## Acquires write access, and then continuously evaluates `body` until the lock
## allows writing, at which time it performs `succ` before releasing write access.
if not lock.wAcquire():
raise newException(OverflowError, "Tried to acquire write status to a WRFLock that already has a writer")
while not lock.wTryWait():
body
succ
doAssert lock.wRelease(), "Releasing write status of the WRFLock was unsuccesful"

template whileTryingRLock*(lock: WRFLock; body: untyped; succ: untyped): untyped =
## Convenience template; raises OverFlow error if there is too many readers.
##
## Acquires read access, and then continuously evaluates `body` until the lock
## allows reading, at which time it performs `succ` before releasing read access.
if not lock.rAcquire():
raise newException(OverflowError, "Tried to acquire read status to a WRFLock that has no tokens remaining. Ensure you release reads with rRelease()")
while not lock.rTryWait():
body
succ
doAssert lock.rRelease(), "Releasing read status of the WRFLock was unsuccesful"

template whileTryingFLock*(lock: WRFLock; body: untyped; succ: untyped): untyped =
## Convenience template; raises OverFlow error if there is already a free/deallocator.
##
## Acquires free access, and then continuously evaluates `body` until the lock
## allows freeing, at which time it performs `succ` before releasing free access.
if not lock.fAcquire():
raise newException(OverflowError, "Tried to acquire free status to a WRFLock that already has a free/deallocator")
while not lock.fTryWait():
body
succ
doAssert lock.fRelease(), "Releasing free status of the WRFLock was unsuccesful"


2 changes: 1 addition & 1 deletion wrflock.nimble
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = "0.2.2"
version = "0.3.0"
author = "Shayan Habibi"
description = "Write, Read, Free lock primitive"
license = "MIT"
Expand Down

0 comments on commit 76cb192

Please sign in to comment.