Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save felixbuenemann/8a6c3bfcdf3ffeafdf8e5ab8f9bec5e0 to your computer and use it in GitHub Desktop.
Save felixbuenemann/8a6c3bfcdf3ffeafdf8e5ab8f9bec5e0 to your computer and use it in GitHub Desktop.
# 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