dp_stm_map 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +622 -0
  5. data/README.md +115 -0
  6. data/Rakefile +1 -0
  7. data/bin/dp_map_manager.rb +47 -0
  8. data/cucumber.yml +2 -0
  9. data/dp_stm_map.gemspec +29 -0
  10. data/features/client_reconnect.feature +13 -0
  11. data/features/persistence.feature +12 -0
  12. data/features/replication.feature +9 -0
  13. data/features/running_manager.feature +19 -0
  14. data/features/step_definitions/client_reconnect_steps.rb +28 -0
  15. data/features/step_definitions/persistence_steps.rb +49 -0
  16. data/features/step_definitions/replication_steps.rb +15 -0
  17. data/features/step_definitions/running_server_steps.rb +10 -0
  18. data/features/step_definitions/transaction_fail_steps.rb +11 -0
  19. data/features/support/env.rb +80 -0
  20. data/features/transaction_fail.feature +7 -0
  21. data/lib/dp_stm_map/Client.rb +268 -0
  22. data/lib/dp_stm_map/ClientLocalStore.rb +119 -0
  23. data/lib/dp_stm_map/InMemoryStmMap.rb +147 -0
  24. data/lib/dp_stm_map/Manager.rb +370 -0
  25. data/lib/dp_stm_map/Message.rb +126 -0
  26. data/lib/dp_stm_map/ObjectStore.rb +99 -0
  27. data/lib/dp_stm_map/version.rb +16 -0
  28. data/lib/dp_stm_map.rb +20 -0
  29. data/server.profile +547 -0
  30. data/spec/dp_stm_map/ClientLocalStore_spec.rb +78 -0
  31. data/spec/dp_stm_map/Client_spec.rb +133 -0
  32. data/spec/dp_stm_map/InMemoryStmMap_spec.rb +10 -0
  33. data/spec/dp_stm_map/Manager_spec.rb +323 -0
  34. data/spec/dp_stm_map/Message_spec.rb +21 -0
  35. data/spec/dp_stm_map/ObjectStore_spec.rb +87 -0
  36. data/spec/dp_stm_map/StmMap_shared.rb +432 -0
  37. metadata +235 -0
