tupelo 0.4 → 0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +42 -9
- data/Rakefile +9 -3
- data/bin/tspy +7 -2
- data/bin/tup +1 -1
- data/example/add-dsl.rb +27 -0
- data/example/boolean-match.rb +2 -19
- data/example/broker-optimistic-v2.rb +33 -0
- data/example/broker-optimistic.rb +15 -15
- data/example/dphil-optimistic-v2.rb +37 -0
- data/example/dphil-optimistic.rb +44 -0
- data/example/dphil.rb +42 -0
- data/example/lock-mgr-with-queue.rb +5 -15
- data/example/lock-mgr.rb +7 -16
- data/example/map-reduce-v2.rb +4 -37
- data/example/map-reduce.rb +2 -18
- data/example/small-simplified.rb +7 -7
- data/example/small.rb +3 -3
- data/example/tcp.rb +8 -1
- data/lib/tupelo/app/trace.rb +23 -0
- data/lib/tupelo/app.rb +45 -7
- data/lib/tupelo/archiver/worker.rb +31 -4
- data/lib/tupelo/client/reader.rb +0 -2
- data/lib/tupelo/client/transaction.rb +83 -23
- data/lib/tupelo/client/worker.rb +14 -5
- data/lib/tupelo/util/boolean.rb +25 -0
- data/lib/tupelo/version.rb +1 -1
- data/test/lib/time-fuzz.rb +36 -0
- data/test/stress/archiver-load.rb +16 -0
- data/test/stress/concurrent-transactions.rb +7 -5
- metadata +12 -5
- data/example/broker-optimistic-async.rb +0 -33
- data/example/broker-queue.rb +0 -2
- /data/lib/tupelo/{irb-shell.rb → app/irb-shell.rb} +0 -0
data/example/map-reduce.rb
CHANGED
@@ -1,23 +1,8 @@
|
|
1
1
|
require 'tupelo/app'
|
2
2
|
|
3
3
|
N = 2 # how many cpus do you want to use for mappers?
|
4
|
-
VERBOSE = ARGV.delete "-v"
|
5
4
|
|
6
5
|
Tupelo.application do |app|
|
7
|
-
if VERBOSE
|
8
|
-
app.child do |client| # a debugger client, to see what's happening
|
9
|
-
note = client.notifier
|
10
|
-
puts "%4s %4s %10s %s" % %w{ tick cid status operation }
|
11
|
-
loop do
|
12
|
-
status, tick, cid, op = note.wait
|
13
|
-
unless status == :attempt
|
14
|
-
s = status == :failure ? "FAILED" : ""
|
15
|
-
puts "%4d %4d %10s %p" % [tick, cid, s, op]
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
6
|
app.child do |client|
|
22
7
|
document = "I will not map reduce in class\n" * 10
|
23
8
|
lineno = 0
|
@@ -51,12 +36,11 @@ Tupelo.application do |app|
|
|
51
36
|
end
|
52
37
|
end
|
53
38
|
|
54
|
-
client.log "
|
55
|
-
client.log "Press ^C to exit"
|
39
|
+
client.log "results = #{results}"
|
56
40
|
end
|
57
41
|
|
58
42
|
N.times do |i|
|
59
|
-
app.child do |client|
|
43
|
+
app.child passive: true do |client|
|
60
44
|
client.log.progname = "mapper #{i}"
|
61
45
|
|
62
46
|
loop do
|
data/example/small-simplified.rb
CHANGED
@@ -2,17 +2,17 @@
|
|
2
2
|
|
3
3
|
require 'tupelo/app'
|
4
4
|
|
5
|
-
Tupelo.application do
|
6
|
-
|
7
|
-
|
8
|
-
_, s =
|
5
|
+
Tupelo.application do
|
6
|
+
child do
|
7
|
+
write [2, 3, "frogs"]
|
8
|
+
_, s = take ["animals", nil]
|
9
9
|
puts s
|
10
10
|
end
|
11
11
|
|
12
|
-
|
13
|
-
x, y, s =
|
12
|
+
child do
|
13
|
+
x, y, s = take [Numeric, Numeric, String]
|
14
14
|
s2 = ([s] * (x + y)).join(" ")
|
15
|
-
|
15
|
+
write ["animals", s2]
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
data/example/small.rb
CHANGED
@@ -39,8 +39,8 @@ EasyServe.start(servers_file: "small-servers.yaml") do |ez|
|
|
39
39
|
|
40
40
|
ez.server :arcd do |svr|
|
41
41
|
require 'tupelo/archiver'
|
42
|
-
arc = Archiver.new svr,
|
43
|
-
|
42
|
+
arc = Tupelo::Archiver.new svr,
|
43
|
+
seq: arc_to_seq_sock, cseq: arc_to_cseq_sock, log: log
|
44
44
|
arc.start
|
45
45
|
end
|
46
46
|
end
|
@@ -49,7 +49,7 @@ EasyServe.start(servers_file: "small-servers.yaml") do |ez|
|
|
49
49
|
log = opts[:log]
|
50
50
|
log.progname = "client <starting in #{log.progname}>"
|
51
51
|
require 'tupelo/client'
|
52
|
-
client = Client.new opts
|
52
|
+
client = Tupelo::Client.new opts
|
53
53
|
client.start do
|
54
54
|
log.progname = "client #{client.client_id}"
|
55
55
|
end
|
data/example/tcp.rb
CHANGED
@@ -5,6 +5,10 @@
|
|
5
5
|
#
|
6
6
|
# ruby tcp.rb --info
|
7
7
|
#
|
8
|
+
# or
|
9
|
+
#
|
10
|
+
# ruby tcp.rb --trace
|
11
|
+
#
|
8
12
|
# You can test with a local client:
|
9
13
|
#
|
10
14
|
# ../bin/tup tcp.yaml
|
@@ -27,7 +31,10 @@ Tupelo.application servers_file: svr,
|
|
27
31
|
arcd_addr: [:tcp, '0.0.0.0', port + 2] do |app|
|
28
32
|
if app.owns_servers
|
29
33
|
puts "server started; ^C to stop"
|
30
|
-
puts "
|
34
|
+
puts "run in another terminal: ../bin/tup tcp.yaml"
|
35
|
+
if app.log.level > Logger::INFO
|
36
|
+
puts "(run with --info or --trace to see events)"
|
37
|
+
end
|
31
38
|
sleep
|
32
39
|
else
|
33
40
|
abort "server seems to be running already; check file #{svr.inspect}"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'tupelo/app'
|
2
|
+
|
3
|
+
# displays every transaction in sequence, specially marking failed ones,
|
4
|
+
# until INT signal
|
5
|
+
class Tupelo::AppBuilder
|
6
|
+
def start_trace
|
7
|
+
child passive: true do |client|
|
8
|
+
trap :INT do
|
9
|
+
exit!
|
10
|
+
end
|
11
|
+
|
12
|
+
note = client.notifier
|
13
|
+
log << ( "%6s %6s %6s %s\n" % %w{ tick cid status operation } )
|
14
|
+
loop do
|
15
|
+
status, tick, cid, op = note.wait
|
16
|
+
unless status == :attempt
|
17
|
+
s = status == :failure ? "FAIL" : ""
|
18
|
+
log << ( "%6d %6d %6s %p\n" % [tick, cid, s, op] )
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/tupelo/app.rb
CHANGED
@@ -22,21 +22,42 @@ module Tupelo
|
|
22
22
|
end
|
23
23
|
|
24
24
|
# Yields a client that runs in this process.
|
25
|
-
def local client_class = Client
|
25
|
+
def local client_class = Client, &block
|
26
26
|
ez.local :seqd, :cseqd, :arcd do |seqd, cseqd, arcd|
|
27
27
|
run_client client_class,
|
28
28
|
seq: seqd, cseq: cseqd, arc: arcd, log: log do |client|
|
29
|
-
|
29
|
+
if block
|
30
|
+
if block.arity == 0
|
31
|
+
client.instance_eval &block
|
32
|
+
else
|
33
|
+
yield client
|
34
|
+
end
|
35
|
+
else
|
36
|
+
client
|
37
|
+
end
|
30
38
|
end
|
31
39
|
end
|
32
40
|
end
|
33
41
|
|
34
42
|
# Yields a client that runs in a subprocess.
|
35
|
-
|
36
|
-
|
43
|
+
#
|
44
|
+
# A passive client will be forced to stop after all active clients exit. Use
|
45
|
+
# the passive flag for processes that wait for tuples and respond in some
|
46
|
+
# way. Then you do not have to manually interrupt the whole application when
|
47
|
+
# the active processes are done. See examples.
|
48
|
+
def child client_class = Client, passive: false, &block
|
49
|
+
ez.client :seqd, :cseqd, :arcd, passive: passive do |seqd, cseqd, arcd|
|
37
50
|
run_client client_class,
|
38
51
|
seq: seqd, cseq: cseqd, arc: arcd, log: log do |client|
|
39
|
-
|
52
|
+
if block
|
53
|
+
if block.arity == 0
|
54
|
+
client.instance_eval &block
|
55
|
+
else
|
56
|
+
yield client
|
57
|
+
end
|
58
|
+
else
|
59
|
+
client
|
60
|
+
end
|
40
61
|
end
|
41
62
|
end
|
42
63
|
end
|
@@ -61,7 +82,7 @@ module Tupelo
|
|
61
82
|
|
62
83
|
def self.application argv: ARGV,
|
63
84
|
servers_file: nil, blob_type: nil,
|
64
|
-
seqd_addr: [], cseqd_addr: [], arcd_addr: []
|
85
|
+
seqd_addr: [], cseqd_addr: [], arcd_addr: [], &block
|
65
86
|
|
66
87
|
log_level = case
|
67
88
|
when argv.delete("--debug"); Logger::DEBUG
|
@@ -79,6 +100,8 @@ module Tupelo
|
|
79
100
|
end
|
80
101
|
blob_type ||= "msgpack"
|
81
102
|
end
|
103
|
+
|
104
|
+
enable_trace = ARGV.delete("--trace")
|
82
105
|
|
83
106
|
svrs = servers_file || argv.shift || "servers-#$$.yaml"
|
84
107
|
|
@@ -115,7 +138,22 @@ module Tupelo
|
|
115
138
|
end
|
116
139
|
end
|
117
140
|
|
118
|
-
|
141
|
+
app = AppBuilder.new(ez, owns_servers: owns_servers)
|
142
|
+
|
143
|
+
if enable_trace
|
144
|
+
require 'tupelo/app/trace'
|
145
|
+
app.start_trace
|
146
|
+
end
|
147
|
+
|
148
|
+
if block
|
149
|
+
if block.arity == 0
|
150
|
+
app.instance_eval &block
|
151
|
+
else
|
152
|
+
yield app
|
153
|
+
end
|
154
|
+
else
|
155
|
+
app
|
156
|
+
end
|
119
157
|
end
|
120
158
|
end
|
121
159
|
end
|
@@ -3,6 +3,11 @@ require 'funl/history-worker'
|
|
3
3
|
class Tupelo::Archiver
|
4
4
|
class Worker < Tupelo::Client::Worker
|
5
5
|
include Funl::HistoryWorker
|
6
|
+
|
7
|
+
def initialize *args
|
8
|
+
super
|
9
|
+
@scheduled_actions = Hash.new {|h,k| h[k] = []}
|
10
|
+
end
|
6
11
|
|
7
12
|
def handle_client_request req
|
8
13
|
case req
|
@@ -17,7 +22,7 @@ class Tupelo::Archiver
|
|
17
22
|
stream = client.arc_server_stream_for req.io
|
18
23
|
|
19
24
|
begin
|
20
|
-
op,
|
25
|
+
op, tags, tick = stream.read
|
21
26
|
rescue EOFError
|
22
27
|
log.debug {"#{stream.peer_name} disconnected from archiver"}
|
23
28
|
return
|
@@ -26,9 +31,19 @@ class Tupelo::Archiver
|
|
26
31
|
end
|
27
32
|
|
28
33
|
log.info {
|
29
|
-
"#{stream.peer_name} requested #{op.inspect}" +
|
30
|
-
(
|
34
|
+
"#{stream.peer_name} requested #{op.inspect} at tick=#{tick}" +
|
35
|
+
(tags ? " on #{tags}" : "")}
|
36
|
+
|
37
|
+
if tick <= global_tick
|
38
|
+
fork_for_op op, tags, tick, stream, req
|
39
|
+
else
|
40
|
+
at_tick tick do
|
41
|
+
fork_for_op op, tags, tick, stream, req
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
31
45
|
|
46
|
+
def fork_for_op op, tags, tick, stream, req
|
32
47
|
fork do
|
33
48
|
begin
|
34
49
|
case op
|
@@ -37,7 +52,7 @@ class Tupelo::Archiver
|
|
37
52
|
when "get range" ### handle this in Funl::HistoryWorker
|
38
53
|
raise "Unimplemented" ###
|
39
54
|
when GET_TUPLESPACE
|
40
|
-
send_tuplespace stream,
|
55
|
+
send_tuplespace stream, tags
|
41
56
|
else
|
42
57
|
raise "Unknown operation: #{op.inspect}"
|
43
58
|
end
|
@@ -50,6 +65,18 @@ class Tupelo::Archiver
|
|
50
65
|
ensure
|
51
66
|
req.io.close
|
52
67
|
end
|
68
|
+
|
69
|
+
def at_tick tick, &action
|
70
|
+
@scheduled_actions[tick] << action
|
71
|
+
end
|
72
|
+
|
73
|
+
def handle_message msg
|
74
|
+
super
|
75
|
+
actions = @scheduled_actions.delete(global_tick)
|
76
|
+
actions and actions.each do |action|
|
77
|
+
action.call
|
78
|
+
end
|
79
|
+
end
|
53
80
|
|
54
81
|
def send_tuplespace stream, templates
|
55
82
|
log.info {
|
data/lib/tupelo/client/reader.rb
CHANGED
@@ -3,7 +3,6 @@ require 'tupelo/client/common'
|
|
3
3
|
class Tupelo::Client
|
4
4
|
# include into class that defines #worker and #log
|
5
5
|
module Api
|
6
|
-
## need read with more complex predicates: |, &, etc
|
7
6
|
def read_wait template
|
8
7
|
waiter = Waiter.new(worker.make_template(template), self)
|
9
8
|
worker << waiter
|
@@ -15,7 +14,6 @@ class Tupelo::Client
|
|
15
14
|
end
|
16
15
|
alias read read_wait
|
17
16
|
|
18
|
-
## need nonwaiting reader that accepts 2 or more templates
|
19
17
|
def read_nowait template
|
20
18
|
matcher = Matcher.new(worker.make_template(template), self)
|
21
19
|
worker << matcher
|
@@ -8,24 +8,35 @@ class Tupelo::Client
|
|
8
8
|
class TransactionFailure < TransactionError; end
|
9
9
|
|
10
10
|
module Api
|
11
|
+
def trans_class
|
12
|
+
Transaction
|
13
|
+
end
|
14
|
+
|
11
15
|
# Transactions are atomic by default, and are always isolated. In the
|
12
16
|
# non-atomic case, a "transaction" is really a batch op. Without a block,
|
13
17
|
# returns the Transaction. In the block form, transaction automatically
|
14
18
|
# waits for successful completion and returns the value of the block.
|
15
|
-
def transaction atomic: true, timeout: nil
|
19
|
+
def transaction atomic: true, timeout: nil, &block
|
16
20
|
deadline = timeout && Time.now + timeout
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
21
|
+
t = trans_class.new self, atomic: atomic, deadline: deadline
|
22
|
+
return t unless block_given?
|
23
|
+
|
24
|
+
val =
|
25
|
+
if block.arity == 0
|
26
|
+
t.instance_eval &block
|
27
|
+
else
|
28
|
+
yield t
|
29
|
+
end
|
30
|
+
|
31
|
+
t.commit.wait
|
32
|
+
return val
|
33
|
+
rescue TransactionFailure => ex
|
34
|
+
log.info {"retrying #{t.inspect}: #{ex}"}
|
35
|
+
retry
|
36
|
+
rescue TransactionAbort
|
37
|
+
log.info {"aborting #{t.inspect}"}
|
38
|
+
ensure
|
39
|
+
t.cancel if t and t.open? and block_given?
|
29
40
|
end
|
30
41
|
|
31
42
|
def batch &bl
|
@@ -81,7 +92,6 @@ class Tupelo::Client
|
|
81
92
|
class Transaction
|
82
93
|
attr_reader :client
|
83
94
|
attr_reader :worker
|
84
|
-
attr_reader :log
|
85
95
|
attr_reader :atomic
|
86
96
|
attr_reader :deadline
|
87
97
|
attr_reader :status
|
@@ -134,6 +144,7 @@ class Tupelo::Client
|
|
134
144
|
@granted_tuples = nil
|
135
145
|
@missing = nil
|
136
146
|
@_take_nowait = nil
|
147
|
+
@_read_nowait = nil
|
137
148
|
|
138
149
|
if deadline
|
139
150
|
worker.at deadline do
|
@@ -144,6 +155,14 @@ class Tupelo::Client
|
|
144
155
|
open!
|
145
156
|
end
|
146
157
|
|
158
|
+
def log *args
|
159
|
+
if args.empty?
|
160
|
+
@log
|
161
|
+
else
|
162
|
+
@log.unknown *args
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
147
166
|
def inspect
|
148
167
|
stat_extra =
|
149
168
|
case
|
@@ -207,7 +226,7 @@ class Tupelo::Client
|
|
207
226
|
template = worker.make_template(template_spec)
|
208
227
|
@take_templates << template
|
209
228
|
log.debug {"asking worker to take #{template_spec.inspect}"}
|
210
|
-
|
229
|
+
worker_push self
|
211
230
|
wait
|
212
231
|
return take_tuples.last
|
213
232
|
end
|
@@ -223,7 +242,7 @@ class Tupelo::Client
|
|
223
242
|
@_take_nowait[i] = true
|
224
243
|
@take_templates << template
|
225
244
|
log.debug {"asking worker to take_nowait #{template_spec.inspect}"}
|
226
|
-
|
245
|
+
worker_push self
|
227
246
|
wait
|
228
247
|
return take_tuples[i]
|
229
248
|
end
|
@@ -237,23 +256,52 @@ class Tupelo::Client
|
|
237
256
|
template = worker.make_template(template_spec)
|
238
257
|
@read_templates << template
|
239
258
|
log.debug {"asking worker to read #{template_spec.inspect}"}
|
240
|
-
|
259
|
+
worker_push self
|
241
260
|
wait
|
242
261
|
return read_tuples.last
|
243
262
|
end
|
244
|
-
|
263
|
+
|
264
|
+
def read_nowait template_spec
|
265
|
+
raise "cannot read in batch" unless atomic
|
266
|
+
raise exception if failed?
|
267
|
+
raise TransactionStateError, "not open: #{inspect}" unless open? or
|
268
|
+
failed?
|
269
|
+
template = worker.make_template(template_spec)
|
270
|
+
@_read_nowait ||= {}
|
271
|
+
i = @read_templates.size
|
272
|
+
@_read_nowait[i] = true
|
273
|
+
@read_templates << template
|
274
|
+
log.debug {"asking worker to read #{template_spec.inspect}"}
|
275
|
+
worker_push self
|
276
|
+
wait
|
277
|
+
return read_tuples[i]
|
278
|
+
end
|
279
|
+
|
280
|
+
# Client may call this before commit. In transaction do...end block,
|
281
|
+
# this causes transaction to be re-executed.
|
282
|
+
def fail!
|
283
|
+
raise if in_worker_thread?
|
284
|
+
raise unless open?
|
285
|
+
failed!
|
286
|
+
raise TransactionFailure
|
287
|
+
end
|
288
|
+
|
245
289
|
# idempotent
|
246
290
|
def commit
|
247
291
|
if open?
|
248
292
|
closed!
|
249
293
|
log.info {"committing #{inspect}"}
|
250
|
-
|
294
|
+
worker_push self
|
251
295
|
else
|
252
296
|
raise exception if failed?
|
253
297
|
end
|
254
298
|
return self
|
255
299
|
end
|
256
|
-
|
300
|
+
|
301
|
+
def worker_push event=Proc.new
|
302
|
+
worker << event
|
303
|
+
end
|
304
|
+
|
257
305
|
def wait
|
258
306
|
return self if done?
|
259
307
|
raise exception if failed?
|
@@ -268,7 +316,7 @@ class Tupelo::Client
|
|
268
316
|
raise "bug: #{inspect}"
|
269
317
|
|
270
318
|
rescue TransactionAbort, Interrupt, TimeoutError => ex ## others?
|
271
|
-
|
319
|
+
worker_push Unwaiter.new(self)
|
272
320
|
raise ex.class,
|
273
321
|
"#{ex.message}: client #{client.client_id} waiting for #{inspect}"
|
274
322
|
end
|
@@ -353,13 +401,24 @@ class Tupelo::Client
|
|
353
401
|
@_take_nowait.delete i
|
354
402
|
end
|
355
403
|
|
404
|
+
skip = nil
|
356
405
|
(read_tuples.size...read_templates.size).each do |i|
|
357
406
|
read_tuples[i] = worker.tuplespace.find_match_for(
|
358
407
|
read_templates[i])
|
359
408
|
if read_tuples[i]
|
360
409
|
log.debug {"prepared #{inspect} with #{read_tuples[i]}"}
|
410
|
+
else
|
411
|
+
if @_read_nowait and @_read_nowait[i]
|
412
|
+
(skip ||= []) << i
|
413
|
+
end
|
361
414
|
end
|
362
415
|
end
|
416
|
+
|
417
|
+
skip and skip.reverse_each do |i|
|
418
|
+
read_tuples.delete_at i
|
419
|
+
read_templates.delete_at i
|
420
|
+
@_read_nowait.delete i
|
421
|
+
end
|
363
422
|
end
|
364
423
|
|
365
424
|
## convert cancelling write/take to pulse
|
@@ -440,9 +499,9 @@ class Tupelo::Client
|
|
440
499
|
|
441
500
|
# Called by another thread to cancel a waiting transaction.
|
442
501
|
def cancel err = TransactionAbort
|
443
|
-
|
502
|
+
worker_push do
|
444
503
|
raise unless in_worker_thread?
|
445
|
-
if @global_tick or @exception
|
504
|
+
if not open? or @global_tick or @exception
|
446
505
|
log.info {"cancel was applied too late: #{inspect}"}
|
447
506
|
else
|
448
507
|
@exception = err.new
|
@@ -450,6 +509,7 @@ class Tupelo::Client
|
|
450
509
|
@queue << false
|
451
510
|
end
|
452
511
|
end
|
512
|
+
nil
|
453
513
|
end
|
454
514
|
end
|
455
515
|
end
|
data/lib/tupelo/client/worker.rb
CHANGED
@@ -9,7 +9,6 @@ class Tupelo::Client
|
|
9
9
|
attr_reader :client
|
10
10
|
attr_reader :seq
|
11
11
|
attr_reader :arc
|
12
|
-
attr_reader :log
|
13
12
|
attr_reader :client_id
|
14
13
|
attr_reader :local_tick
|
15
14
|
attr_reader :global_tick
|
@@ -80,6 +79,14 @@ class Tupelo::Client
|
|
80
79
|
@stopping = false
|
81
80
|
end
|
82
81
|
|
82
|
+
def log *args
|
83
|
+
if args.empty?
|
84
|
+
@log
|
85
|
+
else
|
86
|
+
@log.unknown *args
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
83
90
|
def start
|
84
91
|
return if @worker_thread
|
85
92
|
|
@@ -223,7 +230,7 @@ class Tupelo::Client
|
|
223
230
|
end
|
224
231
|
|
225
232
|
log.info "requesting tuplespace from arc"
|
226
|
-
arc << [GET_TUPLESPACE, nil]
|
233
|
+
arc << [GET_TUPLESPACE, nil, tick]
|
227
234
|
## replace nil with template tuples, if any
|
228
235
|
|
229
236
|
begin
|
@@ -231,19 +238,21 @@ class Tupelo::Client
|
|
231
238
|
log.info "arc says global_tick = #{arc_tick}"
|
232
239
|
|
233
240
|
done = false
|
241
|
+
count = 0
|
234
242
|
arc.each do |tuple|
|
235
243
|
if tuple.nil?
|
236
244
|
done = true
|
237
245
|
else
|
238
246
|
raise "bad object stream from archiver" if done
|
239
247
|
tuplespace.insert tuple
|
248
|
+
count += 1
|
240
249
|
end
|
241
250
|
end
|
242
251
|
unless done
|
243
252
|
raise "did not get all of tuplespace from archiver" ## roll back?
|
244
253
|
end
|
245
254
|
|
246
|
-
log.info "received tuplespace from arc"
|
255
|
+
log.info "received tuplespace from arc: #{count} tuples"
|
247
256
|
|
248
257
|
@global_tick = arc_tick
|
249
258
|
log.info "global_tick = #{global_tick}"
|
@@ -262,9 +271,9 @@ class Tupelo::Client
|
|
262
271
|
# due to archiver timing, for example
|
263
272
|
return
|
264
273
|
elsif msg.global_tick > global_tick + 1
|
265
|
-
log.
|
274
|
+
log.fatal "message out of order: #{msg.inspect}, " +
|
266
275
|
"received at global_tick=#{global_tick}"
|
267
|
-
|
276
|
+
raise "fatal error"
|
268
277
|
end
|
269
278
|
end
|
270
279
|
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Tupelo::Client
|
2
|
+
class Or
|
3
|
+
attr_reader :templates
|
4
|
+
|
5
|
+
def initialize worker, templates
|
6
|
+
@templates = templates.map {|template| worker.make_template(template)}
|
7
|
+
end
|
8
|
+
|
9
|
+
def === obj
|
10
|
+
templates.any? {|template| template === obj}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def or *templates
|
15
|
+
Or.new(worker, templates)
|
16
|
+
end
|
17
|
+
alias match_any or
|
18
|
+
end
|
19
|
+
|
20
|
+
class Tupelo::Client::Transaction
|
21
|
+
def or *templates
|
22
|
+
client.or *templates
|
23
|
+
end
|
24
|
+
alias match_any or
|
25
|
+
end
|
data/lib/tupelo/version.rb
CHANGED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Tupelo
|
2
|
+
module TimeFuzz
|
3
|
+
DEFAULT_SLEEP_MAX = 0.01
|
4
|
+
@sleep_max = DEFAULT_SLEEP_MAX
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_accessor :sleep_max
|
8
|
+
end
|
9
|
+
|
10
|
+
module Api
|
11
|
+
def trans_class
|
12
|
+
TimeFuzz::Transaction
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Transaction < Tupelo::Client::Transaction
|
17
|
+
def worker_push event=Proc.new
|
18
|
+
sleep sleep_duration
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def wait
|
23
|
+
super
|
24
|
+
sleep sleep_duration
|
25
|
+
end
|
26
|
+
|
27
|
+
def sleep_duration
|
28
|
+
rand * TimeFuzz.sleep_max
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Client < Tupelo::Client
|
33
|
+
include TimeFuzz::Api
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Stresses the ability of the archiver to handle highl load, and in particular
|
2
|
+
# situations where the client asks for data more recent than what the archiver
|
3
|
+
# has received.
|
4
|
+
|
5
|
+
require 'tupelo/app'
|
6
|
+
|
7
|
+
N = 100
|
8
|
+
|
9
|
+
Tupelo.application do
|
10
|
+
N.times do |i|
|
11
|
+
sleep i/1000.0
|
12
|
+
child do
|
13
|
+
write [1]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|