dp_stm_map 0.0.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.
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