The lock protocol operates on lock objects to implement simple advisory lock functions. The lock objects may be associated with streams, memory objects or any other arbitrary objects. Locks may be interdependent, as in case of overlapping memory blocks, or simply refer to objects, as in case of streams.
The lock objects are tokens of lock ownership, and every party sharing the lockable resource must have at least one. Losing the lock object is equivalent to releasing the lock, if it wasn't already released.
There are two ways of closing the lock - shared and exclusive. Only one party can hold the exclusive lock at any time; however any number of parties can hold the shared lock simultaneously. If there's at least one party which keeps the lock closed as shared, no other party can close that lock as exclusive. In general, shared locks are used to prevent the resource from being changed when it is in use; exclusive locks are used to prevent everybody else from using the resource while it is changed.
In case when read-modify-write sequence is used and the goal is to ensure consistency during data update phase (as opposed to ensuring uniqueness, when data becomes invalid when it is first read) the resource may be first locked as shared and then the lock may be "upgraded" to exclusive for write cycle.
The locks are assumed to be advisory, i.e. locking the resource does not actually prevent it from being manipulated by other parties; instead it informs those parties that the resource is locked and expects them to refrain from using the resource until the lock is released. The reason for choosing advisory locking instead of mandatory is that the locking party may delegate performing actual operations on resource to other processes.
Lock protocol includes the following requests:
LOCK_SHARED()
This request waits until the lock is opened or becomes locked as shared and then locks it as shared. LOCK_SHARED can be used to downgrade lock from exclusive to shared. In both cases LOCK_SHARED returns result code 1 indicating that the lock was claimed successfully.
If this request is canceled it will return immediately leaving the state of the lock unchanged; the result code in this case will be 0.
LOCK_EXCLUSIVE()
This request waits until the lock is opened and then locks it as exclusive. LOCK_EXCLUSIVE can be used to upgrade lock from shared to exclusive. In both cases LOCK_EXCLUSIVE returns result code 1 indicating that the lock was claimed successfully.
If this request is canceled it will return immediately leaving the state of the lock unchanged; the result code in this case will be 0.
LOCK_NB_SHARED()
This is a non-blocking version of LOCK_SHARED; this request will not wait if the lock is closed as exclusive but will return immediately with return code 0 to indicate that the attempt to claim the lock failed.
If the lock was open or locked as shared, LOCK_NB_SHARED will lock it as shared and return result code 1.
Like LOCK_SHARED this request can be used to downgrade the lock, in which case it always succeeds.
LOCK_NB_EXCLUSIVE()
This is a non-blocking version of LOCK_EXCLUSIVE; this request will not wait if the lock is closed but will return immediately with return code 0 to indicate that the attempt to claim the lock failed.
If the lock was open, LOCK_NB_EXCLUSIVE will lock it as exclusive and return result code 1.
Like LOCK_EXCLUSIVE this request can be used to upgrade the lock, returning immediately if upgrade failed.
LOCK_RELEASE()
This request releases (opens) the lock, that may cause parties waiting on LOCK_SHARED or LOCK_EXCLUSIVE to claim it.
This request is granted.
LOCK_CHECK() -> locker_id
LOCK_CHECK returns 0 and access to null object if the lock is open. If the lock is closed it returns the number of seconds (but no less than 1) since the lock was closed and the access to an object identifying the party which holds the lock for the longest time. The locker_id access is the same as was used to create the lock object.
This request is granted.