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.
- 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"
|