dp_stm_map 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +622 -0
- data/README.md +115 -0
- data/Rakefile +1 -0
- data/bin/dp_map_manager.rb +47 -0
- data/cucumber.yml +2 -0
- data/dp_stm_map.gemspec +29 -0
- data/features/client_reconnect.feature +13 -0
- data/features/persistence.feature +12 -0
- data/features/replication.feature +9 -0
- data/features/running_manager.feature +19 -0
- data/features/step_definitions/client_reconnect_steps.rb +28 -0
- data/features/step_definitions/persistence_steps.rb +49 -0
- data/features/step_definitions/replication_steps.rb +15 -0
- data/features/step_definitions/running_server_steps.rb +10 -0
- data/features/step_definitions/transaction_fail_steps.rb +11 -0
- data/features/support/env.rb +80 -0
- data/features/transaction_fail.feature +7 -0
- data/lib/dp_stm_map/Client.rb +268 -0
- data/lib/dp_stm_map/ClientLocalStore.rb +119 -0
- data/lib/dp_stm_map/InMemoryStmMap.rb +147 -0
- data/lib/dp_stm_map/Manager.rb +370 -0
- data/lib/dp_stm_map/Message.rb +126 -0
- data/lib/dp_stm_map/ObjectStore.rb +99 -0
- data/lib/dp_stm_map/version.rb +16 -0
- data/lib/dp_stm_map.rb +20 -0
- data/server.profile +547 -0
- data/spec/dp_stm_map/ClientLocalStore_spec.rb +78 -0
- data/spec/dp_stm_map/Client_spec.rb +133 -0
- data/spec/dp_stm_map/InMemoryStmMap_spec.rb +10 -0
- data/spec/dp_stm_map/Manager_spec.rb +323 -0
- data/spec/dp_stm_map/Message_spec.rb +21 -0
- data/spec/dp_stm_map/ObjectStore_spec.rb +87 -0
- data/spec/dp_stm_map/StmMap_shared.rb +432 -0
- metadata +235 -0
@@ -0,0 +1,370 @@
|
|
1
|
+
# dp_stm_map - Distributed and Persistent Software Transaction Map
|
2
|
+
# Copyright (C) 2013 Dragan Milic
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
|
14
|
+
require 'socket'
|
15
|
+
require 'thread'
|
16
|
+
require 'set'
|
17
|
+
require 'fileutils'
|
18
|
+
require 'threadsafe-lru'
|
19
|
+
|
20
|
+
module DpStmMap
|
21
|
+
|
22
|
+
class ManagerShutdown < StandardError
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
class Manager
|
28
|
+
|
29
|
+
def initialize port, store_dir
|
30
|
+
@port=port
|
31
|
+
@state=:not_running
|
32
|
+
|
33
|
+
|
34
|
+
FileUtils.mkdir_p(store_dir) unless File.exist? store_dir
|
35
|
+
|
36
|
+
@transaction_log=TransactionLog.new("%s/log" % store_dir)
|
37
|
+
reference_counts=ReferenceCounts.new("%s/refcounts" % store_dir)
|
38
|
+
current_values=CurrentValues.new("%s/current_values" % store_dir)
|
39
|
+
@tx_manager=ClientTransactionManager.new(current_values, reference_counts, @transaction_log)
|
40
|
+
end
|
41
|
+
|
42
|
+
def port
|
43
|
+
@server_socket.addr[1]
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def start
|
48
|
+
@server_socket=TCPServer.new @port
|
49
|
+
@accept_thread=Thread.new do
|
50
|
+
begin
|
51
|
+
active_threads=[]
|
52
|
+
loop do
|
53
|
+
client_socket=@server_socket.accept
|
54
|
+
client_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
55
|
+
|
56
|
+
active_threads << Thread.new do
|
57
|
+
|
58
|
+
begin
|
59
|
+
transport=SocketTransport.new(client_socket)
|
60
|
+
client_handler=ClientHandler.new transport, @tx_manager
|
61
|
+
msg=transport.next_message
|
62
|
+
|
63
|
+
raise "client did not send ClientHelloMessage" unless ClientHelloMessage === msg
|
64
|
+
|
65
|
+
transport.send_message ServerHelloMessage.new
|
66
|
+
|
67
|
+
|
68
|
+
@transaction_log.add_listener(msg.last_transaction_sequence) do |seq, tx|
|
69
|
+
client_handler.handle TransactionMessage.new(seq, *tx)
|
70
|
+
end
|
71
|
+
|
72
|
+
loop do
|
73
|
+
msg=transport.next_message
|
74
|
+
client_handler.handle msg
|
75
|
+
end
|
76
|
+
|
77
|
+
rescue ManagerShutdown => e
|
78
|
+
|
79
|
+
|
80
|
+
rescue => e
|
81
|
+
# puts "Error during processing: #{$!}"
|
82
|
+
# puts "Backtrace:\n\t#{e.backtrace.join("\n\t")}"
|
83
|
+
|
84
|
+
ensure
|
85
|
+
# puts "closed socket to client"
|
86
|
+
client_socket.close
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
rescue ManagerShutdown => e
|
92
|
+
ensure
|
93
|
+
@server_socket.close unless @server_socket.closed?
|
94
|
+
active_threads.each do |thread|
|
95
|
+
thread.raise ManagerShutdown
|
96
|
+
thread.join
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
def stop
|
109
|
+
@accept_thread.raise ManagerShutdown
|
110
|
+
|
111
|
+
begin
|
112
|
+
@accept_thread.join
|
113
|
+
rescue => e
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
class CurrentValues
|
122
|
+
|
123
|
+
def initialize current_values_dir
|
124
|
+
@current_values_dir=current_values_dir
|
125
|
+
FileUtils.mkdir_p(current_values_dir) unless File.exist?(current_values_dir)
|
126
|
+
@cache=ThreadSafeLru::LruCache.new 9000
|
127
|
+
end
|
128
|
+
|
129
|
+
def file_name key
|
130
|
+
"#{@current_values_dir}/#{key}"
|
131
|
+
end
|
132
|
+
|
133
|
+
def [] k
|
134
|
+
@cache.get(k) do |key|
|
135
|
+
File.exist?(file_name(key)) ? File.open(file_name(key),"r"){|f| f.read()} : nil
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def []= key, value
|
140
|
+
if value
|
141
|
+
File.open(file_name(key),"w"){|f| f.write(value)}
|
142
|
+
else
|
143
|
+
File.unlink file_name(key) if File.exist? file_name(key)
|
144
|
+
end
|
145
|
+
@cache.drop(key)
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
|
152
|
+
class ReferenceCounts
|
153
|
+
def initialize references_dir
|
154
|
+
@references_dir=references_dir
|
155
|
+
FileUtils.mkdir_p(references_dir) unless File.exist?(references_dir)
|
156
|
+
@cache=ThreadSafeLru::LruCache.new 9000
|
157
|
+
end
|
158
|
+
|
159
|
+
def file_name ref
|
160
|
+
"#{@references_dir}/#{ref}"
|
161
|
+
end
|
162
|
+
|
163
|
+
def add_reference r
|
164
|
+
count=0
|
165
|
+
@cache.get(r) do |ref|
|
166
|
+
count=File.open(file_name(ref),"r") {|f| f.read().to_i} if File.exist? file_name(ref)
|
167
|
+
end
|
168
|
+
File.open(file_name(r),"w") {|f| f.write(count+1)}
|
169
|
+
@cache.drop(r)
|
170
|
+
end
|
171
|
+
|
172
|
+
def remove_reference ref
|
173
|
+
count=File.open(file_name(ref),"r") {|f| f.read().to_i}
|
174
|
+
File.open(file_name(ref),"w") {|f| f.write(count-1)}
|
175
|
+
@cache.drop(ref)
|
176
|
+
end
|
177
|
+
|
178
|
+
def has_references? r
|
179
|
+
count=0
|
180
|
+
@cache.get(r) do |ref|
|
181
|
+
count=File.open(file_name(ref),"r") {|f| f.read().to_i} if File.exist? file_name(ref)
|
182
|
+
end
|
183
|
+
count > 0
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
class TransactionLog
|
189
|
+
|
190
|
+
def initialize log_dir
|
191
|
+
@log_dir=log_dir
|
192
|
+
|
193
|
+
FileUtils.mkdir_p(log_dir) unless File.exist?(log_dir)
|
194
|
+
|
195
|
+
unless File.exist?("#{log_dir}/last_id.txt")
|
196
|
+
File.open("#{log_dir}/last_id.txt","w") {|f| f.write("0")}
|
197
|
+
@last_id=0
|
198
|
+
else
|
199
|
+
@last_id=File.open("#{log_dir}/last_id.txt","r") {|f| f.read().to_i}
|
200
|
+
end
|
201
|
+
|
202
|
+
@mutex=Mutex.new
|
203
|
+
@change=ConditionVariable.new
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
def read_tx id
|
208
|
+
JSON::parse(File.open("#{@log_dir}/#{id}.json","r") {|f| f.read})
|
209
|
+
end
|
210
|
+
|
211
|
+
def write_tx id, tx
|
212
|
+
|
213
|
+
File.open("#{@log_dir}/#{id}.json","w") {|f| f.write(tx.to_json)}
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
|
218
|
+
def add_listener current_state, &block
|
219
|
+
Thread.new do
|
220
|
+
begin
|
221
|
+
loop do
|
222
|
+
arg=@mutex.synchronize do
|
223
|
+
until current_state < last_id
|
224
|
+
@change.wait(@mutex)
|
225
|
+
end
|
226
|
+
current_state+=1
|
227
|
+
[current_state, read_tx(current_state)]
|
228
|
+
end
|
229
|
+
yield arg
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
rescue => e
|
234
|
+
# puts "Error during processing: #{$!}"
|
235
|
+
# puts "Backtrace:\n\t#{e.backtrace.join("\n\t")}"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
|
241
|
+
def last_id
|
242
|
+
@last_id
|
243
|
+
end
|
244
|
+
|
245
|
+
def store_transaction references_to_change, values_to_add, values_to_remove
|
246
|
+
@mutex.synchronize do
|
247
|
+
txid=last_id+1
|
248
|
+
write_tx txid, [references_to_change, values_to_add, values_to_remove]
|
249
|
+
File.open("#{@log_dir}/last_id.txt","w") {|f| f.write(txid.to_s)}
|
250
|
+
@change.broadcast
|
251
|
+
@last_id=txid
|
252
|
+
txid
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
|
257
|
+
end
|
258
|
+
|
259
|
+
class SocketTransport
|
260
|
+
def initialize socket
|
261
|
+
@socket=socket
|
262
|
+
end
|
263
|
+
|
264
|
+
def next_message
|
265
|
+
length=@socket.read(8).unpack("Q>")[0]
|
266
|
+
JsonMessage::deserialize(@socket.read(length))
|
267
|
+
end
|
268
|
+
|
269
|
+
def send_message msg
|
270
|
+
|
271
|
+
data=msg.serialize
|
272
|
+
@socket.write([data.bytesize].pack("Q>"))
|
273
|
+
@socket.write(data)
|
274
|
+
@socket.flush
|
275
|
+
end
|
276
|
+
|
277
|
+
def close
|
278
|
+
@socket.close
|
279
|
+
end
|
280
|
+
|
281
|
+
|
282
|
+
end
|
283
|
+
|
284
|
+
|
285
|
+
class ClientHandler
|
286
|
+
|
287
|
+
def initialize client_transport, transaction_handler
|
288
|
+
@client_transport=client_transport
|
289
|
+
@transaction_handler=transaction_handler
|
290
|
+
@mutex=Mutex.new
|
291
|
+
end
|
292
|
+
|
293
|
+
def handle message
|
294
|
+
@mutex.synchronize do
|
295
|
+
if TransactionMessage === message
|
296
|
+
@client_transport.send_message message
|
297
|
+
end
|
298
|
+
if ClientTransactionMessage === message
|
299
|
+
begin
|
300
|
+
sequence=@transaction_handler.apply_transaction message.transitions, message.new_content
|
301
|
+
@client_transport.send_message ClientTransactionSuccessfulMessage.new message.transaction_id, sequence
|
302
|
+
rescue StaleTransactionError => e
|
303
|
+
@client_transport.send_message ClientTransactionFailedMessage.new message.transaction_id, e.message
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
end
|
310
|
+
|
311
|
+
|
312
|
+
class StaleTransactionError < StandardError
|
313
|
+
end
|
314
|
+
|
315
|
+
class ClientTransactionManager
|
316
|
+
|
317
|
+
def initialize current_values, reference_counts, transaction_log
|
318
|
+
@current_values=current_values
|
319
|
+
@reference_counts=reference_counts
|
320
|
+
@transaction_log=transaction_log
|
321
|
+
end
|
322
|
+
|
323
|
+
|
324
|
+
def apply_transaction transitions, new_values
|
325
|
+
|
326
|
+
values_to_remove=Set.new
|
327
|
+
values_to_add={}
|
328
|
+
references_to_change={}
|
329
|
+
|
330
|
+
transitions.each do |k,(old,new)|
|
331
|
+
current=@current_values[k]
|
332
|
+
raise StaleTransactionError, "transaction changing stale state" if (current != old)
|
333
|
+
|
334
|
+
next if old == new
|
335
|
+
|
336
|
+
if new && !@reference_counts.has_references?(new)
|
337
|
+
unless new_values.has_key? new
|
338
|
+
raise StaleTransactionError, "referencing not existing data"
|
339
|
+
else
|
340
|
+
values_to_add[new]=new_values[new]
|
341
|
+
end
|
342
|
+
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
transitions.each do |k,(old,new)|
|
347
|
+
if (old != new)
|
348
|
+
values_to_remove << old
|
349
|
+
references_to_change[k]=new
|
350
|
+
@current_values[k]=new
|
351
|
+
@reference_counts.add_reference(new) if new
|
352
|
+
@reference_counts.remove_reference(old) if old
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
values_to_remove=values_to_remove.select {|v| v && !@reference_counts.has_references?(v)}
|
357
|
+
|
358
|
+
# unless references_to_change.empty?
|
359
|
+
@transaction_log.store_transaction references_to_change, values_to_add, values_to_remove.to_set.to_a
|
360
|
+
# end
|
361
|
+
|
362
|
+
|
363
|
+
|
364
|
+
end
|
365
|
+
|
366
|
+
end
|
367
|
+
|
368
|
+
|
369
|
+
|
370
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# dp_stm_map - Distributed and Persistent Software Transaction Map
|
2
|
+
# Copyright (C) 2013 Dragan Milic
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
|
14
|
+
require 'json'
|
15
|
+
|
16
|
+
module DpStmMap
|
17
|
+
|
18
|
+
class JsonMessage
|
19
|
+
|
20
|
+
def self.deserialize string
|
21
|
+
# return YAML::load(string)
|
22
|
+
|
23
|
+
json=JSON.parse(string)
|
24
|
+
type=json.delete('type')
|
25
|
+
cls=class_from_string(type)
|
26
|
+
obj=cls.new
|
27
|
+
obj.from_hash! json
|
28
|
+
obj
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.class_from_string(str)
|
32
|
+
str.split('::').inject(Object) do |mod, class_name|
|
33
|
+
mod.const_get(class_name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def serialize
|
38
|
+
# return YAML::dump(self)
|
39
|
+
hash = {}
|
40
|
+
self.instance_variables.each do |var|
|
41
|
+
hash[var] = self.instance_variable_get var
|
42
|
+
end
|
43
|
+
hash['type']=self.class.name
|
44
|
+
hash.to_json
|
45
|
+
end
|
46
|
+
|
47
|
+
def from_hash! hash
|
48
|
+
hash.each do |var, val|
|
49
|
+
self.instance_variable_set var, val
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
class ClientHelloMessage < JsonMessage
|
58
|
+
|
59
|
+
def initialize last_transaction_sequence=nil
|
60
|
+
@last_transaction_sequence=last_transaction_sequence
|
61
|
+
end
|
62
|
+
|
63
|
+
attr_accessor :last_transaction_sequence
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
class ServerHelloMessage < JsonMessage
|
68
|
+
end
|
69
|
+
|
70
|
+
class ClientTransactionOutcomeMessage < JsonMessage
|
71
|
+
def initialize transaction_id
|
72
|
+
@transaction_id=transaction_id
|
73
|
+
end
|
74
|
+
attr_accessor :transaction_id
|
75
|
+
end
|
76
|
+
|
77
|
+
class ClientTransactionSuccessfulMessage < ClientTransactionOutcomeMessage
|
78
|
+
|
79
|
+
def initialize transaction_id=nil, transaction_sequence=nil
|
80
|
+
super transaction_id
|
81
|
+
@transaction_sequence=transaction_sequence
|
82
|
+
end
|
83
|
+
|
84
|
+
attr_accessor :transaction_sequence
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
class ClientTransactionFailedMessage < ClientTransactionOutcomeMessage
|
90
|
+
|
91
|
+
def initialize transaction_id=nil, reason=nil
|
92
|
+
super transaction_id
|
93
|
+
@reason=reason
|
94
|
+
end
|
95
|
+
|
96
|
+
attr_accessor :reason
|
97
|
+
end
|
98
|
+
|
99
|
+
class ClientTransactionMessage < JsonMessage
|
100
|
+
def initialize transaction_id=nil, transitions=nil, new_content=nil
|
101
|
+
@transaction_id=transaction_id
|
102
|
+
@transitions=transitions
|
103
|
+
@new_content=new_content
|
104
|
+
end
|
105
|
+
|
106
|
+
attr_accessor :transaction_id
|
107
|
+
attr_accessor :transitions
|
108
|
+
attr_accessor :new_content
|
109
|
+
end
|
110
|
+
|
111
|
+
class TransactionMessage < JsonMessage
|
112
|
+
def initialize transaction_sequence=nil, transitions=nil, new_content=nil, delete_content=nil
|
113
|
+
@transaction_sequence=transaction_sequence
|
114
|
+
@transitions=transitions
|
115
|
+
@new_content=new_content
|
116
|
+
@delete_content=delete_content
|
117
|
+
end
|
118
|
+
|
119
|
+
attr_accessor :transaction_sequence
|
120
|
+
attr_accessor :transitions
|
121
|
+
attr_accessor :new_content
|
122
|
+
attr_accessor :delete_content
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# dp_stm_map - Distributed and Persistent Software Transaction Map
|
2
|
+
# Copyright (C) 2013 Dragan Milic
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
|
14
|
+
require 'yaml'
|
15
|
+
|
16
|
+
module DpStmMap
|
17
|
+
|
18
|
+
class ObjectTransactionWrapper
|
19
|
+
def initialize tx
|
20
|
+
@tx=tx
|
21
|
+
end
|
22
|
+
|
23
|
+
def []= domain, key, value
|
24
|
+
@tx[domain_key(domain,key)]=value == nil ? nil : YAML::dump(value)
|
25
|
+
end
|
26
|
+
|
27
|
+
def [] domain, key
|
28
|
+
raw=@tx[domain_key(domain,key)]
|
29
|
+
if (raw == nil)
|
30
|
+
nil
|
31
|
+
else
|
32
|
+
YAML::load(raw)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def has_key? domain, key
|
37
|
+
@tx.has_key?(domain_key(domain,key))
|
38
|
+
end
|
39
|
+
|
40
|
+
def domain_key domain, key
|
41
|
+
"%s:%s" % [domain.to_s, key.to_s]
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
class ObjectStore
|
47
|
+
|
48
|
+
def initialize stm_map
|
49
|
+
@stm_map=stm_map
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def on_atomic
|
54
|
+
@stm_map.on_atomic do |changes|
|
55
|
+
yield changes_to_object_changes changes
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def atomic timeout=nil
|
60
|
+
@stm_map.atomic(timeout) do |stx|
|
61
|
+
yield ObjectTransactionWrapper.new(stx)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def atomic_read
|
66
|
+
@stm_map.atomic_read do |stx|
|
67
|
+
yield ObjectTransactionWrapper.new(stx)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def validate_atomic
|
72
|
+
@stm_map.validate_atomic do |changes|
|
73
|
+
yield changes_to_object_changes changes
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
|
80
|
+
def changes_to_object_changes changes
|
81
|
+
changes.inject({}) {|h, (k,v)| h[string_to_domain_key(k)]=[deserialize_object(v[0]),deserialize_object(v[1])]; h}
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
def string_to_domain_key str
|
86
|
+
parts=str.split(/:/)
|
87
|
+
[parts[0].to_sym, parts[1]]
|
88
|
+
end
|
89
|
+
|
90
|
+
def deserialize_object str
|
91
|
+
str == nil ? nil : YAML::load(str)
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# dp_stm_map - Distributed and Persistent Software Transaction Map
|
2
|
+
# Copyright (C) 2013 Dragan Milic
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
|
14
|
+
module DpStmMap
|
15
|
+
VERSION = "0.0.2"
|
16
|
+
end
|
data/lib/dp_stm_map.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# dp_stm_map - Distributed and Persistent Software Transaction Map
|
2
|
+
# Copyright (C) 2013 Dragan Milic
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
|
14
|
+
require "dp_stm_map/version"
|
15
|
+
require "dp_stm_map/InMemoryStmMap"
|
16
|
+
require "dp_stm_map/Manager"
|
17
|
+
require "dp_stm_map/Message"
|
18
|
+
require "dp_stm_map/Client"
|
19
|
+
require "dp_stm_map/ClientLocalStore"
|
20
|
+
require "dp_stm_map/ObjectStore"
|