mongo-lock 1.1.4 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +8 -8
  2. data/Gemfile.lock +41 -38
  3. data/README.md +7 -4
  4. data/lib/mongo-lock.rb +22 -37
  5. data/lib/mongo-lock/configuration.rb +38 -4
  6. data/lib/mongo-lock/drivers/base.rb +41 -0
  7. data/lib/mongo-lock/drivers/mongo.rb +99 -0
  8. data/lib/mongo-lock/drivers/moped.rb +62 -0
  9. data/lib/mongo-lock/send_with_raise_methods.rb +28 -0
  10. data/lib/mongo-lock/version.rb +1 -1
  11. data/mongo-lock.gemspec +4 -3
  12. data/spec/configuration_spec.rb +66 -0
  13. data/spec/configure_spec.rb +8 -2
  14. data/spec/examples/acquire_example.rb +219 -0
  15. data/spec/examples/acquired_example.rb +54 -0
  16. data/spec/examples/available_example.rb +70 -0
  17. data/spec/examples/clear_expired_example.rb +100 -0
  18. data/spec/examples/ensure_indexes_example.rb +38 -0
  19. data/spec/examples/expired_example.rb +41 -0
  20. data/spec/examples/extend_by_example.rb +137 -0
  21. data/spec/examples/release_all_example.rb +117 -0
  22. data/spec/examples/release_example.rb +166 -0
  23. data/spec/initialise_spec.rb +2 -0
  24. data/spec/mongo_driver_spec.rb +22 -0
  25. data/spec/moped_driver_spec.rb +22 -0
  26. data/spec/rake_spec.rb +1 -1
  27. data/spec/spec_helper.rb +2 -7
  28. data/spec/support/mongo_helper.rb +41 -0
  29. metadata +58 -23
  30. data/lib/mongo-lock/mongo_queries.rb +0 -97
  31. data/spec/acquire_spec.rb +0 -217
  32. data/spec/acquired_spec.rb +0 -53
  33. data/spec/available_spec.rb +0 -68
  34. data/spec/clear_expired_spec.rb +0 -98
  35. data/spec/ensure_indexes_spec.rb +0 -34
  36. data/spec/expired_spec.rb +0 -39
  37. data/spec/extend_by_spec.rb +0 -135
  38. data/spec/release_all_spec.rb +0 -115
  39. data/spec/release_spec.rb +0 -164
