tupelo 0.9 → 0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +31 -1
  3. data/bugs/read-take.rb +19 -0
  4. data/bugs/take-write.rb +7 -7
  5. data/example/app-and-tup.rb +11 -8
  6. data/example/async-transaction.rb +7 -7
  7. data/example/balance-xfer-locking.rb +13 -13
  8. data/example/balance-xfer-retry.rb +16 -16
  9. data/example/balance-xfer.rb +9 -9
  10. data/example/boolean-match.rb +5 -5
  11. data/example/bounded-retry.rb +9 -9
  12. data/example/broker-locking.rb +14 -14
  13. data/example/broker-optimistic.rb +7 -7
  14. data/example/cancel.rb +7 -7
  15. data/example/concurrent-transactions.rb +17 -17
  16. data/example/custom-class.rb +9 -9
  17. data/example/custom-search.rb +8 -8
  18. data/example/fail-and-retry.rb +11 -11
  19. data/example/hash-tuples.rb +12 -10
  20. data/example/increment.rb +8 -8
  21. data/example/load-balancer.rb +2 -1
  22. data/example/lock-mgr-with-queue.rb +18 -18
  23. data/example/lock-mgr.rb +18 -18
  24. data/example/map-reduce-v2.rb +11 -11
  25. data/example/map-reduce.rb +11 -11
  26. data/example/matching.rb +5 -5
  27. data/example/message-bus.rb +6 -3
  28. data/example/notify.rb +17 -17
  29. data/example/optimist.rb +9 -9
  30. data/example/parallel.rb +16 -8
  31. data/example/pregel/distributed.rb +129 -0
  32. data/example/pregel/pagerank.rb +72 -0
  33. data/example/pregel/pregel.rb +102 -0
  34. data/example/pregel/remote.rb +165 -0
  35. data/example/pulse.rb +10 -10
  36. data/example/read-in-trans.rb +15 -15
  37. data/example/subspace.rb +34 -0
  38. data/example/take-nowait.rb +1 -0
  39. data/example/tcp.rb +3 -3
  40. data/example/timeout-trans.rb +5 -5
  41. data/example/timeout.rb +10 -9
  42. data/example/tiny-client.rb +5 -5
  43. data/example/tiny-server.rb +2 -2
  44. data/example/transaction-logic.rb +16 -16
  45. data/example/wait-interrupt.rb +38 -0
  46. data/example/write-wait.rb +11 -11
  47. data/lib/tupelo/archiver/tuplespace.rb +5 -1
  48. data/lib/tupelo/archiver/worker.rb +25 -18
  49. data/lib/tupelo/client/reader.rb +2 -2
  50. data/lib/tupelo/client/transaction.rb +79 -36
  51. data/lib/tupelo/client/tuplespace.rb +1 -0
  52. data/lib/tupelo/client/worker.rb +107 -13
  53. data/lib/tupelo/client.rb +36 -2
  54. data/lib/tupelo/version.rb +1 -1
  55. data/test/lib/mock-client.rb +4 -0
  56. data/test/lib/testable-worker.rb +1 -1
  57. data/test/stress/concurrent-transactions.rb +15 -15
  58. data/test/system/test-archiver.rb +8 -8
  59. data/test/unit/test-ops.rb +56 -0
  60. metadata +72 -68
  61. data/bugs/write-read.rb +0 -15
  62. data/example/broker-queue.rb +0 -35
  63. data/example/child-of-child.rb +0 -34
@@ -1,29 +1,29 @@
1
1
  require 'tupelo/app'
2
2
 
3
- Tupelo.application do |app|
3
+ Tupelo.application do
4
4
  2.times do
5
- app.child do |client|
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 = client.transaction
9
+ t = transaction
10
10
  r = t.take [Integer]
11
- client.log "trying to take #{r.inspect}"
11
+ log "trying to take #{r.inspect}"
12
12
  t.commit.wait
13
- client.log "took #{r.inspect}"
13
+ log "took #{r.inspect}"
14
14
  rescue Tupelo::Client::TransactionFailure => ex
15
- client.log "#{ex} -- retrying"
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
- app.child do |client|
23
- client.write [1]
24
- client.log "wrote #{[1]}"
22
+ child do
23
+ write [1]
24
+ log "wrote #{[1]}"
25
25
  sleep 0.1
