mongo-lock 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MGRiNTY2ZDM1N2YyM2M3MjcwZGNmMTI3ZTVhM2YyMmM1MWNlNWI3Zg==
5
+ data.tar.gz: !binary |-
6
+ Y2VlMzFlMDU2MDljMGE3ZTI1NTQwZWE1NDU2OTJmMmMwYjUzOWRmZQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ZDU5MmM1YTYxN2Y4ZDhjNDMxYmM5NjQzYjliMmZhN2YzZmIwODQ1NzkwMWVi
10
+ MmFlMGNhYmE1NTk2ZTI4NDY1M2ZlNWY1MjMxN2E5YjBhNTlmMzEwMWVmZDFk
11
+ ZTY3YTgyYWJhOWU3YmE3ODE4MmQ0ZjZjZTM2N2M1NTQ0ZTk0Mjc=
12
+ data.tar.gz: !binary |-
13
+ MTQzNWNiYTYxYWVkZDQ2MjNlZGEwNDYzMTQ2MzBkNTA3OTZkODZmNDczYzEx
14
+ YmViNzkyN2RlMzU0MDA4MDE5NGMzNzNlMzE2MWUzNTM2YjUyZTFhYTJmZDQ3
15
+ M2VjYjg0NDQwNWE4YzEyNTFhNzBkMWY4OWUzMDhjNTczOWQxODQ=
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in trakio-ruby.gemspec
4
+ gemspec
@@ -0,0 +1,51 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ mongo-lock (1.0.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ activesupport (4.0.3)
10
+ i18n (~> 0.6, >= 0.6.4)
11
+ minitest (~> 4.2)
12
+ multi_json (~> 1.3)
13
+ thread_safe (~> 0.1)
14
+ tzinfo (~> 0.3.37)
15
+ atomic (1.1.14)
16
+ coderay (1.1.0)
17
+ diff-lcs (1.2.5)
18
+ fuubar (1.3.2)
19
+ rspec (>= 2.14.0, < 3.1.0)
20
+ ruby-progressbar (~> 1.3)
21
+ i18n (0.6.9)
22
+ method_source (0.8.2)
23
+ minitest (4.7.5)
24
+ multi_json (1.8.4)
25
+ pry (0.9.12.6)
26
+ coderay (~> 1.0)
27
+ method_source (~> 0.8)
28
+ slop (~> 3.4)
29
+ rspec (2.14.1)
30
+ rspec-core (~> 2.14.0)
31
+ rspec-expectations (~> 2.14.0)
32
+ rspec-mocks (~> 2.14.0)
33
+ rspec-core (2.14.7)
34
+ rspec-expectations (2.14.5)
35
+ diff-lcs (>= 1.1.3, < 2.0)
36
+ rspec-mocks (2.14.5)
37
+ ruby-progressbar (1.4.1)
38
+ slop (3.4.7)
39
+ thread_safe (0.1.3)
40
+ atomic
41
+ tzinfo (0.3.38)
42
+
43
+ PLATFORMS
44
+ ruby
45
+
46
+ DEPENDENCIES
47
+ activesupport
48
+ fuubar
49
+ mongo-lock!
50
+ pry
51
+ rspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 trak.io Ltd
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,291 @@
1
+ Mongo::Lock
2
+ ==========
3
+
4
+ [![Code Climate](https://codeclimate.com/github/trakio/mongo-lock.png)](https://codeclimate.com/github/trakio/mongo-lock)
5
+
6
+ Key based pessimistic locking for Ruby and MongoDB. Is this key avaliable? Yes - Lock it for me for a sec will you. No - OK I'll just wait here until its ready.
7
+
8
+ It handles timeouts and and vanishing lock owners (such as machine failures)
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'mongo-lock'
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ ```
21
+ $ bundle
22
+ ```
23
+
24
+ Or install it yourself as:
25
+
26
+ ```
27
+ $ gem install mongo-lock
28
+ ```
29
+
30
+ Build you indexes on any collection that is going to hold locks:
31
+
32
+ ```ruby
33
+ Mongo::Lock.ensure_indexes # Will use the collection provided to #configure
34
+ ```
35
+
36
+ For this to work you must have configured your collection or collections in when intializing locks, in #configure or .configure.
37
+
38
+ ## Shout outs
39
+
40
+ We took quite a bit of inspiration from the [redis versions](https://github.com/mlanett/redis-lock) [of this gem](https://github.com/PatrickTulskie/redis-lock) by [@mlanett](https://github.com/mlanett/redis-lock) and [@PatrickTulskie](https://github.com/PatrickTulskie). If you aren't already using MongoDB or are already using Redis in your stack you probably want to think about using one of them.
41
+
42
+ We also looked at [mongo-locking gem](https://github.com/servio/mongo-locking) by [@servio](https://github.com/servio). It was a bit complicated for ours needs, but if you need to lock related and embedded documents rather than just keys it could be what you need.
43
+
44
+ ## Background
45
+
46
+ A lock has an expected lifetime. If the owner of a lock disappears (due to machine failure, network failure, process death), you want the lock to expire and another owner to be able to acquire the lock. At the same time, the owner of a lock should be able to extend its lifetime. Thus, you can acquire a lock with a conservative estimate on lifetime, and extend it as necessary, rather than acquiring the lock with a very long lifetime which will result in long waits in the event of failures.
47
+
48
+ A lock has an owner. Mongo::Lock defaults to using an owner id of HOSTNAME:PID:TID.
49
+
50
+ ## Configuration
51
+
52
+ Mongo::Lock makes no effort to help configure the MongoDB connection - that's
53
+ what the Mongo Ruby Driver is for.
54
+
55
+ Configuring Mongo::Lock with the Mongo Ruby Driver would look like this:
56
+
57
+ ```ruby
58
+ Mongo::Lock.configure collection: Mongo::Connection.new("localhost").db("somedb").collection("locks")
59
+ ```
60
+
61
+ Or using Mongoid:
62
+
63
+ ```ruby
64
+ Mongo::Lock.configure collection: Mongoid.database.collection("locks")
65
+ ```
66
+
67
+ You can add multiple collections with a hash that can be referenced later using symbols:
68
+
69
+ ```ruby
70
+ Mongo::Lock.configure collections: { default: Mongoid.database.collection("locks"), other: Mongoid.database.collection("other_locks") }
71
+ Mongo::Lock.acquire('my_lock') # Locks in the default collection
72
+ Mongo::Lock.acquire('my_lock', collection: :other) # Locks in the other_locks collection
73
+ ```
74
+
75
+ You can also configure using a block:
76
+
77
+ ```ruby
78
+ Mongo::Lock.configure do |config|
79
+ config.collections: {
80
+ default: Mongoid.database.collection("locks"),
81
+ other: Mongoid.database.collection("other_locks")
82
+ }
83
+ end
84
+ ```
85
+
86
+ ### Acquisition timeout_in
87
+
88
+ A lock may need more than one attempt to acquire it. Mongo::Lock offers:
89
+
90
+ ```ruby
91
+ Mongo::Lock.configure do |config|
92
+ config.timeout_in = false # timeout_in in seconds on acquisition; this defaults to false ie no time limit.
93
+ config.limit = 100 # The limit on the number of acquisition attempts; this defaults to 100.
94
+ config.frequency = 1 # Frequency in seconds for acquisition attempts ; this defaults to 1.
95
+ # acquisition_attempt_frequency can also be given as a proc which will be passed the attempt number
96
+ config.frequency = Proc.new { |x| x**2 }
97
+ end
98
+ ```
99
+
100
+ ### Lock Expiry
101
+
102
+ A lock will automatically be relinquished once its expiry has passed. Expired locks are cleaned up by [MongoDB's TTL index](http://docs.mongodb.org/manual/tutorial/expire-data/), which may take up to 60 seconds or more depending on load to actually remove expired locks. Expired locks that have not been cleaned out can still be acquire. **You must have built your indexes to ensure expired locks are cleaned out.**
103
+
104
+ ```ruby
105
+ Mongo::Lock.configure do |config|
106
+ config.expires_after = false # timeout_in in seconds for lock expiry; this defaults to 10.
107
+ end
108
+ ```
109
+
110
+ You can remove expired locks yourself with:
111
+
112
+ ```ruby
113
+ Mongo::Lock.clean_expired
114
+ ```
115
+
116
+
117
+ ### Raising Errors
118
+
119
+ If a lock cannot be acquired, released or extended it will return false, you can set the raise option to true to raise a Mongo::Lock::LockNotAcquiredError or Mongo::Lock::LockNotReleasedError.
120
+
121
+ ```ruby
122
+ Mongo::Lock.configure do |config|
123
+ config.raise = true # Whether to raise an error when acquire, release or extend fail.
124
+ end
125
+ ```
126
+
127
+ Using .acquire!, #acquire!, .release!, #release!, #extend_by! and #extend! will also raise exceptions instead of returning false.
128
+
129
+ ### Owner
130
+
131
+ By default the owner id will be generated using the following Proc:
132
+
133
+ ```ruby
134
+ Proc.new { "#{`hostname`.strip}:#{Process.pid}:#{Thread.object_id}" }
135
+ ```
136
+
137
+ You can override this with either a Proc that returns any object that responds to to_s, or with any object that responds to #to_s.
138
+
139
+ ```ruby
140
+ Mongo::Lock.configure do |config|
141
+ config.owner = ['my', 'owner', 'id']
142
+ end
143
+ # Or
144
+ Mongo::Lock.configure do |config|
145
+ config.owner = Proc.new { [`hostname`.strip, Process.pid] }
146
+ end
147
+ ```
148
+
149
+ Note: Hosts, threads or processes using the same owner can acquire each others locks.
150
+
151
+ ## Usage
152
+
153
+ You can use Mongo::Lock's class methods:
154
+
155
+ ```ruby
156
+ Mongo::Lock.acquire('my_key', options) do |lock|
157
+ # Do Something here that needs my_key locked
158
+ end
159
+
160
+ lock = Mongo::Lock.new('my_key', options).acquire
161
+ # Do Something here that needs my_key locked
162
+ lock.release
163
+ # or
164
+ Mongo::Lock.release('my_key')
165
+ ```
166
+
167
+ Or you can initialise your own instance.
168
+
169
+ ```ruby
170
+ Mongo::Lock.new('my_key', options).acquire do |lock|
171
+ # Do Something here that needs my_key locked
172
+ end
173
+
174
+ lock = Mongo::Lock.acquire('my_key', options)
175
+ # Do Something here that needs my_key locked
176
+ lock.release
177
+ # or
178
+ Mongo::Lock.release('my_key')
179
+ ```
180
+
181
+ ### Options
182
+
183
+ When using Mongo::Lock#acquire, Mongo::Lock#release or Mongo::Lock#new after the key you may overide any of the following options:
184
+
185
+ ```ruby
186
+ Mongo::Lock.new 'my_key', {
187
+ collection: Mongo::Connection.new("localhost").db("somedb").collection("locks"), # May also be a symbol if that symbol was provided in the collections hash to Mongo::Lock.configure
188
+ timeout_in: 10, # timeout_in in seconds on acquisition; this defaults to false ie no time limit.
189
+ limit: 10, # The limit on the number of acquisition attempts; this defaults to 100.
190
+ frequency: 2, # Frequency in seconds for acquisition attempts ; this defaults to 1.
191
+ expires_after: 10,# timeout_in in seconds for lock expiry; this defaults to 10.
192
+ }
193
+ ```
194
+
195
+ ### Extending lock
196
+
197
+ You can extend a lock by calling Mongo::Lock#extend_by with the number of seconds to extend the lock.
198
+
199
+ ```ruby
200
+ Mongo::Lock.new 'my_key' do |lock|
201
+ lock.extend_by 10
202
+ end
203
+ ```
204
+
205
+ You can also call Mongo::Lock#extend and it will extend by the lock's expires_after option.
206
+
207
+ ```ruby
208
+ Mongo::Lock.new 'my_key' do |lock|
209
+ lock.extend
210
+ end
211
+ ```
212
+
213
+ ### Check you still hold a lock
214
+
215
+ ```ruby
216
+ Mongo::Lock.acquire 'my_key', expires_after: 10 do |lock|
217
+ sleep 9
218
+ lock.expired? # False
219
+ sleep 11
220
+ lock.expired? # True
221
+ end
222
+ ```
223
+
224
+ ### Check a key is already locked without acquiring it
225
+
226
+ ```ruby
227
+ Mongo::Lock.available? 'my_key'
228
+ # Or
229
+ lock = Mongo::Lock.new('my_key')
230
+ lock.available?
231
+ ```
232
+
233
+ ### Failures
234
+
235
+ If Mongo::Lock#acquire cannot acquire a lock within its configuration limits it will return false.
236
+
237
+ ```ruby
238
+ unless Mongo::Lock.acquire 'my_key'
239
+ # Maybe try again tomorrow
240
+ end
241
+ ```
242
+
243
+ If Mongo::Lock#release cannot release a lock because it wasn't acquired it will return false. If it has already been released, or has expired it will do nothing and return true.
244
+
245
+ ```ruby
246
+ unless Mongo::Lock.release 'my_key'
247
+ # Eh somebody else should release it eventually
248
+ end
249
+ ```
250
+
251
+ If Mongo::Lock#extend cannot be extended because it has already been released, it is owned by someone else or it was never acquired it will return false.
252
+
253
+ ```ruby
254
+ unless lock.extend_by 10
255
+ # Eh somebody else should release it eventually
256
+ end
257
+ ```
258
+
259
+ If the raise error option is set to true or you append ! to the end of the method name and you call any of the acquire, release, extend_by or extend methods they will raise a Mongo::Lock::NotAcquiredError, Mongo::Lock::NotReleasedError or Mongo::Lock::NotExtendedError instead of returning false.
260
+
261
+ ```ruby
262
+ begin
263
+ Mongo::Lock.acquire! 'my_key'
264
+ rescue Mongo::Lock::LockNotAcquiredError => e
265
+ # Maybe try again tomorrow
266
+ end
267
+
268
+ # Or
269
+
270
+ begin
271
+ Mongo::Lock.acquire 'my_key', raise: true
272
+ rescue Mongo::Lock::LockNotAcquiredError => e
273
+ # Maybe try again tomorrow
274
+ end
275
+ ```
276
+
277
+ ## Contributors
278
+
279
+ Matthew Spence (msaspence)
280
+
281
+ The bulk of this gem has been developed for and by [trak.io](http://trak.io)
282
+
283
+ [![trak.io](http://trak.io/assets/images/logo@2x.png)](http://trak.io)
284
+
285
+ ## Contributing
286
+
287
+ 1. Fork it
288
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
289
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
290
+ 4. Push to the branch (`git push origin my-new-feature`)
291
+ 5. Create new Pull Request
@@ -0,0 +1,8 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << 'test'
5
+ end
6
+
7
+ desc "Run tests"
8
+ task :default => :test
@@ -0,0 +1,296 @@
1
+ require 'mongo-lock/configuration'
2
+
3
+ module Mongo
4
+ class Lock
5
+
6
+ class NotAcquiredError < StandardError ; end
7
+ class NotReleasedError < StandardError ; end
8
+ class NotExtendedError < StandardError ; end
9
+
10
+ attr_accessor :configuration
11
+ attr_accessor :key
12
+ attr_accessor :acquired
13
+ attr_accessor :expires_at
14
+ attr_accessor :released
15
+
16
+ def self.configure options = {}, &block
17
+ defaults = {
18
+ timeout_in: 10,
19
+ limit: 100,
20
+ frequency: 1,
21
+ expires_after: 10,
22
+ raise: false,
23
+ owner: Proc.new { "#{`hostname`.strip}:#{Process.pid}:#{Thread.object_id}" }
24
+ }
25
+ defaults = defaults.merge(@@default_configuration) if defined?(@@default_configuration) && @@default_configuration
26
+ @@default_configuration = Configuration.new(defaults, options, &block)
27
+ end
28
+
29
+ def self.configuration
30
+ if defined? @@default_configuration
31
+ @@default_configuration
32
+ else
33
+ @@default_configuration = configure
34
+ end
35
+ end
36
+
37
+ def self.release_all options = {}
38
+ if options.include? :collection
39
+ release_collection configuration.collection(options[:collection]), options[:owner]
40
+ else
41
+ configuration.collections.each_pair do |key,collection|
42
+ release_collection collection, options[:owner]
43
+ end
44
+ end
45
+ end
46
+
47
+ def self.release_collection collection, owner=nil
48
+ selector = if owner then { owner: owner } else {} end
49
+ collection.remove(selector)
50
+ end
51
+
52
+ def self.init_and_send key, options = {}, method
53
+ lock = self.new(key, options)
54
+ lock.send(method)
55
+ lock
56
+ end
57
+
58
+ def self.acquire key, options = {}
59
+ init_and_send key, options, :acquire
60
+ end
61
+
62
+ def self.release key, options = {}
63
+ init_and_send key, options, :release
64
+ end
65
+
66
+ def self.acquire! key, options = {}
67
+ init_and_send key, options, :acquire!
68
+ end
69
+
70
+ def self.release! key, options = {}
71
+ init_and_send key, options, :release!
72
+ end
73
+
74
+ def self.available? key, options = {}
75
+ init_and_send key, options, :available?
76
+ end
77
+
78
+ def self.ensure_indexes
79
+ configuration.collections.each_pair do |key, collection|
80
+ collection.create_index([
81
+ ['key', Mongo::ASCENDING],
82
+ ['owner', Mongo::ASCENDING],
83
+ ['expires_at', Mongo::ASCENDING]
84
+ ])
85
+ collection.create_index([['ttl', Mongo::ASCENDING]],{ expireAfterSeconds: 0 })
86
+ end
87
+ end
88
+
89
+ def self.clear_expired
90
+ configuration.collections.each_pair do |key,collection|
91
+ collection.remove expires_at: { '$lt' => Time.now }
92
+ end
93
+ end
94
+
95
+ def initialize key, options = {}
96
+ self.configuration = Configuration.new self.class.configuration.to_hash, options
97
+ self.key = key
98
+ acquire_if_acquired
99
+ end
100
+
101
+ def configure options = {}, &block
102
+ self.configuration = Configuration.new self.configuration.to_hash, options
103
+ yield self.configuration if block_given?
104
+ end
105
+
106
+ def acquire options = {}
107
+ options = configuration.to_hash.merge options
108
+ i = 1
109
+ time_spent = 0
110
+
111
+ loop do
112
+ # If timeout has expired
113
+ if options[:timeout_in] && options[:timeout_in] < time_spent
114
+ return raise_or_false options
115
+
116
+ # If limit has expired
117
+ elsif options[:limit] && options[:limit] < i
118
+ return raise_or_false options
119
+
120
+ # If there is an existing lock
121
+ elsif existing_lock = find_or_insert(options)
122
+
123
+ # If the lock is owned by me
124
+ if existing_lock['owner'] == options[:owner]
125
+ self.acquired = true
126
+ extend_by options[:expires_after]
127
+ return true
128
+ end
129
+
130
+ # If the lock was acquired
131
+ else
132
+ self.acquired = true
133
+ return true
134
+
135
+ end
136
+
137
+ if options[:frequency].is_a? Proc
138
+ frequency = options[:frequency].call(i)
139
+ else
140
+ frequency = options[:frequency]
141
+ end
142
+ sleep frequency
143
+ time_spent += frequency
144
+ i += 1
145
+ end
146
+ end
147
+
148
+ def acquire! options = {}
149
+ options[:raise] = true
150
+ acquire options
151
+ end
152
+
153
+ def release options = {}
154
+ options = configuration.to_hash.merge options
155
+
156
+ # If the lock has already been released
157
+ if released?
158
+ return true
159
+
160
+ # If the lock has expired its as good as released
161
+ elsif expired?
162
+ self.released = true
163
+ self.acquired = false
164
+ return true
165
+
166
+ # We must have acquired the lock to release it
167
+ elsif !acquired?
168
+ if acquire options.merge(raise: false)
169
+ return release options
170
+ else
171
+ return raise_or_false options, NotReleasedError
172
+ end
173
+
174
+ else
175
+ self.released = true
176
+ self.acquired = false
177
+ collection.remove key: key, owner: options[:owner]
178
+ return true
179
+ end
180
+ end
181
+
182
+ def release! options = {}
183
+ options[:raise] = true
184
+ release options
185
+ end
186
+
187
+ def raise_or_false options, error = NotAcquiredError
188
+ raise error if options[:raise]
189
+ false
190
+ end
191
+
192
+ def find_or_insert options
193
+ to_expire_at = Time.now + options[:expires_after]
194
+ existing_lock = collection.find_and_modify({
195
+ query: query,
196
+ update: {
197
+ '$setOnInsert' => {
198
+ key: key,
199
+ owner: options[:owner],
200
+ expires_at: to_expire_at,
201
+ ttl: to_expire_at
202
+ }
203
+ },
204
+ upsert: true
205
+ })
206
+
207
+ if existing_lock
208
+ self.expires_at = existing_lock['expires_at']
209
+ else
210
+ self.expires_at = to_expire_at
211
+ end
212
+
213
+ existing_lock
214
+ end
215
+
216
+ def extend_by time, options = {}
217
+ options = configuration.to_hash.merge options
218
+
219
+ # Can't extend a lock that hasn't been acquired
220
+ if !acquired?
221
+ return raise_or_false options, NotExtendedError
222
+
223
+ # Can't extend a lock that has started
224
+ elsif expired?
225
+ return raise_or_false options, NotExtendedError
226
+
227
+ else
228
+ to_expire_at = expires_at + time
229
+ existing_lock = collection.find_and_modify({
230
+ query: query,
231
+ update: {
232
+ '$set' => {
233
+ key: key,
234
+ owner: options[:owner],
235
+ expires_at: to_expire_at,
236
+ ttl: to_expire_at
237
+ }
238
+ },
239
+ upsert: true
240
+ })
241
+ true
242
+ end
243
+ end
244
+
245
+ def extend options = {}
246
+ time = configuration.to_hash.merge(options)[:expires_after]
247
+ extend_by time, options
248
+ end
249
+
250
+ def extend_by! time, options = {}
251
+ options[:raise] = true
252
+ extend_by time, options
253
+ end
254
+
255
+ def extend! options = {}
256
+ options[:raise] = true
257
+ extend options
258
+ end
259
+
260
+ def available? options = {}
261
+ options = configuration.to_hash.merge options
262
+ existing_lock = collection.find(query).first
263
+ !existing_lock || existing_lock['owner'] == options[:owner]
264
+ end
265
+
266
+ def query
267
+ {
268
+ key: key,
269
+ expires_at: { '$gt' => Time.now }
270
+ }
271
+ end
272
+
273
+ def acquired?
274
+ !!acquired && !expired?
275
+ end
276
+
277
+ def expired?
278
+ !!(expires_at && expires_at < Time.now)
279
+ end
280
+
281
+ def released?
282
+ !!released
283
+ end
284
+
285
+ def acquire_if_acquired
286
+ if (collection.find({
287
+ key: key,
288
+ owner: configuration.owner,
289
+ expires_at: { '$gt' => Time.now }
290
+ }).count > 0)
291
+ self.acquired = true
292
+ end
293
+ end
294
+
295
+ end
296
+ end