An advisory lock is a mutex used to ensure no two processes run some process at the same time. When the advisory lock is powered by your database server, as long as it isn't SQLite, your mutex spans hosts.
User is an ActiveRecord model, and
lock_name is some string:
User.with_advisory_lock(lock_name) do do_something_that_needs_locking end
The second parameter for
timeout_seconds, and defaults to
which means wait indefinitely for the lock.
A value of zero will try the lock only once. If the lock is acquired, the block will be yielded to. If the lock is currently being held, the block will not be called.
Note that if a non-nil value is provided for
timeout_seconds, the block will not be invoked if
the lock cannot be acquired within that timeframe.
The return value of
with_advisory_lock will be the result of the yielded block,
if the lock was able to be acquired and the block yielded, or
false, if you provided
a timeout_seconds value and the lock was not able to be acquired in time.
If you needed to check if the advisory lock is currently being held, you can call
Tag.advisory_lock_exists?("foo"), but realize the lock can be acquired between the time you
test for the lock, and the time you try to acquire the lock.
If you want to see if the current Thread is holding a lock, you can call
which will return the name of the current lock. If no lock is currently held,
Add this line to your application's Gemfile:
And then execute:
First off, know that there are lots of different kinds of locks available to you. Pick the finest-grain lock that ensures correctness. If you choose a lock that is too coarse, you are unnecessarily blocking other processes.
These are named mutexes that are inherently "application level"—it is up to the application to acquire, run a critical code section, and release the advisory lock.
If you're building a CRUD application, this will be your most commonly used lock.
Provided through something like the monogamy gem, these prevent concurrent access to any instance of a model. Their coarseness means they aren't going to be commonly applicable, and they can be a source of deadlocks.
Advisory locks with MySQL and PostgreSQL ignore database transaction boundaries.
You will want to wrap your block within a transaction to ensure consistency.
With MySQL (at least <= v5.5), if you ask for a different advisory lock within a
you will be releasing the parent lock (!!!). A
NestedAdvisoryLockErrorwill be raised
in this case. If you ask for the same lock name,
with_advisory_lock won't ask for the
lock again, and the block given will be yielded to.
This is expected if you aren't using MySQL or Postgresql for your tests. See issue 3.
SQLite doesn't have advisory locks, so we resort to file locking, which will only work
FLOCK_DIR is set consistently for all ruby processes.
minitest_helper.rb, add a
before do ENV['FLOCK_DIR'] = Dir.mktmpdir end after do FileUtils.remove_entry_secure ENV['FLOCK_DIR'] end
advisory_lock_exists?to use existing functionality
advisory_lock_exists?. Thanks, Sean Devine, for the great pull request!
WITH_ADVISORY_LOCK_PREFIXenvironment variable. I'm not going to advertise this feature yet. It's a secret. Only you and I know, now. shhh
em-postgresql-adapter. Thanks, lestercsp!
(Hey, github—your notifications are WAY too easy to ignore!)