26
- client.write [2]
27
- client.log "wrote #{[2]}"
26
+ write [2]
27
+ log "wrote #{[2]}"
28
28
  end
29
29
  end
@@ -35,19 +35,21 @@
35
35
 
36
36
  require 'tupelo/app'
37
37
 
38
- Tupelo.application do |app|
39
- app.child do |client|
40
- client.write x: 1, y: 2
38
+ Tupelo.application do
39
+ child do
40
+ write x: 1, y: 2
41
41
  end
42
42
 
43
- app.child do |client|
44
- t = client.take x: Numeric, y: Numeric
45
- client.write x: t["x"], y: t["y"], sum: t["x"] + t["y"]
46
- client.log "sum result: #{client.read x: nil, y: nil, sum: nil}"
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
- client.log client.read_all x: nil
50
- client.log client.read_all y: nil
51
- client.log client.read_all x: nil, y: nil, z: nil
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 |app|
5
+ Tupelo.application do
6
6
  N.times do |i|
7
- app.child do |client|
8
- client.transaction do |t|
9
- n, s = t.take [Numeric, String]
7
+ child do
8
+ transaction do
9
+ n, s = take [Numeric, String]
10
10
  #sleep rand # No race conditions here!
11
- t.write [n + 1, s + "\n incremented by client #{i}"]
11
+ write [n + 1, s + "\n incremented by client #{i}"]
12
12
  end
13
13
  end
14
14
  end
15
15
 
16
- app.child do |client|
17
- client.write [0, "started with 0"]
18
- n, s = client.take [N, String]
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
@@ -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 |app|
13
- app.child passive: true do |client| # the lock manager
14
- client.log.progname << " (lock mgr)"
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
- client.take ["request", "resource", nil, nil]
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
- client.write ["resource", client_id, duration]
27
+ write ["resource", client_id, duration]
28
28
  begin
29
- client.take ["done", "resource", client_id], timeout: duration
29
+ take ["done", "resource", client_id], timeout: duration
30
30
  rescue TimeoutError
31
- client.log "forcing client #{client_id} to stop using resource."
31
+ log "forcing client #{client_id} to stop using resource."
32
32
  end
33
- client.take ["resource", client_id, duration]
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
- app.child do |client|
41
- client.write ["request", "resource", client.client_id, 0.5]
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
- client.transaction do |t|
50
- t.read ["resource", client.client_id, nil]
51
- t.write ["c#{client.client_id}##{j}"]
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
- app.child passive: true do |client|
57
+ child passive: true do
58
58
  # This client never even tries to lock the resource, so it cannot write.
59
- client.transaction do |t|
60
- t.read ["resource", client.client_id, nil]
61
- t.write ["c#{client.client_id}##{j}"]
59
+ transaction do
60
+ read ["resource", client_id, nil]
61
+ write ["c#{client_id}##{j}"]
62
62
  end
63
- client.log.error "should never get here"
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 |app|
8
- app.child passive: true do |client| # the lock manager
9
- client.log.progname << " (lock mgr)"
7
+ Tupelo.application do
8
+ child passive: true do # the lock manager
9
+ log.progname << " (lock mgr)"
10
10
  loop do
11
- client.write ["resource", "none"]
12
- lock_id, client_id, duration = client.read ["resource", nil, nil]
11
+ write ["resource", "none"]
12
+ lock_id, client_id, duration = read ["resource", nil, nil]
13
13
  sleep duration
14
- client.take [lock_id, client_id, duration]
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
- app.child do |client|
24
- client.transaction do |t|
25
- t.take ["resource", "none"]
26
- t.write ["resource", client.client_id, 0.5]
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
- client.transaction do |t|
38
- t.read ["resource", client.client_id, nil]
39
- t.write ["c#{client.client_id}##{j}"]
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
- app.child passive: true do |client|
45
+ child passive: true do
46
46
  # This client never even tries to lock the resource, so it cannot write.
47
- client.transaction do |t|
48
- t.read ["resource", client.client_id, nil]
49
- t.write ["c#{client.client_id}##{j}"]
47
+ transaction do
48
+ read ["resource", client_id, nil]
49
+ write ["c#{client_id}##{j}"]
50
50
  end
51
- client.log.error "should never get here"
51
+ log.error "should never get here"
52
52
  end
53
53
  end
