tupelo 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/COPYING +22 -0
- data/README.md +422 -0
- data/Rakefile +77 -0
- data/bench/pipeline.rb +25 -0
- data/bugs/take-write.rb +19 -0
- data/bugs/write-read.rb +15 -0
- data/example/add.rb +19 -0
- data/example/app-and-tup.rb +30 -0
- data/example/async-transaction.rb +16 -0
- data/example/balance-xfer-locking.rb +50 -0
- data/example/balance-xfer-retry.rb +55 -0
- data/example/balance-xfer.rb +33 -0
- data/example/boolean-match.rb +32 -0
- data/example/bounded-retry.rb +35 -0
- data/example/broker-locking.rb +43 -0
- data/example/broker-optimistic-async.rb +33 -0
- data/example/broker-optimistic.rb +41 -0
- data/example/broker-queue.rb +2 -0
- data/example/cancel.rb +17 -0
- data/example/concurrent-transactions.rb +39 -0
- data/example/custom-class.rb +29 -0
- data/example/custom-search.rb +27 -0
- data/example/fail-and-retry.rb +29 -0
- data/example/hash-tuples.rb +53 -0
- data/example/increment.rb +21 -0
- data/example/lock-mgr-with-queue.rb +75 -0
- data/example/lock-mgr.rb +62 -0
- data/example/map-reduce-v2.rb +96 -0
- data/example/map-reduce.rb +77 -0
- data/example/matching.rb +9 -0
- data/example/notify.rb +35 -0
- data/example/optimist.rb +20 -0
- data/example/pulse.rb +24 -0
- data/example/read-in-trans.rb +56 -0
- data/example/small-simplified.rb +18 -0
- data/example/small.rb +76 -0
- data/example/tcp.rb +35 -0
- data/example/timeout-trans.rb +21 -0
- data/example/timeout.rb +27 -0
- data/example/tiny-client.rb +14 -0
- data/example/tiny-server.rb +12 -0
- data/example/transaction-logic.rb +40 -0
- data/example/write-wait.rb +17 -0
- data/lib/tupelo/app.rb +121 -0
- data/lib/tupelo/archiver/tuplespace.rb +68 -0
- data/lib/tupelo/archiver/worker.rb +87 -0
- data/lib/tupelo/archiver.rb +86 -0
- data/lib/tupelo/client/common.rb +10 -0
- data/lib/tupelo/client/reader.rb +124 -0
- data/lib/tupelo/client/transaction.rb +455 -0
- data/lib/tupelo/client/tuplespace.rb +50 -0
- data/lib/tupelo/client/worker.rb +493 -0
- data/lib/tupelo/client.rb +44 -0
- data/lib/tupelo/version.rb +3 -0
- data/test/lib/mock-client.rb +38 -0
- data/test/lib/mock-msg.rb +47 -0
- data/test/lib/mock-queue.rb +42 -0
- data/test/lib/mock-seq.rb +50 -0
- data/test/lib/testable-worker.rb +24 -0
- data/test/stress/concurrent-transactions.rb +42 -0
- data/test/system/test-archiver.rb +35 -0
- data/test/unit/test-mock-queue.rb +93 -0
- data/test/unit/test-mock-seq.rb +39 -0
- data/test/unit/test-ops.rb +222 -0
- metadata +134 -0
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'tupelo/app'
|
2
|
+
|
3
|
+
N = 2 # how many cpus do you want to use for mappers?
|
4
|
+
VERBOSE = ARGV.delete "-v"
|
5
|
+
|
6
|
+
class Tupelo::Client
|
7
|
+
class Or
|
8
|
+
attr_reader :templates
|
9
|
+
|
10
|
+
def initialize worker, templates
|
11
|
+
@templates = templates.map {|template| worker.make_template(template)}
|
12
|
+
end
|
13
|
+
|
14
|
+
def === obj
|
15
|
+
templates.any? {|template| template === obj}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def or *templates
|
20
|
+
Or.new(worker, templates)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Tupelo.application do |app|
|
25
|
+
if VERBOSE
|
26
|
+
app.child do |client| # a debugger client, to see what's happening
|
27
|
+
note = client.notifier
|
28
|
+
puts "%4s %4s %10s %s" % %w{ tick cid status operation }
|
29
|
+
loop do
|
30
|
+
status, tick, cid, op = note.wait
|
31
|
+
unless status == :attempt
|
32
|
+
s = status == :failure ? "FAILED" : ""
|
33
|
+
puts "%4d %4d %10s %p" % [tick, cid, s, op]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
app.child do |client|
|
40
|
+
document = "I will not map reduce in class\n" * 10
|
41
|
+
lineno = 0
|
42
|
+
document.each_line do |line|
|
43
|
+
lineno += 1
|
44
|
+
client.write line: line, lineno: lineno
|
45
|
+
# Note that tuples should be small, so if the data is large, the line
|
46
|
+
# should be a reference, not the actual data.
|
47
|
+
# Also, in a complex application you might want to add another
|
48
|
+
# key/value pair to avoid collisions (owner: ..., for example).
|
49
|
+
end
|
50
|
+
|
51
|
+
results = Hash.new(0)
|
52
|
+
lines_remaining = lineno
|
53
|
+
results_remaining = 0
|
54
|
+
result_template = client.or(
|
55
|
+
{word: String, count: Integer},
|
56
|
+
{lineno: Integer, result_count: Integer}
|
57
|
+
)
|
58
|
+
|
59
|
+
until lines_remaining == 0 and results_remaining == 0 do
|
60
|
+
result = client.take result_template
|
61
|
+
|
62
|
+
if result["word"]
|
63
|
+
results[result["word"]] += result["count"]
|
64
|
+
results_remaining -= 1
|
65
|
+
elsif result["lineno"]
|
66
|
+
lines_remaining -= 1
|
67
|
+
results_remaining += result["result_count"]
|
68
|
+
else
|
69
|
+
log.error "bad keys in result: #{result}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
client.log "DONE. results = #{results}"
|
74
|
+
client.log "Press ^C to exit"
|
75
|
+
end
|
76
|
+
|
77
|
+
N.times do |i|
|
78
|
+
app.child do |client|
|
79
|
+
client.log.progname = "mapper #{i}"
|
80
|
+
|
81
|
+
loop do
|
82
|
+
input = client.take line: String, lineno: Integer
|
83
|
+
|
84
|
+
h = Hash.new(0)
|
85
|
+
input["line"].split.each do |word|
|
86
|
+
h[word] += 1
|
87
|
+
end
|
88
|
+
|
89
|
+
h.each do |word, count|
|
90
|
+
client.write word: word, count: count
|
91
|
+
end
|
92
|
+
client.write lineno: input["lineno"], result_count: h.size
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'tupelo/app'
|
2
|
+
|
3
|
+
N = 2 # how many cpus do you want to use for mappers?
|
4
|
+
VERBOSE = ARGV.delete "-v"
|
5
|
+
|
6
|
+
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
|
+
app.child do |client|
|
22
|
+
document = "I will not map reduce in class\n" * 10
|
23
|
+
lineno = 0
|
24
|
+
document.each_line do |line|
|
25
|
+
lineno += 1
|
26
|
+
client.write ["wc input", lineno, line]
|
27
|
+
# Note that tuples should be small, so if the data is large, the line
|
28
|
+
# should be a reference, not the actual data.
|
29
|
+
end
|
30
|
+
|
31
|
+
results = Hash.new(0)
|
32
|
+
lines_remaining = lineno
|
33
|
+
results_remaining = 0
|
34
|
+
until lines_remaining == 0 and results_remaining == 0 do
|
35
|
+
event, *a = client.take [/wc (?:output|done)/, nil, nil]
|
36
|
+
# Using a regex is hacky. Better to use an "or" template. See
|
37
|
+
# boolean-match.rb. That's not a standard feature yet.
|
38
|
+
# Also, in real use it might be better to use hash tuples rather than
|
39
|
+
# arrays.
|
40
|
+
# See map-reduce-v2.rb.
|
41
|
+
|
42
|
+
case event
|
43
|
+
when "wc output"
|
44
|
+
word, count = a
|
45
|
+
results[word] += count
|
46
|
+
results_remaining -= 1
|
47
|
+
when "wc done"
|
48
|
+
lineno, result_count = a
|
49
|
+
lines_remaining -= 1
|
50
|
+
results_remaining += result_count
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
client.log "DONE. results = #{results}"
|
55
|
+
client.log "Press ^C to exit"
|
56
|
+
end
|
57
|
+
|
58
|
+
N.times do |i|
|
59
|
+
app.child do |client|
|
60
|
+
client.log.progname = "mapper #{i}"
|
61
|
+
|
62
|
+
loop do
|
63
|
+
_, lineno, line = client.take ["wc input", Integer, String]
|
64
|
+
|
65
|
+
h = Hash.new(0)
|
66
|
+
line.split.each do |word|
|
67
|
+
h[word] += 1
|
68
|
+
end
|
69
|
+
|
70
|
+
h.each do |word, count|
|
71
|
+
client.write ["wc output", word, count]
|
72
|
+
end
|
73
|
+
client.write ["wc done", lineno, h.size]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/example/matching.rb
ADDED
data/example/notify.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# See also bin/tspy
|
2
|
+
|
3
|
+
require 'tupelo/app'
|
4
|
+
|
5
|
+
Tupelo.application do |app|
|
6
|
+
app.child do |client|
|
7
|
+
Thread.new do
|
8
|
+
note = client.notifier
|
9
|
+
client.write ["start"]
|
10
|
+
|
11
|
+
client.log "%10s %10s %10s %s" % %w{ status tick client operation }
|
12
|
+
loop do
|
13
|
+
status, global_tick, client_id, op = note.wait
|
14
|
+
client.log "%10s %10d %10d %p" % [status, global_tick, client_id, op]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
client.take ["finish"]
|
19
|
+
end
|
20
|
+
|
21
|
+
app.child do |client|
|
22
|
+
client.take ["start"]
|
23
|
+
|
24
|
+
client.write [1, 2]
|
25
|
+
client.write [3, 4]
|
26
|
+
client.write foo: "bar", baz: ["zap"]
|
27
|
+
|
28
|
+
client.transaction do |t|
|
29
|
+
x, y = t.take [Numeric, Numeric]
|
30
|
+
t.write [x, y, x + y]
|
31
|
+
end
|
32
|
+
|
33
|
+
client.write ["finish"]
|
34
|
+
end
|
35
|
+
end
|
data/example/optimist.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'tupelo/app'
|
2
|
+
|
3
|
+
Tupelo.application do |app|
|
4
|
+
app.child do |client|
|
5
|
+
client.write [1]
|
6
|
+
sleep 0.1
|
7
|
+
client.take [1]
|
8
|
+
client.write [2]
|
9
|
+
end
|
10
|
+
|
11
|
+
app.child do |client|
|
12
|
+
final_i =
|
13
|
+
client.take([Integer]) do |optimistic_i|
|
14
|
+
client.log "optimistic_i = #{optimistic_i}"
|
15
|
+
sleep 0.2
|
16
|
+
end
|
17
|
+
|
18
|
+
client.log "final_i = #{final_i}"
|
19
|
+
end
|
20
|
+
end
|
data/example/pulse.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'tupelo/app'
|
2
|
+
|
3
|
+
Tupelo.application do |app|
|
4
|
+
app.child do |client|
|
5
|
+
client.take ['start']
|
6
|
+
10.times do |i|
|
7
|
+
client.pulse [i]
|
8
|
+
sleep 0.1
|
9
|
+
end
|
10
|
+
client.write ['finish']
|
11
|
+
end
|
12
|
+
|
13
|
+
app.child do |client|
|
14
|
+
Thread.new do
|
15
|
+
loop do
|
16
|
+
client.log client.read [Integer]
|
17
|
+
client.log client.read_all [Integer]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
client.write ['start']
|
22
|
+
client.take ['finish']
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'tupelo/app'
|
2
|
+
|
3
|
+
Thread.abort_on_exception = true
|
4
|
+
|
5
|
+
N = 5
|
6
|
+
|
7
|
+
Tupelo.application do |app|
|
8
|
+
N.times do |i|
|
9
|
+
app.child do |client|
|
10
|
+
Thread.new do
|
11
|
+
step = 0
|
12
|
+
loop do
|
13
|
+
step += 1
|
14
|
+
client.transaction do |t|
|
15
|
+
t.read ["enabled", i]
|
16
|
+
t.write ["output", i, step]
|
17
|
+
end
|
18
|
+
sleep 0.2
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
client.read ["done"]
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
app.child do |client|
|
28
|
+
t = Thread.new do
|
29
|
+
loop do
|
30
|
+
msg, i, step = client.take [nil, nil, nil]
|
31
|
+
printf "%20s from %2d at step %3d\n", msg, i, step
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
puts "Turning on 0 and 4"
|
36
|
+
client.write ["enabled", 0]
|
37
|
+
client.write ["enabled", 4]
|
38
|
+
sleep 2
|
39
|
+
|
40
|
+
puts "Turning off 0"
|
41
|
+
client.take ["enabled", 0]
|
42
|
+
sleep 2
|
43
|
+
|
44
|
+
puts "Turning off 4"
|
45
|
+
client.take ["enabled", 4]
|
46
|
+
sleep 2
|
47
|
+
|
48
|
+
puts "Turning on 1 and 3"
|
49
|
+
client.write ["enabled", 1]
|
50
|
+
client.write ["enabled", 3]
|
51
|
+
sleep 2
|
52
|
+
|
53
|
+
puts "Bye!"
|
54
|
+
client.write ["done"]
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Same as small.rb, but with the generic setup code factored out.
|
2
|
+
|
3
|
+
require 'tupelo/app'
|
4
|
+
|
5
|
+
Tupelo.application do |app|
|
6
|
+
app.child do |client|
|
7
|
+
client.write [2, 3, "frogs"]
|
8
|
+
_, s = client.take ["animals", nil]
|
9
|
+
puts s
|
10
|
+
end
|
11
|
+
|
12
|
+
app.child do |client|
|
13
|
+
x, y, s = client.take [Numeric, Numeric, String]
|
14
|
+
s2 = ([s] * (x + y)).join(" ")
|
15
|
+
client.write ["animals", s2]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
data/example/small.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# An example of using EasyServe directly (the hard way) rather than tupelo/app.
|
2
|
+
# See small-simplified.rb for the difference.
|
3
|
+
|
4
|
+
require 'easy-serve'
|
5
|
+
|
6
|
+
log_level = case
|
7
|
+
when ARGV.delete("--debug"); Logger::DEBUG
|
8
|
+
when ARGV.delete("--info"); Logger::INFO
|
9
|
+
when ARGV.delete("--warn"); Logger::WARN
|
10
|
+
when ARGV.delete("--error"); Logger::ERROR
|
11
|
+
when ARGV.delete("--fatal"); Logger::FATAL
|
12
|
+
else Logger::WARN
|
13
|
+
end
|
14
|
+
|
15
|
+
EasyServe.start(servers_file: "small-servers.yaml") do |ez|
|
16
|
+
log = ez.log
|
17
|
+
log.level = log_level
|
18
|
+
log.progname = "parent"
|
19
|
+
|
20
|
+
ez.start_servers do
|
21
|
+
arc_to_seq_sock, seq_to_arc_sock = UNIXSocket.pair
|
22
|
+
arc_to_cseq_sock, cseq_to_arc_sock = UNIXSocket.pair
|
23
|
+
|
24
|
+
ez.server :seqd do |svr|
|
25
|
+
require 'funl/message-sequencer'
|
26
|
+
seq = Funl::MessageSequencer.new svr, seq_to_arc_sock, log: log,
|
27
|
+
blob_type: 'msgpack' # the default
|
28
|
+
#blob_type: 'marshal' # if you need to pass general ruby objects
|
29
|
+
#blob_type: 'yaml' # less general ruby objects, but cross-language
|
30
|
+
#blob_type: 'json' # more portable than yaml, but more restrictive
|
31
|
+
seq.start
|
32
|
+
end
|
33
|
+
|
34
|
+
ez.server :cseqd do |svr|
|
35
|
+
require 'funl/client-sequencer'
|
36
|
+
cseq = Funl::ClientSequencer.new svr, cseq_to_arc_sock, log: log
|
37
|
+
cseq.start
|
38
|
+
end
|
39
|
+
|
40
|
+
ez.server :arcd do |svr|
|
41
|
+
require 'tupelo/archiver'
|
42
|
+
arc = Archiver.new svr, seq: arc_to_seq_sock, cseq: arc_to_cseq_sock,
|
43
|
+
log: log
|
44
|
+
arc.start
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def run_client opts
|
49
|
+
log = opts[:log]
|
50
|
+
log.progname = "client <starting in #{log.progname}>"
|
51
|
+
require 'tupelo/client'
|
52
|
+
client = Client.new opts
|
53
|
+
client.start do
|
54
|
+
log.progname = "client #{client.client_id}"
|
55
|
+
end
|
56
|
+
yield client
|
57
|
+
ensure
|
58
|
+
client.stop if client # gracefully exit the tuplespace management thread
|
59
|
+
end
|
60
|
+
|
61
|
+
ez.client :seqd, :cseqd, :arcd do |seqd, cseqd, arcd|
|
62
|
+
run_client seq: seqd, cseq: cseqd, arc: arcd, log: log do |client|
|
63
|
+
client.write [2, 3, "frogs"]
|
64
|
+
_, s = client.take ["animals", nil]
|
65
|
+
puts s
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
ez.client :seqd, :cseqd, :arcd do |seqd, cseqd, arcd|
|
70
|
+
run_client seq: seqd, cseq: cseqd, arc: arcd, log: log do |client|
|
71
|
+
x, y, s = client.take [Numeric, Numeric, String]
|
72
|
+
s2 = ([s] * (x + y)).join(" ")
|
73
|
+
client.write ["animals", s2]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/example/tcp.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Most examples in this dir use unix sockets for simplicity. With a little
|
2
|
+
# more effort, you can switch to tcp for remote access.
|
3
|
+
#
|
4
|
+
# Run this file like:
|
5
|
+
#
|
6
|
+
# ruby tcp.rb --info
|
7
|
+
#
|
8
|
+
# You can test with a local client:
|
9
|
+
#
|
10
|
+
# ../bin/tup tcp.yaml
|
11
|
+
#
|
12
|
+
# Copy tcp.yaml to a remote host, and in the remote copy edit the
|
13
|
+
# addr field to a hostname (or ip addr) isntead of 0.0.0.0.
|
14
|
+
#
|
15
|
+
# Then run a client like this:
|
16
|
+
#
|
17
|
+
# bin/tup remote-copy-of-tcp.yaml
|
18
|
+
|
19
|
+
require 'tupelo/app'
|
20
|
+
|
21
|
+
svr = "tcp.yaml" # copy this file to remote clients, setting host as needed
|
22
|
+
port = 9901 # Use 0 to let system choose free port
|
23
|
+
|
24
|
+
Tupelo.application servers_file: svr,
|
25
|
+
seqd_addr: [:tcp, '0.0.0.0', port],
|
26
|
+
cseqd_addr: [:tcp, '0.0.0.0', port + 1],
|
27
|
+
arcd_addr: [:tcp, '0.0.0.0', port + 2] do |app|
|
28
|
+
if app.owns_servers
|
29
|
+
puts "server started; ^C to stop"
|
30
|
+
puts "(run with --info to see events)" if app.log.level > Logger::INFO
|
31
|
+
sleep
|
32
|
+
else
|
33
|
+
abort "server seems to be running already; check file #{svr.inspect}"
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Use tupelo's scheduler to time out a transaction. See also timeout.rb.
|
2
|
+
# This has the advantage of using just one extra thread for all timeouts,
|
3
|
+
# rather than one thread per timeout.
|
4
|
+
|
5
|
+
require 'tupelo/app'
|
6
|
+
|
7
|
+
Tupelo.application do |app|
|
8
|
+
app.child do |client|
|
9
|
+
result =
|
10
|
+
begin
|
11
|
+
client.transaction timeout: 1 do |t|
|
12
|
+
t.take ["foo"]
|
13
|
+
end
|
14
|
+
rescue TimeoutError => ex
|
15
|
+
ex
|
16
|
+
end
|
17
|
+
client.log "result = #{result.inspect}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
data/example/timeout.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# Use ruby's timeout lib to time out a read. For transactions there is also
|
2
|
+
# the timeout parameter (which is more efficient). See timeout-trans.rb.
|
3
|
+
|
4
|
+
require 'tupelo/app'
|
5
|
+
|
6
|
+
Tupelo.application do |app|
|
7
|
+
app.child do |client|
|
8
|
+
begin
|
9
|
+
n_sec = 2
|
10
|
+
Timeout.timeout n_sec do
|
11
|
+
client.log "waiting for non-existing tuple #{[0]}"
|
12
|
+
client.read [0]
|
13
|
+
end
|
14
|
+
rescue TimeoutError
|
15
|
+
end
|
16
|
+
|
17
|
+
r = client.read [1]
|
18
|
+
client.log "got #{r}"
|
19
|
+
end
|
20
|
+
|
21
|
+
app.child do |client|
|
22
|
+
sleep 1
|
23
|
+
client.log "writing [1]"
|
24
|
+
client.write [1]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'tupelo/app'
|
2
|
+
|
3
|
+
svr = "tiny-server.yaml"
|
4
|
+
|
5
|
+
Tupelo.application servers_file: svr do |app|
|
6
|
+
if app.owns_servers
|
7
|
+
abort "server not running"
|
8
|
+
end
|
9
|
+
|
10
|
+
app.child do |client|
|
11
|
+
client.write ["Hello", "world!"]
|
12
|
+
p client.take [nil, nil]
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'tupelo/app'
|
2
|
+
|
3
|
+
Tupelo.application do |app|
|
4
|
+
app.child do |client|
|
5
|
+
done = false
|
6
|
+
until done do
|
7
|
+
client.transaction do |t|
|
8
|
+
op, x, y = t.take [String, Numeric, Numeric]
|
9
|
+
# further operations within this transaction can depend on the above.
|
10
|
+
|
11
|
+
case op
|
12
|
+
when "+"
|
13
|
+
t.write [op, x, y, x + y]
|
14
|
+
when "*"
|
15
|
+
t.write [op, x, y, x * y]
|
16
|
+
when "select"
|
17
|
+
_, _, _, z = t.take [nil, nil, nil, x..y]
|
18
|
+
t.write [op, x, y, z]
|
19
|
+
when "stop"
|
20
|
+
done = true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
app.child do |client|
|
27
|
+
client.write ["+", 1, 2]
|
28
|
+
results = client.read ["+", 1, 2, nil]
|
29
|
+
p results
|
30
|
+
|
31
|
+
client.write ["*", 3, 4]
|
32
|
+
results = client.read ["*", 3, 4, nil]
|
33
|
+
p results
|
34
|
+
|
35
|
+
client.write ["select", 10, 20]
|
36
|
+
p client.read ["select", 10, 20, nil]
|
37
|
+
|
38
|
+
client.write ["stop", 0, 0]
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'tupelo/app'
|
2
|
+
|
3
|
+
Tupelo.application do |app|
|
4
|
+
app.local do |client|
|
5
|
+
client.write [1]
|
6
|
+
client.write [2]
|
7
|
+
w = client.write [3]
|
8
|
+
p client.read_all [nil]
|
9
|
+
w.wait # wait for the write to come back and be applied to the client
|
10
|
+
p client.read_all [nil]
|
11
|
+
|
12
|
+
client.write [4]
|
13
|
+
client.write [5]
|
14
|
+
client.write_wait [6]
|
15
|
+
p client.read_all [nil]
|
16
|
+
end
|
17
|
+
end
|