tupelo 0.13 → 0.14
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.
- checksums.yaml +4 -4
- data/example/map-reduce/prime-factor-balanced.rb +55 -0
- data/example/map-reduce/prime-factor.rb +39 -0
- data/example/multi-tier/kvspace.rb +2 -2
- data/example/subspaces/addr-book.rb +145 -0
- data/example/subspaces/ramp.rb +146 -0
- data/example/subspaces/sorted-set-space.rb +63 -32
- data/example/uniq-id.rb +34 -0
- data/lib/tupelo/client/transaction.rb +7 -2
- data/lib/tupelo/client/worker.rb +32 -9
- data/lib/tupelo/client.rb +7 -1
- data/lib/tupelo/version.rb +1 -1
- metadata +91 -91
- data/example/broker-queue.rb +0 -35
- data/example/child-of-child.rb +0 -34
- data/example/fish01.rb +0 -48
- data/example/pregel/dist-opt.rb +0 -15
- data/example/subspaces/addr-book-v1.rb +0 -106
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f4eb65df5eb526b3a885d5f0ce41736bb3ccc216
|
4
|
+
data.tar.gz: 96f7e2b11e86b299af62b6598081564e98ceb159
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd91077b980c38858cb3b4549cec5df43ccb91a58bc209c026fe9db23c9ec6a90486c1cecd8f92c564d420fa26ef00cda93fa6b92e6e9af171d9abd23af78717
|
7
|
+
data.tar.gz: b5de5df9f3e7baf6a02a4440b05e0cbb9a3fa04d37d5e46db3e0950ff5d59e29c23ac49809a9810f94fdd1a54916c47c98ce05a651167e72c9cfe8d60bd227ea
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Factor numbers using remote hosts. Run with --trace to see contention.
|
2
|
+
# This is more "map" than "map-reduce", though you could aggregate the
|
3
|
+
# factored numbers, such as by finding the largest prime factor.
|
4
|
+
|
5
|
+
require 'tupelo/app/remote'
|
6
|
+
|
7
|
+
hosts = ARGV.shift or abort "usage: #$0 <ssh-hostname>,<ssh-hostname>,..."
|
8
|
+
hosts = hosts.split(",")
|
9
|
+
|
10
|
+
Tupelo.tcp_application do
|
11
|
+
hosts.each_with_index do |host, hi|
|
12
|
+
remote host: host, passive: true, eval: %{
|
13
|
+
require 'prime' # ruby stdlib for prime factorization
|
14
|
+
class M
|
15
|
+
def initialize nh, hi
|
16
|
+
@nh, @hi = nh, hi
|
17
|
+
end
|
18
|
+
def === x
|
19
|
+
Array === x and
|
20
|
+
x[0] == "input" and
|
21
|
+
x[1] % @nh == @hi
|
22
|
+
end
|
23
|
+
end
|
24
|
+
my_pref = M.new(#{hosts.size}, #{hi})
|
25
|
+
loop do
|
26
|
+
_, input =
|
27
|
+
begin
|
28
|
+
take(my_pref, timeout: 1.0) # fewer fails (5.0 -> none at all)
|
29
|
+
rescue TimeoutError
|
30
|
+
take(["input", Integer])
|
31
|
+
end
|
32
|
+
write ["output", input, input.prime_division]
|
33
|
+
end
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
local do
|
38
|
+
t0 = Time.now
|
39
|
+
inputs = 1_000_000_000_000 .. 1_000_000_000_050
|
40
|
+
|
41
|
+
inputs.each do |input|
|
42
|
+
write ["input", input]
|
43
|
+
end
|
44
|
+
|
45
|
+
inputs.size.times do
|
46
|
+
_, input, outputs = take ["output", Integer, nil]
|
47
|
+
output_str = outputs.map {|prime, exp|
|
48
|
+
exp == 1 ? prime : "#{prime}**#{exp}"}.join(" * ")
|
49
|
+
log "#{input} == #{output_str}"
|
50
|
+
end
|
51
|
+
|
52
|
+
t1 = Time.now
|
53
|
+
log "elapsed: %6.2f seconds" % (t1-t0)
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Factor numbers using remote hosts. Run with --trace to see contention.
|
2
|
+
# This is more "map" than "map-reduce", though you could aggregate the
|
3
|
+
# factored numbers, such as by finding the largest prime factor.
|
4
|
+
|
5
|
+
require 'tupelo/app/remote'
|
6
|
+
|
7
|
+
hosts = ARGV.shift or abort "usage: #$0 <ssh-hostname>,<ssh-hostname>,..."
|
8
|
+
hosts = hosts.split(",")
|
9
|
+
|
10
|
+
Tupelo.tcp_application do
|
11
|
+
hosts.each do |host|
|
12
|
+
remote host: host, passive: true, eval: %{
|
13
|
+
require 'prime' # ruby stdlib for prime factorization
|
14
|
+
loop do
|
15
|
+
_, input = take(["input", Integer])
|
16
|
+
write ["output", input, input.prime_division]
|
17
|
+
end
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
local do
|
22
|
+
t0 = Time.now
|
23
|
+
inputs = 1_000_000_000_000 .. 1_000_000_000_050
|
24
|
+
|
25
|
+
inputs.each do |input|
|
26
|
+
write ["input", input]
|
27
|
+
end
|
28
|
+
|
29
|
+
inputs.size.times do
|
30
|
+
_, input, outputs = take ["output", Integer, nil]
|
31
|
+
output_str = outputs.map {|prime, exp|
|
32
|
+
exp == 1 ? prime : "#{prime}**#{exp}"}.join(" * ")
|
33
|
+
log "#{input} == #{output_str}"
|
34
|
+
end
|
35
|
+
|
36
|
+
t1 = Time.now
|
37
|
+
log "elapsed: %6.2f seconds" % (t1-t0)
|
38
|
+
end
|
39
|
+
end
|
@@ -32,7 +32,7 @@ class KVSpace
|
|
32
32
|
def each
|
33
33
|
hash.each do |k, vs|
|
34
34
|
vs.each do |v|
|
35
|
-
yield tag, k, v
|
35
|
+
yield [tag, k, v]
|
36
36
|
end
|
37
37
|
end
|
38
38
|
metas.each do |tuple|
|
@@ -67,7 +67,7 @@ class KVSpace
|
|
67
67
|
|
68
68
|
else
|
69
69
|
if i=metas.index(tuple)
|
70
|
-
delete_at i
|
70
|
+
metas.delete_at i
|
71
71
|
end
|
72
72
|
end
|
73
73
|
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# Example of attaching a data structure to a subspace. In this case, we
|
2
|
+
# use an in-memory structure, a red-black tree, to maintain the tuples in
|
3
|
+
# sorted order. (For a simpler example, with a hash instead of a tree,
|
4
|
+
# see [memo example using subspaces](../multi-tier/memo2.rb). The process(es)
|
5
|
+
# that manages the rbtree needs to subscribe to this subspace, so it can
|
6
|
+
# apply writes to the rbtree.
|
7
|
+
#
|
8
|
+
# We also have subspaces for query commands and responses so that other clients
|
9
|
+
# can access the sorted structure. The process(es) that host the rbtree also
|
10
|
+
# subscribe to the command subspace (and write to, but not subscribe to, the
|
11
|
+
# response subspace.) The process that query
|
12
|
+
# do so by writing to the command subspace and subscribing to the response
|
13
|
+
# subspace.
|
14
|
+
#
|
15
|
+
# This is kinda like redis, but the data is distributed, not stored on the same
|
16
|
+
# process that is managing concurrency. Multiple replicas increase concurrency.
|
17
|
+
# Run this example with --show-handlers to see which replicas are responding.
|
18
|
+
#
|
19
|
+
# Note that a subspace can be sharded to different clients, and different
|
20
|
+
# clients can each use their own data structure for these tuples.
|
21
|
+
|
22
|
+
require 'tupelo/app'
|
23
|
+
require_relative 'sorted-set-space'
|
24
|
+
|
25
|
+
SHOW_HANDLERS = ARGV.delete("--show-handlers")
|
26
|
+
|
27
|
+
N_REPLICAS = 3
|
28
|
+
|
29
|
+
ab_tag = "my address book"
|
30
|
+
cmd_tag = "#{ab_tag} commands"
|
31
|
+
resp_tag = "#{ab_tag} responses"
|
32
|
+
|
33
|
+
Tupelo.application do
|
34
|
+
local do
|
35
|
+
use_subspaces!
|
36
|
+
|
37
|
+
# Subspace for tuples belonging to the addr book.
|
38
|
+
define_subspace(
|
39
|
+
tag: ab_tag,
|
40
|
+
template: [
|
41
|
+
{value: ab_tag},
|
42
|
+
{type: "string"}, # name
|
43
|
+
nil # address; can be any object
|
44
|
+
]
|
45
|
+
)
|
46
|
+
|
47
|
+
# Subspace for commands for fetch, delete, first, last, prev, next.
|
48
|
+
# We can't use #read and #take for fetch and delete because then the
|
49
|
+
# requesting client would have to subscribe to the ab_tag subspace.
|
50
|
+
define_subspace(
|
51
|
+
tag: cmd_tag,
|
52
|
+
template: [
|
53
|
+
{value: cmd_tag},
|
54
|
+
nil, # request id, such as [client_id, uniq_id]
|
55
|
+
{type: "string"}, # cmd name
|
56
|
+
{type: "list"} # arguments
|
57
|
+
]
|
58
|
+
)
|
59
|
+
|
60
|
+
# Subspace for responses to commands. A response identifies the command
|
61
|
+
# it is responding to in two ways: by copying it and by an id. The
|
62
|
+
# former is so that another client can "spy" on one client's query
|
63
|
+
# responses, perhaps saving effort. The latter is to distinguish between
|
64
|
+
# iterations of the same command (first, first, ...).
|
65
|
+
define_subspace(
|
66
|
+
tag: resp_tag,
|
67
|
+
template: [
|
68
|
+
{value: resp_tag},
|
69
|
+
nil, # in response to this request id
|
70
|
+
{type: "string"}, # cmd name
|
71
|
+
{type: "list"}, # arguments
|
72
|
+
nil, # result of query -- type depends on command
|
73
|
+
]
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
N_REPLICAS.times do |i|
|
78
|
+
# Inserts are just writes, which are handled by Worker and SortedSetSpace,
|
79
|
+
# so this child's app loop only needs to handle the special commands.
|
80
|
+
child tuplespace: [SortedSetSpace, ab_tag],
|
81
|
+
subscribe: [ab_tag, cmd_tag], passive: true do
|
82
|
+
|
83
|
+
log.progname = "replica ##{i}"
|
84
|
+
|
85
|
+
loop do
|
86
|
+
_, rqid, cmd, args = take(subspace cmd_tag)
|
87
|
+
if SHOW_HANDLERS
|
88
|
+
log "handling request for #{cmd} #{args}"
|
89
|
+
end
|
90
|
+
|
91
|
+
case cmd
|
92
|
+
when "delete" # handled by one replica
|
93
|
+
args.each do |name|
|
94
|
+
take [ab_tag, name, nil] # propagates to all replicas
|
95
|
+
end
|
96
|
+
|
97
|
+
when "fetch"
|
98
|
+
_, _, addr = read_nowait [ab_tag, args[0], nil] # addr might be nil
|
99
|
+
write [resp_tag, rqid, cmd, args, addr]
|
100
|
+
|
101
|
+
when "next", "prev", "first", "last"
|
102
|
+
_, name, addr = read_nowait SortedSetTemplate[ab_tag, cmd, *args]
|
103
|
+
write [resp_tag, rqid, cmd, args, [name, addr]]
|
104
|
+
|
105
|
+
else # maybe write an error message in a tuple
|
106
|
+
log.error "bad command: #{cmd}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
child subscribe: resp_tag do
|
113
|
+
log.progname = "user agent"
|
114
|
+
|
115
|
+
counter = 0 # this is a bit hacky -- could use prev txn's global tick
|
116
|
+
next_rqid = proc { [client_id, counter+=1] }
|
117
|
+
# Protect this with a mutex if other threads need it.
|
118
|
+
|
119
|
+
# write some ab entries
|
120
|
+
write [ab_tag, "Eliza", "100 E St."]
|
121
|
+
write [ab_tag, "Alice", "100 A St."]
|
122
|
+
write [ab_tag, "Daisy", "100 D St."]
|
123
|
+
write [ab_tag, "Bob", "100 B St."]
|
124
|
+
write [ab_tag, "Charles", "100 C St."]
|
125
|
+
|
126
|
+
# make some queries
|
127
|
+
rqid = next_rqid.call
|
128
|
+
name = "Daisy"
|
129
|
+
write [cmd_tag, rqid, "fetch", [name]]
|
130
|
+
addr = take( [resp_tag, rqid, nil, nil, nil] ).last
|
131
|
+
log "found: #{name} => #{addr}"
|
132
|
+
|
133
|
+
rqid = next_rqid.call
|
134
|
+
write [cmd_tag, rqid, "first", []]
|
135
|
+
name, addr = take( [resp_tag, rqid, nil, nil, nil] ).last
|
136
|
+
log "first entry: #{name} => #{addr}"
|
137
|
+
|
138
|
+
5.times do
|
139
|
+
rqid = next_rqid.call
|
140
|
+
write [cmd_tag, rqid, "next", [name]]
|
141
|
+
name, addr = take( [resp_tag, rqid, nil, nil, nil] ).last
|
142
|
+
log( name ? "next entry: #{name} => #{addr}" : "no more entries" )
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# Read-atomic multipartition transactions, as per:
|
2
|
+
# http://www.youtube.com/watch?v=_rAdJkAbGls (around minutes 28-30)
|
3
|
+
# http://www.bailis.org/blog/non-blocking-transactional-atomicity
|
4
|
+
#
|
5
|
+
# Example of transacting separately on two subspaces (i.e. shardable subsets of
|
6
|
+
# the tuplespace), but hiding intermediate tuples so that the results show up
|
7
|
+
# atomically when readers look for them. (Note that this is different from
|
8
|
+
# atomically transacting on replicas of the same shard, which is inherent in
|
9
|
+
# tupelo transactions.)
|
10
|
+
#
|
11
|
+
# In tupelo, we could use the classic tuplespace technique of taking a lock
|
12
|
+
# tuple to protect the sequence ot two transactions on the two subspaces, but
|
13
|
+
# that would reduce concurrency and require a lease mechanism in case the lock
|
14
|
+
# holder dies. That's possible, but not scalable. So we use transactions with a
|
15
|
+
# trick...
|
16
|
+
#
|
17
|
+
# Tupelo doesn't allow transactions to cross subspace boundaries (except in the
|
18
|
+
# special case of writes outside of a subspace--see
|
19
|
+
# [doc/subspace.md](doc/subspace.md)). We can get around this at the application
|
20
|
+
# level, with a few extra steps. This adds latency, but preserves effective
|
21
|
+
# atomicity from the application's point of view and does not introduce any
|
22
|
+
# fragile locks or blocking. The main trick (as in Bailis's talk) is to use a
|
23
|
+
# globally unique value -- in his talk he used a transaction id. We could use
|
24
|
+
# the global_tick of a successful transaction (same idea) or a unique id based
|
25
|
+
# on client_id -- see [example/uniq-id.rb](example/uniq-id.rb).
|
26
|
+
|
27
|
+
# todo: use a smarter data structure for the x and y subspaces
|
28
|
+
|
29
|
+
require 'tupelo/app'
|
30
|
+
|
31
|
+
N_ITER = 6
|
32
|
+
X_REPLICATIONS = 1 # number of copies of the shard of X data
|
33
|
+
Y_REPLICATIONS = 1 # number of copies of the shard of Y data
|
34
|
+
|
35
|
+
def next_local_id
|
36
|
+
@counter = 0
|
37
|
+
@counter += 1
|
38
|
+
# Protect this with a mutex or queue if other threads need it, or
|
39
|
+
# use the atomic gem. It's ok in a multiprocess app without mutex,
|
40
|
+
# because each process has its own copy.
|
41
|
+
end
|
42
|
+
|
43
|
+
Tupelo.application do
|
44
|
+
|
45
|
+
local do
|
46
|
+
use_subspaces!
|
47
|
+
|
48
|
+
define_subspace(
|
49
|
+
tag: "x",
|
50
|
+
template: {
|
51
|
+
x: {type: "number"}, # data payload
|
52
|
+
id: {type: "list"}, # [client_id, local_id]
|
53
|
+
final: {type: "boolean"} # false means pending
|
54
|
+
}
|
55
|
+
)
|
56
|
+
|
57
|
+
define_subspace(
|
58
|
+
tag: "y",
|
59
|
+
template: {
|
60
|
+
y: {type: "number"}, # data payload
|
61
|
+
id: {type: "list"}, # [client_id, local_id]
|
62
|
+
final: {type: "boolean"} # false means pending
|
63
|
+
}
|
64
|
+
)
|
65
|
+
|
66
|
+
define_subspace(
|
67
|
+
tag: "ack", # could make this per-client
|
68
|
+
template: {
|
69
|
+
ack: {type: "string"}, # state ack-ed: "pending"
|
70
|
+
id: {type: "list"} # [client_id, local_id]
|
71
|
+
}
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
X_REPLICATIONS.times do |xi|
|
76
|
+
child subscribe: ["x"], passive: true do
|
77
|
+
log.progname = "x#{xi}"
|
78
|
+
|
79
|
+
read x: nil, id: nil, final: nil do |t|
|
80
|
+
log t
|
81
|
+
if t["final"]
|
82
|
+
# co-writes are at least pending at this point in global time
|
83
|
+
# ("stable"), so remove pending tuple when final tuple exists.
|
84
|
+
# First responding replica wins, and the take propagates to others.
|
85
|
+
take_nowait t.merge(final: false)
|
86
|
+
else
|
87
|
+
write ack: "pending", id: t["id"]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
Y_REPLICATIONS.times do |yi|
|
94
|
+
child subscribe: ["y"], passive: true do
|
95
|
+
log.progname = "y#{yi}"
|
96
|
+
|
97
|
+
read y: nil, id: nil, final: nil do |t|
|
98
|
+
log t
|
99
|
+
if t["final"]
|
100
|
+
# co-writes are at least pending at this point in global time
|
101
|
+
# ("stable"), so remove pending tuple when final tuple exists.
|
102
|
+
# First responding replica wins, and the take propagates to others.
|
103
|
+
take_nowait t.merge(final: false)
|
104
|
+
else
|
105
|
+
write ack: "pending", id: t["id"]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
child subscribe: ["ack"] do
|
112
|
+
# Does not subscribe to x or y, so can only write to those spaces.
|
113
|
+
log.progname = "writer"
|
114
|
+
|
115
|
+
N_ITER.times do |i|
|
116
|
+
uniq_id = [client_id, next_local_id]
|
117
|
+
|
118
|
+
x = {x: i, id: uniq_id}
|
119
|
+
y = {y: i, id: uniq_id}
|
120
|
+
|
121
|
+
write x.merge(final: false), y.merge(final: false) # pending
|
122
|
+
(X_REPLICATIONS + Y_REPLICATIONS).times do
|
123
|
+
take ack: "pending", id: uniq_id # wait for one to be pending
|
124
|
+
end
|
125
|
+
write x.merge(final: true), y.merge(final: true)
|
126
|
+
|
127
|
+
# Note that each of the two above writes is a multi-space transaction
|
128
|
+
# which is allowed because it is purely writes (no reads or takes).
|
129
|
+
# However, this only guarantees read atomicity for tupelo clients (because
|
130
|
+
# of the global transaction ordering). If some processes are accessing the
|
131
|
+
# x and y data stores through protocols other than tupelo (such as sql
|
132
|
+
# over sockets), this is not enough--they could see inconsistent state.
|
133
|
+
# Hence the explicit wait for an ack to truly synchronize the state.
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# This doesn't test that RAMP is working -- it will always see a consistent
|
138
|
+
# view because of tupelo, even without the pending/ack trick. It is more
|
139
|
+
# informative to look at the log output from the x and y clients.
|
140
|
+
child subscribe: ["x", "y"], passive: true do
|
141
|
+
log.progname = "reader"
|
142
|
+
read do |t|
|
143
|
+
log t
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -1,12 +1,17 @@
|
|
1
1
|
require 'rbtree'
|
2
2
|
|
3
|
+
## TODO
|
4
|
+
##
|
5
|
+
## generalize SortedSetSpace to accept params that indicate which fields
|
6
|
+
## are key and value
|
7
|
+
|
3
8
|
class SortedSetTemplate
|
4
9
|
class << self
|
5
10
|
alias [] new
|
6
11
|
end
|
7
12
|
|
8
13
|
# cmd can be "next", "prev", "first", "last"
|
9
|
-
# for next/prev, args is [
|
14
|
+
# for next/prev, args is [key]
|
10
15
|
# for first/last, args is empty
|
11
16
|
def initialize tag, cmd, *args
|
12
17
|
@tag = tag
|
@@ -14,15 +19,38 @@ class SortedSetTemplate
|
|
14
19
|
@args = args
|
15
20
|
end
|
16
21
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
def find_in rbtree
|
22
|
+
def find_in tree, distinct_from: []
|
23
|
+
# for simplicity, ignore distinct_from
|
24
|
+
# -- we never take/read multiple keys in the tree
|
22
25
|
case @cmd
|
23
26
|
when "first"
|
24
|
-
|
25
|
-
|
27
|
+
k, v = tree.first
|
28
|
+
k && [@tag, k, v.first]
|
29
|
+
when "last"
|
30
|
+
tree.last
|
31
|
+
k && [@tag, k, v.last]
|
32
|
+
when "prev"
|
33
|
+
k = @args[0]
|
34
|
+
(k1,v1),(k2,v2) = tree.bound(tree.first[0], k).last(2)
|
35
|
+
## Bad rbtree! This will be much less efficient than "next".
|
36
|
+
if k == k2
|
37
|
+
k1 && [@tag, k1, v1.last]
|
38
|
+
else
|
39
|
+
k2 && [@tag, k2, v2.last]
|
40
|
+
end
|
41
|
+
## anomaly: can't iterate through multivalues
|
42
|
+
when "next"
|
43
|
+
k = @args[0]
|
44
|
+
(k1,v1),(k2,v2) = tree.bound(k, tree.last[0]).first(2)
|
45
|
+
## Bad rbtree! There is no bounded search with < (rather than <=)
|
46
|
+
if k == k1
|
47
|
+
k2 && [@tag, k2, v2.first]
|
48
|
+
else
|
49
|
+
k1 && [@tag, k1, v1.first]
|
50
|
+
end
|
51
|
+
else
|
52
|
+
raise "bad command"
|
53
|
+
end
|
26
54
|
end
|
27
55
|
end
|
28
56
|
|
@@ -30,19 +58,22 @@ end
|
|
30
58
|
# The object may be any serializable object (built up from numbers, booleans,
|
31
59
|
# nil, strings, hashes and arrays).
|
32
60
|
#
|
33
|
-
#
|
61
|
+
# By default, multiple values per key are allowed. (This differs from a typical
|
62
|
+
# key-value store, in wihch a given key_string may occur only once.)
|
34
63
|
# It is up to the application to decide whether to enforce key uniqueness or
|
35
64
|
# not (for example, by taking (k,...) before writing (k,v).
|
36
65
|
#
|
37
66
|
# This store should be used only by clients that subscribe to a subspace
|
38
|
-
# that can be represented as
|
67
|
+
# that can be represented as triples (tag, key_string, value), where
|
68
|
+
# the tag is a single literal value that is the same for all triples.
|
69
|
+
# (See memo2.rb.)
|
39
70
|
#
|
40
|
-
# This store also manages meta tuples, which it keeps in an array,
|
41
|
-
# the default Tuplespace class does.
|
71
|
+
# This store also manages command and meta tuples, which it keeps in an array,
|
72
|
+
# just like the default Tuplespace class does.
|
42
73
|
class SortedSetSpace
|
43
74
|
include Enumerable
|
44
75
|
|
45
|
-
attr_reader :tag, :
|
76
|
+
attr_reader :tag, :tree, :metas
|
46
77
|
|
47
78
|
def initialize tag
|
48
79
|
@tag = tag
|
@@ -50,17 +81,17 @@ class SortedSetSpace
|
|
50
81
|
end
|
51
82
|
|
52
83
|
def clear
|
53
|
-
@
|
54
|
-
# It's up to the application to enforce
|
84
|
+
@tree = RBTree.new{|t,k| t[k] = []}
|
85
|
+
# It's up to the application to enforce one entry per key.
|
55
86
|
@metas = []
|
56
87
|
# We are automatically subscribed to tupelo metadata (subspace defs), so
|
57
|
-
# we need to keep them somewhere.
|
88
|
+
# we need to keep them somewhere. Also, the command tuples.
|
58
89
|
end
|
59
90
|
|
60
91
|
def each
|
61
|
-
|
92
|
+
tree.each do |k, vs|
|
62
93
|
vs.each do |v|
|
63
|
-
yield tag, k, v
|
94
|
+
yield [tag, k, v]
|
64
95
|
end
|
65
96
|
end
|
66
97
|
metas.each do |tuple|
|
@@ -69,11 +100,11 @@ class SortedSetSpace
|
|
69
100
|
end
|
70
101
|
|
71
102
|
def insert tuple
|
72
|
-
if tuple.kind_of? Array
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
103
|
+
if tuple.kind_of? Array and tuple.size == 3 and
|
104
|
+
tuple[0] == tag and tuple[1].kind_of? String
|
105
|
+
|
106
|
+
_, k, v = tuple
|
107
|
+
tree[k] << v
|
77
108
|
|
78
109
|
else
|
79
110
|
metas << tuple
|
@@ -81,13 +112,13 @@ class SortedSetSpace
|
|
81
112
|
end
|
82
113
|
|
83
114
|
def delete_once tuple
|
84
|
-
if tuple.kind_of? Array
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
if
|
89
|
-
|
90
|
-
|
115
|
+
if tuple.kind_of? Array and tuple.size == 3 and
|
116
|
+
tuple[0] == tag and tuple[1].kind_of? String
|
117
|
+
|
118
|
+
_, k, v = tuple
|
119
|
+
if tree.key?(k) and tree[k].include? v
|
120
|
+
tree[k].delete v
|
121
|
+
tree.delete k if tree[k].empty?
|
91
122
|
true
|
92
123
|
else
|
93
124
|
false
|
@@ -95,7 +126,7 @@ class SortedSetSpace
|
|
95
126
|
|
96
127
|
else
|
97
128
|
if i=metas.index(tuple)
|
98
|
-
delete_at i
|
129
|
+
metas.delete_at i
|
99
130
|
end
|
100
131
|
end
|
101
132
|
end
|
@@ -119,7 +150,7 @@ class SortedSetSpace
|
|
119
150
|
def find_match_for template, distinct_from: []
|
120
151
|
case template
|
121
152
|
when SortedSetTemplate
|
122
|
-
template.find_in
|
153
|
+
template.find_in tree, distinct_from: distinct_from
|
123
154
|
else
|
124
155
|
# fall back to linear search
|
125
156
|
find do |tuple|
|
data/example/uniq-id.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# The underlying messaging protocol, funl, keeps track of a unique id per
|
2
|
+
# message. This id is awailable in the transaction object, but only after
|
3
|
+
# commit has succeeded. We can get the id from the transaction object and
|
4
|
+
# use it as a globally unique id.
|
5
|
+
#
|
6
|
+
# Another source of unique ids is the client id, which is unique per client.
|
7
|
+
# You can get an id that is uniq per message by combining it with any value
|
8
|
+
# that is unique, in that client, to the message, such as a counter.
|
9
|
+
# This has the advantage of not requiring a transaction.
|
10
|
+
|
11
|
+
require 'tupelo/app'
|
12
|
+
|
13
|
+
Tupelo.application do
|
14
|
+
local do
|
15
|
+
tr = pulse_wait ["noop"] # returns transaction
|
16
|
+
uniq_id = tr.global_tick # available after transaction commits
|
17
|
+
log "unique id is #{uniq_id}"
|
18
|
+
|
19
|
+
# now, we can use that unique id in some other tuples
|
20
|
+
write foo: "bar", id: uniq_id
|
21
|
+
log take foo: nil, id: nil
|
22
|
+
|
23
|
+
@counter = 0
|
24
|
+
next_local_id = proc { @counter+=1 }
|
25
|
+
# Protect this with a mutex or queue if other threads need it, or
|
26
|
+
# use the atomic gem.
|
27
|
+
|
28
|
+
cid = client_id
|
29
|
+
|
30
|
+
uniq_id2 = [next_local_id.call, cid]
|
31
|
+
write foo: "baz", id: uniq_id2
|
32
|
+
log take foo: nil, id: nil
|
33
|
+
end
|
34
|
+
end
|
@@ -6,6 +6,7 @@ class Tupelo::Client
|
|
6
6
|
class TransactionStateError < TransactionError; end
|
7
7
|
class TransactionAbort < TransactionError; end
|
8
8
|
class TransactionFailure < TransactionError; end
|
9
|
+
class TransactionSubspaceError < TransactionError; end
|
9
10
|
|
10
11
|
module Api
|
11
12
|
def trans_class
|
@@ -166,6 +167,10 @@ class Tupelo::Client
|
|
166
167
|
client.client_id
|
167
168
|
end
|
168
169
|
|
170
|
+
def subspace tag
|
171
|
+
client.subspace tag
|
172
|
+
end
|
173
|
+
|
169
174
|
def log *args
|
170
175
|
if args.empty?
|
171
176
|
@log
|
@@ -345,8 +350,8 @@ class Tupelo::Client
|
|
345
350
|
|
346
351
|
rescue TransactionAbort, Interrupt, TimeoutError => ex ## others?
|
347
352
|
worker_push Unwaiter.new(self)
|
348
|
-
|
349
|
-
|
353
|
+
cstr = "client #{client_id} (#{log.progname})"
|
354
|
+
raise ex.class, "#{ex.message}: #{cstr} waiting for #{inspect}"
|
350
355
|
end
|
351
356
|
|
352
357
|
def value
|
data/lib/tupelo/client/worker.rb
CHANGED
@@ -527,15 +527,19 @@ class Tupelo::Client
|
|
527
527
|
## in case there is an optimization
|
528
528
|
matcher.fails
|
529
529
|
else
|
530
|
-
tuple = tuplespace.find_match_for
|
530
|
+
tuple = tuplespace.find_match_for matcher.template
|
531
531
|
if tuple
|
532
|
-
|
532
|
+
matcher.peek tuple
|
533
533
|
else
|
534
534
|
matcher.fails
|
535
535
|
end
|
536
536
|
end
|
537
537
|
end
|
538
538
|
|
539
|
+
def collect_tags tuple
|
540
|
+
subspaces.select {|subspace| subspace === tuple}.map(&:tag)
|
541
|
+
end
|
542
|
+
|
539
543
|
def send_transaction transaction
|
540
544
|
msg = message_class.new
|
541
545
|
msg.client_id = client_id
|
@@ -550,14 +554,33 @@ class Tupelo::Client
|
|
550
554
|
reads = transaction.read_tuples_for_remote.compact
|
551
555
|
|
552
556
|
unless msg.tags
|
553
|
-
tags =
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
tags
|
559
|
-
|
557
|
+
tags = nil
|
558
|
+
[takes, reads].compact.flatten(1).each do |tuple|
|
559
|
+
if tags
|
560
|
+
tuple_tags = collect_tags(tuple)
|
561
|
+
unless tuple_tags == tags
|
562
|
+
d = (tuple_tags - tags) + (tags - tuple_tags)
|
563
|
+
raise TransactionSubspaceError,
|
564
|
+
"tuples crossing subspaces: #{d} in #{transaction.inspect}"
|
560
565
|
end
|
566
|
+
else
|
567
|
+
tags = collect_tags(tuple)
|
568
|
+
end
|
569
|
+
end
|
570
|
+
tags ||= []
|
571
|
+
|
572
|
+
write_tags = []
|
573
|
+
[writes, pulses].compact.flatten(1).each do |tuple|
|
574
|
+
write_tags |= collect_tags(tuple)
|
575
|
+
end
|
576
|
+
|
577
|
+
if takes.empty? and reads.empty?
|
578
|
+
tags = write_tags
|
579
|
+
else
|
580
|
+
d = write_tags - tags
|
581
|
+
unless d.empty?
|
582
|
+
raise TransactionSubspaceError,
|
583
|
+
"writes crossing subspaces: #{d} in #{transaction.inspect}"
|
561
584
|
end
|
562
585
|
end
|
563
586
|
|
data/lib/tupelo/client.rb
CHANGED
@@ -80,7 +80,13 @@ module Tupelo
|
|
80
80
|
|
81
81
|
def subspace tag
|
82
82
|
tag = tag.to_s
|
83
|
-
worker.subspaces.find {|sp| sp.tag == tag}
|
83
|
+
worker.subspaces.find {|sp| sp.tag == tag} or begin
|
84
|
+
if subscribed_tags.include? tag
|
85
|
+
read __tupelo__: "subspace", tag: tag, addr: nil, template: nil
|
86
|
+
worker.subspaces.find {|sp| sp.tag == tag}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
## this impl will not be safe with dynamic subspaces
|
84
90
|
end
|
85
91
|
end
|
86
92
|
end
|
data/lib/tupelo/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tupelo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.14'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joel VanderWerf
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-12-
|
11
|
+
date: 2013-12-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: atdo
|
@@ -84,119 +84,119 @@ files:
|
|
84
84
|
- lib/tupelo/app/trace.rb
|
85
85
|
- lib/tupelo/app/builder.rb
|
86
86
|
- lib/tupelo/client.rb
|
87
|
-
- lib/tupelo/
|
88
|
-
- lib/tupelo/archiver.rb
|
89
|
-
- lib/tupelo/client/worker.rb
|
90
|
-
- lib/tupelo/client/common.rb
|
91
|
-
- lib/tupelo/client/tuplespace.rb
|
92
|
-
- lib/tupelo/client/transaction.rb
|
93
|
-
- lib/tupelo/client/atdo.rb
|
94
|
-
- lib/tupelo/client/reader.rb
|
87
|
+
- lib/tupelo/tuplets/persistent-archiver.rb
|
95
88
|
- lib/tupelo/tuplets/persistent-archiver/worker.rb
|
96
89
|
- lib/tupelo/tuplets/persistent-archiver/tuplespace.rb
|
97
|
-
- lib/tupelo/
|
90
|
+
- lib/tupelo/util/boolean.rb
|
91
|
+
- lib/tupelo/app.rb
|
92
|
+
- lib/tupelo/archiver.rb
|
98
93
|
- lib/tupelo/archiver/persister.rb
|
99
94
|
- lib/tupelo/archiver/worker.rb
|
100
95
|
- lib/tupelo/archiver/tuplespace.rb
|
101
96
|
- lib/tupelo/archiver/persistent-tuplespace.rb
|
102
|
-
- lib/tupelo/
|
97
|
+
- lib/tupelo/client/transaction.rb
|
98
|
+
- lib/tupelo/client/worker.rb
|
99
|
+
- lib/tupelo/client/reader.rb
|
100
|
+
- lib/tupelo/client/tuplespace.rb
|
101
|
+
- lib/tupelo/client/atdo.rb
|
102
|
+
- lib/tupelo/client/common.rb
|
103
103
|
- lib/tupelo/version.rb
|
104
104
|
- bench/pipeline.rb
|
105
105
|
- bugs/read-take.rb
|
106
106
|
- bugs/take-write.rb
|
107
|
-
- example/pubsub.rb
|
108
|
-
- example/timeout-trans.rb
|
109
|
-
- example/fish01.rb
|
110
|
-
- example/tiny-client.rb
|
111
|
-
- example/add.rb
|
112
|
-
- example/parallel.rb
|
113
|
-
- example/socket-broker.rb
|
114
|
-
- example/multi-tier/memo2.rb
|
115
|
-
- example/multi-tier/drb.rb
|
116
|
-
- example/multi-tier/memo.rb
|
117
|
-
- example/multi-tier/kvspace.rb
|
118
|
-
- example/multi-tier/http.rb
|
119
|
-
- example/multi-tier/multi-sinatras.rb
|
120
|
-
- example/app-and-tup.rb
|
121
|
-
- example/small.rb
|
122
|
-
- example/bounded-retry.rb
|
123
|
-
- example/fish.rb
|
124
|
-
- example/zk/lock.rb
|
125
|
-
- example/concurrent-transactions.rb
|
126
|
-
- example/cancel.rb
|
127
|
-
- example/map-reduce/map-reduce-v2.rb
|
128
|
-
- example/map-reduce/remote-map-reduce.rb
|
129
|
-
- example/map-reduce/map-reduce.rb
|
130
|
-
- example/tiny-server.rb
|
131
|
-
- example/write-wait.rb
|
132
|
-
- example/tcp.rb
|
133
107
|
- example/timeout.rb
|
134
|
-
- example/read-in-trans.rb
|
135
|
-
- example/subspaces/simple.rb
|
136
|
-
- example/subspaces/pubsub.rb
|
137
|
-
- example/subspaces/addr-book-v1.rb
|
138
|
-
- example/subspaces/addr-book-v2.rb
|
139
|
-
- example/subspaces/shop/shop-v2.rb
|
140
|
-
- example/subspaces/shop/shop-v1.rb
|
141
|
-
- example/subspaces/sorted-set-space.rb
|
142
|
-
- example/balance-xfer-retry.rb
|
143
|
-
- example/take-nowait-caution.rb
|
144
|
-
- example/lock-mgr-with-queue.rb
|
145
|
-
- example/hash-tuples.rb
|
146
|
-
- example/pulse.rb
|
147
|
-
- example/transaction-logic.rb
|
148
|
-
- example/lease.rb
|
149
|
-
- example/chat/chat.rb
|
150
|
-
- example/chat/chat-nohistory.rb
|
151
|
-
- example/balance-xfer.rb
|
152
108
|
- example/add-dsl.rb
|
153
|
-
- example/
|
154
|
-
- example/
|
155
|
-
- example/
|
109
|
+
- example/remote.rb
|
110
|
+
- example/increment.rb
|
111
|
+
- example/matching.rb
|
112
|
+
- example/dphil.rb
|
113
|
+
- example/broker-optimistic.rb
|
156
114
|
- example/fail-and-retry.rb
|
157
|
-
- example/
|
115
|
+
- example/load-balancer.rb
|
116
|
+
- example/read-in-trans.rb
|
117
|
+
- example/bounded-retry.rb
|
118
|
+
- example/pregel/remote.rb
|
119
|
+
- example/pregel/pagerank.rb
|
158
120
|
- example/pregel/pregel.rb
|
159
121
|
- example/pregel/distributed.rb
|
160
|
-
- example/pregel/pagerank.rb
|
161
122
|
- example/pregel/update.rb
|
162
|
-
- example/pregel/remote.rb
|
163
|
-
- example/pregel/dist-opt.rb
|
164
|
-
- example/dphil-optimistic-v2.rb
|
165
|
-
- example/broker-optimistic-v2.rb
|
166
|
-
- example/remote.rb
|
167
123
|
- example/take-nowait.rb
|
168
|
-
- example/
|
169
|
-
- example/
|
124
|
+
- example/boolean-match.rb
|
125
|
+
- example/lease.rb
|
126
|
+
- example/broker-locking.rb
|
127
|
+
- example/transaction-logic.rb
|
170
128
|
- example/message-bus.rb
|
129
|
+
- example/small-simplified.rb
|
130
|
+
- example/small.rb
|
131
|
+
- example/lock-mgr.rb
|
132
|
+
- example/take-nowait-caution.rb
|
133
|
+
- example/concurrent-transactions.rb
|
134
|
+
- example/tcp.rb
|
135
|
+
- example/notify.rb
|
136
|
+
- example/pulse.rb
|
137
|
+
- example/chat/chat.rb
|
138
|
+
- example/chat/chat-nohistory.rb
|
139
|
+
- example/hash-tuples.rb
|
171
140
|
- example/balance-xfer-locking.rb
|
172
|
-
- example/
|
173
|
-
- example/child-of-child.rb
|
174
|
-
- example/custom-class.rb
|
175
|
-
- example/matching.rb
|
141
|
+
- example/balance-xfer-retry.rb
|
176
142
|
- example/custom-search.rb
|
177
|
-
- example/
|
178
|
-
- example/
|
179
|
-
- example/
|
180
|
-
- example/
|
181
|
-
- example/
|
182
|
-
- example/
|
183
|
-
- example/
|
143
|
+
- example/app-and-tup.rb
|
144
|
+
- example/multi-tier/memo2.rb
|
145
|
+
- example/multi-tier/http.rb
|
146
|
+
- example/multi-tier/multi-sinatras.rb
|
147
|
+
- example/multi-tier/kvspace.rb
|
148
|
+
- example/multi-tier/memo.rb
|
149
|
+
- example/multi-tier/drb.rb
|
184
150
|
- example/take-many.rb
|
151
|
+
- example/subspaces/ramp.rb
|
152
|
+
- example/subspaces/sorted-set-space.rb
|
153
|
+
- example/subspaces/addr-book.rb
|
154
|
+
- example/subspaces/simple.rb
|
155
|
+
- example/subspaces/shop/shop-v2.rb
|
156
|
+
- example/subspaces/shop/shop-v1.rb
|
157
|
+
- example/subspaces/pubsub.rb
|
158
|
+
- example/subspaces/addr-book-v2.rb
|
159
|
+
- example/dphil-optimistic.rb
|
160
|
+
- example/async-transaction.rb
|
161
|
+
- example/wait-interrupt.rb
|
162
|
+
- example/fish0.rb
|
163
|
+
- example/zk/lock.rb
|
185
164
|
- example/deadlock.rb
|
186
|
-
- example/
|
187
|
-
-
|
165
|
+
- example/fish.rb
|
166
|
+
- example/add.rb
|
167
|
+
- example/dphil-optimistic-v2.rb
|
168
|
+
- example/parallel.rb
|
169
|
+
- example/tiny-client.rb
|
170
|
+
- example/map-reduce/map-reduce.rb
|
171
|
+
- example/map-reduce/remote-map-reduce.rb
|
172
|
+
- example/map-reduce/map-reduce-v2.rb
|
173
|
+
- example/map-reduce/prime-factor.rb
|
174
|
+
- example/map-reduce/prime-factor-balanced.rb
|
175
|
+
- example/lock-mgr-with-queue.rb
|
176
|
+
- example/balance-xfer.rb
|
177
|
+
- example/cancel.rb
|
178
|
+
- example/socket-broker.rb
|
179
|
+
- example/timeout-trans.rb
|
180
|
+
- example/uniq-id.rb
|
181
|
+
- example/optimist.rb
|
182
|
+
- example/tiny-server.rb
|
183
|
+
- example/pubsub.rb
|
184
|
+
- example/broker-optimistic-v2.rb
|
185
|
+
- example/write-wait.rb
|
186
|
+
- example/custom-class.rb
|
187
|
+
- test/stress/archiver-load.rb
|
188
|
+
- test/stress/concurrent-transactions.rb
|
189
|
+
- test/system/test-archiver.rb
|
190
|
+
- test/lib/mock-client.rb
|
191
|
+
- test/lib/time-fuzz.rb
|
192
|
+
- test/lib/mock-queue.rb
|
188
193
|
- test/lib/mock-seq.rb
|
194
|
+
- test/lib/testable-worker.rb
|
189
195
|
- test/lib/mock-msg.rb
|
190
|
-
- test/lib/mock-queue.rb
|
191
|
-
- test/lib/time-fuzz.rb
|
192
|
-
- test/lib/mock-client.rb
|
193
|
-
- test/system/test-archiver.rb
|
194
|
-
- test/unit/test-ops.rb
|
195
|
-
- test/unit/test-mock-client.rb
|
196
196
|
- test/unit/test-mock-seq.rb
|
197
197
|
- test/unit/test-mock-queue.rb
|
198
|
-
- test/
|
199
|
-
- test/
|
198
|
+
- test/unit/test-ops.rb
|
199
|
+
- test/unit/test-mock-client.rb
|
200
200
|
- bin/tup
|
201
201
|
- bin/tspy
|
202
202
|
homepage: https://github.com/vjoel/tupelo
|
@@ -231,8 +231,8 @@ signing_key:
|
|
231
231
|
specification_version: 4
|
232
232
|
summary: Distributed tuplespace
|
233
233
|
test_files:
|
234
|
-
- test/unit/test-ops.rb
|
235
|
-
- test/unit/test-mock-client.rb
|
236
234
|
- test/unit/test-mock-seq.rb
|
237
235
|
- test/unit/test-mock-queue.rb
|
236
|
+
- test/unit/test-ops.rb
|
237
|
+
- test/unit/test-mock-client.rb
|
238
238
|
has_rdoc:
|
data/example/broker-queue.rb
DELETED
@@ -1,35 +0,0 @@
|
|
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
|
data/example/child-of-child.rb
DELETED
@@ -1,34 +0,0 @@
|
|
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
DELETED
@@ -1,48 +0,0 @@
|
|
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
|
data/example/pregel/dist-opt.rb
DELETED
@@ -1,106 +0,0 @@
|
|
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
|