@@ -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 |app|
7
- app.child do |client|
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
- client.write line: line, lineno: lineno
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 = client.match_any(
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 = client.take result_template
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
- client.log "results = #{results}"
41
+ log "results = #{results}"
42
42
  end
43
43
 
44
44
  N.times do |i|
45
- app.child passive: true do |client|
46
- client.log.progname = "mapper #{i}"
45
+ child passive: true do
46
+ log.progname = "mapper #{i}"
47
47
 
48
48
  loop do
49
- input = client.take line: String, lineno: Integer
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
- client.write word: word, count: count
57
+ write word: word, count: count
58
58
  end
59
- client.write lineno: input["lineno"], result_count: h.size
59
+ write lineno: input["lineno"], result_count: h.size
60
60
  end
61
61
  end
62
62
  end
@@ -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 |app|
6
- app.child do |client|
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
- client.write ["wc input", lineno, line]
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 = client.take [/wc (?:output|done)/, nil, nil]
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. That's not a standard feature yet.
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
- client.log "results = #{results}"
39
+ log "results = #{results}"
40
40
  end
41
41
 
42
42
  N.times do |i|
43
- app.child passive: true do |client|
44
- client.log.progname = "mapper #{i}"
43
+ child passive: true do
44
+ log.progname = "mapper #{i}"
45
45
 
46
46
  loop do
47
- _, lineno, line = client.take ["wc input", Integer, String]
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
- client.write ["wc output", word, count]
55
+ write ["wc output", word, count]
56
56
  end
57
- client.write ["wc done", lineno, h.size]
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 |app|
4
- app.local do |client|
5
- client.write_wait ["foo", 42.5]
6
- p client.read_all [/oo/, nil]
7
- p client.read_all [nil, 5..95]
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
@@ -24,7 +24,7 @@ Tupelo.application do
24
24
  sleep delay
25
25
  ch = channels[ pi % channels.size ]
26
26
  transaction do
27
- take_nowait [ch, nil]
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
- t = read_nowait [ch, nil]
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 |app|
6
- app.child do |client|
5
+ Tupelo.application do
6
+ child do
7
7
  Thread.new do
8
- note = client.notifier
9
- client.write ["start"]
8
+ note = notifier
9
+ write ["start"]
10
10
 
11
- client.log "%10s %10s %10s %s" % %w{ status tick client operation }
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
- client.log "%10s %10d %10d %p" % [status, global_tick, client_id, op]
14
+ log "%10s %10d %10d %p" % [status, global_tick, client_id, op]
15
15
  end
16
16
  end
17
17
 
18
- client.take ["finish"]
18
+ take ["finish"]
19
19
  end
20
20
 
21
- app.child do |client|
22
- client.take ["start"]
21
+ child do
22
+ take ["start"]
23
23
 
24
- client.write [1, 2]
25
- client.write [3, 4]
26
- client.write foo: "bar", baz: ["zap"]
24
+ write [1, 2]
25
+ write [3, 4]
26
+ write foo: "bar", baz: ["zap"]
27
27
 
28
- client.transaction do |t|
29
- x, y = t.take [Numeric, Numeric]
30
- t.write [x, y, x + y]
28
+ transaction do
29
+ x, y = take [Numeric, Numeric]
30
+ write [x, y, x + y]
31
31
  end
32
32
 
33
- client.write ["finish"]
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 |app|
4
- app.child do |client|
5
- client.write [1]
3
+ Tupelo.application do
4
+ child do
5
+ write [1]
6
6
  sleep 0.1
7
- client.take [1]
8
- client.write [2]
7
+ take [1]
8
+ write [2]
9
9
  end
10
10
 
11
- app.child do |client|
11
+ child do
12
12
  final_i =
13
- client.take([Integer]) do |optimistic_i|
14
- client.log "optimistic_i = #{optimistic_i}"
13
+ take([Integer]) do |optimistic_i|
14
+ log "optimistic_i = #{optimistic_i}"
15
15
  sleep 0.2
16
16
  end
17
17
 
18
- client.log "final_i = #{final_i}"
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
- abort <<END unless hosts and
13
- map[0] == "map" and reduce[0] == "reduce" and reduce[3]
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> [<infile> ...]
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
- Input can be provided on standard input or as the contents of the files
18
- specified in the infile arguments. Writes the result of the last
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]}|