tupelo 0.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 526ed0a8bc267debbd03d9306433256bc225ff41
4
- data.tar.gz: 1be5ebae3fff130e0d0dde261490604f4936d78e
3
+ metadata.gz: 1f296ad08b928805d512dc88cbdc0f5b1196fde6
4
+ data.tar.gz: f25b3ac8ef8fac55f5a9affc1b33cf42409aebc6
5
5
  SHA512:
6
- metadata.gz: a98abb4bf4465e3e166c5295065984120f62a06021e369aa53db12f8ad83308253cb170a2b63fca017c8b83a0abfc1d15c29268af1b22ac5bca89b0d3119ffb4
7
- data.tar.gz: 24a8d32264737f932a124abccaf9c9512febd28473930b762baaada6c159d8a5a7afb805b8ee55ac251764c9c19162b9185e29efd2508d06cbc1186c4a5ae066
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., --msgpaack, --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.
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. Deugging: in addition to the --info switch on all bin and example programs, bin/tspy is also really useful, and see the debugger client in example/lock-mgr.rb.
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
- 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.
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{ status tick client operation }
39
+ client.log "%10s %10s %10s %s" % %w{ tick client status operation }
36
40
  loop do
37
- client.log "%10s %10d %10d %p" % note.wait
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
@@ -155,7 +155,7 @@ EasyServe.start ez_opts do |ez|
155
155
  "starting shell. Commands: #{TupClient::CMD_ALIASES.join(", ")}"
156
156
  }
157
157
 
158
- require 'tupelo/irb-shell'
158
+ require 'tupelo/app/irb-shell'
159
159
  IRB.start_session(client)
160
160
 
161
161
  client.stop
@@ -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
@@ -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.or [0..2, String], [3..5, Hash]
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
- 1.times do
18
- begin
19
- t = client.transaction
20
- if t.take_nowait name: me
21
- you = t.take(name: nil)["name"]
22
- t.write(
23
- player1: me,
24
- player2: you)
25
- t.commit.wait
26
- break
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
- redo unless game
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| # a debugger client, to see what's happening
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
- 10.times do |j|
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| # a debugger client, to see what's happening
7
- note = client.notifier
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
- # A better example would have a queue -- see lock-mgr-with-queue.rb.
29
+ # Can be avoided with a queue -- see lock-mgr-with-queue.rb.
39
30
 
40
- 10.times do |j|
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]
@@ -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.or(
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 "DONE. results = #{results}"
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