@@ -0,0 +1,41 @@
1
+ module Mongo
2
+ class Lock
3
+ module Drivers
4
+ class Base
5
+
6
+ attr_accessor :lock
7
+
8
+ def initialize lock
9
+ self.lock = lock
10
+ end
11
+
12
+ def key
13
+ lock.key
14
+ end
15
+
16
+ def query
17
+ {
18
+ key: key,
19
+ expires_at: { '$gt' => Time.now }
20
+ }
21
+ end
22
+
23
+ def find_or_insert options
24
+ options[:expire_at] = Time.now + options[:expire_in]
25
+ options[:insert] = true
26
+ find_and_modify options
27
+ end
28
+
29
+ def find_and_update time, options
30
+ options[:expire_at] = lock.expires_at + time
31
+ find_and_modify options
32
+ end
33
+
34
+ def is_acquired?
35
+ find_already_acquired.count > 0
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,99 @@
1
+ module Mongo
2
+ class Lock
3
+ module Drivers
4
+ class Mongo < Base
5
+
6
+ attr_accessor :lock
7
+
8
+ def self.release_collection collection, owner=nil
9
+ selector = if owner then { owner: owner } else {} end
10
+ collection.remove(selector)
11
+ end
12
+
13
+ def self.ensure_indexes collection
14
+ collection.create_index([
15
+ ['key', ::Mongo::ASCENDING],
16
+ ['owner', ::Mongo::ASCENDING],
17
+ ['expires_at', ::Mongo::ASCENDING]
18
+ ])
19
+ collection.create_index([['ttl', ::Mongo::ASCENDING]],{ expireAfterSeconds: 0 })
20
+ end
21
+
22
+ def self.clear_expired collection
23
+ collection.remove expires_at: { '$lt' => Time.now }
24
+ end
25
+
26
+ def initialize lock
27
+ self.lock = lock
28
+ end
29
+
30
+ def key
31
+ lock.key
32
+ end
33
+
34
+ def query
35
+ {
36
+ key: key,
37
+ expires_at: { '$gt' => Time.now }
38
+ }
39
+ end
40
+
41
+ def find_or_insert options
42
+ options[:expire_at] = Time.now + options[:expire_in]
43
+ options[:insert] = true
44
+ find_and_modify options
45
+ end
46
+
47
+ def find_and_update time, options
48
+ options[:expire_at] = lock.expires_at + time
49
+ find_and_modify options
50
+ end
51
+
52
+ def find_and_modify options
53
+ operation = options[:insert] ? '$setOnInsert' : '$set'
54
+ existing_lock = lock.configuration.collection.find_and_modify({
55
+ query: query,
56
+ update: {
57
+ operation => {
58
+ key: key,
59
+ owner: options[:owner],
60
+ expires_at: options[:expire_at],
61
+ ttl: options[:expire_at]
62
+ }
63
+ },
64
+ upsert: !!options[:insert]
65
+ })
66
+
67
+ if existing_lock
68
+ lock.expires_at = existing_lock['expires_at']
69
+ else
70
+ lock.expires_at = options[:expire_at]
71
+ end
72
+
73
+ existing_lock
74
+ end
75
+
76
+ def remove options
77
+ lock.configuration.collection.remove key: key, owner: options[:owner]
78
+ end
79
+
80
+ def is_acquired?
81
+ find_already_acquired.count > 0
82
+ end
83
+
84
+ def find_already_acquired
85
+ lock.configuration.collection.find({
86
+ key: key,
87
+ owner: lock.configuration.owner,
88
+ expires_at: { '$gt' => Time.now }
89
+ })
90
+ end
91
+
92
+ def find_existing
93
+ lock.configuration.collection.find(query).first
94
+ end
95
+
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,62 @@
1
+ module Mongo
2
+ class Lock
3
+ module Drivers
4
+ class Moped < Base
5
+
6
+ def self.release_collection collection, owner=nil
7
+ selector = if owner then { owner: owner } else {} end
8
+ collection.find(selector).remove_all
9
+ end
10
+
11
+ def self.ensure_indexes collection
12
+ collection.indexes.create({ key: 1, owner: 1, expires_at: 1 })
13
+ collection.indexes.create({ ttl: 1 }, { expireAfterSeconds: 0 })
14
+ end
15
+
16
+ def self.clear_expired collection
17
+ collection.find(expires_at: { '$lt' => Time.now }).remove_all
18
+ end
19
+
20
+ def find_and_modify options
21
+ operation = options[:insert] ? '$setOnInsert' : '$set'
22
+ existing_lock = lock.configuration.collection.
23
+ find(query).
24
+ modify({
25
+ operation => {
26
+ key: key,
27
+ owner: options[:owner],
28
+ expires_at: options[:expire_at],
29
+ ttl: options[:expire_at]
30
+ }
31
+ }, { upsert: !!options[:insert] })
32
+ existing_lock = nil if existing_lock == {} # Moped returns {} for an empty result
33
+
34
+ if existing_lock
35
+ lock.expires_at = existing_lock['expires_at']
36
+ else
37
+ lock.expires_at = options[:expire_at]
38
+ end
39
+
40
+ existing_lock
41
+ end
42
+
43
+ def remove options
44
+ lock.configuration.collection.find( key: key, owner: options[:owner] ).remove_all
45
+ end
46
+
47
+ def find_already_acquired
48
+ lock.configuration.collection.find({
49
+ key: key,
50
+ owner: lock.configuration.owner,
51
+ expires_at: { '$gt' => Time.now }
52
+ })
53
+ end
54
+
55
+ def find_existing
56
+ lock.configuration.collection.find(query).first
57
+ end
58
+
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,28 @@
1
+ module Mongo
2
+ class Lock
3
+ module SendWithRaiseMethods
4
+
5
+ def send_with_raise method, *args
6
+ args.last[:should_raise] = true
7
+ self.send(method, *args)
8
+ end
9
+
10
+ def acquire! options = {}
11
+ send_with_raise :acquire, options
12
+ end
13
+
14
+ def release! options = {}
15
+ send_with_raise :release, options
16
+ end
17
+
18
+ def extend_by! time, options = {}
19
+ send_with_raise :extend_by, time, options
20
+ end
21
+
22
+ def extend! options = {}
23
+ send_with_raise :extend, options
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -1,5 +1,5 @@
1
1
  module Mongo
2
2
  class Lock
3
- VERSION = "1.1.4"
3
+ VERSION = "1.2.0"
4
4
  end
