tupelo 0.9 → 0.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +31 -1
- data/bugs/read-take.rb +19 -0
- data/bugs/take-write.rb +7 -7
- data/example/app-and-tup.rb +11 -8
- data/example/async-transaction.rb +7 -7
- data/example/balance-xfer-locking.rb +13 -13
- data/example/balance-xfer-retry.rb +16 -16
- data/example/balance-xfer.rb +9 -9
- data/example/boolean-match.rb +5 -5
- data/example/bounded-retry.rb +9 -9
- data/example/broker-locking.rb +14 -14
- data/example/broker-optimistic.rb +7 -7
- data/example/cancel.rb +7 -7
- data/example/concurrent-transactions.rb +17 -17
- data/example/custom-class.rb +9 -9
- data/example/custom-search.rb +8 -8
- data/example/fail-and-retry.rb +11 -11
- data/example/hash-tuples.rb +12 -10
- data/example/increment.rb +8 -8
- data/example/load-balancer.rb +2 -1
- data/example/lock-mgr-with-queue.rb +18 -18
- data/example/lock-mgr.rb +18 -18
- data/example/map-reduce-v2.rb +11 -11
- data/example/map-reduce.rb +11 -11
- data/example/matching.rb +5 -5
- data/example/message-bus.rb +6 -3
- data/example/notify.rb +17 -17
- data/example/optimist.rb +9 -9
- data/example/parallel.rb +16 -8
- data/example/pregel/distributed.rb +129 -0
- data/example/pregel/pagerank.rb +72 -0
- data/example/pregel/pregel.rb +102 -0
- data/example/pregel/remote.rb +165 -0
- data/example/pulse.rb +10 -10
- data/example/read-in-trans.rb +15 -15
- data/example/subspace.rb +34 -0
- data/example/take-nowait.rb +1 -0
- data/example/tcp.rb +3 -3
- data/example/timeout-trans.rb +5 -5
- data/example/timeout.rb +10 -9
- data/example/tiny-client.rb +5 -5
- data/example/tiny-server.rb +2 -2
- data/example/transaction-logic.rb +16 -16
- data/example/wait-interrupt.rb +38 -0
- data/example/write-wait.rb +11 -11
- data/lib/tupelo/archiver/tuplespace.rb +5 -1
- data/lib/tupelo/archiver/worker.rb +25 -18
- data/lib/tupelo/client/reader.rb +2 -2
- data/lib/tupelo/client/transaction.rb +79 -36
- data/lib/tupelo/client/tuplespace.rb +1 -0
- data/lib/tupelo/client/worker.rb +107 -13
- data/lib/tupelo/client.rb +36 -2
- data/lib/tupelo/version.rb +1 -1
- data/test/lib/mock-client.rb +4 -0
- data/test/lib/testable-worker.rb +1 -1
- data/test/stress/concurrent-transactions.rb +15 -15
- data/test/system/test-archiver.rb +8 -8
- data/test/unit/test-ops.rb +56 -0
- metadata +72 -68
- data/bugs/write-read.rb +0 -15
- data/example/broker-queue.rb +0 -35
- data/example/child-of-child.rb +0 -34
data/example/fail-and-retry.rb
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
require 'tupelo/app'
|
2
2
|
|
3
|
-
Tupelo.application do
|
3
|
+
Tupelo.application do
|
4
4
|
2.times do
|
5
|
-
|
5
|
+
child do
|
6
6
|
begin
|
7
7
|
# the block is re-executed for the client that fails to take [1]
|
8
8
|
# this is also true in the transaction do...end construct.
|
9
|
-
t =
|
9
|
+
t = transaction
|
10
10
|
r = t.take [Integer]
|
11
|
-
|
11
|
+
log "trying to take #{r.inspect}"
|
12
12
|
t.commit.wait
|
13
|
-
|
13
|
+
log "took #{r.inspect}"
|
14
14
|
rescue Tupelo::Client::TransactionFailure => ex
|
15
|
-
|
15
|
+
log "#{ex} -- retrying"
|
16
16
|
retry
|
17
17
|
# manually emulate the effect of transaction do...end
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
child do
|
23
|
+
write [1]
|
24
|
+
log "wrote #{[1]}"
|
25
25
|
sleep 0.1
|
26
|
-
|
27
|
-
|
26
|
+
write [2]
|
27
|
+
log "wrote #{[2]}"
|
28
28
|
end
|
29
29
|
end
|
data/example/hash-tuples.rb
CHANGED
@@ -35,19 +35,21 @@
|
|
35
35
|
|
36
36
|
require 'tupelo/app'
|
37
37
|
|
38
|
-
Tupelo.application do
|
39
|
-
|
40
|
-
|
38
|
+
Tupelo.application do
|
39
|
+
child do
|
40
|
+
write x: 1, y: 2
|
41
41
|
end
|
42
42
|
|
43
|
-
|
44
|
-
t =
|
45
|
-
|
46
|
-
|
43
|
+
child do
|
44
|
+
t = take x: Numeric, y: Numeric
|
45
|
+
write x: t["x"], y: t["y"], sum: t["x"] + t["y"]
|
46
|
+
log "sum result: #{read x: nil, y: nil, sum: nil}"
|
47
47
|
|
48
48
|
# N.B.: these are all empty, for the reason given above.
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
log read_all x: nil
|
50
|
+
log read_all y: nil
|
51
|
+
log read_all x: nil, y: nil, z: nil
|
52
|
+
|
53
|
+
log read_all
|
52
54
|
end
|
53
55
|
end
|
data/example/increment.rb
CHANGED
@@ -2,20 +2,20 @@ require 'tupelo/app'
|
|
2
2
|
|
3
3
|
N = 5
|
4
4
|
|
5
|
-
Tupelo.application do
|
5
|
+
Tupelo.application do
|
6
6
|
N.times do |i|
|
7
|
-
|
8
|
-
|
9
|
-
n, s =
|
7
|
+
child do
|
8
|
+
transaction do
|
9
|
+
n, s = take [Numeric, String]
|
10
10
|
#sleep rand # No race conditions here!
|
11
|
-
|
11
|
+
write [n + 1, s + "\n incremented by client #{i}"]
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
n, s =
|
16
|
+
child do
|
17
|
+
write [0, "started with 0"]
|
18
|
+
n, s = take [N, String]
|
19
19
|
puts s, "result is #{n}"
|
20
20
|
end
|
21
21
|
end
|
data/example/load-balancer.rb
CHANGED
@@ -33,7 +33,8 @@ Tupelo.application do
|
|
33
33
|
req_id = nil
|
34
34
|
transaction do
|
35
35
|
# grouping the following ops in a transaction is not necessary for
|
36
|
-
# correctness, but it does reduce latency
|
36
|
+
# correctness, but it does reduce latency. Also, it's more robust
|
37
|
+
# in that a crash or network problem cannot cause a lost tuple.
|
37
38
|
_, req_id = take ["next_req_id", Integer]
|
38
39
|
write ["next_req_id", req_id + 1]
|
39
40
|
write ["request", req_id, req_dat]
|
@@ -9,36 +9,36 @@ require 'tupelo/app'
|
|
9
9
|
|
10
10
|
N = 3
|
11
11
|
|
12
|
-
Tupelo.application do
|
13
|
-
|
14
|
-
|
12
|
+
Tupelo.application do
|
13
|
+
child passive: true do # the lock manager
|
14
|
+
log.progname << " (lock mgr)"
|
15
15
|
waiters = Queue.new
|
16
16
|
|
17
17
|
Thread.new do
|
18
18
|
loop do
|
19
19
|
_, _, client_id, duration =
|
20
|
-
|
20
|
+
take ["request", "resource", nil, nil]
|
21
21
|
waiters << [client_id, duration]
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
25
|
loop do
|
26
26
|
client_id, duration = waiters.pop
|
27
|
-
|
27
|
+
write ["resource", client_id, duration]
|
28
28
|
begin
|
29
|
-
|
29
|
+
take ["done", "resource", client_id], timeout: duration
|
30
30
|
rescue TimeoutError
|
31
|
-
|
31
|
+
log "forcing client #{client_id} to stop using resource."
|
32
32
|
end
|
33
|
-
|
33
|
+
take ["resource", client_id, duration]
|
34
34
|
end
|
35
35
|
# exercise for reader: make this work with 2 or more resources
|
36
36
|
# exercise: rewrite this with hash tuples instead of array tuples
|
37
37
|
end
|
38
38
|
|
39
39
|
N.times do |i|
|
40
|
-
|
41
|
-
|
40
|
+
child do
|
41
|
+
write ["request", "resource", client_id, 0.5]
|
42
42
|
|
43
43
|
2.times do |j|
|
44
44
|
# Now we are ouside of transaction, but still no other client may use
|
@@ -46,20 +46,20 @@ Tupelo.application do |app|
|
|
46
46
|
# as all clients follow the read protocol below.
|
47
47
|
sleep 0.2
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
transaction do
|
50
|
+
read ["resource", client_id, nil]
|
51
|
+
write ["c#{client_id}##{j}"]
|
52
52
|
end
|
53
53
|
end
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
|
-
|
57
|
+
child passive: true do
|
58
58
|
# This client never even tries to lock the resource, so it cannot write.
|
59
|
-
|
60
|
-
|
61
|
-
|
59
|
+
transaction do
|
60
|
+
read ["resource", client_id, nil]
|
61
|
+
write ["c#{client_id}##{j}"]
|
62
62
|
end
|
63
|
-
|
63
|
+
log.error "should never get here"
|
64
64
|
end
|
65
65
|
end
|
data/example/lock-mgr.rb
CHANGED
@@ -4,14 +4,14 @@ require 'tupelo/app'
|
|
4
4
|
|
5
5
|
N = 3
|
6
6
|
|
7
|
-
Tupelo.application do
|
8
|
-
|
9
|
-
|
7
|
+
Tupelo.application do
|
8
|
+
child passive: true do # the lock manager
|
9
|
+
log.progname << " (lock mgr)"
|
10
10
|
loop do
|
11
|
-
|
12
|
-
lock_id, client_id, duration =
|
11
|
+
write ["resource", "none"]
|
12
|
+
lock_id, client_id, duration = read ["resource", nil, nil]
|
13
13
|
sleep duration
|
14
|
-
|
14
|
+
take [lock_id, client_id, duration]
|
15
15
|
# optimization: combine take and write in a transaction, just to
|
16
16
|
# reduce delay
|
17
17
|
end
|
@@ -20,10 +20,10 @@ Tupelo.application do |app|
|
|
20
20
|
end
|
21
21
|
|
22
22
|
N.times do |i|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
child do
|
24
|
+
transaction do
|
25
|
+
take ["resource", "none"]
|
26
|
+
write ["resource", client_id, 0.5]
|
27
27
|
end
|
28
28
|
# Thundering herd -- all N clients can respond at the same time.
|
29
29
|
# Can be avoided with a queue -- see lock-mgr-with-queue.rb.
|
@@ -34,20 +34,20 @@ Tupelo.application do |app|
|
|
34
34
|
# as all clients follow the read protocol below.
|
35
35
|
sleep 0.2
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
transaction do
|
38
|
+
read ["resource", client_id, nil]
|
39
|
+
write ["c#{client_id}##{j}"]
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
-
|
45
|
+
child passive: true do
|
46
46
|
# This client never even tries to lock the resource, so it cannot write.
|
47
|
-
|
48
|
-
|
49
|
-
|
47
|
+
transaction do
|
48
|
+
read ["resource", client_id, nil]
|
49
|
+
write ["c#{client_id}##{j}"]
|
50
50
|
end
|
51
|
-
|
51
|
+
log.error "should never get here"
|
52
52
|
end
|
53
53
|
end
|
data/example/map-reduce-v2.rb
CHANGED
@@ -3,13 +3,13 @@ require 'tupelo/util/boolean'
|
|
3
3
|
|
4
4
|
N = 2 # how many cpus do you want to use for mappers?
|
5
5
|
|
6
|
-
Tupelo.application do
|
7
|
-
|
6
|
+
Tupelo.application do
|
7
|
+
child do
|
8
8
|
document = "I will not map reduce in class\n" * 10
|
9
9
|
lineno = 0
|
10
10
|
document.each_line do |line|
|
11
11
|
lineno += 1
|
12
|
-
|
12
|
+
write line: line, lineno: lineno
|
13
13
|
# Note that tuples should be small, so if the data is large, the line
|
14
14
|
# should be a reference, not the actual data.
|
15
15
|
# Also, in a complex application you might want to add another
|
@@ -19,13 +19,13 @@ Tupelo.application do |app|
|
|
19
19
|
results = Hash.new(0)
|
20
20
|
lines_remaining = lineno
|
21
21
|
results_remaining = 0
|
22
|
-
result_template =
|
22
|
+
result_template = match_any(
|
23
23
|
{word: String, count: Integer},
|
24
24
|
{lineno: Integer, result_count: Integer}
|
25
25
|
)
|
26
26
|
|
27
27
|
until lines_remaining == 0 and results_remaining == 0 do
|
28
|
-
result =
|
28
|
+
result = take result_template
|
29
29
|
|
30
30
|
if result["word"]
|
31
31
|
results[result["word"]] += result["count"]
|
@@ -38,15 +38,15 @@ Tupelo.application do |app|
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
|
41
|
+
log "results = #{results}"
|
42
42
|
end
|
43
43
|
|
44
44
|
N.times do |i|
|
45
|
-
|
46
|
-
|
45
|
+
child passive: true do
|
46
|
+
log.progname = "mapper #{i}"
|
47
47
|
|
48
48
|
loop do
|
49
|
-
input =
|
49
|
+
input = take line: String, lineno: Integer
|
50
50
|
|
51
51
|
h = Hash.new(0)
|
52
52
|
input["line"].split.each do |word|
|
@@ -54,9 +54,9 @@ Tupelo.application do |app|
|
|
54
54
|
end
|
55
55
|
|
56
56
|
h.each do |word, count|
|
57
|
-
|
57
|
+
write word: word, count: count
|
58
58
|
end
|
59
|
-
|
59
|
+
write lineno: input["lineno"], result_count: h.size
|
60
60
|
end
|
61
61
|
end
|
62
62
|
end
|
data/example/map-reduce.rb
CHANGED
@@ -2,13 +2,13 @@ require 'tupelo/app'
|
|
2
2
|
|
3
3
|
N = 2 # how many cpus do you want to use for mappers?
|
4
4
|
|
5
|
-
Tupelo.application do
|
6
|
-
|
5
|
+
Tupelo.application do
|
6
|
+
child do
|
7
7
|
document = "I will not map reduce in class\n" * 10
|
8
8
|
lineno = 0
|
9
9
|
document.each_line do |line|
|
10
10
|
lineno += 1
|
11
|
-
|
11
|
+
write ["wc input", lineno, line]
|
12
12
|
# Note that tuples should be small, so if the data is large, the line
|
13
13
|
# should be a reference, not the actual data.
|
14
14
|
end
|
@@ -17,9 +17,9 @@ Tupelo.application do |app|
|
|
17
17
|
lines_remaining = lineno
|
18
18
|
results_remaining = 0
|
19
19
|
until lines_remaining == 0 and results_remaining == 0 do
|
20
|
-
event, *a =
|
20
|
+
event, *a = take [/wc (?:output|done)/, nil, nil]
|
21
21
|
# Using a regex is hacky. Better to use an "or" template. See
|
22
|
-
# boolean-match.rb.
|
22
|
+
# boolean-match.rb.
|
23
23
|
# Also, in real use it might be better to use hash tuples rather than
|
24
24
|
# arrays.
|
25
25
|
# See map-reduce-v2.rb.
|
@@ -36,15 +36,15 @@ Tupelo.application do |app|
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
|
39
|
+
log "results = #{results}"
|
40
40
|
end
|
41
41
|
|
42
42
|
N.times do |i|
|
43
|
-
|
44
|
-
|
43
|
+
child passive: true do
|
44
|
+
log.progname = "mapper #{i}"
|
45
45
|
|
46
46
|
loop do
|
47
|
-
_, lineno, line =
|
47
|
+
_, lineno, line = take ["wc input", Integer, String]
|
48
48
|
|
49
49
|
h = Hash.new(0)
|
50
50
|
line.split.each do |word|
|
@@ -52,9 +52,9 @@ Tupelo.application do |app|
|
|
52
52
|
end
|
53
53
|
|
54
54
|
h.each do |word, count|
|
55
|
-
|
55
|
+
write ["wc output", word, count]
|
56
56
|
end
|
57
|
-
|
57
|
+
write ["wc done", lineno, h.size]
|
58
58
|
end
|
59
59
|
end
|
60
60
|
end
|
data/example/matching.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'tupelo/app'
|
2
2
|
|
3
|
-
Tupelo.application do
|
4
|
-
|
5
|
-
|
6
|
-
p
|
7
|
-
p
|
3
|
+
Tupelo.application do
|
4
|
+
local do
|
5
|
+
write_wait ["foo", 42.5]
|
6
|
+
p read_all [/oo/, nil]
|
7
|
+
p read_all [nil, 5..95]
|
8
8
|
end
|
9
9
|
end
|
data/example/message-bus.rb
CHANGED
@@ -24,7 +24,7 @@ Tupelo.application do
|
|
24
24
|
sleep delay
|
25
25
|
ch = channels[ pi % channels.size ]
|
26
26
|
transaction do
|
27
|
-
|
27
|
+
take [ch, nil]
|
28
28
|
write [ch, "pub #{pi} slept for #{delay} sec"]
|
29
29
|
end
|
30
30
|
end
|
@@ -36,8 +36,7 @@ Tupelo.application do
|
|
36
36
|
ch = channels[ si % channels.size ]
|
37
37
|
loop do
|
38
38
|
sleep 1
|
39
|
-
|
40
|
-
log t if t
|
39
|
+
log read [ch, nil] # note asynchronous reads
|
41
40
|
end
|
42
41
|
end
|
43
42
|
end
|
@@ -54,6 +53,10 @@ Tupelo.application do
|
|
54
53
|
end
|
55
54
|
|
56
55
|
local do
|
56
|
+
channels.each do |ch|
|
57
|
+
write [ch, nil]
|
58
|
+
end
|
59
|
+
|
57
60
|
write ['start']
|
58
61
|
end
|
59
62
|
end
|
data/example/notify.rb
CHANGED
@@ -1,35 +1,35 @@
|
|
1
|
-
# See also bin/tspy
|
1
|
+
# See also bin/tspy and the --trace switch on all tupelo apps and examples.
|
2
2
|
|
3
3
|
require 'tupelo/app'
|
4
4
|
|
5
|
-
Tupelo.application do
|
6
|
-
|
5
|
+
Tupelo.application do
|
6
|
+
child do
|
7
7
|
Thread.new do
|
8
|
-
note =
|
9
|
-
|
8
|
+
note = notifier
|
9
|
+
write ["start"]
|
10
10
|
|
11
|
-
|
11
|
+
log "%10s %10s %10s %s" % %w{ status tick client operation }
|
12
12
|
loop do
|
13
13
|
status, global_tick, client_id, op = note.wait
|
14
|
-
|
14
|
+
log "%10s %10d %10d %p" % [status, global_tick, client_id, op]
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
|
18
|
+
take ["finish"]
|
19
19
|
end
|
20
20
|
|
21
|
-
|
22
|
-
|
21
|
+
child do
|
22
|
+
take ["start"]
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
write [1, 2]
|
25
|
+
write [3, 4]
|
26
|
+
write foo: "bar", baz: ["zap"]
|
27
27
|
|
28
|
-
|
29
|
-
x, y =
|
30
|
-
|
28
|
+
transaction do
|
29
|
+
x, y = take [Numeric, Numeric]
|
30
|
+
write [x, y, x + y]
|
31
31
|
end
|
32
32
|
|
33
|
-
|
33
|
+
write ["finish"]
|
34
34
|
end
|
35
35
|
end
|
data/example/optimist.rb
CHANGED
@@ -1,20 +1,20 @@
|
|
1
1
|
require 'tupelo/app'
|
2
2
|
|
3
|
-
Tupelo.application do
|
4
|
-
|
5
|
-
|
3
|
+
Tupelo.application do
|
4
|
+
child do
|
5
|
+
write [1]
|
6
6
|
sleep 0.1
|
7
|
-
|
8
|
-
|
7
|
+
take [1]
|
8
|
+
write [2]
|
9
9
|
end
|
10
10
|
|
11
|
-
|
11
|
+
child do
|
12
12
|
final_i =
|
13
|
-
|
14
|
-
|
13
|
+
take([Integer]) do |optimistic_i|
|
14
|
+
log "optimistic_i = #{optimistic_i}"
|
15
15
|
sleep 0.2
|
16
16
|
end
|
17
17
|
|
18
|
-
|
18
|
+
log "final_i = #{final_i}"
|
19
19
|
end
|
20
20
|
end
|
data/example/parallel.rb
CHANGED
@@ -9,14 +9,18 @@ hosts = ARGV.shift
|
|
9
9
|
map = ARGV.slice!(0,3)
|
10
10
|
reduce = ARGV.slice!(0,4)
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
args_ok = hosts && map[0] == "map" && reduce[0] == "reduce" && reduce[3]
|
13
|
+
|
14
|
+
abort <<END unless args_ok
|
14
15
|
|
15
|
-
usage: #$0 <ssh-host>,... map <var> <expr> reduce <var> <var> <expr>
|
16
|
+
usage: #$0 <ssh-host>,... map <var> <expr> reduce <var> <var> <expr>
|
17
|
+
|
18
|
+
Input is provided on standard input
|
19
|
+
|
20
|
+
Writes the result of the last reduction to standard output.
|
16
21
|
|
17
|
-
|
18
|
-
|
19
|
-
reduction to standard output.
|
22
|
+
If <ssh-host> is of the form host:<number> than <number> processes are
|
23
|
+
started on host.
|
20
24
|
|
21
25
|
If --show-steps is set then intermediate reductions are printed as they
|
22
26
|
are computed. If input is stdin at the terminal, then you can see these
|
@@ -28,12 +32,16 @@ abort <<END unless hosts and
|
|
28
32
|
Example:
|
29
33
|
|
30
34
|
ruby #$0 localhost,localhost map s s.length reduce l1 l2 l1+l2
|
31
|
-
|
35
|
+
|
32
36
|
Use `s.split.length` to get word count instead of char count.
|
33
37
|
|
34
38
|
END
|
35
39
|
|
36
|
-
hosts = hosts.split(",")
|
40
|
+
hosts = hosts.split(",").map do |s|
|
41
|
+
s.slice!(/:(\d+)\z/)
|
42
|
+
$1 ? [s] * Integer($1) : s
|
43
|
+
end
|
44
|
+
hosts.flatten!
|
37
45
|
|
38
46
|
map_str = <<END
|
39
47
|
proc do |#{map[1]}|
|