tupelo 0.9 → 0.10

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.
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]}|