class ActiveRecord::DatabaseMutex::Implementation
Attributes
Returns the name of this mutex as given as a constructor argument.
Public Class Methods
# File lib/active_record/database_mutex/implementation.rb, line 13 def check_size? if defined? @check_size @check_size else version = db.execute("SHOW VARIABLES LIKE 'version'").first.last. delete('^0-9.').version @check_size = version >= '5.7'.version end end
# File lib/active_record/database_mutex/implementation.rb, line 9 def db ActiveRecord::Base.connection end
Creates a mutex with the name given with the option :name.
# File lib/active_record/database_mutex/implementation.rb, line 25 def initialize(opts = {}) @name = opts[:name] or raise ArgumentError, "mutex requires a :name argument" counter or raise ArgumentError, 'argument :name is too long' end
Public Instance Methods
Returns true if this mutex is locked by this database connection.
# File lib/active_record/database_mutex/implementation.rb, line 117 def aquired_lock? query("SELECT CONNECTION_ID() = IS_USED_LOCK(#{quote(name)})") == 1 end
Locks the mutex and returns true if successful. If the mutex is already locked and the timeout in seconds is given as the :timeout option, this method raises a MutexLocked
exception after that many seconds. If the :timeout option wasn't given, this method blocks until the lock could be aquired.
# File lib/active_record/database_mutex/implementation.rb, line 61 def lock(opts = {}) if opts[:nonblock] # XXX document begin lock_with_timeout :timeout => 0 rescue MutexLocked end elsif opts[:timeout] lock_with_timeout opts else spin_timeout = opts[:spin_timeout] || 1 # XXX document begin lock_with_timeout :timeout => spin_timeout rescue MutexLocked retry end end end
Returns true if this mutex is locked at the moment.
# File lib/active_record/database_mutex/implementation.rb, line 112 def locked? not unlocked? end
Returns true if this mutex is not locked by this database connection.
# File lib/active_record/database_mutex/implementation.rb, line 122 def not_aquired_lock? not aquired_lock? end
Locks the mutex if it isn't already locked via another database connection and yields to the given block. After executing the block's content the mutex is unlocked (only if it was locked by this synchronize method before).
If the mutex was already locked by another database connection the method blocks until it could aquire the lock and only then the block's content is executed. If the mutex was already locked by the current database connection then the block's content is run and the the mutex isn't unlocked afterwards.
If a value in seconds is passed to the :timeout option the blocking ends after that many seconds and the method returns immediately if the lock couldn't be aquired during that time.
# File lib/active_record/database_mutex/implementation.rb, line 47 def synchronize(opts = {}) locked = lock(opts) or return yield rescue ActiveRecord::DatabaseMutex::MutexLocked return nil ensure locked and unlock end
Returns a string representation of this DatabaseMutex
instance.
# File lib/active_record/database_mutex/implementation.rb, line 127 def to_s "#<#{self.class} #{name}>" end
Unlocks the mutex and returns true if successful. Otherwise this method raises a MutexLocked
exception.
# File lib/active_record/database_mutex/implementation.rb, line 81 def unlock(*) if aquired_lock? decrease_counter if counter_zero? case query("SELECT RELEASE_LOCK(#{quote(name)})") when 1 true when 0, nil raise MutexUnlockFailed, "unlocking of mutex '#{name}' failed" end end else raise MutexUnlockFailed, "unlocking of mutex '#{name}' failed" end end
Unlock this mutex and return self if successful, otherwise (the mutex was not locked) nil is returned.
# File lib/active_record/database_mutex/implementation.rb, line 99 def unlock?(*a) unlock(*a) self rescue MutexUnlockFailed nil end
Returns true if this mutex is unlocked at the moment.
# File lib/active_record/database_mutex/implementation.rb, line 107 def unlocked? query("SELECT IS_FREE_LOCK(#{quote(name)})") == 1 end
Private Instance Methods
# File lib/active_record/database_mutex/implementation.rb, line 139 def counter encoded_name = ?$ + Base64.encode64(name).delete('^A-Za-z0-9+/'). gsub(/[+\/]/, ?+ => ?_, ?/ => ?.) if !self.class.check_size? || encoded_name.size <= 64 # mysql 5.7 only allows size <=64 variable names "@#{encoded_name}" end end
# File lib/active_record/database_mutex/implementation.rb, line 155 def counter_value query("SELECT #{counter}").to_i end
# File lib/active_record/database_mutex/implementation.rb, line 159 def counter_zero? counter_value.zero? end
# File lib/active_record/database_mutex/implementation.rb, line 151 def decrease_counter query("SET #{counter} = #{counter} - 1") end
# File lib/active_record/database_mutex/implementation.rb, line 147 def increase_counter query("SET #{counter} = IF(#{counter} IS NULL OR #{counter} = 0, 1, #{counter} + 1)") end
# File lib/active_record/database_mutex/implementation.rb, line 163 def lock_with_timeout(opts = {}) if aquired_lock? increase_counter true else timeout = opts[:timeout] || 1 case query("SELECT GET_LOCK(#{quote(name)}, #{timeout})") when 1 increase_counter true when 0 raise MutexLocked, "mutex '#{name}' is already locked" when nil raise MutexSystemError, "mutex '#{name}' not locked due to system error" end end end
# File lib/active_record/database_mutex/implementation.rb, line 181 def query(sql) if result = self.class.db.execute(sql) result = result.first.first.to_i $DEBUG and warn %{query("#{sql}") = #{result}} end result rescue ActiveRecord::StatementInvalid nil end
# File lib/active_record/database_mutex/implementation.rb, line 135 def quote(value) ActiveRecord::Base.connection.quote(value) end