5
5
  end
@@ -22,13 +22,14 @@ Gem::Specification.new do |spec|
22
22
  # s.add_dependency 'some-gem'
23
23
  spec.extra_rdoc_files = ['README.md', 'LICENSE']
24
24
 
25
- spec.add_dependency 'mongo'
26
-
25
+ spec.add_development_dependency 'mongo'
26
+ spec.add_development_dependency 'moped'
27
27
  spec.add_development_dependency 'rspec'
28
28
  spec.add_development_dependency 'fuubar'
29
29
  spec.add_development_dependency 'pry'
30
30
  spec.add_development_dependency 'activesupport'
31
31
  spec.add_development_dependency 'coveralls'
32
- spec.add_development_dependency 'rails'
32
+ spec.add_development_dependency 'bson_ext'
33
+ spec.add_development_dependency 'rails', '~> 4.0.0'
33
34
 
34
35
  end
@@ -63,6 +63,23 @@ describe Mongo::Lock::Configuration do
63
63
  expect(subject.instance_variable_get('@collections')).to be collections
64
64
  end
65
65
 
66
+ context "when collections are mixed" do
67
+
68
+ it "raises an error" do
69
+ expect{subject.collections = [my_collection, my_moped_collection]}.to raise_error(Mongo::Lock::MixedCollectionsError, "Collections must be of the same class")
70
+ end
71
+
72
+ end
73
+
74
+ context "when collections are Moped it" do
75
+
76
+ it "chooses the correct driver" do
77
+ subject.collections = [my_moped_collection]
78
+ expect(subject.driver).to eql Mongo::Lock::Drivers::Moped
79
+ end
80
+
81
+ end
82
+
66
83
  end
67
84
 
68
85
  describe "#set_collections_keep_default" do
@@ -91,6 +108,23 @@ describe Mongo::Lock::Configuration do
91
108
  expect(subject.instance_variable_get('@collections')[:default]).to be my_collection
92
109
  end
93
110
 
111
+ context "when collections are Moped it" do
112
+
113
+ it "chooses the correct driver" do
114
+ subject.collection = my_moped_collection
115
+ expect(subject.driver).to eql Mongo::Lock::Drivers::Moped
116
+ end
117
+
118
+ end
119
+
120
+ context "when collection is not a valid class" do
121
+
122
+ it "raises an error" do
123
+ expect{subject.collection = Object.new}.to raise_error(Mongo::Lock::InvalidCollectionError, "Object is not a valid collection class")
124
+ end
125
+
126
+ end
127
+
94
128
  end
95
129
 
96
130
  describe "#collection" do
@@ -235,4 +269,36 @@ describe Mongo::Lock::Configuration do
235
269
 
236
270
  end
237
271
 
272
+ describe "#driver=" do
273
+
274
+ context "when given a string" do
275
+
276
+ it "should set convert the value into a driver class" do
277
+ subject.driver = 'moped'
278
+ expect(subject.instance_variable_get('@driver')).to eql Mongo::Lock::Drivers::Moped
279
+ end
280
+
281
+ end
282
+
283
+ context "when given a class" do
284
+
285
+ it "should set the driver value" do
286
+ subject.driver = Mongo::Lock::Drivers::Moped
287
+ expect(subject.instance_variable_get('@driver')).to eql Mongo::Lock::Drivers::Moped
288
+ end
289
+
290
+ end
291
+
292
+ end
293
+
294
+ describe "#driver" do
295
+
296
+ it "should return the driver value" do
297
+ subject.instance_variable_set('@driver', Mongo::Lock::Drivers::Moped)
298
+ expect(subject.driver).to eql Mongo::Lock::Drivers::Moped
299
+ end
300
+
301
+
302
+ end
303
+
238
304
  end
@@ -2,6 +2,8 @@ require 'spec_helper'
2
2
 
3
3
  describe Mongo::Lock do
4
4
 
5
+ configure_for_mongo
6
+
5
7
  describe '.configure' do
6
8
 
7
9
  after :each do
@@ -52,8 +54,12 @@ describe Mongo::Lock do
52
54
  expect(Mongo::Lock.configuration.owner).to eql "#{`hostname`.strip}:#{Process.pid}:#{Thread.object_id}"
53
55
  end
54
56
 
