Module: ZK::Locker
- Defined in:
- lib/zk/locker.rb,
lib/zk/locker/semaphore.rb,
lib/zk/locker/locker_base.rb,
lib/zk/locker/lock_options.rb,
lib/zk/locker/shared_locker.rb,
lib/zk/locker/exclusive_locker.rb
Overview
These lock instances are not safe for use across threads. If you want to use the same Locker instance between threads, it is your responsibility to synchronize operations.
Lockers are instances that hold the lock. A single connection may have many instances trying to lock the same path and only one (in the case of an ExclusiveLocker) will hold the lock.
This module contains implementations of the locking primitives described in the ZooKeeper recipes that allow a user to obtain cluster-wide global locks (with both blocking and non-blocking semantics). One important (and attractive) attribute of these locks is that they are automatically released when the connection closes. You never have to worry about a stale lock mucking up coordination because some process was killed and couldn't clean up after itself.
There are both shared and exclusive lock implementations.
The implementation is fairly true to the description in the recipes, and
the key is generated using a combination of the name provided, and a
root_lock_node
path whose default value is /_zklocking
. If you look
below at the 'Key path creation' example, you'll see that we do a very
simple escaping of the name given. There was a distinct tradeoff to be made
between making the locks easy to debug in zookeeper and making them more
collision tolerant. If the key naming causes issues, please file a bug and
we'll try to work out a solution (hearing about use cases is incredibly helpful
in guiding development).
If you're interested in how the algorithm works, have a look at ExclusiveLocker's documentation.
Shared/Exclusive lock interaction
The shared and exclusive locks can be used to create traditional read/write locks, and are designed to be fair in terms of ordering. Given the following children of a given lock node (where 'sh' is shared, and 'ex' is exclusive)
[ex00, sh01, sh02, sh03, ex04, ex05, sh06, sh07]
Assuming all of these locks are blocking, the following is how the callers would obtain the lock
ex00
holds the lock, everyone else is blockedex00
releases the lock[sh01, sh02, sh03]
all unblock and hold a shared lock[ex04, ...]
are blocked
[sh01, sh02, sh03]
all releaseex04
is unblocked, holds the lock[ex05, ...]
are blocked
ex04
releases the lockex05
unblocks, holds the lock[sh06, sh07]
are blocked
ex05
releases the lock[sh06, sh07]
are unblocked, hold the lock
In this way, the locks are fair-queued (FIFO), and shared locks will not starve exclusive locks (all lock types have the same priority)
Defined Under Namespace
Classes: ExclusiveLocker, LockerBase, Semaphore, SharedLocker
Constant Summary collapse
- SHARED_LOCK_PREFIX =
'sh'.freeze
- EXCLUSIVE_LOCK_PREFIX =
'ex'.freeze
- SEMAPHORE_LOCK_PREFIX =
'sem'.freeze
Class Attribute Summary collapse
-
.default_root_lock_node ⇒ Object
the default root path we will use when a value is not given to a constructor.
Class Method Summary collapse
-
.cleanup(client, root_lock_node = default_root_lock_node) ⇒ Object
Clean up dead locker directories.
-
.exclusive_locker(client, name, *args) ⇒ ExclusiveLocker
Create an ExclusiveLocker instance.
-
.semaphore(client, name, semaphore_size, *args) ⇒ Semaphore
Create a Semaphore instance.
-
.shared_locker(client, name, *args) ⇒ SharedLocker
Create a SharedLocker instance.
Class Attribute Details
.default_root_lock_node ⇒ Object
the default root path we will use when a value is not given to a constructor
102 103 104 |
# File 'lib/zk/locker.rb', line 102 def default_root_lock_node @default_root_lock_node end |
Class Method Details
.cleanup(client, root_lock_node = default_root_lock_node) ⇒ Object
Clean up dead locker directories. There are situations (particularly session expiration) where a lock's directory will never be cleaned up.
It is intened to be run periodically (perhaps from cron).
This implementation goes through each lock directory and attempts to acquire an exclusive lock. If the lock is acquired then when it unlocks it will remove the locker directory. This is safe because the unlock code is designed to deal with the inherent race conditions.
155 156 157 158 159 160 161 |
# File 'lib/zk/locker.rb', line 155 def cleanup(client, root_lock_node=default_root_lock_node) client.children(root_lock_node).each do |name| exclusive_locker(client, name, root_lock_node).tap do |locker| locker.unlock if locker.lock end end end |
.exclusive_locker(client, name, *args) ⇒ ExclusiveLocker
Create an ExclusiveLocker instance
118 119 120 |
# File 'lib/zk/locker.rb', line 118 def exclusive_locker(client, name, *args) ExclusiveLocker.new(client, name, *args) end |
.semaphore(client, name, semaphore_size, *args) ⇒ Semaphore
Create a Semaphore instance
128 129 130 |
# File 'lib/zk/locker.rb', line 128 def semaphore(client, name, semaphore_size, *args) Semaphore.new(client, name, semaphore_size, *args) end |
.shared_locker(client, name, *args) ⇒ SharedLocker
Create a SharedLocker instance
109 110 111 |
# File 'lib/zk/locker.rb', line 109 def shared_locker(client, name, *args) SharedLocker.new(client, name, *args) end |