Module: MasterLock
- Defined in:
- lib/master_lock.rb,
lib/master_lock/version.rb,
lib/master_lock/registry.rb,
lib/master_lock/redis_lock.rb,
lib/master_lock/redis_scripts.rb
Overview
MasterLock is a system for interprocess locking. Resources can be locked by a string identifier such that only one thread may have the lock at a time. Lock state and owners are stored on a Redis server shared by all processes. Locks are held until either the block of synchronized code completes or the thread that obtained the lock is killed. To prevent the locks from being held indefinitely in the event that the process dies without releasing them, the locks have an expiration time in Redis. While the thread owning the lock is alive, a separate thread will extend the lifetime of the locks so that they do not expire even when the code in the critical section takes a long time to execute.
Defined Under Namespace
Modules: RedisScripts Classes: Config, LockNotAcquiredError, NotStartedError, RedisLock, Registry, UnconfiguredError
Constant Summary collapse
- DEFAULT_ACQUIRE_TIMEOUT =
5
- DEFAULT_EXTEND_INTERVAL =
15
- DEFAULT_KEY_PREFIX =
"masterlock".freeze
- DEFAULT_SLEEP_TIME =
5
- DEFAULT_TTL =
60
- VERSION =
"0.11.0"
Class Method Summary collapse
-
.config ⇒ Config
MasterLock configuration settings.
-
.configure {|Config| ... } ⇒ Object
Configure MasterLock using block syntax.
-
.logger ⇒ Logger
Get the configured logger.
-
.start ⇒ Object
Starts the background thread to manage and extend currently held locks.
-
.started? ⇒ Boolean
Returns true if the registry has been started, otherwise false.
-
.synchronize(key, options = {}) ⇒ Object
Obtain a mutex around a critical section of code.
Class Method Details
.config ⇒ Config
Returns MasterLock configuration settings.
126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/master_lock.rb', line 126 def config if !defined?(@config) @config = Config.new @config.acquire_timeout = DEFAULT_ACQUIRE_TIMEOUT @config.extend_interval = DEFAULT_EXTEND_INTERVAL @config.hostname = Socket.gethostname @config.logger = Logger.new(STDOUT) @config.logger.progname = name @config.key_prefix = DEFAULT_KEY_PREFIX @config.sleep_time = DEFAULT_SLEEP_TIME @config.ttl = DEFAULT_TTL end @config end |
.configure {|Config| ... } ⇒ Object
Configure MasterLock using block syntax. Simply yields #config to the block.
145 146 147 |
# File 'lib/master_lock.rb', line 145 def configure yield config end |
.logger ⇒ Logger
Get the configured logger.
121 122 123 |
# File 'lib/master_lock.rb', line 121 def logger config.logger end |
.start ⇒ Object
Starts the background thread to manage and extend currently held locks. The thread remains alive for the lifetime of the process. This must be called before any locks may be acquired.
102 103 104 105 106 107 108 109 110 |
# File 'lib/master_lock.rb', line 102 def start @registry = Registry.new Thread.new do loop do @registry.extend_locks sleep(config.sleep_time) end end end |
.started? ⇒ Boolean
Returns true if the registry has been started, otherwise false
114 115 116 |
# File 'lib/master_lock.rb', line 114 def started? !@registry.nil? end |
.synchronize(key, options = {}) ⇒ Object
Obtain a mutex around a critical section of code. Only one thread on any machine can execute the given block at a time. Returns the result of the block.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/master_lock.rb', line 58 def synchronize(key, = {}) check_configured raise NotStartedError unless @registry ttl = [:ttl] || config.ttl acquire_timeout = [:acquire_timeout] || config.acquire_timeout extend_interval = [:extend_interval] || config.extend_interval raise ArgumentError, "extend_interval cannot be negative" if extend_interval < 0 raise ArgumentError, "ttl must be greater extend_interval" if ttl <= extend_interval if (.include?(:if) && ![:if]) || (.include?(:unless) && [:unless]) return yield end lock = RedisLock.new( redis: config.redis, key: key, ttl: ttl, owner: generate_owner ) if !lock.acquire(timeout: acquire_timeout) raise LockNotAcquiredError, key end registration = @registry.register(lock, extend_interval) logger.debug("Acquired lock #{key}") begin yield ensure @registry.unregister(registration) if lock.release logger.debug("Released lock #{key}") else logger.warn("Failed to release lock #{key}") end end end |