55
- it "sets default raise" do
56
- expect(Mongo::Lock.configuration.raise).to be_false
57
+ it "sets default should_raise" do
58
+ expect(Mongo::Lock.configuration.should_raise).to be_false
59
+ end
60
+
61
+ it "sets default driver" do
62
+ expect(Mongo::Lock.configuration.driver).to eql Mongo::Lock::Drivers::Mongo
57
63
  end
58
64
 
59
65
  end
@@ -0,0 +1,219 @@
1
+ shared_examples "MongoLock driver that can aquire locks" do
2
+
3
+ describe Mongo::Lock do
4
+
5
+ describe '.acquire' do
6
+
7
+ it "creates and returns a new Mongo::Lock instance" do
8
+ expect(Mongo::Lock.acquire 'my_lock').to be_a Mongo::Lock
9
+ end
10
+
11
+ it "calls #acquire to acquire the lock" do
12
+ expect_any_instance_of(Mongo::Lock).to receive(:acquire)
13
+ Mongo::Lock.acquire 'my_lock'
14
+ end
15
+
16
+ context "when options are provided" do
17
+
18
+ it "passes them to the new lock" do
19
+ lock = Mongo::Lock.acquire('my_lock', { limit: 3 })
20
+ expect(lock.configuration.limit).to be 3
21
+ end
22
+
23
+ end
24
+
25
+ context "when a block is provided" do
26
+
27
+ it "passes it to the new lock" do
28
+ block = Proc.new { |lock| }
29
+ expect_any_instance_of(Mongo::Lock).to receive(:acquire).with( &block)
30
+ lock = Mongo::Lock.acquire('my_lock', { limit: 3 }, &block)
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+
37
+ describe '#acquire' do
38
+
39
+ let(:lock) { Mongo::Lock.new 'my_lock', owner: 'spence' }
40
+
41
+ context "when lock is available" do
42
+
43
+ it "acquires the lock" do
44
+ lock.acquire
45
+ expect(my_collection.find(key: 'my_lock').count).to be 1
46
+ end
47
+
48
+ it "sets the lock to expire" do
49
+ lock.acquire
50
+ expect(my_collection.find(key: 'my_lock').first['expires_at']).to be_within(1.second).of(10.seconds.from_now)
51
+ expect(my_collection.find(key: 'my_lock').first['ttl']).to be_within(1.second).of(10.seconds.from_now)
52
+ end
53
+
54
+ it "returns true" do
55
+ expect(lock.acquire).to be_true
56
+ end
57
+
58
+ end
59
+
60
+ context "when the frequency option is a Proc" do
61
+
62
+ let(:lock) { Mongo::Lock.new 'my_lock' }
63
+
64
+ it "should call the Proc with the attempt number" do
65
+ my_collection.insert key: 'my_lock', owner: 'tobie', expires_at: 10.seconds.from_now
66
+ proc = Proc.new{ |x| x }
67
+ expect(proc).to receive(:call).with(1).and_return(0.01)
68
+ expect(proc).to receive(:call).with(2).and_return(0.01)
69
+ expect(proc).to receive(:call).with(3).and_return(0.01)
70
+ lock.acquire limit: 3, frequency: proc
71
+ end
72
+
73
+ end
74
+
75
+ context "when the lock is unavailable" do
76
+
77
+ it "retries until it can acquire it" do
78
+ my_collection.insert key: 'my_lock', owner: 'tobie', expires_at: 0.1.seconds.from_now
79
+ lock.acquire frequency: 0.01, timeout_in: 0.2, limit: 20
80
+ expect(my_collection.find(key: 'my_lock', owner: 'spence').count).to be 1
81
+ end
82
+
83
+ end
84
+
85
+ context "when the lock is already acquired but by the same owner" do
86
+
87
+ before :each do
88
+ my_collection.insert key: 'my_lock', owner: 'spence', expires_at: 10.minutes.from_now
89
+ end
90
+
91
+ it "doesn't create a new lock" do
92
+ lock.acquire
93
+ expect(my_collection.find(key: 'my_lock').count).to be 1
94
+ end
95
+
96
+ it "returns true" do
97
+ expect(lock.acquire).to be_true
98
+ end
99
+
100
+ it "sets this instance as acquired" do
101
+ lock.acquire
102
+ expect(lock.instance_variable_get('@acquired')).to be_true
103
+ end
104
+
105
+ end
106
+
107
+ context "when the lock cannot be acquired" do
108
+
109
+ context "and acquisition timeout_in occurs" do
110
+
111
+ let(:lock) { Mongo::Lock.new 'my_lock', owner: 'spence', timeout_in: 0.03, frequency: 0.01 }
112
+
113
+ it "should return false" do
114
+ my_collection.insert key: 'my_lock', owner: 'tobie', expires_at: 1.second.from_now
115
+ expect(lock.acquire).to be_false
116
+ end
117
+
118
+ end
119
+
120
+ context "and acquisition limit is exceeded" do
121
+
122
+ let(:lock) { Mongo::Lock.new 'my_lock', owner: 'spence', timeout_in: 0.4, limit: 3, frequency: 0.01 }
123
+
124
+ it "should return false" do
125
+ my_collection.insert key: 'my_lock', owner: 'tobie', expires_at: 1.second.from_now
126
+ expect(lock.acquire).to be_false
127
+ end
128
+
129
+ end
130
+
131
+ end
132
+
133
+ context "when the lock cannot be acquired and raise option is set to true" do
134
+
135
+ context "and acquisition timeout_in occurs" do
136
+
137
+ let(:lock) { Mongo::Lock.new 'my_lock', owner: 'tobie', timeout_in: 0.4, limit: 3, frequency: 0.01, should_raise: true }
138
+
139
+ it "should raise Mongo::Lock::NotAcquiredError" do
140
+ my_collection.insert key: 'my_lock', owner: 'spence', expires_at: 1.second.from_now
141
+ expect{lock.acquire}.to raise_error Mongo::Lock::NotAcquiredError
142
+ end
143
+
144
+ end
145
+
146
+ context "and acquisition limit is exceeded" do
147
+
148
+ let(:lock) { Mongo::Lock.new 'my_lock', owner: 'tobie', timeout_in: 0.3, limit: 3, frequency: 0.01, should_raise: true }
149
+
150
+ it "should raise Mongo::Lock::NotAcquiredError" do
151
+ my_collection.insert key: 'my_lock', owner: 'spence', expires_at: 1.second.from_now
152
+ expect{lock.acquire}.to raise_error Mongo::Lock::NotAcquiredError
153
+ end
154
+
155
+ end
156
+
157
+ end
158
+
159
+ context "when options are provided" do
160
+
161
+ let(:lock) { Mongo::Lock.new 'my_lock', owner: 'tobie', timeout_in: 0.2, limit: 11, frequency: 0.01, should_raise: true }
162
+
163
+ it "overrides the lock's" do
164
+ my_collection.insert key: 'my_lock', owner: 'spence', expires_at: 1.second.from_now
165
+ expect(lock.acquire timeout_in: 0.05, limit: 3, frequency: 0.02, should_raise: false).to be_false
166
+ end
167
+
168
+ end
169
+
170
+ context "when a block is provided" do
171
+
172
+ let(:lock) { Mongo::Lock.new 'my_lock', owner: 'tobie', timeout_in: 0.2, limit: 11, frequency: 0.01, should_raise: true }
173
+
174
+ it "should acquire the lock" do
175
+ lock.acquire do |lock|
176
+ expect(Mongo::Lock.available? 'my_lock', owner: 'spence').to be_false
177
+ end
178
+ end
179
+
180
+ it "should call the block" do
181
+ expect{ |block| lock.acquire &block }.to yield_with_args lock
182
+ end
183
+
184
+ it "should release the lock" do
185
+ lock.acquire do |lock|
186
+ # Do something
187
+ end
188
+ expect(Mongo::Lock.available?('my_lock', owner: 'spence')).to be_true
189
+ end
190
+
191
+ end
192
+
193
+ end
194
+
195
+ describe '.acquire!' do
196
+
197
+ let(:lock) { Mongo::Lock.new 'my_lock', owner: 'spence' }
198
+
199
+ it "calls .acquire with raise errors option set to true" do
200
+ expect(Mongo::Lock).to receive(:init_and_send).with('my_lock', { limit: 3 }, :acquire!)
201
+ Mongo::Lock.acquire! 'my_lock', limit: 3
202
+ end
203
+
204
+ end
205
+
206
+ describe '#acquire!' do
207
+
208
+ let(:lock) { Mongo::Lock.new 'my_lock', owner: 'spence' }
209
+
210
+ it "calls #acquire with raise errors option set to true" do
211
+ expect(lock).to receive(:acquire).with({ limit: 3, should_raise: true })
212
+ lock.acquire! limit: 3
213
+ end
214
+
215
+ end
216
+
217
+ end
218
+
219
+ end