Module: ZK::Mongoid::Locking

Defined in:
lib/z_k/mongoid.rb

Overview

provides a lock_for_update method based on the current class name and Mongoid document _id.

Before use (in one of your Rails initializers, for example) you should assign either a ZK::Client or ZK::Pool subclass to ZK::Mongoid::Locking.zk_lock_pool.

this class assumes the availability of a ‘logger’ method in the mixee

Constant Summary collapse

VALID_MODES =
[:exclusive, :shared].freeze
@@zk_lock_pool =
nil

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.zk_lock_poolObject



17
18
19
# File 'lib/z_k/mongoid.rb', line 17

def self.zk_lock_pool
  @@zk_lock_pool
end

.zk_lock_pool=(pool) ⇒ Object



21
22
23
# File 'lib/z_k/mongoid.rb', line 21

def self.zk_lock_pool=(pool)
  @@zk_lock_pool = pool 
end

Instance Method Details

#assert_locked_for_share!(name = nil) ⇒ Object

raises MustBeShareLockedException if we’re not currently inside a shared lock (optionally with name)



100
101
102
# File 'lib/z_k/mongoid.rb', line 100

def assert_locked_for_share!(name=nil)
  raise ZK::Exceptions::MustBeShareLockedException unless locked_for_share?(name)
end

#assert_locked_for_update!(name = nil) ⇒ Object

raises MustBeExclusivelyLockedException if we’re not currently inside a lock (optionally with name)



94
95
96
# File 'lib/z_k/mongoid.rb', line 94

def assert_locked_for_update!(name=nil)
  raise ZK::Exceptions::MustBeExclusivelyLockedException unless locked_for_update?(name)
end

#lock_for_update(name = nil) ⇒ Object Also known as: with_exclusive_lock

Provides a re-entrant zookeeper-based lock of a record.

This also makes it possible to detect if the record has been locked before performing a potentially dangerous operation by using the assert_locked_for_update! instance method

Locks are re-entrant per-thread, but will work as a mutex between threads.

You can optionally provide a ‘name’ which will act as a sub-lock of sorts. For example, if you are going to create an embedded document, and only want one process to be able to create it at a time (without clobbering one another), but don’t want to lock the entire record, you can specify a name for the lock, that way the same code running elsewhere will synchronize based on the parent record and the particular action specified by name.

Example

use of “name”

class Thing
  include Mongoid::Document
  include ZK::Mongoid::Locking

  embedded_in :parent, :inverse_of => :thing
end

class Parent
  include Mongoid::Document
  include ZK::Mongoid::Locking

  embeds_one :thing

  def lets_create_a_thing
    lock_for_update('thing_creation') do
      raise "We already got one! it's very nice!" if thing

      do_something_that_might_take_a_while
      create_thing
    end
  end
end

Now, while the creation of the Thing is synchronized, other processes can update other aspects of Parent.



74
75
76
77
78
79
80
81
# File 'lib/z_k/mongoid.rb', line 74

def lock_for_update(name=nil)
  if locked_for_update?(name)
    logger.debug { "we are locked for update, yield to the block" }
    yield
  else
    zk_with_lock(:mode => :exclusive, :name => name) { yield }
  end
end

#locked_for_share?(name = nil) ⇒ Boolean

:nodoc:

Returns:

  • (Boolean)


108
109
110
# File 'lib/z_k/mongoid.rb', line 108

def locked_for_share?(name=nil) #:nodoc:
  zk_mongoid_lock_registry[:shared].include?(zk_lock_name(name))
end

#locked_for_update?(name = nil) ⇒ Boolean

:nodoc:

Returns:

  • (Boolean)


104
105
106
# File 'lib/z_k/mongoid.rb', line 104

def locked_for_update?(name=nil) #:nodoc:
  zk_mongoid_lock_registry[:exclusive].include?(zk_lock_name(name))
end

#with_shared_lock(name = nil) ⇒ Object



84
85
86
87
88
89
90
# File 'lib/z_k/mongoid.rb', line 84

def with_shared_lock(name=nil)
  if locked_for_share?(name)
    yield
  else
    zk_with_lock(:mode => :shared, :name => name) { yield }
  end
end

#zk_add_path_lock(opts = {}) ⇒ Object (protected)

Raises:

  • (ArgumentError)


121
122
123
124
125
126
127
128
129
130
131
# File 'lib/z_k/mongoid.rb', line 121

def zk_add_path_lock(opts={})
  mode, name = opts.values_at(:mode, :name)

  raise ArgumentError, "You must specify a :mode option" unless mode

  zk_assert_valid_mode!(mode)

  logger.debug { "adding #{zk_lock_name(name).inspect} to #{mode} lock registry" }

  self.zk_mongoid_lock_registry[mode] << zk_lock_name(name)
end

#zk_assert_valid_mode!(mode) ⇒ Object (protected)

Raises:

  • (ArgumentError)


167
168
169
# File 'lib/z_k/mongoid.rb', line 167

def zk_assert_valid_mode!(mode)
  raise ArgumentError, "#{mode.inspect} is not a valid mode value" unless VALID_MODES.include?(mode)
end

#zk_lock_name(name = nil) ⇒ Object

:nodoc:



112
113
114
# File 'lib/z_k/mongoid.rb', line 112

def zk_lock_name(name=nil) #:nodoc:
  [self.class.to_s, self.id.to_s, name].compact.join('-')
end

#zk_lock_poolObject (protected)



163
164
165
# File 'lib/z_k/mongoid.rb', line 163

def zk_lock_pool
  @zk_lock_pool ||= ::ZK::Mongoid::Locking.zk_lock_pool
end

#zk_mongoid_lock_registryObject (protected)



117
118
119
# File 'lib/z_k/mongoid.rb', line 117

def zk_mongoid_lock_registry
  Thread.current.zk_mongoid_lock_registry ||= { :shared => Set.new, :exclusive => Set.new }
end

#zk_remove_path_lock(opts = {}) ⇒ Object (protected)

Raises:

  • (ArgumentError)


133
134
135
136
137
138
139
140
141
142
143
# File 'lib/z_k/mongoid.rb', line 133

def zk_remove_path_lock(opts={})
  mode, name = opts.values_at(:mode, :name)

  raise ArgumentError, "You must specify a :mode option" unless mode

  zk_assert_valid_mode!(mode)

  logger.debug { "removing #{zk_lock_name(name).inspect} from #{mode} lock registry" }

  zk_mongoid_lock_registry[mode].delete(zk_lock_name(name))
end

#zk_with_lock(opts = {}) ⇒ Object (protected)



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/z_k/mongoid.rb', line 145

def zk_with_lock(opts={})
  mode, name = opts.values_at(:mode, :name)

  zk_assert_valid_mode!(mode)

  zk_lock_pool.with_lock(zk_lock_name(name), :mode => mode) do
    zk_add_path_lock(opts)

    begin
      logger.debug { "acquired #{zk_lock_name(name).inspect}" }
      yield
    ensure
      logger.debug { "releasing #{zk_lock_name(name).inspect}" }
      zk_remove_path_lock(opts)
    end
  end
end