wrflock

  Source   Edit

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.

Types

WaitType = enum
  WriteBlock = 1, WriteYield = 2, ReadBlock = 4, ReadYield = 8, FreeBlock = 16,
  FreeYield = 32
Flags for setting the wait behaviour of WRFLock   Source   Edit
WRFLock = ptr WRFLockObj
  Source   Edit
WRFLockOp = enum
  Write, Read, Free
  Source   Edit

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 fTryWait(lock: WRFLock): bool {....raises: [], tags: [].}
Non blocking check to see if the thread can perform its free/cleaning actions.   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 rTryWait(lock: WRFLock): bool {....raises: [], tags: [].}
Non blocking check to see if the thread can perform its read actions.   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 tryWait(lock: WRFLock; op: static WRFLockOp): bool
  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 wait(lock: WRFLock; op: static WRFLockOp; time: int = 0): bool
  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 wTryWait(lock: WRFLock): bool {....raises: [], tags: [].}
Non blocking check to see if the thread can perform its write actions.   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
template withLock(lock: WRFLock; op: static WRFLockOp; body: untyped): untyped

Convenience template; raises OverFlow error if there is already too many accesses of the given op to the lock.

Blocks until the lock allows the given op to proceed

  Source   Edit