Copyright (c) 2021 Shayan Habibi Copyright (c) 2019 Mariusz Orlikowski - algorithm
Pure nim implementation of a specialised lock which enforces linear operations of write, reading and freeing/deallocating while allowing multiple readers, and a single writer and deallocator; known as the WRFLock.
Principally, the WRFLock acts as a state machine. Its advantages is the use of futexes or schedule yielding and a incredibly small memory footprint (8 bytes).
It's primary implementation purpose is for the Single Producer Multiple Producer ring buffer queue proposed by the algorithm author Mariusz Orlikowski. It's use is quite flexible however and can be used in a variety of designs.
While the schedule yielding waits should be platform independent, the blocking waits are only implemented on operating systems that have been tested for functional implementations of futexes. Linux futex, darwin (macosx) ulocks and windows WaitOnAddress kernel primitives are used.
Procs
proc acquire(lock: WRFLock; op: static WRFLockOp): bool {.discardable.}
-
Acquires the specified access in op from the WRFLock.
acquire(lock, Read) is therefore the same as rAcquire(lock)
acquire(lock, Write) is therefore the same as wAcquire(lock)
acquire(lock, Free) is therefore the same as fAcquire(lock)
Source Edit proc fAcquire(lock: WRFLock): bool {.discardable, ...raises: [], tags: [].}
-
Acquires free access to the WRFLock. Will return false if there is already a free/deallocater.
This is a non blocking operation and must be coupled with a successful fWait/fTimeWait/fTryWait followed by a fRelease.
Source Edit proc freeWRFLock(lock: WRFLock) {....raises: [], tags: [].}
- Deallocates a WRFLock. Source Edit
proc fRelease(lock: WRFLock): bool {.discardable, ...raises: [], tags: [].}
-
Releases free access to the WRFLock. Will return false if there isn't a registered free/deallocater access.
This is a non blocking operation and must be coupled with a prior fAcquire.
Success of this operation will allow writers to proceed by returning wWait operations successfuly.
Source Edit proc fWait(lock: WRFLock; time: int = 0): bool {....raises: [], tags: [TimeEffect].}
-
Waits for time in msecs (0 = infinite) for permission to execute its free operations. Returns false if it times out or otherwise errors (depending on OS).
NOTE: At most the true time waited may be up to double the passed time when blocking. This is not the same when schedule yielding.
Source Edit proc getCurrState(lock: WRFLock): WRFLockOp {....raises: [ValueError], tags: [].}
-
For debugging purposes; checks what state the lock is currently in.
raises ValueError if no valid state is found.
Source Edit proc initWRFLock(waitType: set[WaitType] = {}; pshared: bool = false): WRFLock {. ...raises: [], tags: [].}
-
Initialise a WRFLock. pShared arg is nonfunctional at the moment.
Default operation for write, read and free waits are blocking. Pass WriteYield, ReadYield and/or FreeYield to waitType to change the operations to schedule yielding respectively.
Note: Yield flags take precedence over Block flags when there are conflicting flags in the waitType set
Source Edit proc rAcquire(lock: WRFLock): bool {.discardable, ...raises: [], tags: [].}
-
Acquires read access to the WRFLock. Will return false if there are too many readers already holding access (65535 readers).
This is a non blocking operation and must be coupled with a successful rWait/rTimeWait/rTryWait followed by a rRelease.
Source Edit proc release(lock: WRFLock; op: static WRFLockOp): bool {.discardable.}
-
Releases the specified access in op from the WRFLock.
release(lock, Read) is therefore the same as rRelease(lock)
release(lock, Write) is therefore the same as wRelease(lock)
release(lock, Free) is therefore the same as fRelease(lock)
Source Edit proc rRelease(lock: WRFLock): bool {.discardable, ...raises: [], tags: [].}
-
Releases read access to the WRFLock. Will return false if there isn't a registered read access.
This is a non blocking operation and must be coupled with a prior rAcquire to prevent overflow errors.
Success of this operation reduces the reader counter by 1. When all readers release their access, the thread with 'free' access will be allowed to continue via returning fWait operations successfully.
Source Edit proc rWait(lock: WRFLock; time: int = 0): bool {....raises: [], tags: [TimeEffect].}
-
Waits for time in msecs (0 = infinite) for permission to execute its read operations. Returns false if it times out or otherwise errors (depending on OS).
NOTE: At most the true time waited may be up to double the passed time when blocking. This is not the same when schedule yielding.
Source Edit proc setFlags(lock: WRFLock; flags: set[WaitType]) {....raises: [], tags: [].}
- 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. Source Edit
proc wAcquire(lock: WRFLock): bool {.discardable, ...raises: [], tags: [].}
-
Acquires write access to the WRFLock. Will return false if there is already a writer holding write access.
This is a non blocking operation and must be coupled with a successful wWait/wTimeWait/wTryWait followed by a wRelease.
Source Edit proc wRelease(lock: WRFLock): bool {.discardable, ...raises: [], tags: [].}
-
Releases write access to the WRFLock. Will return false if there isn't a registered write access.
This is a non blocking operation and must be coupled with a prior wAcquire.
Success of this operation will allow readers to proceed by returning rWait operations successfuly.
Source Edit proc wWait(lock: WRFLock; time: int = 0): bool {....raises: [], tags: [TimeEffect].}
-
Waits for time in msecs (0 = infinite) for permission to execute its write operations. Returns false if it times out or otherwise errors (depending on OS).
NOTE: At most the true time waited may be up to double the passed time when blocking. This is not the same when schedule yielding.
Source Edit
Templates
template whileTryingLock(lock: WRFLock; op: static WRFLockOp; body: untyped; succ: untyped): untyped
-
Convenience template; raises OverFlow error if there is already too many accesses of the given op to the lock.
Acquires access, and then continuously evaluates body until the lock allows the given op, at which time it performs succ before releasing access.
Source Edit