Last active
June 29, 2017 17:53
-
-
Save felixbuenemann/8a6c3bfcdf3ffeafdf8e5ab8f9bec5e0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Backport rails/rails#22122 to rails 4.2.x | |
# See: https://github.com/rails/rails/pull/22122 | |
fail "This monkey patch is no longer needed in ActiveRecord >= 5" if ActiveRecord::VERSION::MAJOR >= 5 | |
module ActiveRecord | |
module ConnectionAdapters | |
class AbstractAdapter | |
def supports_advisory_locks? | |
false | |
end | |
def get_advisory_lock(key) | |
end | |
def release_advisory_lock(key) | |
end | |
end | |
class AbstractMysqlAdapter < AbstractAdapter | |
def supports_advisory_locks? | |
version >= '3.21.27' | |
end | |
def get_advisory_lock(key, timeout = 0) | |
select_value("SELECT GET_LOCK('#{key}', #{timeout})").to_s == '1' | |
end | |
def release_advisory_lock(key) | |
select_value("SELECT RELEASE_LOCK('#{key}')").to_s == '1' | |
end | |
end | |
class PostgreSQLAdapter < AbstractAdapter | |
def supports_advisory_locks? | |
true | |
end | |
def get_advisory_lock(key) | |
unless key.is_a?(Integer) && key.bit_length <= 63 | |
raise(ArgumentError, "Postgres requires advisory lock keys to be a signed 64 bit integer") | |
end | |
# ActiveRecord 4 change: select_value returns 't' / 'f' instead of boolean | |
select_value("SELECT pg_try_advisory_lock(#{key})") == 't' | |
end | |
def release_advisory_lock(key) | |
unless key.is_a?(Integer) && key.bit_length <= 63 | |
raise(ArgumentError, "Postgres requires advisory lock keys to be a signed 64 bit integer") | |
end | |
# ActiveRecord 4 change: select_value returns 't' / 'f' instead of boolean | |
select_value("SELECT pg_advisory_unlock(#{key})") == 't' | |
end | |
end | |
end | |
class ConcurrentMigrationError < MigrationError | |
DEFAULT_MESSAGE = "Cannot run migrations because another migration process is currently running.".freeze | |
def initialize(message = DEFAULT_MESSAGE) | |
super | |
end | |
end | |
class Migrator | |
def run_with_lock | |
if use_advisory_lock? | |
with_advisory_lock { run_without_lock } | |
else | |
run_without_lock | |
end | |
end | |
alias_method_chain :run, :lock | |
def migrate_with_lock | |
if use_advisory_lock? | |
with_advisory_lock { migrate_without_lock } | |
else | |
migrate_without_lock | |
end | |
end | |
alias_method_chain :migrate, :lock | |
def migrated | |
@migrated_versions || load_migrated | |
end | |
def load_migrated | |
@migrated_versions = Set.new(self.class.get_all_versions) | |
end | |
private | |
def use_advisory_lock? | |
Base.connection.supports_advisory_locks? | |
end | |
def with_advisory_lock | |
key = generate_migrator_advisory_lock_key | |
got_lock = Base.connection.get_advisory_lock(key) | |
raise ConcurrentMigrationError unless got_lock | |
load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock | |
yield | |
ensure | |
Base.connection.release_advisory_lock(key) if got_lock | |
end | |
MIGRATOR_SALT = 2053462845 | |
def generate_migrator_advisory_lock_key | |
db_name_hash = Zlib.crc32(Base.connection.current_database) | |
MIGRATOR_SALT * db_name_hash | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment