tupelo 0.14 → 0.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +27 -12
- data/bin/tspy +4 -4
- data/bin/tup +28 -18
- data/example/app-and-tup.rb +3 -3
- data/example/broker-queue.rb +35 -0
- data/example/chat/chat-nohistory.rb +2 -2
- data/example/chat/chat.rb +2 -2
- data/example/child-of-child.rb +34 -0
- data/example/fish01.rb +48 -0
- data/example/map-reduce/remote-map-reduce.rb +3 -1
- data/example/pregel/dist-opt.rb +15 -0
- data/example/small.rb +8 -8
- data/example/subspaces/addr-book-v1.rb +106 -0
- data/example/subspaces/sorted-set-space-OLD.rb +130 -0
- data/example/tcp.rb +9 -10
- data/example/tiny-client.rb +4 -4
- data/example/tiny-service.rb +12 -0
- data/lib/tupelo/app/builder.rb +4 -4
- data/lib/tupelo/app.rb +18 -20
- data/lib/tupelo/client/transaction.rb +9 -23
- data/lib/tupelo/client/worker.rb +16 -22
- data/lib/tupelo/client.rb +4 -0
- data/lib/tupelo/version.rb +1 -1
- metadata +96 -91
- data/example/map-reduce/prime-factor-balanced.rb +0 -55
- data/example/tiny-server.rb +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 79bc191b109fc7e11c3200a9e993725299558ead
|
4
|
+
data.tar.gz: 736b92e95728520e46c0516e857a26a2fd74e766
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 33db9c43e57b0405190165d965a09099b842dcc7bab9767ee7ec9f3ac11536de37fd8cc56f97709daa1c4c40c69a4f9055634ef318c90211148856c38a38adb9
|
7
|
+
data.tar.gz: a0b6f65625f735ec0b24bb06a02f07102661bf45b483a3b013ac7cafb4b3fceb27ac10711a7311638d2e305b2a522ea7eea1951739255ce0b31de10ee9bbe338
|
data/README.md
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
**NEWS**: Come hear a talk on Tupelo on December 11 in San Francisco at the [SF Distributed Computing meetup](http://www.meetup.com/San-Francisco-Distributed-Computing/events/153886592/). Abstract: [doc/sfdc.txt](doc/sfdc.md).
|
2
|
-
|
3
1
|
tupelo
|
4
2
|
==
|
5
3
|
|
@@ -25,6 +23,7 @@ Documentation
|
|
25
23
|
|
26
24
|
* [FAQ](doc/faq.md)
|
27
25
|
* [Subspaces](doc/subspace.md)
|
26
|
+
* [Abstract](sfdc.md) and [slides](doc/sfdc.pdf) for San Francisco Distributed Systems meetup
|
28
27
|
|
29
28
|
Getting started
|
30
29
|
==========
|
@@ -78,7 +77,12 @@ Getting started
|
|
78
77
|
|
79
78
|
read_nowait <template>
|
80
79
|
|
81
|
-
|
80
|
+
Note that neither #read nor #read_nowait wait for any previously issued writes to complete. The difference is that #read waits for a match to exist and #read_nowait does not. Compare:
|
81
|
+
|
82
|
+
write [1]; read_nowait [1] # ==> nil, probably
|
83
|
+
write [2]; read [2] # ==> [2]
|
84
|
+
|
85
|
+
Read all tuples matching a template, no waiting (like #read_nowait):
|
82
86
|
|
83
87
|
ra <template>
|
84
88
|
read_all <template>
|
@@ -144,6 +148,17 @@ Getting started
|
|
144
148
|
p t.read_nowait [3] # => nil
|
145
149
|
end
|
146
150
|
|
151
|
+
Be careful about context within the do...end. If you omit the `|t|` block argument, then all operations are automatically scoped to the transaction, rather than the client. The following is equivalent to the previous example:
|
152
|
+
|
153
|
+
client = self # local var that we can use inside the block
|
154
|
+
transaction do
|
155
|
+
write [3]
|
156
|
+
p read [3]
|
157
|
+
p client.read_all
|
158
|
+
take [3]
|
159
|
+
p read_nowait [3]
|
160
|
+
end
|
161
|
+
|
147
162
|
You can timeout a transaction:
|
148
163
|
|
149
164
|
transaction timeout: 1 do
|
@@ -162,11 +177,11 @@ Getting started
|
|
162
177
|
|
163
178
|
4. Run tup with a server file so that two sessions can interact. Do this in two terminals in the same dir:
|
164
179
|
|
165
|
-
$ tup
|
180
|
+
$ tup sv
|
166
181
|
|
167
|
-
(The '
|
182
|
+
(The 'sv' argument names a file that the first instance of tup uses to store information like socket addresses and the second instance uses to connect. The first instance starts the servers as child processes. However, both instances appear in the terminal as interactive shells.)
|
168
183
|
|
169
|
-
To do this on two hosts, copy the
|
184
|
+
To do this on two hosts, copy the sv file and, if necessary, edit its connect_host field.
|
170
185
|
|
171
186
|
5. Look at the examples. You may need to dig a bit to find the gem installation. For example:
|
172
187
|
|
@@ -176,17 +191,17 @@ Getting started
|
|
176
191
|
|
177
192
|
6. Debugging: in addition to the --info switch on all bin and example programs, bin/tspy is also really useful; it shows all tuplespace events in sequence that they occur. For example, run
|
178
193
|
|
179
|
-
$ tspy
|
194
|
+
$ tspy sv
|
180
195
|
|
181
|
-
in another terminal after running `tup
|
196
|
+
in another terminal after running `tup sv`. The output shows the clock tick, sending client, operation, and operation status (success or failure).
|
182
197
|
|
183
198
|
There is also the similar --trace switch that is available to all bin and example programs. This turns on diagnostic output for each transaction. For example:
|
184
199
|
|
185
200
|
```
|
186
201
|
tick cid status operation
|
187
|
-
1 2
|
188
|
-
2 2
|
189
|
-
3 3
|
202
|
+
1 2 write ["x", 1]
|
203
|
+
2 2 write ["y", 2]
|
204
|
+
3 3 take ["x", 1], ["y", 2]
|
190
205
|
```
|
191
206
|
|
192
207
|
The `Tupelo.application` command, provided by `tupelo/app`, is the source of all these options and is available to your programs. It's a kind of lightweight process deployment and control framework; however `Tupelo.application` is not necessary to use tupelo.
|
@@ -303,7 +318,7 @@ Transactions combine operations into a group that take effect at the same instan
|
|
303
318
|
|
304
319
|
However, it may take some time to prepare the transaction. This is true in terms of both real time (clock and process) and logical time (global sequence of operations). Preparing a transaction means finding tuples that match the criteria of the read and take operations. Finding tuples may require searching (locally) for tuples, or waiting for new tuples to be written by others. Also, the transaction may fail even after matching tuples are found (when another process takes tuples of interest). Then the transaction needs to be prepared again. Once prepared, transaction is sent to all clients, where it may either succeed (in all clients) or fail (for the same reason as before--someone else grabbed one of our tuples). If it fails, then the preparation begins again. A transaction guarantees that, when it completes, all the operations were performed on the tuples at the same logical time. It does not guarantee that the world stands still while one process is inside the `transaction {...}` block.
|
305
320
|
|
306
|
-
Transactions are not just about batching up operations into a more efficient package
|
321
|
+
Transactions are not just about batching up operations into a more efficient package. A transaction makes the combined operations execute atomically: the transaction finishes only when all of its operations can be successfully performed. Writes and pulses can always succeed, but takes and reads only succeed if the tuples exist.
|
307
322
|
|
308
323
|
Transactions give you a means of optimistic locking: the transaction proceeds in a way that depends on preconditions. See [example/increment.rb](example/increment.rb) for a very simple example. Not only can you make a transaction depend on the existence of a tuple, you can make the effect of the transaction a function of existing tuples (see [example/transaction-logic.rb](example/transaction-logic.rb) and [example/broker-optimistic.rb](example/broker-optimistic.rb)).
|
309
324
|
|
data/bin/tspy
CHANGED
@@ -3,18 +3,18 @@
|
|
3
3
|
if ARGV.delete("-h") or ARGV.delete("--help")
|
4
4
|
puts <<-END
|
5
5
|
Usage:
|
6
|
-
#$0
|
6
|
+
#$0 services_file
|
7
7
|
|
8
|
-
Connect to the tuplespace specified by the
|
8
|
+
Connect to the tuplespace specified by the services_file and
|
9
9
|
use the notification api to print out all events.
|
10
10
|
|
11
11
|
For example, you can start a tup with
|
12
12
|
|
13
|
-
tup
|
13
|
+
tup sv
|
14
14
|
|
15
15
|
and then in another terminal
|
16
16
|
|
17
|
-
tspy
|
17
|
+
tspy sv
|
18
18
|
|
19
19
|
Options:
|
20
20
|
|
data/bin/tup
CHANGED
@@ -5,21 +5,21 @@ if ARGV.delete("-h") or ARGV.delete("--help")
|
|
5
5
|
Usage:
|
6
6
|
#$0
|
7
7
|
|
8
|
-
#$0
|
9
|
-
#$0
|
8
|
+
#$0 services_file
|
9
|
+
#$0 services_file <script...
|
10
10
|
|
11
|
-
#$0
|
12
|
-
#$0
|
11
|
+
#$0 services_file unix [path]
|
12
|
+
#$0 services_file tcp [host [port]]]
|
13
13
|
|
14
|
-
The first form starts a tuplespace
|
14
|
+
The first form starts a tuplespace service as a child process. Then it
|
15
15
|
enters an interactive session in which the current object is the proxy to
|
16
16
|
that tuplespace. This form is useful for an isolated tuplespace for
|
17
17
|
simple experiments.
|
18
18
|
|
19
|
-
The second form tries to open the
|
20
|
-
first form, it starts a tuplespace
|
21
|
-
address to
|
22
|
-
connects to the referenced tuplespace
|
19
|
+
The second form tries to open the services_file. If it cannot, then as in
|
20
|
+
the first form, it starts a tuplespace service child process and writes its
|
21
|
+
address to services_file. If it can open the services_file, then it simply
|
22
|
+
connects to the referenced tuplespace service. In either case, as in the
|
23
23
|
first form, an interactive session starts. This form (in its two variants)
|
24
24
|
is useful for starting two sessions operating on the same tuplespace.
|
25
25
|
|
@@ -29,13 +29,13 @@ if ARGV.delete("-h") or ARGV.delete("--help")
|
|
29
29
|
transcripts.
|
30
30
|
|
31
31
|
The fourth and fifth forms are like the previous, but can be used to expose
|
32
|
-
the
|
33
|
-
tcp case, the
|
34
|
-
to connect to the
|
32
|
+
the service on more or less public sockets with specified addresses. In the
|
33
|
+
tcp case, the services_file can be copied to other hosts and used with tup
|
34
|
+
to connect to the services (adjust the host references as needed). The
|
35
35
|
default for unix is, as in the first three forms, a path in a tmpdir. The
|
36
36
|
default for tcp is localhost with port 0, and hence a dynamically chosen
|
37
|
-
port. These forms are only for starting a new
|
38
|
-
existing
|
37
|
+
port. These forms are only for starting a new service; connecting to an
|
38
|
+
existing service uses the simpler form "#$0 services_file".
|
39
39
|
|
40
40
|
Options:
|
41
41
|
|
@@ -62,9 +62,10 @@ if ARGV.delete("-h") or ARGV.delete("--help")
|
|
62
62
|
|
63
63
|
--persist-dir DIR
|
64
64
|
load and save tuplespace to DIR
|
65
|
+
(only needs to be set on first tup invocation)
|
65
66
|
|
66
67
|
--use-subspaces
|
67
|
-
enable subspaces for this tupelo
|
68
|
+
enable subspaces for this tupelo service
|
68
69
|
(only needs to be set on first tup invocation)
|
69
70
|
--subscribe TAG,TAG,...
|
70
71
|
subscribe to specified subspaces; use "" for none
|
@@ -86,13 +87,22 @@ if i=argv.index("--subscribe") # default is to subscribe to all
|
|
86
87
|
subscribed_tags = argv.delete_at(i).split(",")
|
87
88
|
end
|
88
89
|
|
89
|
-
|
90
|
-
|
90
|
+
services_file = argv.shift
|
91
|
+
proto = (argv.shift || :unix).to_sym
|
92
|
+
addr = {proto: proto}
|
93
|
+
case proto
|
94
|
+
when :unix
|
95
|
+
addr[:path] = argv.shift
|
96
|
+
when :tcp
|
97
|
+
addr[:bind_host] = argv.shift
|
98
|
+
addr[:port] = argv.shift
|
99
|
+
addr[:port] = Integer(addr[:port]) if addr[:port]
|
100
|
+
end
|
91
101
|
|
92
102
|
Tupelo.application(
|
93
103
|
argv: argv,
|
94
104
|
**tupelo_opts,
|
95
|
-
|
105
|
+
services_file: services_file,
|
96
106
|
seqd_addr: addr,
|
97
107
|
cseqd_addr: addr, # using same addr causes autoincrement of port/filename
|
98
108
|
arcd_addr: addr) do
|
data/example/app-and-tup.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# It's very easy to connect tup to an existing app.
|
2
2
|
# Just run this file, and then do (in another terminal):
|
3
3
|
#
|
4
|
-
# ../bin/tup
|
4
|
+
# ../bin/tup services-nnnn.yaml
|
5
5
|
#
|
6
6
|
# where nnnn is determined by looking in this dir. You can also
|
7
7
|
# set the filename explicitly (first ARGV), rather than let it be generated
|
@@ -17,10 +17,10 @@
|
|
17
17
|
|
18
18
|
require 'tupelo/app'
|
19
19
|
|
20
|
-
filename = "
|
20
|
+
filename = "services-#$$.yaml"
|
21
21
|
puts "run this in another shell: tup #{filename}"
|
22
22
|
|
23
|
-
Tupelo.application
|
23
|
+
Tupelo.application services_file: filename do
|
24
24
|
child do
|
25
25
|
loop do
|
26
26
|
transaction do
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# more like how you would do it in redis, except that the queue is not stored in
|
2
|
+
# the central server, so operations on it are not a bottleneck, FWIW
|
3
|
+
|
4
|
+
require 'tupelo/app'
|
5
|
+
|
6
|
+
N_PLAYERS = 10
|
7
|
+
|
8
|
+
Tupelo.application do
|
9
|
+
N_PLAYERS.times do
|
10
|
+
# sleep rand / 10 # reduce contention -- could also randomize inserts
|
11
|
+
child do
|
12
|
+
me = client_id
|
13
|
+
write name: me
|
14
|
+
|
15
|
+
you = transaction do
|
16
|
+
game = read_nowait(
|
17
|
+
player1: nil,
|
18
|
+
player2: me)
|
19
|
+
break game["player1"] if game
|
20
|
+
|
21
|
+
unless take_nowait name: me
|
22
|
+
raise Tupelo::Client::TransactionFailure
|
23
|
+
end
|
24
|
+
|
25
|
+
you = take(name: nil)["name"]
|
26
|
+
write(
|
27
|
+
player1: me,
|
28
|
+
player2: you)
|
29
|
+
you
|
30
|
+
end
|
31
|
+
|
32
|
+
log "now playing with #{you}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'tupelo/app'
|
4
4
|
|
5
|
-
|
5
|
+
sv = "chat-nohistory.yaml"
|
6
6
|
|
7
7
|
Thread.abort_on_exception = true
|
8
8
|
|
@@ -13,7 +13,7 @@ def display_message msg
|
|
13
13
|
puts "#{from}@#{time_str}> #{line}"
|
14
14
|
end
|
15
15
|
|
16
|
-
Tupelo.tcp_application
|
16
|
+
Tupelo.tcp_application services_file: sv do
|
17
17
|
me = argv.shift
|
18
18
|
|
19
19
|
local do
|
data/example/chat/chat.rb
CHANGED
@@ -15,7 +15,7 @@
|
|
15
15
|
|
16
16
|
require 'tupelo/app'
|
17
17
|
|
18
|
-
|
18
|
+
sv = "chat.yaml"
|
19
19
|
history_period = 60 # seconds -- discard _my_ messages older than this
|
20
20
|
|
21
21
|
Thread.abort_on_exception = true
|
@@ -27,7 +27,7 @@ def display_message msg
|
|
27
27
|
puts "#{from}@#{time_str}> #{line}"
|
28
28
|
end
|
29
29
|
|
30
|
-
Tupelo.tcp_application
|
30
|
+
Tupelo.tcp_application services_file: sv do
|
31
31
|
me = argv.shift
|
32
32
|
|
33
33
|
local do
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'tupelo/app'
|
2
|
+
|
3
|
+
### need a programmatic way to start up clients
|
4
|
+
|
5
|
+
Tupelo.application do |app|
|
6
|
+
|
7
|
+
app.child do ## local still hangs
|
8
|
+
3.times do |i|
|
9
|
+
app.child do
|
10
|
+
write [i]
|
11
|
+
log "wrote #{i}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
3.times do
|
16
|
+
log take [nil]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
__END__
|
22
|
+
|
23
|
+
this hangs sometimes but not always:
|
24
|
+
|
25
|
+
tick cid status operation
|
26
|
+
A: client 3: wrote 0
|
27
|
+
A: client 4: wrote 1
|
28
|
+
1 3 batch write [0]
|
29
|
+
2 4 batch write [1]
|
30
|
+
A: client 2: [0]
|
31
|
+
3 2 atomic take [0]
|
32
|
+
4 2 atomic take [1]
|
33
|
+
A: client 2: [1]
|
34
|
+
A: client 5: wrote 2
|
data/example/fish01.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# This works, but requires a fix-up step.
|
2
|
+
|
3
|
+
require 'tupelo/app'
|
4
|
+
|
5
|
+
Tupelo.application do
|
6
|
+
2.times do
|
7
|
+
child passive: true do
|
8
|
+
loop do
|
9
|
+
fish = nil
|
10
|
+
|
11
|
+
transaction do
|
12
|
+
fish, _ = take([String])
|
13
|
+
n, _ = take_nowait([Integer, fish])
|
14
|
+
if n
|
15
|
+
write [n + 1, fish]
|
16
|
+
else
|
17
|
+
write [1, fish] # another process might also write this, so ...
|
18
|
+
end
|
19
|
+
end
|
20
|
+
### what if both processes die here?
|
21
|
+
transaction do # ... fix up the two tuples.
|
22
|
+
n1, _ = take_nowait [Integer, fish]; abort unless n1
|
23
|
+
n2, _ = take_nowait [Integer, fish]; abort unless n2
|
24
|
+
#log "fixing: #{[n1 + n2, fish]}"
|
25
|
+
write [n1 + n2, fish]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
local do
|
32
|
+
seed = 3
|
33
|
+
srand seed
|
34
|
+
log "seed = #{seed}"
|
35
|
+
|
36
|
+
fishes = %w{ trout marlin char salmon }
|
37
|
+
|
38
|
+
a = fishes * 10
|
39
|
+
a.shuffle!
|
40
|
+
a.each do |fish|
|
41
|
+
write [fish]
|
42
|
+
end
|
43
|
+
|
44
|
+
fishes.each do |fish|
|
45
|
+
log take [10, fish]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -2,12 +2,14 @@
|
|
2
2
|
|
3
3
|
require 'tupelo/app/remote'
|
4
4
|
|
5
|
+
tunnel = !!ARGV.delete("--tunnel")
|
6
|
+
|
5
7
|
hosts = ARGV.shift or abort "usage: #$0 <ssh-hostname>,<ssh-hostname>,..."
|
6
8
|
hosts = hosts.split(",")
|
7
9
|
|
8
10
|
Tupelo.tcp_application do
|
9
11
|
hosts.each do |host|
|
10
|
-
remote host: host, passive: true, eval: %{
|
12
|
+
remote host: host, passive: true, tunnel: tunnel, eval: %{
|
11
13
|
loop do
|
12
14
|
len = take([String])[0].size
|
13
15
|
write [len]
|
data/example/small.rb
CHANGED
@@ -12,18 +12,18 @@ log_level = case
|
|
12
12
|
else Logger::WARN
|
13
13
|
end
|
14
14
|
|
15
|
-
EasyServe.start(
|
15
|
+
EasyServe.start(services_file: "small-services.yaml") do |ez|
|
16
16
|
log = ez.log
|
17
17
|
log.level = log_level
|
18
18
|
log.progname = "parent"
|
19
19
|
|
20
|
-
ez.
|
20
|
+
ez.start_services do
|
21
21
|
arc_to_seq_sock, seq_to_arc_sock = UNIXSocket.pair
|
22
22
|
arc_to_cseq_sock, cseq_to_arc_sock = UNIXSocket.pair
|
23
23
|
|
24
|
-
ez.
|
24
|
+
ez.service :seqd do |sv|
|
25
25
|
require 'funl/message-sequencer'
|
26
|
-
seq = Funl::MessageSequencer.new
|
26
|
+
seq = Funl::MessageSequencer.new sv, seq_to_arc_sock, log: log,
|
27
27
|
blob_type: 'msgpack' # the default
|
28
28
|
#blob_type: 'marshal' # if you need to pass general ruby objects
|
29
29
|
#blob_type: 'yaml' # less general ruby objects, but cross-language
|
@@ -31,15 +31,15 @@ EasyServe.start(servers_file: "small-servers.yaml") do |ez|
|
|
31
31
|
seq.start
|
32
32
|
end
|
33
33
|
|
34
|
-
ez.
|
34
|
+
ez.service :cseqd do |sv|
|
35
35
|
require 'funl/client-sequencer'
|
36
|
-
cseq = Funl::ClientSequencer.new
|
36
|
+
cseq = Funl::ClientSequencer.new sv, cseq_to_arc_sock, log: log
|
37
37
|
cseq.start
|
38
38
|
end
|
39
39
|
|
40
|
-
ez.
|
40
|
+
ez.service :arcd do |sv|
|
41
41
|
require 'tupelo/archiver'
|
42
|
-
arc = Tupelo::Archiver.new
|
42
|
+
arc = Tupelo::Archiver.new sv,
|
43
43
|
seq: arc_to_seq_sock, cseq: arc_to_cseq_sock, log: log
|
44
44
|
arc.start
|
45
45
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
## TODO
|
2
|
+
##
|
3
|
+
## scaling params
|
4
|
+
|
5
|
+
require 'tupelo/app'
|
6
|
+
|
7
|
+
ab_tag = "my address book"
|
8
|
+
ab_sort_field = 1
|
9
|
+
ab_val_field = 2
|
10
|
+
cmd_tag = "#{ab_tag} commands"
|
11
|
+
resp_tag = "#{ab_tag} responses"
|
12
|
+
|
13
|
+
Tupelo.application do
|
14
|
+
local do
|
15
|
+
use_subspaces!
|
16
|
+
|
17
|
+
# Subspace for tuples belonging to the addr book.
|
18
|
+
define_subspace(
|
19
|
+
tag: ab_tag,
|
20
|
+
template: [
|
21
|
+
{value: ab_tag},
|
22
|
+
{type: "string"}, # name <-- ab_sort_field references this field
|
23
|
+
nil # address; can be any object <-- ab_val_field
|
24
|
+
]
|
25
|
+
)
|
26
|
+
|
27
|
+
# Subspace for commands for fetch and delete.
|
28
|
+
# We can't use #read and #take because then the requesting client
|
29
|
+
# would have to subscribe to the ab_tag subspace.
|
30
|
+
define_subspace(
|
31
|
+
tag: cmd_tag,
|
32
|
+
template: [
|
33
|
+
{value: cmd_tag},
|
34
|
+
{type: "string"}, # cmd name
|
35
|
+
{type: "list"} # arguments
|
36
|
+
]
|
37
|
+
)
|
38
|
+
|
39
|
+
# Subspace for responses to commands. Identify the command this is in
|
40
|
+
# response to by copying it (alternately, could use ids).
|
41
|
+
define_subspace(
|
42
|
+
tag: resp_tag,
|
43
|
+
template: [
|
44
|
+
{value: resp_tag},
|
45
|
+
{type: "string"}, # cmd name
|
46
|
+
{type: "list"}, # arguments
|
47
|
+
nil # result of query -- type depends on command
|
48
|
+
]
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
## Could set N_SORTED_SET_SPACE > 1, but lookups are so fast it would
|
53
|
+
## just lead to contention and redundant computation. Redundancy is useful
|
54
|
+
## though.
|
55
|
+
|
56
|
+
# Inserts are just writes, which are handled by Worker and SortedSetSpace,
|
57
|
+
# so this child's app loop only needs to handle special commands: fetch and
|
58
|
+
# delete, which are delegated to the SortedSetSpace.
|
59
|
+
child tuplespace: [SortedSetSpace, ab_tag, ab_sort_field, ab_val_field],
|
60
|
+
subscribe: [ab_tag, cmd_tag], passive: true do
|
61
|
+
loop do
|
62
|
+
transaction do
|
63
|
+
_, cmd, args = take(subspace cmd_tag)
|
64
|
+
|
65
|
+
case cmd
|
66
|
+
when "delete"
|
67
|
+
args.each do |name|
|
68
|
+
take [ab_tag, name, nil]
|
69
|
+
end
|
70
|
+
|
71
|
+
when "fetch"
|
72
|
+
name = args[0]
|
73
|
+
_, _, addr = read [ab_tag, name, nil]
|
74
|
+
write [resp_tag, name, args, addr]
|
75
|
+
|
76
|
+
when "next", "prev"
|
77
|
+
name = args[0]
|
78
|
+
_, name2, addr = read SortedSetTemplate[ab_tag, cmd, name]
|
79
|
+
write [resp_tag, name, args, name2, addr]
|
80
|
+
|
81
|
+
when "first", "last"
|
82
|
+
_, name, addr = read SortedSetTemplate[ab_tag, cmd]
|
83
|
+
write [resp_tag, name, args, name, addr]
|
84
|
+
|
85
|
+
else # maybe write an error message in a tuple
|
86
|
+
log.error "bad command: #{cmd}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
child subscribe: resp_tag do
|
93
|
+
# write some ab entries
|
94
|
+
write [ab_tag, "McFirst, Firsty", "123 W. Crescent Terrace"]
|
95
|
+
write [ab_tag, "Secondismus, Deuce", "456 S. West Way"]
|
96
|
+
|
97
|
+
# make some queries
|
98
|
+
write [cmd_tag, "first", []]
|
99
|
+
*, name, addr = take [resp_tag, "first", [], nil, nil]
|
100
|
+
log "first entry: #{name} => #{addr}"
|
101
|
+
|
102
|
+
write [cmd_tag, "next", [name]]
|
103
|
+
*, name, addr = take [resp_tag, "next", [name], nil, nil]
|
104
|
+
log "next entry: #{name} => #{addr}"
|
105
|
+
end
|
106
|
+
end
|