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.9.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.
-
.synchronize(key, options = {}) ⇒ Object
Obtain a mutex around a critical section of code.
Class Method Details
.config ⇒ Config
Returns MasterLock configuration settings.
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/master_lock.rb', line 121 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.process_id = Process.pid @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.
141 142 143 |
# File 'lib/master_lock.rb', line 141 def configure yield config end |
.logger ⇒ Logger
Get the configured logger.
116 117 118 |
# File 'lib/master_lock.rb', line 116 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.
103 104 105 106 107 108 109 110 111 |
# File 'lib/master_lock.rb', line 103 def start @registry = Registry.new Thread.new do loop do @registry.extend_locks sleep(config.sleep_time) end end 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.
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 98 |
# File 'lib/master_lock.rb', line 59 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 |