Class: Mongo::Lock

Inherits:
Object
  • Object
show all
Defined in:
lib/mongo-lock.rb,
lib/mongo-lock/version.rb,
lib/mongo-lock/configuration.rb

Defined Under Namespace

Classes: Configuration, NotAcquiredError, NotExtendedError, NotReleasedError

Constant Summary collapse

VERSION =
"1.0.0"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(key, options = {}) ⇒ Lock

Returns a new instance of Lock.



95
96
97
98
99
# File 'lib/mongo-lock.rb', line 95

def initialize key, options = {}
  self.configuration = Configuration.new self.class.configuration.to_hash, options
  self.key = key
  acquire_if_acquired
end

Instance Attribute Details

#acquiredObject

Returns the value of attribute acquired.



12
13
14
# File 'lib/mongo-lock.rb', line 12

def acquired
  @acquired
end

#configurationObject

Returns the value of attribute configuration.



10
11
12
# File 'lib/mongo-lock.rb', line 10

def configuration
  @configuration
end

#expires_atObject

Returns the value of attribute expires_at.



13
14
15
# File 'lib/mongo-lock.rb', line 13

def expires_at
  @expires_at
end

#keyObject

Returns the value of attribute key.



11
12
13
# File 'lib/mongo-lock.rb', line 11

def key
  @key
end

#releasedObject

Returns the value of attribute released.



14
15
16
# File 'lib/mongo-lock.rb', line 14

def released
  @released
end

Class Method Details

.acquire(key, options = {}) ⇒ Object



58
59
60
# File 'lib/mongo-lock.rb', line 58

def self.acquire key, options = {}
  init_and_send key, options, :acquire
end

.acquire!(key, options = {}) ⇒ Object



66
67
68
# File 'lib/mongo-lock.rb', line 66

def self.acquire! key, options = {}
  init_and_send key, options, :acquire!
end

.available?(key, options = {}) ⇒ Boolean

Returns:

  • (Boolean)


74
75
76
# File 'lib/mongo-lock.rb', line 74

def self.available? key, options = {}
  init_and_send key, options, :available?
end

.clear_expiredObject



89
90
91
92
93
# File 'lib/mongo-lock.rb', line 89

def self.clear_expired
  configuration.collections.each_pair do |key,collection|
    collection.remove expires_at: { '$lt' => Time.now }
  end
end

.configurationObject



29
30
31
32
33
34
35
# File 'lib/mongo-lock.rb', line 29

def self.configuration
  if defined? @@default_configuration
    @@default_configuration
  else
    @@default_configuration = configure
  end
end

.configure(options = {}, &block) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/mongo-lock.rb', line 16

def self.configure options = {}, &block
  defaults = {
    timeout_in: 10,
    limit: 100,
    frequency: 1,
    expires_after: 10,
    raise: false,
    owner: Proc.new { "#{`hostname`.strip}:#{Process.pid}:#{Thread.object_id}" }
  }
  defaults = defaults.merge(@@default_configuration) if defined?(@@default_configuration) && @@default_configuration
  @@default_configuration = Configuration.new(defaults, options, &block)
end

.ensure_indexesObject



78
79
80
81
82
83
84
85
86
87
# File 'lib/mongo-lock.rb', line 78

def self.ensure_indexes
  configuration.collections.each_pair do |key, collection|
    collection.create_index([
      ['key', Mongo::ASCENDING],
      ['owner', Mongo::ASCENDING],
      ['expires_at', Mongo::ASCENDING]
    ])
    collection.create_index([['ttl', Mongo::ASCENDING]],{ expireAfterSeconds: 0 })
  end
end

.init_and_send(key, options = {}, method) ⇒ Object



52
53
54
55
56
# File 'lib/mongo-lock.rb', line 52

def self.init_and_send key, options = {}, method
  lock = self.new(key, options)
  lock.send(method)
  lock
end

.release(key, options = {}) ⇒ Object



62
63
64
# File 'lib/mongo-lock.rb', line 62

def self.release key, options = {}
  init_and_send key, options, :release
end

.release!(key, options = {}) ⇒ Object



70
71
72
# File 'lib/mongo-lock.rb', line 70

def self.release! key, options = {}
  init_and_send key, options, :release!
end

.release_all(options = {}) ⇒ Object



37
38
39
40
41
42
43
44
45
# File 'lib/mongo-lock.rb', line 37

def self.release_all options = {}
  if options.include? :collection
    release_collection configuration.collection(options[:collection]), options[:owner]
  else
    configuration.collections.each_pair do |key,collection|
      release_collection collection, options[:owner]
    end
  end
end

.release_collection(collection, owner = nil) ⇒ Object



47
48
49
50
# File 'lib/mongo-lock.rb', line 47

def self.release_collection collection, owner=nil
  selector = if owner then { owner: owner } else {} end
  collection.remove(selector)
end

Instance Method Details

#acquire(options = {}) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/mongo-lock.rb', line 106

