module Mongoid::Locker
Constants
- MODULE_METHODS
-
Available parameters for
Mongoid::Locker
module, a class where the module is included and it’s instances. - VERSION
Public Class Methods
Source
# File lib/mongoid/locker.rb, line 85 def configure yield(self) if block_given? end
Sets configuration using a block.
@example
Mongoid::Locker.configure do |config| config.locking_name_field = :locking_name config.locked_at_field = :locked_at config.lock_timeout = 5 config.locker_write_concern = { w: 1 } config.maximum_backoff = 60.0 config.backoff_algorithm = :exponential_backoff config.locking_name_generator = :secure_locking_name end
Source
# File lib/mongoid/locker.rb, line 47 def exponential_backoff(_doc, opts) 2**opts[:attempt] + rand end
Returns random number of seconds depend on passed options.
@example
Mongoid::Locker.exponential_backoff(doc, { attempt: 0 }) #=> 1.2280675023095662 Mongoid::Locker.exponential_backoff(doc, { attempt: 1 }) #=> 2.901641863236713 Mongoid::Locker.exponential_backoff(doc, { attempt: 2 }) #=> 4.375030664612267
@param _doc [Mongoid::Document] @param opts [Hash] (see with_lock
) @return [Float]
Source
# File lib/mongoid/locker.rb, line 105 def included(klass) klass.extend ClassMethods klass.singleton_class.instance_eval { attr_accessor(*MODULE_METHODS) } klass.locking_name_field = locking_name_field klass.locked_at_field = locked_at_field klass.lock_timeout = lock_timeout klass.locker_write_concern = locker_write_concern klass.maximum_backoff = maximum_backoff klass.backoff_algorithm = backoff_algorithm klass.locking_name_generator = locking_name_generator klass.delegate(*MODULE_METHODS, to: :class) klass.singleton_class.delegate(*(methods(false) - MODULE_METHODS.flat_map { |method| [method, "#{method}=".to_sym] } - %i[included reset! configure]), to: self) end
@api private
Source
# File lib/mongoid/locker.rb, line 61 def locked_at_backoff(doc, opts) return doc.maximum_backoff if opts[:attempt] * doc.lock_timeout >= doc.maximum_backoff locked_at = Wrapper.locked_at(doc).to_f return 0 unless locked_at > 0 current_time = Wrapper.current_mongodb_time(doc.class).to_f delay = doc.lock_timeout - (current_time - locked_at) delay < 0 ? 0 : delay + rand end
Returns time in seconds remaining to complete the lock of the provided document. Makes requests to the database.
@example
Mongoid::Locker.locked_at_backoff(doc, opts) #=> 2.32422359
@param doc [Mongoid::Document] @param opts [Hash] (see with_lock
) @return [Float | Integer] @return [0] if the provided document is not locked
Source
# File lib/mongoid/locker.rb, line 93 def reset! # The parameters used by default. self.locking_name_field = :locking_name self.locked_at_field = :locked_at self.lock_timeout = 5 self.locker_write_concern = { w: 1 } self.maximum_backoff = 60.0 self.backoff_algorithm = :exponential_backoff self.locking_name_generator = :secure_locking_name end
Resets to default configuration.
@example
Mongoid::Locker.reset!
Source
# File lib/mongoid/locker.rb, line 30 def secure_locking_name(_doc, opts) "#{SecureRandom.urlsafe_base64}##{opts[:attempt]}" end
Generates secure random string of +name#attempt+ format.
@example
Mongoid::Locker.secure_locking_name(doc, { attempt: 1 }) #=> "zLmulhOy9yn_NE886OWNYw#1"
@param doc [Mongoid::Document] @param opts [Hash] (see with_lock
) @return [String]
Public Instance Methods
Source
# File lib/mongoid/locker.rb, line 231 def has_lock? @has_lock || false end
Returns whether the current instance has the lock or not.
@example
document.has_lock? #=> false
@return [Boolean] true if locked, false otherwise
Source
# File lib/mongoid/locker.rb, line 220 def locked? persisted? && self.class.where(_id: id).locked.limit(1).count == 1 end
Returns whether the document is currently locked in the database or not.
@example
document.locked? #=> false
@return [Boolean] true if locked, false otherwise
Source
# File lib/mongoid/locker.rb, line 248 def with_lock(**opts) opts = opts.dup opts[:retries] ||= Float::INFINITY opts[:reload] = opts[:reload] != false acquire_lock(opts) if persisted? && (had_lock = !has_lock?) begin yield ensure unlock!(opts) if had_lock end end
Executes the provided code once the document has been successfully locked. Otherwise, raises error after the number of retries to lock the document is exhausted or it is reached {ClassMethods#maximum_backoff} limit (depending what comes first).
@example
document.with_lock(reload: true, retries: 3) do document.quantity = 17 document.save! end
@param [Hash] opts for the locking mechanism @option opts [Fixnum] :retries (INFINITY) If the document is currently locked, the number of times to retry @option opts [Boolean] :reload (true) After acquiring the lock, reload the document @option opts [Integer] :attempt (0) Increment with each retry (not accepted by the method) @option opts [String] :locking_name Generate with each retry (not accepted by the method)
Protected Instance Methods
Source
# File lib/mongoid/locker.rb, line 264 def acquire_lock(opts) opts[:attempt] = 0 loop do opts[:locking_name] = self.class.send(locking_name_generator, self, opts) return if lock!(opts) opts[:attempt] += 1 delay = self.class.send(backoff_algorithm, self, opts) raise Errors::DocumentCouldNotGetLock.new(self.class, id) if delay >= maximum_backoff || opts[:attempt] >= opts[:retries] sleep delay end end
Source
# File lib/mongoid/locker.rb, line 280 def lock!(opts) result = Mongoid::Locker::Wrapper.find_and_lock(self, opts) if result if opts[:reload] reload else self[locking_name_field] = result[locking_name_field.to_s] self[locked_at_field] = result[locked_at_field.to_s] end @has_lock = true else @has_lock = false end end
Source
# File lib/mongoid/locker.rb, line 297 def unlock!(opts) Mongoid::Locker::Wrapper.find_and_unlock(self, opts) unless destroyed? self[locking_name_field] = nil self[locked_at_field] = nil end @has_lock = false end