lib/mongoid/locker.rb in mongoid-locker-0.1.1 vs lib/mongoid/locker.rb in mongoid-locker-0.2.0
- old
+ new
@@ -1,5 +1,7 @@
+require File.expand_path(File.join(File.dirname(__FILE__), 'locker', 'wrapper'))
+
module Mongoid
module Locker
module ClassMethods
# A scope to retrieve all locked documents in the collection.
#
@@ -46,68 +48,74 @@
# @return [Boolean] true if locked, false otherwise
def locked?
!!(self.locked_until && self.locked_until > Time.now)
end
+ # Returns whether the current instance has the lock or not.
+ #
+ # @return [Boolean] true if locked, false otherwise
+ def has_lock?
+ @has_lock && self.locked?
+ end
+
# Primary method of plugin: execute the provided code once the document has been successfully locked.
#
# @param [Hash] opts for the locking mechanism
# @option opts [Fixnum] :timeout The number of seconds until the lock is considered "expired" - defaults to the {ClassMethods#lock_timeout}
# @option opts [Boolean] :wait If the document is currently locked, wait until the lock expires and try again
# @return [void]
def with_lock opts={}, &block
- self.lock opts
+ # don't try to re-lock/unlock on recursive calls
+ had_lock = self.has_lock?
+ self.lock(opts) unless had_lock
+
begin
yield
ensure
- self.unlock
+ self.unlock unless had_lock
end
end
protected
def lock opts={}
- coll = self.class.collection
time = Time.now
timeout = opts[:timeout] || self.class.lock_timeout
expiration = time + timeout
# lock the document atomically in the DB without persisting entire doc
- record = coll.find_and_modify(
- :query => {
+ locked = Mongoid::Locker::Wrapper.update(
+ self.class,
+ {
:_id => self.id,
'$or' => [
# not locked
{:locked_until => nil},
# expired
{:locked_until => {'$lte' => time}}
]
},
- :update => {
+ {
'$set' => {
:locked_at => time,
:locked_until => expiration
}
}
)
- if record
- # lock successful
+ if locked
+ # document successfully updated, meaning it was locked
self.locked_at = time
self.locked_until = expiration
+ @has_lock = true
else
# couldn't grab lock
- existing_query = {
- :_id => self.id,
- :locked_until => {'$exists' => true}
- }
-
- if opts[:wait] && existing = coll.find(existing_query, :limit => 1).first
+ if opts[:wait] && locked_until = Mongoid::Locker::Wrapper.locked_until(self)
# doc is locked - wait until it expires
- wait_time = existing.locked_until - Time.now
+ wait_time = locked_until - Time.now
sleep wait_time if wait_time > 0
# only wait once
opts.dup
opts.delete :wait
@@ -120,18 +128,23 @@
end
end
def unlock
# unlock the document in the DB without persisting entire doc
- self.class.collection.update({:_id => self.id}, {
- '$set' => {
- :locked_at => nil,
- :locked_until => nil,
+ Mongoid::Locker::Wrapper.update(
+ self.class,
+ {:_id => self.id},
+ {
+ '$set' => {
+ :locked_at => nil,
+ :locked_until => nil,
+ }
}
- }, {:safe => true})
+ )
self.locked_at = nil
self.locked_until = nil
+ @has_lock = false
end
end
# Error thrown if document could not be successfully locked.
class LockError < Exception; end