tupelo 0.4 → 0.5
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.
- 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
|