tupelo 0.4 → 0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +42 -9
- data/Rakefile +9 -3
- data/bin/tspy +7 -2
- data/bin/tup +1 -1
- data/example/add-dsl.rb +27 -0
- data/example/boolean-match.rb +2 -19
- data/example/broker-optimistic-v2.rb +33 -0
- data/example/broker-optimistic.rb +15 -15
- data/example/dphil-optimistic-v2.rb +37 -0
- data/example/dphil-optimistic.rb +44 -0
- data/example/dphil.rb +42 -0
- data/example/lock-mgr-with-queue.rb +5 -15
- data/example/lock-mgr.rb +7 -16
- data/example/map-reduce-v2.rb +4 -37
- data/example/map-reduce.rb +2 -18
- data/example/small-simplified.rb +7 -7
- data/example/small.rb +3 -3
- data/example/tcp.rb +8 -1
- data/lib/tupelo/app/trace.rb +23 -0
- data/lib/tupelo/app.rb +45 -7
- data/lib/tupelo/archiver/worker.rb +31 -4
- data/lib/tupelo/client/reader.rb +0 -2
- data/lib/tupelo/client/transaction.rb +83 -23
- data/lib/tupelo/client/worker.rb +14 -5
- data/lib/tupelo/util/boolean.rb +25 -0
- data/lib/tupelo/version.rb +1 -1
- data/test/lib/time-fuzz.rb +36 -0
- data/test/stress/archiver-load.rb +16 -0
- data/test/stress/concurrent-transactions.rb +7 -5
- metadata +12 -5
- data/example/broker-optimistic-async.rb +0 -33
- data/example/broker-queue.rb +0 -2
- /data/lib/tupelo/{irb-shell.rb → app/irb-shell.rb} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f296ad08b928805d512dc88cbdc0f5b1196fde6
|
4
|
+
data.tar.gz: f25b3ac8ef8fac55f5a9affc1b33cf42409aebc6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2a88ccb3f784fc9106fb4d92c522fb26e61ae7ec0ad24fecbb805a94c4f01329ad717e035ed2f9fc8b795076aaec9c840d2601759452afb8502f61b7999ef86f
|
7
|
+
data.tar.gz: 4d3662603023f504618ea0f3f8e84ef076dce52f86150634ad4d8e540a610279f3604bb5071b6bd5e16c074bf330dcd7914f70397a52397e1966dae9d55ba8f3
|
data/README.md
CHANGED
@@ -21,7 +21,7 @@ Tupelo differs from other spaces in several ways:
|
|
21
21
|
Getting started
|
22
22
|
==========
|
23
23
|
|
24
|
-
1. Install ruby 2 (not 1.9) from http://ruby-lang.org. Examples and tests will not work on windows (they use fork and unix sockets), though probably the underying libs will (using tcp sockets).
|
24
|
+
1. Install ruby 2 (not 1.9) from http://ruby-lang.org. Examples and tests will not work on windows (they use fork and unix sockets) or jruby, though probably the underying libs will (using tcp sockets).
|
25
25
|
|
26
26
|
2. Install the gem and its dependencies (you may need to `sudo` this):
|
27
27
|
|
@@ -132,9 +132,17 @@ Getting started
|
|
132
132
|
|
133
133
|
ls -d /usr/local/lib/ruby/gems/*/gems/tupelo*
|
134
134
|
|
135
|
-
Note that all bin and example programs accept blob type (e.g., --
|
135
|
+
Note that all bin and example programs accept blob type (e.g., --msgpack, --json) on command line (it only needs to be specified for server -- the clients discover it). Also, all these programs accept log level on command line. The default is --warn. The --info level is a good way to get an idea of what is happening, without the verbosity of --debug.
|
136
136
|
|
137
|
-
6.
|
137
|
+
6. Debugging: in addition to the --info switch on all bin and example programs, bin/tspy is also really useful. There is also the similar --trace switch that is available to all bin and example programs, for example:
|
138
|
+
|
139
|
+
tick cid status operation
|
140
|
+
1 2 batch write ["x", 1]
|
141
|
+
2 2 batch write ["y", 2]
|
142
|
+
3 3 atomic take ["x", 1], ["y", 2]
|
143
|
+
|
144
|
+
|
145
|
+
The `Tupelo.application` command, provided by `tupelo/app`, is the source of all these options and is available to your programs.
|
138
146
|
|
139
147
|
|
140
148
|
What is a tuplespace?
|
@@ -147,7 +155,7 @@ See https://en.wikipedia.org/wiki/Tuple_space for general information and histor
|
|
147
155
|
What is a tuple?
|
148
156
|
----------------
|
149
157
|
|
150
|
-
A tuple is the unit of information in a tuplespace. It is immutable in the context of the tuplespace -- you can write a tuple into the space and you can read or take one from the space, but you cannot update a tuple within a space.
|
158
|
+
A tuple is the unit of information in a tuplespace. It is immutable in the context of the tuplespace -- you can write a tuple into the space and you can read or take one from the space, but you cannot update a tuple within a space. A tuples does not have an identity other than the data it is made from.
|
151
159
|
|
152
160
|
A tuple is either an array:
|
153
161
|
|
@@ -174,6 +182,8 @@ In other words, a tuple is a fairly general object, though this depends on the s
|
|
174
182
|
|
175
183
|
It's kind of like a "JSON object", except that in the json blob case, the hash keys can only be strings. In the msgpack case, keys have no special limitations. In the case of the marshal and yaml modes, tuples can contain many other kinds of objects.
|
176
184
|
|
185
|
+
A tuplespace can contain multiple copies of the same tuple.
|
186
|
+
|
177
187
|
What is a template?
|
178
188
|
-------------------
|
179
189
|
|
@@ -193,6 +203,12 @@ but not these tuples:
|
|
193
203
|
|
194
204
|
The nil wildcard matches anything. The Range, Regexp, and Class entries function as wildcards because of the way they define the #=== (match) method. See ruby docs for general information on "threequals" matching.
|
195
205
|
|
206
|
+
Every tuple can also be used as a template. The template:
|
207
|
+
|
208
|
+
[4, 7, "foobar", "xyz"]
|
209
|
+
|
210
|
+
matches itself.
|
211
|
+
|
196
212
|
Here's a template for matching some hash tuples:
|
197
213
|
|
198
214
|
{name: String, location: "home"}
|
@@ -206,6 +222,10 @@ A template doesn't have to be a tuple pattern with wildcards, though. It can be
|
|
206
222
|
read_all Array
|
207
223
|
read_all Object
|
208
224
|
|
225
|
+
An optional library, `tupelo/util/boolean`, provides a #match_any method to construct the boolean or of other templates:
|
226
|
+
|
227
|
+
read_all match_any( [1,2,3], {foo: "bar"} )
|
228
|
+
|
209
229
|
Unlike in some tuplespace implementations, templates are a client-side concept (except for subspace-defining templates), which is a source of efficiency and scalability. Matching operations (which can be computationally heavy) are performed on the client, rather than on the server, which would bottleneck the whole system.
|
210
230
|
|
211
231
|
What are the operations on tuples?
|
@@ -232,17 +252,25 @@ Transactions are not just about batching up operations into a more efficient pac
|
|
232
252
|
|
233
253
|
Transactions give you a means of optimistic locking: the transaction proceeds in a way that depends on preconditions. See 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 and example/broker-optimistic.rb).
|
234
254
|
|
235
|
-
If you prefer classical tuplespace locking, you can simply use certain tuples as locks, using take/write to lock/unlock them. See the examples. If you have a lot of contention and want to avoid the thundering herd, see example/lock-mgr-with-queue.rb.
|
255
|
+
If you prefer classical tuplespace locking, you can simply use certain tuples as locks, using take/write to lock/unlock them. See the examples, such as example/broker-locking.rb. If you have a lot of contention and want to avoid the thundering herd, see example/lock-mgr-with-queue.rb.
|
236
256
|
|
237
257
|
If an optimistic transaction fails (for example, it is trying to take a tuple, but the tuple has just been taken by another transaction), then the transaction block is re-executed, possibly waiting for new matches to the templates. Application code must be aware of the possible re-execution of the block. This is better explained in the examples...
|
238
258
|
|
239
|
-
|
259
|
+
Transactions have a significant disadvantage compared to lock tuples: a transaction can protect only resources that are represented in the tuplespace, whereas a lock can protect anything: a file, a device, a service, etc. This is because a transaction begins and ends within a single instant of logical (tuplespace) time, whereas a lock tuple can be taken out for an arbitrary duration of real time. Furthermore, the instant of logical time in which a transaction takes effect may occur at different wall-clock times on different processes, even on the same host.
|
260
|
+
|
261
|
+
Tupelo transactions are ACID in the following sense. They are Atomic and Isolated -- this is enforced by the transaction processing in each client. Consistency is enforced by the underlying message sequencer: each client's copy of the space is the deterministic result of the same sequence of operations. Durability is optional, but can be provided by the archiver (to be implemented) or other clients.
|
240
262
|
|
241
263
|
On the CAP spectrum, tupelo tends towards consistency.
|
242
264
|
|
243
265
|
Tupelo transactions do not require two-phase commit, because they are less powerful than general transactions. Each client has enough information to decide (in the same way as all other clients) whether the transaction succeeds or fails. This has performance advantages, but imposes some limitations on transactions over subspaces that are known to one client but not another.
|
244
266
|
|
245
267
|
|
268
|
+
Syntax
|
269
|
+
======
|
270
|
+
|
271
|
+
You can use tupelo with a simplified syntax, like a "domain-specific language". Each construct with a block can be used in either of two forms, with an explicit block param or without. Compare example/add-dsl.rb and example/add.rb.
|
272
|
+
|
273
|
+
|
246
274
|
Advantages
|
247
275
|
==========
|
248
276
|
|
@@ -252,6 +280,8 @@ Speed (latency, throughput):
|
|
252
280
|
|
253
281
|
* minimal system-wide bottlenecks
|
254
282
|
|
283
|
+
* non-blocking socket reads
|
284
|
+
|
255
285
|
* read -- local and hence very fast
|
256
286
|
|
257
287
|
* write -- fast, pipelined (waiting for acknowledgement is optional);
|
@@ -322,6 +352,8 @@ Tupelo also supports custom classes in tuples, but only with marshal / yaml; mus
|
|
322
352
|
|
323
353
|
Both: tuples can be arrays or hashes.
|
324
354
|
|
355
|
+
Spaces have an advantage over distributed hash tables: different clients may acccess tuples in terms of different dimensions. For example, a producer generates [producer_id, value]; a consumer looks for [nil, SomeParticularValues]. Separation of concerns, decoupling in the data space.
|
356
|
+
|
325
357
|
|
326
358
|
To compare
|
327
359
|
----------
|
@@ -342,6 +374,7 @@ To compare
|
|
342
374
|
|
343
375
|
* datomic -- similar distribution of "facts", but not tuplespace
|
344
376
|
|
377
|
+
* job queues: sidekiq, resque, delayedjob, http://queues.io
|
345
378
|
|
346
379
|
Architecture
|
347
380
|
============
|
@@ -354,7 +387,7 @@ Two central processes:
|
|
354
387
|
|
355
388
|
Specialized clients:
|
356
389
|
|
357
|
-
* archiver -- dumps tuplespace state to clients joining the system later than t=0
|
390
|
+
* archiver -- dumps tuplespace state to clients joining the system later than t=0; at least one archiver is required, unless all clients start at t=0.
|
358
391
|
|
359
392
|
* tup -- command line shell for accessing (and creating) tuplespaces
|
360
393
|
|
@@ -368,14 +401,14 @@ General application clients:
|
|
368
401
|
|
369
402
|
* worker thread manages local tuplespace state and requests to modify or access it
|
370
403
|
|
371
|
-
* client threads construct transactions and wait for results (communicating with the worker thread over queues)
|
404
|
+
* client threads construct transactions and wait for results (communicating with the worker thread over queues); they may also use asynchronous transactions
|
372
405
|
|
373
406
|
Protocol
|
374
407
|
--------
|
375
408
|
|
376
409
|
Nothing in the protocol specifies local searching or storage, or matching, or notification, or templating. That's all up to each client. The protocol only contains tuples and operations on them (take, write, pulse, read), combined into transactions.
|
377
410
|
|
378
|
-
The protocol has two layers. The outer (message) layer is 6 fields, managed by the funl gem, using msgpack for serialization.
|
411
|
+
The protocol has two layers. The outer (message) layer is 6 fields, managed by the funl gem, using msgpack for serialization. All socket reads are non-blocking, so a slow sender will not block other activity in the system.
|
379
412
|
|
380
413
|
The inner (blob) layer manages one of those 6 field using msgpack (by default), marshal, json, or yaml. This layer contains the transaction operations. The blob is not unpacked by the server, only by clients.
|
381
414
|
|
data/Rakefile
CHANGED
@@ -29,14 +29,14 @@ namespace :test do
|
|
29
29
|
desc "Run system tests"
|
30
30
|
task :system do |t|
|
31
31
|
FileList["test/system/*.rb"].each do |f|
|
32
|
-
ruby f
|
32
|
+
ruby "-Itest/lib", f
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
36
|
desc "Run stress tests"
|
37
37
|
task :stress do |t|
|
38
38
|
FileList["test/stress/*.rb"].each do |f|
|
39
|
-
ruby f
|
39
|
+
ruby "-Itest/lib", f
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
@@ -67,10 +67,16 @@ end
|
|
67
67
|
namespace :release do
|
68
68
|
desc "Diff to latest release"
|
69
69
|
task :diff do
|
70
|
-
latest = `git describe --abbrev=0 --tags --match '#{PRJ}-*'
|
70
|
+
latest = `git describe --abbrev=0 --tags --match '#{PRJ}-*'`.chomp
|
71
71
|
sh "git diff #{latest}"
|
72
72
|
end
|
73
73
|
|
74
|
+
desc "Log to latest release"
|
75
|
+
task :log do
|
76
|
+
latest = `git describe --abbrev=0 --tags --match '#{PRJ}-*'`.chomp
|
77
|
+
sh "git log #{latest}.."
|
78
|
+
end
|
79
|
+
|
74
80
|
task :is_new_version do
|
75
81
|
abort "#{tag} exists; update version!" unless `git tag -l #{tag}`.empty?
|
76
82
|
end
|
data/bin/tspy
CHANGED
@@ -31,10 +31,15 @@ require 'tupelo/app'
|
|
31
31
|
|
32
32
|
Tupelo.application do |app|
|
33
33
|
app.local do |client|
|
34
|
+
trap :INT do
|
35
|
+
exit!
|
36
|
+
end
|
37
|
+
|
34
38
|
note = client.notifier
|
35
|
-
client.log "%10s %10s %10s %s" % %w{
|
39
|
+
client.log "%10s %10s %10s %s" % %w{ tick client status operation }
|
36
40
|
loop do
|
37
|
-
|
41
|
+
status, tick, cid, op = note.wait
|
42
|
+
client.log "%10d %10d %10s %p" % [tick, cid, status, op]
|
38
43
|
end
|
39
44
|
end
|
40
45
|
end
|
data/bin/tup
CHANGED
data/example/add-dsl.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# A little ruby magic to sweeten the syntax. Compare add.rb
|
2
|
+
#
|
3
|
+
# The old syntax is still accepted. Switching between syntaxes is caused by the
|
4
|
+
# presence of the '|...|' form. When present, it's important to keep in mind
|
5
|
+
# that 'self' and instance vars inside the block are not the same as outside the
|
6
|
+
# block. In other respects, such as local vars, these closures behave normally.
|
7
|
+
# This is ruby's famous instance_eval gotcha.
|
8
|
+
|
9
|
+
require 'tupelo/app'
|
10
|
+
|
11
|
+
Tupelo.application do
|
12
|
+
child do
|
13
|
+
write ['x', 1]
|
14
|
+
write ['y', 2]
|
15
|
+
end
|
16
|
+
|
17
|
+
child do
|
18
|
+
sum =
|
19
|
+
transaction do
|
20
|
+
_, x = take ['x', Numeric]
|
21
|
+
_, y = take ['y', Numeric]
|
22
|
+
x + y
|
23
|
+
end
|
24
|
+
|
25
|
+
log "sum = #{sum}"
|
26
|
+
end
|
27
|
+
end
|
data/example/boolean-match.rb
CHANGED
@@ -1,26 +1,9 @@
|
|
1
1
|
require 'tupelo/app'
|
2
|
-
|
3
|
-
class Tupelo::Client
|
4
|
-
class Or
|
5
|
-
attr_reader :templates
|
6
|
-
|
7
|
-
def initialize worker, templates
|
8
|
-
@templates = templates.map {|template| worker.make_template(template)}
|
9
|
-
end
|
10
|
-
|
11
|
-
def === obj
|
12
|
-
templates.any? {|template| template === obj}
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def or *templates
|
17
|
-
Or.new(worker, templates)
|
18
|
-
end
|
19
|
-
end
|
2
|
+
require 'tupelo/util/boolean'
|
20
3
|
|
21
4
|
Tupelo.application do |app|
|
22
5
|
app.local do |client|
|
23
|
-
tm = client.
|
6
|
+
tm = client.match_any [0..2, String], [3..5, Hash]
|
24
7
|
|
25
8
|
client.write(
|
26
9
|
[0, "a"], [1, {b: 0}], [2, "c"],
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Modified in two ways:
|
2
|
+
# - use simplified block syntax
|
3
|
+
# - make it fit in a transaction do..end block
|
4
|
+
|
5
|
+
require 'tupelo/app'
|
6
|
+
|
7
|
+
N_PLAYERS = 10
|
8
|
+
|
9
|
+
Tupelo.application do
|
10
|
+
N_PLAYERS.times do
|
11
|
+
# sleep rand / 10 # reduce contention -- could also randomize inserts
|
12
|
+
child do
|
13
|
+
me = client_id
|
14
|
+
write name: me
|
15
|
+
|
16
|
+
you = transaction do
|
17
|
+
game = read_nowait(
|
18
|
+
player1: nil,
|
19
|
+
player2: me)
|
20
|
+
break game["player1"] if game
|
21
|
+
|
22
|
+
take_nowait(name: me) or fail!
|
23
|
+
you = take(name: nil)["name"]
|
24
|
+
write(
|
25
|
+
player1: me,
|
26
|
+
player2: you)
|
27
|
+
you
|
28
|
+
end
|
29
|
+
|
30
|
+
log "now playing with #{you}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -2,6 +2,8 @@
|
|
2
2
|
# broker-locking.rb, but it has far fewer bottlenecks (try inserting some sleep
|
3
3
|
# 1 calls), and it is not possible for a token to be lost (leaving the lock in a
|
4
4
|
# locked state) if a process dies.
|
5
|
+
# However, this version is vulnerable to contention. Try this: N_PLAYERS=40
|
6
|
+
# and comment out the sleep line.
|
5
7
|
|
6
8
|
require 'tupelo/app'
|
7
9
|
|
@@ -9,29 +11,27 @@ N_PLAYERS = 10
|
|
9
11
|
|
10
12
|
Tupelo.application do |app|
|
11
13
|
N_PLAYERS.times do
|
14
|
+
# sleep rand / 10 # reduce contention -- could also randomize inserts
|
12
15
|
app.child do |client|
|
13
16
|
me = client.client_id
|
14
17
|
client.write name: me
|
15
|
-
you = nil
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
28
|
-
rescue Tupelo::Client::TransactionFailure => ex
|
19
|
+
begin
|
20
|
+
t = client.transaction
|
21
|
+
if t.take_nowait name: me
|
22
|
+
you = t.take(name: nil)["name"]
|
23
|
+
t.write(
|
24
|
+
player1: me,
|
25
|
+
player2: you)
|
26
|
+
t.commit.wait
|
27
|
+
else
|
28
|
+
t.fail!
|
29
29
|
end
|
30
|
-
|
30
|
+
rescue Tupelo::Client::TransactionFailure => ex
|
31
31
|
game = client.read_nowait(
|
32
32
|
player1: nil,
|
33
33
|
player2: me)
|
34
|
-
|
34
|
+
retry unless game
|
35
35
|
you = game["player1"]
|
36
36
|
end
|
37
37
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Dining philosopher example. Like dphil-optimistic.rb, but replaces
|
2
|
+
# each take-write pair with a read. Same effect, inside a transaction,
|
3
|
+
# and it is a little faster.
|
4
|
+
|
5
|
+
require 'tupelo/app'
|
6
|
+
|
7
|
+
N_PHIL = 5
|
8
|
+
N_ITER = 10
|
9
|
+
|
10
|
+
Tupelo.application do
|
11
|
+
N_PHIL.times do |i|
|
12
|
+
child do
|
13
|
+
log.progname << ": phil #{i}"
|
14
|
+
write ["eat", i, 0] # amount eaten
|
15
|
+
|
16
|
+
N_ITER.times do
|
17
|
+
transaction do
|
18
|
+
# lock the resource (in transaction, so optimistically):
|
19
|
+
read ["chopstick", i]
|
20
|
+
read ["chopstick", (i+1)%N_PHIL]
|
21
|
+
|
22
|
+
# use the resource:
|
23
|
+
_,_,count = take ["eat", i, nil]
|
24
|
+
write ["eat", i, count+1]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
log "ate #{read(["eat", i, nil])[2]}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
local do
|
33
|
+
N_PHIL.times do |i|
|
34
|
+
write ["chopstick", i]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Dining philosopher example. Like dphil.rb, but uses transactions and can work
|
2
|
+
# correctly without the "room tickets", which are really global locks. We
|
3
|
+
# optimistically let all philosophers dine at the same time. This version is
|
4
|
+
# several times faster than the locking version (try increasing N_PHIL). Note,
|
5
|
+
# however, that in the transaction-based version only operations within the
|
6
|
+
# transaction are protected from concurrent users. That may not be suitable if
|
7
|
+
# the intent of the lock is to protect some resource external to the tuplespace.
|
8
|
+
|
9
|
+
require 'tupelo/app'
|
10
|
+
|
11
|
+
N_PHIL = 5
|
12
|
+
N_ITER = 10
|
13
|
+
|
14
|
+
Tupelo.application do
|
15
|
+
N_PHIL.times do |i|
|
16
|
+
child do
|
17
|
+
log.progname << ": phil #{i}"
|
18
|
+
write ["eat", i, 0] # amount eaten
|
19
|
+
|
20
|
+
N_ITER.times do
|
21
|
+
transaction do
|
22
|
+
# lock the resource (in transaction, so optimistically):
|
23
|
+
c0 = take ["chopstick", i]
|
24
|
+
c1 = take ["chopstick", (i+1)%N_PHIL]
|
25
|
+
|
26
|
+
# use the resource:
|
27
|
+
_,_,count = take ["eat", i, nil]
|
28
|
+
write ["eat", i, count+1]
|
29
|
+
|
30
|
+
# release the resource:
|
31
|
+
write c0, c1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
log "ate #{read(["eat", i, nil])[2]}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
local do
|
40
|
+
N_PHIL.times do |i|
|
41
|
+
write ["chopstick", i]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/example/dphil.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# Dining philosopher example, based on:
|
2
|
+
#
|
3
|
+
# http://www.lindaspaces.com/book/chap9.htm
|
4
|
+
|
5
|
+
require 'tupelo/app'
|
6
|
+
|
7
|
+
N_PHIL = 5
|
8
|
+
N_ITER = 10
|
9
|
+
|
10
|
+
Tupelo.application do
|
11
|
+
N_PHIL.times do |i|
|
12
|
+
child do
|
13
|
+
log.progname << ": phil #{i}"
|
14
|
+
write ["eat", i, 0] # amount eaten
|
15
|
+
|
16
|
+
N_ITER.times do
|
17
|
+
# lock the resource:
|
18
|
+
ticket = take ["room ticket"]
|
19
|
+
c0 = take ["chopstick", i]
|
20
|
+
c1 = take ["chopstick", (i+1)%N_PHIL]
|
21
|
+
|
22
|
+
# use the resource:
|
23
|
+
_,_,count = take ["eat", i, nil]
|
24
|
+
write ["eat", i, count+1]
|
25
|
+
|
26
|
+
# release the resource:
|
27
|
+
write c0, c1, ticket
|
28
|
+
end
|
29
|
+
|
30
|
+
log "ate #{read(["eat", i, nil])[2]}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
local do
|
35
|
+
N_PHIL.times do |i|
|
36
|
+
write ["chopstick", i]
|
37
|
+
end
|
38
|
+
(N_PHIL-1).times do
|
39
|
+
write ["room ticket"]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -2,25 +2,15 @@
|
|
2
2
|
# problem. You can observe this by seeing "FAILED" in the output of the former
|
3
3
|
# but not in the latter. This means that competing offers are resolved by the
|
4
4
|
# queue, rather than by propagating them to all clients.
|
5
|
+
#
|
6
|
+
# Run with --trace
|
5
7
|
|
6
8
|
require 'tupelo/app'
|
7
9
|
|
8
10
|
N = 3
|
9
11
|
|
10
12
|
Tupelo.application do |app|
|
11
|
-
app.child do |client| #
|
12
|
-
note = client.notifier
|
13
|
-
puts "%4s %4s %10s %s" % %w{ tick cid status operation }
|
14
|
-
loop do
|
15
|
-
status, tick, cid, op = note.wait
|
16
|
-
unless status == :attempt
|
17
|
-
s = status == :failure ? "FAILED" : ""
|
18
|
-
puts "%4d %4d %10s %p" % [tick, cid, s, op]
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
app.child do |client| # the lock manager
|
13
|
+
app.child passive: true do |client| # the lock manager
|
24
14
|
client.log.progname << " (lock mgr)"
|
25
15
|
waiters = Queue.new
|
26
16
|
|
@@ -50,7 +40,7 @@ Tupelo.application do |app|
|
|
50
40
|
app.child do |client|
|
51
41
|
client.write ["request", "resource", client.client_id, 0.5]
|
52
42
|
|
53
|
-
|
43
|
+
2.times do |j|
|
54
44
|
# Now we are ouside of transaction, but still no other client may use
|
55
45
|
# "resource" until lock expires (or is otherwise removed), as long
|
56
46
|
# as all clients follow the read protocol below.
|
@@ -64,7 +54,7 @@ Tupelo.application do |app|
|
|
64
54
|
end
|
65
55
|
end
|
66
56
|
|
67
|
-
app.child do |client|
|
57
|
+
app.child passive: true do |client|
|
68
58
|
# This client never even tries to lock the resource, so it cannot write.
|
69
59
|
client.transaction do |t|
|
70
60
|
t.read ["resource", client.client_id, nil]
|
data/example/lock-mgr.rb
CHANGED
@@ -1,21 +1,12 @@
|
|
1
|
+
# Run with --trace
|
2
|
+
|
1
3
|
require 'tupelo/app'
|
2
4
|
|
3
5
|
N = 3
|
4
6
|
|
5
7
|
Tupelo.application do |app|
|
6
|
-
app.child do |client| #
|
7
|
-
|
8
|
-
puts "%4s %4s %10s %s" % %w{ tick cid status operation }
|
9
|
-
loop do
|
10
|
-
status, tick, cid, op = note.wait
|
11
|
-
unless status == :attempt
|
12
|
-
s = status == :failure ? "FAILED" : ""
|
13
|
-
puts "%4d %4d %10s %p" % [tick, cid, s, op]
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
app.child do |client| # the lock manager
|
8
|
+
app.child passive: true do |client| # the lock manager
|
9
|
+
client.log.progname << " (lock mgr)"
|
19
10
|
loop do
|
20
11
|
client.write ["resource", "none"]
|
21
12
|
lock_id, client_id, duration = client.read ["resource", nil, nil]
|
@@ -35,9 +26,9 @@ Tupelo.application do |app|
|
|
35
26
|
t.write ["resource", client.client_id, 0.5]
|
36
27
|
end
|
37
28
|
# Thundering herd -- all N clients can respond at the same time.
|
38
|
-
#
|
29
|
+
# Can be avoided with a queue -- see lock-mgr-with-queue.rb.
|
39
30
|
|
40
|
-
|
31
|
+
2.times do |j|
|
41
32
|
# Now we are ouside of transaction, but still no other client may use
|
42
33
|
# "resource" until lock expires (or is otherwise removed), as long
|
43
34
|
# as all clients follow the read protocol below.
|
@@ -51,7 +42,7 @@ Tupelo.application do |app|
|
|
51
42
|
end
|
52
43
|
end
|
53
44
|
|
54
|
-
app.child do |client|
|
45
|
+
app.child passive: true do |client|
|
55
46
|
# This client never even tries to lock the resource, so it cannot write.
|
56
47
|
client.transaction do |t|
|
57
48
|
t.read ["resource", client.client_id, nil]
|
data/example/map-reduce-v2.rb
CHANGED
@@ -1,41 +1,9 @@
|
|
1
1
|
require 'tupelo/app'
|
2
|
+
require 'tupelo/util/boolean'
|
2
3
|
|
3
4
|
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
5
|
|
24
6
|
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
7
|
app.child do |client|
|
40
8
|
document = "I will not map reduce in class\n" * 10
|
41
9
|
lineno = 0
|
@@ -51,7 +19,7 @@ Tupelo.application do |app|
|
|
51
19
|
results = Hash.new(0)
|
52
20
|
lines_remaining = lineno
|
53
21
|
results_remaining = 0
|
54
|
-
result_template = client.
|
22
|
+
result_template = client.match_any(
|
55
23
|
{word: String, count: Integer},
|
56
24
|
{lineno: Integer, result_count: Integer}
|
57
25
|
)
|
@@ -70,12 +38,11 @@ Tupelo.application do |app|
|
|
70
38
|
end
|
71
39
|
end
|
72
40
|
|
73
|
-
client.log "
|
74
|
-
client.log "Press ^C to exit"
|
41
|
+
client.log "results = #{results}"
|
75
42
|
end
|
76
43
|
|
77
44
|
N.times do |i|
|
78
|
-
app.child do |client|
|
45
|
+
app.child passive: true do |client|
|
79
46
|
client.log.progname = "mapper #{i}"
|
80
47
|
|
81
48
|
loop do
|