Adds advisory locking (mutexes) to ActiveRecord 3.0, 3.1, 3.2, and 4.0.0 when used with MySQL or PostgreSQL. SQLite resorts to file locking.
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.
Where 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 thread will wait indefinitely until the lock is acquired.
- While inside the block, you will exclusively own the advisory lock.
- The lock will be released after your block ends, even if an exception is raised in the block.
The second parameter for with_advisory_lock
is timeout_seconds
, and defaults to nil
,
which means wait indefinitely for the lock.
If a non-nil value is provided, the block may not be invoked.
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.
Add this line to your application's Gemfile:
gem 'with_advisory_lock'
And then execute:
$ bundle
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.
Whether optimistic or pessimistic, row-level locks prevent concurrent modification to a given model.
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 with_advisory_lock
block,
you will be releasing the parent lock (!!!). A NestedAdvisoryLockError
will 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
if the FLOCK_DIR
is set consistently for all ruby processes.
In your spec_helper.rb
or minitest_helper.rb
, add a before
and after
block:
before do
ENV['FLOCK_DIR'] = Dir.mktmpdir
end
after do
FileUtils.remove_entry_secure ENV['FLOCK_DIR']
end
- Explicitly added MIT licensing to the gemspec.
- Merged in Postgis Adapter Support to address issue 7 Thanks for the pull request, Abdelkader Boudih!
- The database switching code had to be duplicated by Closure Tree,
so I extracted a new
WithAdvisoryLock::DatabaseAdapterSupport
one-trick pony. - Builds were failing on Travis, so I introduced a global lock prefix that can be set with the
WITH_ADVISORY_LOCK_PREFIX
environment variable. I'm not going to advertise this feature yet. It's a secret. Only you and I know, now. shhh
- Addressed issue 5 by using a deterministic hash for Postgresql + MRI >= 1.9. Thanks for the pull request, Joel Turkel!
- Addressed issue 2 by using a cache-busting query for MySQL and Postgres to deal with AR value caching bug. Thanks for the pull request, Jaime Giraldo!
- Addressed issue 4 by
adding support for
em-postgresql-adapter
. Thanks, lestercsp!
(Hey, github—your notifications are WAY too easy to ignore!)
- Added Travis tests for Rails 3.0, 3.1, 3.2, and 4.0
- Fixed MySQL bug with select_value returning a string instead of an integer when using AR 3.0.x
- Only require ActiveRecord >= 3.0.x
- Fixed MySQL error reporting
- Asking for the currently acquired advisory lock doesn't re-ask for the lock now.
- Introduced NestedAdvisoryLockError when asking for different, nested advisory locksMySQL
- Moved require into on_load, which should speed loading when AR doesn't have to spin up
- Fought with ActiveRecord 3.0.x and 3.1.x. You don't want them if you use threads—they fail predictably.
- Added warning log message for nested MySQL lock calls
- Randomized lock wait time, which can help ameliorate lock contention
- First whack