class ActiveRecord::DatabaseMutex::Implementation

Attributes

name[R]

Returns the name of this mutex as given as a constructor argument.

Public Class Methods

check_size?() click to toggle source
# 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
db() click to toggle source
# File lib/active_record/database_mutex/implementation.rb, line 9
def db
  ActiveRecord::Base.connection
end
new(opts = {}) click to toggle source

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

aquired_lock?() click to toggle source

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
inspect()
Alias for: to_s
lock(opts = {}) click to toggle source

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
locked?() click to toggle source

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
not_aquired_lock?() click to toggle source

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
synchronize(opts = {}) { || ... } click to toggle source

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
to_s() click to toggle source

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
Also aliased as: inspect
unlock(*) click to toggle source

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?(*a) click to toggle source

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
unlocked?() click to toggle source

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

counter() click to toggle source
# 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
counter_value() click to toggle source
# File lib/active_record/database_mutex/implementation.rb, line 155
def counter_value
  query("SELECT #{counter}").to_i
end
counter_zero?() click to toggle source
# File lib/active_record/database_mutex/implementation.rb, line 159
def counter_zero?
  counter_value.zero?
end
decrease_counter() click to toggle source
# File lib/active_record/database_mutex/implementation.rb, line 151
def decrease_counter
  query("SET #{counter} = #{counter} - 1")
end
increase_counter() click to toggle source
# 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
lock_with_timeout(opts = {}) click to toggle source
# 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
query(sql) click to toggle source
# 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
quote(value) click to toggle source
# File lib/active_record/database_mutex/implementation.rb, line 135
def quote(value)
  ActiveRecord::Base.connection.quote(value)
end