def acquire options = {}
  options = configuration.to_hash.merge options
  i = 1
  time_spent = 0

  loop do
    # If timeout has expired
    if options[:timeout_in] && options[:timeout_in] < time_spent
      return raise_or_false options

    # If limit has expired
    elsif options[:limit] && options[:limit] < i
      return raise_or_false options

    # If there is an existing lock
    elsif existing_lock = find_or_insert(options)

      # If the lock is owned by me
      if existing_lock['owner'] == options[:owner]
        self.acquired = true
        extend_by options[:expires_after]
        return true
      end

    # If the lock was acquired
    else
      self.acquired = true
      return true

    end

    if options[:frequency].is_a? Proc
      frequency = options[:frequency].call(i)
    else
      frequency = options[:frequency]
    end
    sleep frequency
    time_spent += frequency
    i += 1
  end
end

#acquire!(options = {}) ⇒ Object



148
149
150
151
# File 'lib/mongo-lock.rb', line 148

def acquire! options = {}
  options[:raise] = true
  acquire options
end

#acquire_if_acquiredObject



285
286
287
288
289
290
291
292
293
# File 'lib/mongo-lock.rb', line 285

def acquire_if_acquired
  if (collection.find({
      key: key,
      owner: configuration.owner,
      expires_at: { '$gt' => Time.now }
    }).count > 0)
    self.acquired = true
  end
end

#acquired?Boolean

Returns:

  • (Boolean)


273
274
275
# File 'lib/mongo-lock.rb', line 273

def acquired?
  !!acquired && !expired?
end

#available?(options = {}) ⇒ Boolean

Returns:

  • (Boolean)


260
261
262
263
264
# File 'lib/mongo-lock.rb', line 260

def available? options = {}
  options = configuration.to_hash.merge options
  existing_lock = collection.find(query).first
  !existing_lock || existing_lock['owner'] == options[:owner]
end

#configure(options = {}) {|self.configuration| ... } ⇒ Object

Yields:



101
102
103
104
# File 'lib/mongo-lock.rb', line 101

def configure options = {}, &block
  self.configuration = Configuration.new self.configuration.to_hash, options
  yield self.configuration if block_given?
end

#expired?Boolean

Returns:

  • (Boolean)


277
278
279
# File 'lib/mongo-lock.rb', line 277

def expired?
  !!(expires_at && expires_at < Time.now)
end

#extend(options = {}) ⇒ Object



245
246
247
248
# File 'lib/mongo-lock.rb', line 245

def extend options = {}
  time = configuration.to_hash.merge(options)[:expires_after]
  extend_by time, options
end

#extend!(options = {}) ⇒ Object



255
256
257
258
# File 'lib/mongo-lock.rb', line 255

def extend! options = {}
  options[:raise] = true
  extend options
end

#extend_by(time, options = {}) ⇒ Object



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/mongo-lock.rb', line 216

def extend_by time, options = {}
  options = configuration.to_hash.merge options

  # Can't extend a lock that hasn't been acquired
  if !acquired?
    return raise_or_false options, NotExtendedError

  # Can't extend a lock that has started
  elsif expired?
    return raise_or_false options, NotExtendedError

  else
    to_expire_at = expires_at + time
    existing_lock = collection.find_and_modify({
      query: query,
      update: {
        '$set' => {
          key: key,
          owner: options[:owner],
          expires_at: to_expire_at,
          ttl: to_expire_at
        }
      },
      upsert: true
    })
    true
  end
end

#extend_by!(time, options = {}) ⇒ Object



250
251
252
253
# File 'lib/mongo-lock.rb', line 250

def extend_by! time, options = {}
  options[:raise] = true
  extend_by time, options
end

#find_or_insert(options) ⇒ Object



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/mongo-lock.rb', line 192

def find_or_insert options
  to_expire_at = Time.now + options[:expires_after]
  existing_lock = collection.find_and_modify({
    query: query,
    update: {
      '$setOnInsert' => {
        key: key,
        owner: options[:owner],
        expires_at: to_expire_at,
        ttl: to_expire_at
      }
    },
    upsert: true
  })

  if existing_lock
    self.expires_at = existing_lock['expires_at']
  else
    self.expires_at = to_expire_at
  end

  existing_lock
end

#queryObject



266
267
268
269
270
271
# File 'lib/mongo-lock.rb', line 266

def query
  {
    key: key,
    expires_at: { '$gt' => Time.now }
  }
end

#raise_or_false(options, error = NotAcquiredError) ⇒ Object



187
188
189
190
# File 'lib/mongo-lock.rb', line 187

def raise_or_false options, error = NotAcquiredError
  raise error if options[:raise]
  false
end

#release(options = {}) ⇒ Object



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/mongo-lock.rb', line 153

def release options = {}
  options = configuration.to_hash.merge options

  # If the lock has already been released
  if released?
    return true

  # If the lock has expired its as good as released
  elsif expired?
    self.released = true
    self.acquired = false
    return true

  # We must have acquired the lock to release it
  elsif !acquired?
    if acquire options.merge(raise: false)
      return release options
    else
      return raise_or_false options, NotReleasedError
    end

  else
    self.released = true
    self.acquired = false
    collection.remove key: key, owner: options[:owner]
    return true
  end
end

#release!(options = {}) ⇒ Object



182
183
184
185
# File 'lib/mongo-lock.rb', line 182

def release! options = {}
  options[:raise] = true
  release options
end

#released?Boolean

Returns:

  • (Boolean)


281
282
283
# File 'lib/mongo-lock.rb', line 281

def released?
  !!released
end