aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
diff options
context:
space:
mode:
authorSam Davies <seivadmas@gmail.com>2015-10-29 18:26:54 -0300
committerSam Davies <seivadmas@gmail.com>2015-10-30 14:04:16 -0300
commit2c2a8755460ec3d32ece91c9766dbd0304ece028 (patch)
tree350a241e5ff5e07ad395455fe3d567dea70ede0f /activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
parent0174837bfa263c55cf727f23028d8f28da192d14 (diff)
downloadrails-2c2a8755460ec3d32ece91c9766dbd0304ece028.tar.gz
rails-2c2a8755460ec3d32ece91c9766dbd0304ece028.tar.bz2
rails-2c2a8755460ec3d32ece91c9766dbd0304ece028.zip
Use advisory locks to prevent concurrent migrations
- Addresses issue #22092 - Works on Postgres and MySQL - Uses advisory locks because of two important properties: 1. The can be obtained outside of the context of a transaction 2. They are automatically released when the session ends, so if a migration process crashed for whatever reason the lock is not left open perpetually - Adds get_advisory_lock and release_advisory_lock methods to database adapters - Attempting to run a migration while another one is in process will raise a ConcurrentMigrationError instead of attempting to run in parallel with undefined behavior. This could be rescued and the migration could exit cleanly instead. Perhaps as a configuration option? Technical Notes ============== The Migrator uses generate_migrator_advisory_lock_key to build the key for the lock. In order to be compatible across multiple adapters there are some constraints on this key. - Postgres limits us to 64 bit signed integers - MySQL advisory locks are server-wide so we have to scope to the database - To fulfil these requirements we use a Migrator salt (a randomly chosen signed integer with max length of 31 bits) that identifies the Rails migration process as the owner of the lock. We multiply this salt with a CRC32 unsigned integer hash of the database name to get a signed 64 bit integer that can also be converted to a string to act as a lock key in MySQL databases. - It is important for subsequent versions of the Migrator to use the same salt, otherwise different versions of the Migrator will not see each other's locks.
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters/abstract_adapter.rb')
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb19
1 files changed, 19 insertions, 0 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 402159ac13..f5b2e9fa9d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -214,6 +214,11 @@ module ActiveRecord
false
end
+ # Does this adapter support application-enforced advisory locking?
+ def supports_advisory_locks?
+ false
+ end
+
# Should primary key values be selected from their corresponding
# sequence before the insert statement? If true, next_sequence_value
# is called before each insert to set the record's primary key.
@@ -280,6 +285,20 @@ module ActiveRecord
def enable_extension(name)
end
+ # This is meant to be implemented by the adapters that support advisory
+ # locks
+ #
+ # Return true if we got the lock, otherwise false
+ def get_advisory_lock(key) # :nodoc:
+ end
+
+ # This is meant to be implemented by the adapters that support advisory
+ # locks.
+ #
+ # Return true if we released the lock, otherwise false
+ def release_advisory_lock(key) # :nodoc:
+ end
+
# A list of extensions, to be filled in by adapters that support them.
def extensions
[]