proc do
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 }")
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