mongomatic 0.7.1 → 0.7.2
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.
- data/lib/mongomatic/base.rb +6 -4
- data/lib/mongomatic/exceptions.rb +2 -0
- data/lib/mongomatic/transaction_lock.rb +35 -0
- data/lib/mongomatic.rb +1 -0
- data/test/helper.rb +12 -2
- data/test/test_transaction_lock.rb +76 -0
- metadata +8 -5
data/lib/mongomatic/base.rb
CHANGED
@@ -71,8 +71,6 @@ module Mongomatic
|
|
71
71
|
do_callback(:after_drop)
|
72
72
|
end
|
73
73
|
|
74
|
-
private
|
75
|
-
|
76
74
|
def do_callback(meth)
|
77
75
|
return false unless respond_to?(meth, true)
|
78
76
|
send(meth)
|
@@ -292,8 +290,6 @@ module Mongomatic
|
|
292
290
|
@doc || {}
|
293
291
|
end
|
294
292
|
|
295
|
-
protected
|
296
|
-
|
297
293
|
def hash_for_field(field, break_if_dne=false)
|
298
294
|
parts = field.split(".")
|
299
295
|
curr_hash = self.doc
|
@@ -313,5 +309,11 @@ module Mongomatic
|
|
313
309
|
return false unless respond_to?(meth, true)
|
314
310
|
send(meth)
|
315
311
|
end
|
312
|
+
|
313
|
+
def transaction(key=nil, duration=5, &block)
|
314
|
+
raise Mongomatic::Exceptions::DocumentIsNew if new?
|
315
|
+
key ||= [self.class.name, self["_id"].to_s].join("-")
|
316
|
+
TransactionLock.start(key, duration, &block)
|
317
|
+
end
|
316
318
|
end
|
317
319
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Mongomatic
|
2
|
+
|
3
|
+
class TransactionLock < Base
|
4
|
+
def self.create_indexes
|
5
|
+
collection.create_index("key", :unique => true, :drop_dups => true)
|
6
|
+
collection.create_index("expire_at")
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.start(key, duration, &block)
|
10
|
+
lock = new(:key => key, :expire_at => Time.now.utc + duration)
|
11
|
+
|
12
|
+
# we need to get a lock
|
13
|
+
begin
|
14
|
+
lock.insert!
|
15
|
+
rescue Mongo::OperationFailure => e
|
16
|
+
remove_stale_locks
|
17
|
+
if find_one(:key => key) == nil
|
18
|
+
return start(key, duration, &block)
|
19
|
+
end
|
20
|
+
raise Mongomatic::Exceptions::CannotGetTransactionLock
|
21
|
+
end
|
22
|
+
|
23
|
+
begin
|
24
|
+
block.call
|
25
|
+
ensure
|
26
|
+
lock.remove
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.remove_stale_locks
|
31
|
+
collection.remove({:expire_at => {"$lte" => Time.now.utc}}, {:safe => true})
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
data/lib/mongomatic.rb
CHANGED
@@ -45,3 +45,4 @@ require "#{File.dirname(__FILE__)}/mongomatic/active_model_compliancy"
|
|
45
45
|
require "#{File.dirname(__FILE__)}/mongomatic/type_converters"
|
46
46
|
require "#{File.dirname(__FILE__)}/mongomatic/typed_fields"
|
47
47
|
require "#{File.dirname(__FILE__)}/mongomatic/base"
|
48
|
+
require "#{File.dirname(__FILE__)}/mongomatic/transaction_lock"
|
data/test/helper.rb
CHANGED
@@ -122,5 +122,15 @@ class Rig < Mongomatic::Base
|
|
122
122
|
typed_field "friends_rig_id", :type => :object_id, :cast => true
|
123
123
|
end
|
124
124
|
|
125
|
-
|
126
|
-
|
125
|
+
class Clock < Mongomatic::Base
|
126
|
+
typed_field "ticks", :type => :fixnum, :cast => true
|
127
|
+
|
128
|
+
def tick!
|
129
|
+
transaction do
|
130
|
+
self.reload
|
131
|
+
self["ticks"] ||= 0
|
132
|
+
self["ticks"] += 1
|
133
|
+
self.update!
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
|
4
|
+
class TestTransactionLock < MiniTest::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
Person.collection.drop
|
7
|
+
Mongomatic::TransactionLock.collection.drop
|
8
|
+
Mongomatic::TransactionLock.create_indexes
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_will_rm_lock_when_complete
|
12
|
+
assert_equal 0, Mongomatic::TransactionLock.count
|
13
|
+
p1 = Person.new(:name => "Jordan")
|
14
|
+
p1.insert!
|
15
|
+
p1.transaction { assert_equal 1, Mongomatic::TransactionLock.count }
|
16
|
+
assert_equal 0, Mongomatic::TransactionLock.count
|
17
|
+
p1.transaction { assert_equal 1, Mongomatic::TransactionLock.count }
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_will_raise_if_cant_get_lock
|
21
|
+
p1 = Person.new(:name => "Jordan")
|
22
|
+
p1.insert!
|
23
|
+
|
24
|
+
l = Mongomatic::TransactionLock.new(:key => "Person-#{p1["_id"]}", :expire_at => Time.now.utc + 1.day)
|
25
|
+
l.insert!
|
26
|
+
|
27
|
+
assert_raises(Mongomatic::Exceptions::CannotGetTransactionLock) do
|
28
|
+
p1.transaction { true }
|
29
|
+
end
|
30
|
+
|
31
|
+
assert_raises(Mongomatic::Exceptions::CannotGetTransactionLock) do
|
32
|
+
p1.transaction { true }
|
33
|
+
end
|
34
|
+
|
35
|
+
l.remove!
|
36
|
+
|
37
|
+
p1.transaction { assert_equal 1, Mongomatic::TransactionLock.count }
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_will_rm_stale_locks
|
41
|
+
p1 = Person.new(:name => "Jordan")
|
42
|
+
p1.insert!
|
43
|
+
|
44
|
+
l = Mongomatic::TransactionLock.new(:key => "Person-#{p1["_id"]}", :expire_at => Time.now.utc - 1.day)
|
45
|
+
l.insert!
|
46
|
+
|
47
|
+
p1.transaction { assert_equal 1, Mongomatic::TransactionLock.count }
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_race_condition
|
51
|
+
c = Clock.new
|
52
|
+
c.insert!
|
53
|
+
threads = []
|
54
|
+
|
55
|
+
lock_errors = 0
|
56
|
+
|
57
|
+
50.times do
|
58
|
+
threads << Thread.new do
|
59
|
+
begin
|
60
|
+
Mongomatic.db = Mongo::Connection.new.db("mongomatic_test")
|
61
|
+
c = Clock.find_one(c["_id"])
|
62
|
+
c.tick!
|
63
|
+
rescue Mongomatic::Exceptions::CannotGetTransactionLock => e
|
64
|
+
lock_errors += 1
|
65
|
+
sleep(0.05); retry
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
threads.each { |th| th.join }
|
70
|
+
|
71
|
+
c = Clock.find_one(c["_id"])
|
72
|
+
assert_equal 50, c["ticks"]
|
73
|
+
|
74
|
+
assert lock_errors > 0
|
75
|
+
end
|
76
|
+
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 7
|
8
|
-
-
|
9
|
-
version: 0.7.
|
8
|
+
- 2
|
9
|
+
version: 0.7.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Ben Myles
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-11-
|
17
|
+
date: 2010-11-19 00:00:00 -08:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -101,6 +101,7 @@ files:
|
|
101
101
|
- lib/mongomatic/modifiers.rb
|
102
102
|
- lib/mongomatic/observable.rb
|
103
103
|
- lib/mongomatic/observer.rb
|
104
|
+
- lib/mongomatic/transaction_lock.rb
|
104
105
|
- lib/mongomatic/type_converters.rb
|
105
106
|
- lib/mongomatic/typed_fields.rb
|
106
107
|
- lib/mongomatic/util.rb
|
@@ -113,6 +114,7 @@ files:
|
|
113
114
|
- test/test_modifiers.rb
|
114
115
|
- test/test_observable.rb
|
115
116
|
- test/test_persistence.rb
|
117
|
+
- test/test_transaction_lock.rb
|
116
118
|
- test/test_typed_fields.rb
|
117
119
|
- test/test_validations.rb
|
118
120
|
has_rdoc: true
|
@@ -120,8 +122,8 @@ homepage: http://mongomatic.com/
|
|
120
122
|
licenses: []
|
121
123
|
|
122
124
|
post_install_message:
|
123
|
-
rdoc_options:
|
124
|
-
|
125
|
+
rdoc_options: []
|
126
|
+
|
125
127
|
require_paths:
|
126
128
|
- lib
|
127
129
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -155,5 +157,6 @@ test_files:
|
|
155
157
|
- test/test_modifiers.rb
|
156
158
|
- test/test_observable.rb
|
157
159
|
- test/test_persistence.rb
|
160
|
+
- test/test_transaction_lock.rb
|
158
161
|
- test/test_typed_fields.rb
|
159
162
|
- test/test_validations.rb
|