@@ -0,0 +1,133 @@
1
+ require 'dp_stm_map'
2
+
3
+ require 'thread'
4
+ require 'timeout'
5
+ require 'tmpdir'
6
+ require 'fileutils'
7
+ Dir["./spec/**/*_shared.rb"].sort.each {|f| require f}
8
+
9
+ module DpStmMap
10
+
11
+ describe DistributedPersistentStmMap do
12
+
13
+ before do
14
+ @storage_dir=Dir.mktmpdir
15
+ Dir.mkdir "%s/server" % @storage_dir
16
+ Dir.mkdir "%s/local" % @storage_dir
17
+ server.start
18
+ end
19
+
20
+ after do
21
+ server.stop
22
+ FileUtils.rm_rf(@storage_dir)
23
+ subject.stop
24
+ end
25
+
26
+ let(:server) {Manager.new port, "%s/server" % @storage_dir}
27
+
28
+ let(:port) {31337}
29
+
30
+ subject { DistributedPersistentStmMap.new 'localhost', port, "%s/local" % @storage_dir }
31
+
32
+
33
+ describe :map_behaviour do
34
+
35
+ before do
36
+ queue=Queue.new
37
+ subject.on_connected do
38
+ queue << "connected"
39
+ end
40
+ subject.start
41
+ timeout(1) do
42
+ queue.pop.should == "connected"
43
+ end
44
+ end
45
+
46
+
47
+ it_behaves_like "StmMap"
48
+ end
49
+
50
+
51
+ it "should reconnect to server that has rebooted" do
52
+ queue=Queue.new
53
+ subject.on_connected do
54
+ queue << true
55
+ end
56
+ subject.start
57
+ end
58
+
59
+ describe :on_connected do
60
+ it "should be executed when client did connect to the manager" do
61
+
62
+ queue=Queue.new
63
+ subject.on_disconnected do
64
+ queue << "disconnected"
65
+ end
66
+ subject.on_connected do
67
+ queue << "connected"
68
+ end
69
+ subject.start
70
+ timeout(1) do
71
+ queue.pop.should == "connected"
72
+ end
73
+
74
+ sleep 0.1
75
+ server.stop
76
+
77
+ timeout(5) do
78
+ queue.pop.should == "disconnected"
79
+ end
80
+
81
+ server.start
82
+
83
+ timeout(5) do
84
+ queue.pop.should == "connected"
85
+ end
86
+
87
+ end
88
+ end
89
+
90
+ describe :on_disconnected do
91
+ it "should be executed when client did disconnect from the manager" do
92
+ queue=Queue.new
93
+ subject.on_disconnected do
94
+ queue << "disconnected"
95
+ end
96
+ subject.on_connected do
97
+ queue << "connected"
98
+ end
99
+ subject.start
100
+
101
+ timeout(1) do
102
+ queue.pop.should == "connected"
103
+ end
104
+
105
+ subject.stop
106
+
107
+ timeout(1) do
108
+ queue.pop.should == "disconnected"
109
+ end
110
+ end
111
+ end
112
+
113
+ describe :stop do
114
+ it "should stop client" do
115
+ queue=Queue.new
116
+ subject.on_disconnected do
117
+ queue << "disconnected"
118
+ end
119
+ subject.on_connected do
120
+ queue << "connected"
121
+ end
122
+ subject.start
123
+ timeout(1) do
124
+ queue.pop.should == "connected"
125
+ end
126
+ subject.stop
127
+ timeout(1) do
128
+ queue.pop.should == "disconnected"
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,10 @@
1
+ require 'dp_stm_map'
2
+ Dir["./spec/**/*_shared.rb"].sort.each {|f| require f}
3
+
4
+ module DpStmMap
5
+
6
+ describe InMemoryStmMap do
7
+ it_behaves_like "StmMap"
8
+ end
9
+
10
+ end
@@ -0,0 +1,323 @@
1
+ require 'dp_stm_map'
2
+ require 'tmpdir'
3
+ require 'yaml'
4
+ require 'set'
5
+
6
+ def send_message socket, message
7
+ yaml=message.serialize
8
+ socket.write([yaml.bytesize].pack("Q>"))
9
+ socket.write(yaml)
10
+ socket.flush
11
+ socket.rewind
12
+ end
13
+
14
+ module DpStmMap
15
+
16
+
17
+ describe CurrentValues do
18
+
19
+ before do
20
+ @storage_dir=Dir.mktmpdir
21
+ end
22
+
23
+ after do
24
+ FileUtils.rm_rf(@storage_dir)
25
+ end
26
+
27
+ subject {CurrentValues.new @storage_dir}
28
+
29
+ it "should store a new value" do
30
+ subject['x']='y'
31
+ subject['x'].should == 'y'
32
+ end
33
+
34
+
35
+ it "should change existing value" do
36
+ subject['x']='y'
37
+ subject['x']='z'
38
+ subject['x'].should == 'z'
39
+ end
40
+
41
+ it "should store nil" do
42
+ subject['x']=nil
43
+ subject['x'].should be_nil
44
+ end
45
+
46
+ end
47
+
48
+
49
+
50
+ describe TransactionLog do
51
+
52
+ before do
53
+ @storage_dir=Dir.mktmpdir
54
+ end
55
+
56
+ after do
57
+ FileUtils.rm_rf(@storage_dir)
58
+ end
59
+
60
+ subject {TransactionLog.new @storage_dir}
61
+
62
+ describe :store_transaction do
63
+ it "should store first transaction with id 1" do
64
+ subject.store_transaction({'x' => 'abc'},{'abc' => 'some data'},[]).should == 1
65
+ end
66
+ end
67
+
68
+
69
+ describe :add_listener do
70
+ it "should call listener with transaction when new transaction is available" do
71
+ queue=Queue.new
72
+ subject.add_listener(0) do |txid, change|
73
+ queue << [txid, change]
74
+ end
75
+ subject.store_transaction({'x' => 'abc'},{'abc' => 'some data'},[])
76
+ queue.pop.should == [1,[{'x' => 'abc'},{'abc' => 'some data'},[]]]
77
+ end
78
+
79
+
80
+ it "should call listener for all missed transactions" do
81
+ queue=Queue.new
82
+ subject.store_transaction({'x' => 'abc'},{'abc' => 'some data'},[])
83
+ subject.add_listener(0) do |txid, change|
84
+ queue << [txid, change]
85
+ end
86
+ queue.pop.should == [1,[{'x' => 'abc'},{'abc' => 'some data'},[]]]
87
+ end
88
+
89
+
90
+ it "should call all registered listeners" do
91
+ queue1=Queue.new
92
+ queue2=Queue.new
93
+ subject.store_transaction({'x' => 'abc'},{'abc' => 'some data'},[])
94
+ subject.add_listener(0) do |txid, change|
95
+ queue1 << [txid, change]
96
+ end
97
+ subject.add_listener(0) do |txid, change|
98
+ queue2 << [txid, change]
99
+ end
100
+ queue1.pop.should == [1,[{'x' => 'abc'},{'abc' => 'some data'},[]]]
101
+ queue2.pop.should == [1,[{'x' => 'abc'},{'abc' => 'some data'},[]]]
102
+ end
103
+
104
+
105
+ end
106
+ end
107
+
108
+
109
+ describe ReferenceCounts do
110
+
111
+ before do
112
+ @storage_dir=Dir.mktmpdir
113
+ end
114
+
115
+ after do
116
+ FileUtils.rm_rf(@storage_dir)
117
+ end
118
+
119
+
120
+ subject {ReferenceCounts.new @storage_dir}
121
+
122
+
123
+ describe :add_reference do
124
+ it "should record new reference" do
125
+ subject.add_reference 'abc'
126
+ subject.should have_references('abc')
127
+ end
128
+ end
129
+
130
+ describe :remove_reference do
131
+ it "should remove existing reference" do
132
+ subject.add_reference 'abc'
133
+ subject.remove_reference 'abc'
134
+ subject.should_not have_references('abc')
135
+ end
136
+
137
+ it "should raise exception when reference does not exist" do
138
+ expect{subject.remove_reference 'abc'}.to raise_error
139
+ end
140
+ end
141
+
142
+ describe :has_references? do
143
+ context "when there is one reference" do
144
+ before {subject.add_reference 'abc'}
145
+ it "should return true" do
146
+ subject.should have_references('abc')
147
+ end
148
+ end
149
+ context "when there are no references" do
150
+ it "should return false" do
151
+ subject.should_not have_references('abc')
152
+ end
153
+ end
154
+
155
+ end
156
+
157
+ end
158
+
159
+ describe ClientTransactionManager do
160
+
161
+ let(:current_values) {double("current values")}
162
+ let(:reference_counts) {double("reference counts")}
163
+ let(:transaction_log) {double("transaction log")}
164
+
165
+ subject {ClientTransactionManager.new current_values, reference_counts, transaction_log}
166
+
167
+ it "should apply valid transaction" do
168
+ current_values.should_receive(:[]).with('x').and_return(nil)
169
+ current_values.should_receive(:[]=).with('x','abc')
170
+ reference_counts.should_receive(:has_references?).with('abc').and_return(false)
171
+ reference_counts.should_receive(:add_reference).with('abc')
172
+ transaction_log.should_receive(:store_transaction).with({'x' => 'abc'},{'abc' => 'some value'}, []).and_return(1)
173
+ subject.apply_transaction({'x' => [nil,'abc']},{'abc' => 'some value'}).should == 1
174
+ end
175
+
176
+ it "should increase reference count of the new value" do
177
+ current_values.should_receive(:[]).with('x').and_return('def')
178
+ current_values.should_receive(:[]=).with('x','abc')
179
+ reference_counts.should_receive(:has_references?).with('abc').and_return(false)
180
+ reference_counts.should_receive(:has_references?).with('def').and_return(false)
181
+ reference_counts.should_receive(:add_reference).with('abc')
182
+ reference_counts.should_receive(:remove_reference).with('def')
183
+ transaction_log.should_receive(:store_transaction).with({'x' => 'abc'},{'abc' => 'some value'}, ['def'])
184
+ subject.apply_transaction({'x' => ['def','abc']},{'abc' => 'some value'})
185
+ end
186
+
187
+ it "should not increase reference count of nil" do
188
+ current_values.should_receive(:[]).with('x').and_return('def')
189
+ current_values.should_receive(:[]=).with('x',nil)
190
+ reference_counts.should_receive(:remove_reference).with('def')
191
+ reference_counts.should_receive(:has_references?).with('def').and_return(false)
192
+ transaction_log.should_receive(:store_transaction).with({'x' => nil},{}, ['def'])
193
+ subject.apply_transaction({'x' => ['def',nil]},{})
194
+ end
195
+
196
+ it "should refuse transaction referencing not existing data" do
197
+ current_values.should_receive(:[]).with('x').and_return(nil)
198
+ reference_counts.should_receive(:has_references?).with('abc').and_return(false)
199
+ expect {subject.apply_transaction({'x' => [nil,'abc']},{})}.to raise_error StaleTransactionError
200
+ end
201
+
202
+ it "should apply changes if nothing has changed" do
203
+ current_values.should_receive(:[]).with('x').and_return('abc')
204
+ transaction_log.should_receive(:store_transaction).with({},{}, [])
205
+ subject.apply_transaction({'x' => ['abc','abc']},{})
206
+ end
207
+
208
+ it "should refuse stale transaction" do
209
+ current_values.should_receive(:[]).with('x').and_return('def')
210
+ expect {subject.apply_transaction({'x' => [nil,'abc']},{'abc' => 'some value'})}.to raise_error StaleTransactionError
211
+ end
212
+
213
+
214
+ end
215
+
216
+
217
+
218
+ describe Manager do
219
+
220
+
221
+ let (:port) {0}
222
+
223
+
224
+ before do
225
+ @storage_dir=Dir.mktmpdir
226
+ subject.start
227
+ end
228
+
229
+ after do
230
+ FileUtils.rm_rf(@storage_dir)
231
+ begin
232
+ subject.stop
233
+ rescue => ex
234
+ STDERR.puts "#{ex.backtrace.join("\n")}: #{ex.message} (#{ex.class})"
235
+ end
236
+ end
237
+
238
+
239
+ before do
240
+ @storage_dir=Dir.mktmpdir
241
+ end
242
+
243
+ subject {Manager.new port, @storage_dir}
244
+
245
+ it "should start and stop" do
246
+ TCPSocket.new 'localhost',subject.port
247
+ end
248
+
249
+ end
250
+
251
+
252
+ describe SocketTransport do
253
+
254
+ let (:socket) {StringIO.new}
255
+
256
+ subject {SocketTransport.new socket}
257
+
258
+ describe :next_message do
259
+ it "should read sent message" do
260
+ send_message(socket, ClientHelloMessage.new(0))
261
+ subject.next_message.should be_a ClientHelloMessage
262
+ end
263
+ end
264
+
265
+ describe :send_message do
266
+ it "should serialize message" do
267
+ subject.send_message ClientHelloMessage.new(1)
268
+ socket.rewind
269
+ subject.next_message.should be_a ClientHelloMessage
270
+ end
271
+ end
272
+
273
+ describe :close do
274
+ it "should close socket" do
275
+ subject.close
276
+ socket.should be_closed
277
+ end
278
+ end
279
+ end
280
+
281
+
282
+ describe ClientHandler do
283
+
284
+ let (:client_transport) {double("client transport")}
285
+ let (:transaction_manager) {double("transaction manager")}
286
+
287
+
288
+ subject {ClientHandler.new client_transport, transaction_manager}
289
+
290
+
291
+ # it "should send server hello message to client" do
292
+ # transaction_log.should_receive(:register_listener).with(0)
293
+ # client_transport.should_receive(:send_message).with(an_instance_of(ServerHelloMessage))
294
+ # subject.handle(ClientHelloMessage.new 0)
295
+ # end
296
+
297
+
298
+ it "should send transaction messages for all previous state transitions" do
299
+ client_transport.should_receive(:send_message).with(an_instance_of(TransactionMessage))
300
+ subject.handle(TransactionMessage.new(1, {},{}, []))
301
+ end
302
+
303
+ it "should invoke transaction manager operations on client transaction messages" do
304
+ transaction_manager.should_receive(:apply_transaction).with({"x"=>["a", "b"]}, {"b"=>"content"}).and_return(1)
305
+ client_transport.should_receive(:send_message) do |msg|
306
+ msg.should be_a ClientTransactionSuccessfulMessage
307
+ msg.transaction_sequence.should == 1
308
+ end
309
+ subject.handle(ClientTransactionMessage.new('tx1',{'x' => ['a','b']},{'b' => 'content'}))
310
+ end
311
+
312
+ it "should send ClientTransactionFailedMessage when transaction fails" do
313
+ transaction_manager.should_receive(:apply_transaction).with({"x"=>["a", "b"]}, {"b"=>"content"}).and_raise(StaleTransactionError)
314
+ client_transport.should_receive(:send_message).with(an_instance_of(ClientTransactionFailedMessage))
315
+ subject.handle(ClientTransactionMessage.new('tx1',{'x' => ['a','b']},{'b' => 'content'}))
316
+ end
317
+
318
+
319
+
320
+
321
+
322
+ end
323
+ end
@@ -0,0 +1,21 @@
1
+ require 'dp_stm_map/Message'
2
+
3
+ module DpStmMap
4
+
5
+ describe JsonMessage do
6
+
7
+ subject {ServerHelloMessage.new}
8
+
9
+ # it "should be serializable to json" do
10
+ # subject.serialize.should == "{\"type\":\"DpStmMap::ServerHelloMessage\"}"
11
+ # end
12
+
13
+ # it "should be deserializable from json" do
14
+ # JsonMessage.deserialize("{\"type\":\"DpStmMap::ServerHelloMessage\"}").should be_a ServerHelloMessage
15
+ # end
16
+
17
+
18
+ end
19
+
20
+
21
+ end
@@ -0,0 +1,87 @@
1
+ require 'dp_stm_map'
2
+
3
+ module DpStmMap
4
+
5
+ class SomeObject
6
+ attr_accessor :value
7
+
8
+ def == other
9
+ case other
10
+ when SomeObject
11
+ other.value == self.value
12
+ else
13
+ false
14
+ end
15
+ end
16
+ end
17
+
18
+ describe ObjectStore do
19
+
20
+
21
+ let(:some_object) {v=SomeObject.new; v.value='v1'; v}
22
+
23
+ subject {ObjectStore.new InMemoryStmMap.new}
24
+
25
+
26
+ describe :on_atomic do
27
+ it "should be executed after atomic change" do
28
+ changes=nil
29
+ subject.on_atomic do |c|
30
+ changes=c
31
+ end
32
+ subject.atomic do |tx|
33
+ tx[:user,'abc']=some_object
34
+ end
35
+ changes.should == {[:user, 'abc'] => [nil, some_object]}
36
+ end
37
+ end
38
+
39
+
40
+
41
+ describe :validate_atomic do
42
+ it "should stop transaction when exception is raised" do
43
+
44
+ changes=nil
45
+ subject.validate_atomic do |c|
46
+ changes=c
47
+ raise "something went wrong"
48
+ end
49
+
50
+ expect do
51
+ subject.atomic do |tx|
52
+ tx[:user,'abc']=some_object
53
+ end
54
+ end.to raise_error "something went wrong"
55
+
56
+ changes.should == {[:user, 'abc'] => [nil, some_object]}
57
+
58
+ subject.atomic_read do |tx|
59
+ tx[:user,'abc'].should == nil
60
+ end
61
+
62
+ end
63
+ end
64
+
65
+ describe :atomic do
66
+ it "should store Ruby objects" do
67
+ subject.atomic do |tx|
68
+ tx[:user,'abc']=some_object
69
+ end
70
+
71
+ subject.atomic_read do |tx|
72
+ tx.should have_key(:user, 'abc')
73
+ tx[:user,'abc'].should == some_object
74
+ end
75
+ end
76
+ it "should store nil values" do
77
+ subject.atomic do |tx|
78
+ tx[:user,'abc']=nil
79
+ end
80
+
81
+ subject.atomic_read do |tx|
82
+ tx[:user,'abc'].should == nil
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end