Module: Mongoid::Bolt::Ability

Defined in:
lib/mongoid-bolt.rb

Constant Summary collapse

Code =
proc do
## embedded lock class and associations
#
  target_class = self

  class << target_class
    attr_accessor :lock_class
  end

  const_set(:Bolt, Class.new)
  lock_class = const_get(:Bolt)

  target_class.lock_class = lock_class

  lock_class.class_eval do
    define_method(:target_class){ target_class }
    define_method(:lock_class){ lock_class }

    include Mongoid::Document
    include Mongoid::Timestamps

    field(:hostname, :default => proc{ ::Mongoid::Bolt.hostname })
    field(:ppid, :default => proc{ ::Mongoid::Bolt.ppid })
    field(:pid, :default => proc{ ::Mongoid::Bolt.pid })
    field(:tid, :default => proc{ ::Mongoid::Bolt.tid })

    attr_accessor :stolen
    alias_method :stolen?, :stolen

    def initialize(*args, &block)
      super
    ensure
      now = Time.now
      self.created_at ||= now
      self.updated_at ||= now
      @locked = false
    end

    def localhost?
      ::Mongoid::Bolt.hostname == hostname
    end

    def alive?
      return true unless localhost?

      process_alive = ::Mongoid::Bolt.process_alive?(ppid, pid)

      same_process = ::Mongoid::Bolt.pid == pid

      if process_alive
        if same_process
          ::Mongoid::Bolt.thread_alive?(tid)
        else
          false
        end
      else
        false
      end
    end

    def stale?
      not alive?
    end

    def relock!
      reload

      conditions = {
        '_lock._id'      => id,
        '_lock.hostname' => hostname,
        '_lock.ppid'     => ppid,
        '_lock.pid'      => pid,
        '_lock.tid'      => tid
      }

      update = {
        '$set' => {
          '_lock.hostname'   => ::Mongoid::Bolt.hostname,
          '_lock.ppid'       => ::Mongoid::Bolt.ppid,
          '_lock.pid'        => ::Mongoid::Bolt.pid,
          '_lock.tid'        => ::Mongoid::Bolt.tid,
          '_lock.updated_at' => Time.now.utc
        }
      }

      result =
          target_class.
            with(safe: true).
              where(conditions).
                find_and_modify(update, new: false)

    ensure
      reload
    end

    def steal!
      self.stolen = !!relock!
    end

    def owner?
      ::Mongoid::Bolt.identifier == identifier
    end

    def identifier
      {:hostname => hostname, :ppid => ppid, :pid => pid, :tid => tid}
    end
  end

  target_association_name = "_" + target_class.name.underscore.split(%r{/}).last
  
  lock_class.class_eval do
    embedded_in(target_association_name, :class_name => "::#{ target_class.name }")
  end

  embeds_one(:_lock, :class_name => "::#{ lock_class.name }")

## locking methods
#
  def target_class.lock!(options = {})
    options.to_options!

    conditions = (options[:conditions] || {}).to_options!
    update = (options[:update] || {}).to_options!

    conditions[:_lock] = nil

    update[:$set] = {:_lock => lock_class.new.attributes}

    with(safe: true).
      where(conditions).
        find_and_modify(update, new: true)
  end

  def target_class.reserve!(options = {})
    target_class.lock!(options = {})
  end

  def lock!(conditions = {})
    conditions.to_options!

    begin
      if _lock and _lock.stale? and _lock.steal!
        return _lock
      end
    rescue
      nil
    end

    conditions[:_id] = id

    if self.class.lock!(conditions)
      reload

      begin
        @locked = _lock && _lock.owner?
      rescue
        nil
      end
    else
      false
    end
  end

  def unlock!
    unlocked = false

    if _lock and _lock.owner?
      begin
        _lock.destroy
        @locked = false
        unlocked = true
      rescue
        nil
      end

      reload
    end

    unlocked
  end

  def relock!
    raise(::Mongoid::Bolt::Error, "#{ name } is not locked!") unless @locked

    _lock.relock!
  end

  def locked?
    begin
      _lock and _lock.owner?
    rescue
      nil
    end
  end

  def lock(options = {}, &block)
    options.to_options!

    return block.call(_lock) if locked?

    loop do
      if lock!
        return _lock unless block

        begin
          return block.call(_lock)
        ensure
          unlock!
        end
      else
        if options[:blocking] == false
          if block
            raise(::Mongoid::Bolt::Error, name)
          else
            return(false)
          end
        end

        if options[:waiting]
          options[:waiting].call(reload._lock)
        end

        sleep(rand)
      end
    end
  end
end

Class Method Summary collapse

Class Method Details

.included(other) ⇒ Object



288
289
290
291
292
# File 'lib/mongoid-bolt.rb', line 288

def Ability.included(other)
  super
ensure
  other.module_eval(&Code)
end