tupelo 0.5 → 0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +22 -20
- data/example/broker-optimistic-v2.rb +7 -3
- data/example/broker-queue.rb +35 -0
- data/example/load-balancer.rb +51 -0
- data/example/message-bus.rb +59 -0
- data/example/parallel.rb +1 -0
- data/example/pubsub.rb +53 -0
- data/example/pulse.rb +2 -0
- data/example/remote.rb +28 -0
- data/example/take-many.rb +14 -0
- data/example/take-nowait.rb +20 -0
- data/lib/tupelo/app/remote.rb +68 -0
- data/lib/tupelo/app.rb +10 -0
- data/lib/tupelo/version.rb +1 -1
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1bd2a467d93d42b9237dc027c9ee33160c7f44a3
|
4
|
+
data.tar.gz: 16c3d47bc0acd033f1f8be8bc4578310edb3f653
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1f20d07a716db5de35606c9500562863c64a5f3e74129336bfb9d52221b2751385d3d2c8a6ca873bd9841c403edafcc5911df776a143bf83a743d0d257af7ce6
|
7
|
+
data.tar.gz: 40b77909cf54701fabfaf35d189dd15f9ebc8beb4bbfa8a6a55b2cc6ee1eb512eab682c5c26df375e94a939d1a018a1f463e1400ae9131412819ebd09f4c093c
|
data/README.md
CHANGED
@@ -97,7 +97,7 @@ Getting started
|
|
97
97
|
|
98
98
|
There is no guarantee that `x_final == x_optimistic`. The block may execute more than once.
|
99
99
|
|
100
|
-
Take a tuple matching a template, but only if a local match
|
100
|
+
Take a tuple matching a template, but only if a local match exists (otherwise return nil):
|
101
101
|
|
102
102
|
take_nowait <template>
|
103
103
|
|
@@ -105,12 +105,11 @@ Getting started
|
|
105
105
|
...
|
106
106
|
end
|
107
107
|
|
108
|
-
Note that a local match is still not a guarantee of
|
109
|
-
|
108
|
+
Note that a local match is still not a guarantee of `x_final == x_optimistic`. Another process may take `x_optimistic` first, and the take will be re-executed.
|
110
109
|
Perform a general transaction:
|
111
110
|
|
112
111
|
result =
|
113
|
-
|
112
|
+
transaction do |t|
|
114
113
|
rval = t.read ... # optimistic value
|
115
114
|
t.write ...
|
116
115
|
t.pulse ...
|
@@ -136,11 +135,12 @@ Getting started
|
|
136
135
|
|
137
136
|
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
137
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
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
144
|
|
145
145
|
The `Tupelo.application` command, provided by `tupelo/app`, is the source of all these options and is available to your programs.
|
146
146
|
|
@@ -155,7 +155,7 @@ See https://en.wikipedia.org/wiki/Tuple_space for general information and histor
|
|
155
155
|
What is a tuple?
|
156
156
|
----------------
|
157
157
|
|
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
|
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 tuple does not have an identity other than the data it contains. A tuplespace can contain multiple copies of the same tuple. (In the ruby client, two tuples are considered the same when they are #==.)
|
159
159
|
|
160
160
|
A tuple is either an array:
|
161
161
|
|
@@ -182,8 +182,6 @@ In other words, a tuple is a fairly general object, though this depends on the s
|
|
182
182
|
|
183
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.
|
184
184
|
|
185
|
-
A tuplespace can contain multiple copies of the same tuple.
|
186
|
-
|
187
185
|
What is a template?
|
188
186
|
-------------------
|
189
187
|
|
@@ -222,7 +220,7 @@ A template doesn't have to be a tuple pattern with wildcards, though. It can be
|
|
222
220
|
read_all Array
|
223
221
|
read_all Object
|
224
222
|
|
225
|
-
An optional library, `tupelo/util/boolean`, provides a #match_any method to construct the boolean or of other templates:
|
223
|
+
An optional library, `tupelo/util/boolean`, provides a #match_any method to construct the boolean `or` of other templates:
|
226
224
|
|
227
225
|
read_all match_any( [1,2,3], {foo: "bar"} )
|
228
226
|
|
@@ -237,7 +235,7 @@ What are the operations on tuples?
|
|
237
235
|
|
238
236
|
* take - search the space for matching tuples, waiting if none found, removing the tuple if found
|
239
237
|
|
240
|
-
* pulse - write and take the tuple; readers see it, but it cannot be taken (
|
238
|
+
* pulse - write and take the tuple; readers see it, but it cannot be taken by other client, and it cannot be read later (this is not a classical tuplespace operation, but is useful for publish-subscribe communication patterns)
|
241
239
|
|
242
240
|
These operations have a few variations (wait vs nowait) and options (timeouts).
|
243
241
|
|
@@ -250,9 +248,9 @@ However, it may take some time to prepare the transaction. This is true in terms
|
|
250
248
|
|
251
249
|
Transactions are not just about batching up operations into a more efficient package (though you can do that with the #batch api). A transaction makes the combined operations execute atomically: the transaction finishes only when all of its operations can be successfully performed. Writes and pulses can always succeed, but takes and reads only succeed if the tuples exist.
|
252
250
|
|
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).
|
251
|
+
Transactions give you a means of optimistic locking: the transaction proceeds in a way that depends on preconditions. See [example/increment.rb](example/increment.rb) for a very simple example. Not only can you make a transaction depend on the existence of a tuple, you can make the effect of the transaction a function of existing tuples (see [example/transaction-logic.rb](example/transaction-logic.rb) and [example/broker-optimistic.rb](example/broker-optimistic.rb)).
|
254
252
|
|
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.
|
253
|
+
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](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](example/lock-mgr-with-queue.rb).
|
256
254
|
|
257
255
|
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...
|
258
256
|
|
@@ -260,7 +258,7 @@ Transactions have a significant disadvantage compared to lock tuples: a transact
|
|
260
258
|
|
261
259
|
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.
|
262
260
|
|
263
|
-
On the CAP spectrum, tupelo tends towards consistency.
|
261
|
+
On the CAP spectrum, tupelo tends towards consistency: for all clients, write and take operations are applied in the same order, so the state of the entire system up through a given tick of discrete time is universally agreed upon. Of course, because of the difficulties of distributed systems, one client may not yet have seen the same range of ticks as another.
|
264
262
|
|
265
263
|
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.
|
266
264
|
|
@@ -268,7 +266,7 @@ Tupelo transactions do not require two-phase commit, because they are less power
|
|
268
266
|
Syntax
|
269
267
|
======
|
270
268
|
|
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.
|
269
|
+
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](example/add-dsl.rb) and [example/add.rb](example/add.rb).
|
272
270
|
|
273
271
|
|
274
272
|
Advantages
|
@@ -290,6 +288,8 @@ Speed (latency, throughput):
|
|
290
288
|
|
291
289
|
Can use optimal data structure for each subspace of tuplespace.
|
292
290
|
|
291
|
+
Decouples storage from query. (E.g. archiver for storage, optimized for just insert, delete, dump. And in-memory data structure, such as red-black tree, optimized for sorted query.)
|
292
|
+
|
293
293
|
Each client can have its own matching agorithms and api -- matching is not part of the comm protocol, which is defined purely in terms of tuples.
|
294
294
|
|
295
295
|
Data replication is easy--hard to avoid in fact.
|
@@ -348,7 +348,7 @@ Rinda has a severe bottleneck, though: all matching, waiting, etc. are performed
|
|
348
348
|
|
349
349
|
Rinda is rpc-based, which is slower and also more vulnerable due to the extra client-server state; tupelo is imlemented on a message layer, rather than rpc. This also helps with pipelined writes.
|
350
350
|
|
351
|
-
Tupelo also supports custom classes in tuples, but only with marshal / yaml; must define #==; see example/custom-class.rb
|
351
|
+
Tupelo also supports custom classes in tuples, but only with marshal / yaml; must define #==; see [example/custom-class.rb](example/custom-class.rb)
|
352
352
|
|
353
353
|
Both: tuples can be arrays or hashes.
|
354
354
|
|
@@ -372,10 +372,12 @@ To compare
|
|
372
372
|
|
373
373
|
* lmax -- minimal spof
|
374
374
|
|
375
|
-
* datomic -- similar distribution of "facts", but not tuplespace
|
375
|
+
* datomic -- similar distribution of "facts", but not tuplespace; similar use of pluggable storage managers
|
376
376
|
|
377
377
|
* job queues: sidekiq, resque, delayedjob, http://queues.io
|
378
378
|
|
379
|
+
* pubsubs: kafka
|
380
|
+
|
379
381
|
Architecture
|
380
382
|
============
|
381
383
|
|
@@ -11,20 +11,24 @@ Tupelo.application do
|
|
11
11
|
# sleep rand / 10 # reduce contention -- could also randomize inserts
|
12
12
|
child do
|
13
13
|
me = client_id
|
14
|
+
you = nil
|
14
15
|
write name: me
|
15
16
|
|
16
|
-
|
17
|
+
transaction do
|
17
18
|
game = read_nowait(
|
18
19
|
player1: nil,
|
19
20
|
player2: me)
|
20
|
-
|
21
|
+
|
22
|
+
if game
|
23
|
+
you = game["player1"]
|
24
|
+
break
|
25
|
+
end
|
21
26
|
|
22
27
|
take_nowait(name: me) or fail!
|
23
28
|
you = take(name: nil)["name"]
|
24
29
|
write(
|
25
30
|
player1: me,
|
26
31
|
player2: you)
|
27
|
-
you
|
28
32
|
end
|
29
33
|
|
30
34
|
log "now playing with #{you}"
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# more like how you would do it in redis, except that the queue is not stored in
|
2
|
+
# the central server, so operations on it are not a bottleneck, FWIW
|
3
|
+
|
4
|
+
require 'tupelo/app'
|
5
|
+
|
6
|
+
N_PLAYERS = 10
|
7
|
+
|
8
|
+
Tupelo.application do
|
9
|
+
N_PLAYERS.times do
|
10
|
+
# sleep rand / 10 # reduce contention -- could also randomize inserts
|
11
|
+
child do
|
12
|
+
me = client_id
|
13
|
+
write name: me
|
14
|
+
|
15
|
+
you = transaction do
|
16
|
+
game = read_nowait(
|
17
|
+
player1: nil,
|
18
|
+
player2: me)
|
19
|
+
break game["player1"] if game
|
20
|
+
|
21
|
+
unless take_nowait name: me
|
22
|
+
raise Tupelo::Client::TransactionFailure
|
23
|
+
end
|
24
|
+
|
25
|
+
you = take(name: nil)["name"]
|
26
|
+
write(
|
27
|
+
player1: me,
|
28
|
+
player2: you)
|
29
|
+
you
|
30
|
+
end
|
31
|
+
|
32
|
+
log "now playing with #{you}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'tupelo/app'
|
2
|
+
|
3
|
+
N_WORKERS = 10
|
4
|
+
N_CLIENTS = 2
|
5
|
+
|
6
|
+
Tupelo.application do
|
7
|
+
|
8
|
+
N_WORKERS.times do |i|
|
9
|
+
child passive: true do
|
10
|
+
log.progname = "worker #{i}"
|
11
|
+
worker_delay = rand 1.0..3.0 # how long it takes this worker to do a task
|
12
|
+
|
13
|
+
loop do
|
14
|
+
_, req_id, req_dat = take ["request", Integer, nil]
|
15
|
+
# we could modify this client's tuplespace storage so that
|
16
|
+
# the requst are taken in req_id order
|
17
|
+
|
18
|
+
log.info "handling request #{req_id} for #{req_dat.inspect}"
|
19
|
+
sleep worker_delay
|
20
|
+
write ["response", req_id]
|
21
|
+
log.info "handled request #{req_id} for #{req_dat.inspect}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
N_CLIENTS.times do |i|
|
27
|
+
child do
|
28
|
+
log.progname = "client #{i}"
|
29
|
+
|
30
|
+
req_data = 1..20 # task spec here
|
31
|
+
|
32
|
+
req_data.each do |req_dat|
|
33
|
+
req_id = nil
|
34
|
+
transaction do
|
35
|
+
# grouping the following ops in a transaction is not necessary for
|
36
|
+
# correctness, but it does reduce latency
|
37
|
+
_, req_id = take ["next_req_id", Integer]
|
38
|
+
write ["next_req_id", req_id + 1]
|
39
|
+
write ["request", req_id, req_dat]
|
40
|
+
end
|
41
|
+
|
42
|
+
take ["response", req_id]
|
43
|
+
log "got response for request #{req_id}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
local do
|
49
|
+
write ["next_req_id", 0]
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Asynchronously deliver messages via a shared "data bus" aka "data hub".
|
2
|
+
# A message is written to a channel. It remains there until a new value is
|
3
|
+
# written to the channel. A reader can pick it up at any time. Compare
|
4
|
+
# pubsub.rb.
|
5
|
+
#
|
6
|
+
# Run with the --show-final-state switch to verify that the last tuple
|
7
|
+
# written to a channel does stay there.
|
8
|
+
|
9
|
+
require 'tupelo/app'
|
10
|
+
|
11
|
+
show_final_state = ARGV.delete "--show-final-state"
|
12
|
+
|
13
|
+
N_PUBS = 6
|
14
|
+
N_SUBS = 6
|
15
|
+
N_CHAN = 3
|
16
|
+
|
17
|
+
Tupelo.application do
|
18
|
+
channels = (0...N_CHAN).map {|i| "channel #{i}"}
|
19
|
+
|
20
|
+
N_PUBS.times do |pi|
|
21
|
+
child do
|
22
|
+
read ['start']
|
23
|
+
delay = pi
|
24
|
+
sleep delay
|
25
|
+
ch = channels[ pi % channels.size ]
|
26
|
+
transaction do
|
27
|
+
take_nowait [ch, nil]
|
28
|
+
write [ch, "pub #{pi} slept for #{delay} sec"]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
N_SUBS.times do |si|
|
34
|
+
child passive: true do # passive means process will exit when pubs exit
|
35
|
+
log.progname = "sub #{si}"
|
36
|
+
ch = channels[ si % channels.size ]
|
37
|
+
loop do
|
38
|
+
sleep 1
|
39
|
+
t = read_nowait [ch, nil]
|
40
|
+
log t if t
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
if show_final_state
|
46
|
+
child passive: true do
|
47
|
+
log.progname = "final"
|
48
|
+
def self.stop
|
49
|
+
log read_all
|
50
|
+
super
|
51
|
+
end
|
52
|
+
sleep
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
local do
|
57
|
+
write ['start']
|
58
|
+
end
|
59
|
+
end
|
data/example/parallel.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# like gnu parallel
|
data/example/pubsub.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# Synchronously deliver published messages to subscribers. Subscriber gets the
|
2
|
+
# message only if waiting while the message is sent.
|
3
|
+
# Compare message-bus.rb.
|
4
|
+
#
|
5
|
+
# Run with the --show-final-state switch to verify that published tuples
|
6
|
+
# don't stay in the space.
|
7
|
+
|
8
|
+
require 'tupelo/app'
|
9
|
+
|
10
|
+
show_final_state = ARGV.delete "--show-final-state"
|
11
|
+
|
12
|
+
N_PUBS = 6
|
13
|
+
N_SUBS = 6
|
14
|
+
N_CHAN = 3
|
15
|
+
|
16
|
+
Tupelo.application do
|
17
|
+
channels = (0...N_CHAN).map {|i| "channel #{i}"}
|
18
|
+
|
19
|
+
N_PUBS.times do |pi|
|
20
|
+
child do
|
21
|
+
read ['start']
|
22
|
+
delay = pi/10.0
|
23
|
+
sleep delay
|
24
|
+
ch = channels[ pi % channels.size ]
|
25
|
+
pulse [ch, "pub #{pi} slept for #{delay} sec"]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
N_SUBS.times do |si|
|
30
|
+
child passive: true do # passive means process will exit when pubs exit
|
31
|
+
log.progname = "sub #{si}"
|
32
|
+
ch = channels[ si % channels.size ]
|
33
|
+
loop do
|
34
|
+
log read [ch, nil]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
if show_final_state
|
40
|
+
child passive: true do
|
41
|
+
log.progname = "final"
|
42
|
+
def self.stop
|
43
|
+
log read_all
|
44
|
+
super
|
45
|
+
end
|
46
|
+
sleep
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
local do
|
51
|
+
write ['start']
|
52
|
+
end
|
53
|
+
end
|
data/example/pulse.rb
CHANGED
data/example/remote.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# simple use of remote to start process on remote host and connect it to
|
2
|
+
# the tupelo app
|
3
|
+
|
4
|
+
require 'tupelo/app/remote'
|
5
|
+
|
6
|
+
host = ARGV.shift or abort "usage: #$0 <ssh-hostname>"
|
7
|
+
|
8
|
+
Tupelo.tcp_application do
|
9
|
+
remote host: host do
|
10
|
+
write host: `hostname`.chomp, mode: "drb", client: client_id
|
11
|
+
# this actually returns local hostname, because the block executes
|
12
|
+
# locally -- only the tupelo ops are remote. So this mode is really
|
13
|
+
# only for examples and tests, not production.
|
14
|
+
end
|
15
|
+
|
16
|
+
remote host: host, eval: %{
|
17
|
+
write host: `hostname`.chomp, mode: "eval", client: client_id
|
18
|
+
}
|
19
|
+
# rather than embed large chunks of code in the string, it's better to
|
20
|
+
# load or require a file and pass self (which is a Client instance) to
|
21
|
+
# a method in that file.
|
22
|
+
|
23
|
+
local do
|
24
|
+
2.times do
|
25
|
+
log take host: nil, mode: nil, client: nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Run this with --trace to see that, even in the FAIL case, take_nowait never
|
2
|
+
# hangs waiting for a match. The "ready" tuple is just to keep the take
|
3
|
+
# requests fairly close in time, increasing the chance of transaction failure.
|
4
|
+
|
5
|
+
require 'tupelo/app'
|
6
|
+
|
7
|
+
Tupelo.application do
|
8
|
+
20.times do
|
9
|
+
child do
|
10
|
+
read ["ready"]
|
11
|
+
r = take_nowait [1]
|
12
|
+
log "winner! result = #{r.inspect}" if r
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
local do
|
17
|
+
write [1]
|
18
|
+
write ["ready"]
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'tupelo/app'
|
2
|
+
|
3
|
+
module Tupelo
|
4
|
+
class AppBuilder
|
5
|
+
# Perform client operations on another host.
|
6
|
+
#
|
7
|
+
# There are two modes: drb and eval. In the drb mode, a block executes
|
8
|
+
# locally, performing tuplespace operations by proxy to the remote
|
9
|
+
# tupelo client instance. Results of the ops are available in the local
|
10
|
+
# process. This is very useful for testing and examples.
|
11
|
+
#
|
12
|
+
# In the eval mode, a string is evaluated on the remote host in the context
|
13
|
+
# of a client instance.
|
14
|
+
#
|
15
|
+
# There are some minor limitations compared to #child:
|
16
|
+
#
|
17
|
+
# In eval mode, the code string is always treated as in the arity 0 case
|
18
|
+
# of #child, in other words "DSL mode", in other words the self is the
|
19
|
+
# client.
|
20
|
+
#
|
21
|
+
# Unlike #child, there is no mode that returns a Client instance.
|
22
|
+
#
|
23
|
+
def remote client_class = Client,
|
24
|
+
client_lib: 'tupelo/client', host: nil, **opts
|
25
|
+
require 'easy-serve/remote'
|
26
|
+
raise if opts[:passive] ## not supported yet
|
27
|
+
## detach option so that remote process doesn't keep ssh connection
|
28
|
+
snames = :seqd, :cseqd, :arcd
|
29
|
+
|
30
|
+
if opts[:eval]
|
31
|
+
ez.remote *snames, host: host, **opts, eval: %{
|
32
|
+
require #{client_lib.inspect}
|
33
|
+
|
34
|
+
seqd, cseqd, arcd = *conns
|
35
|
+
client_class = Object.const_get(#{client_class.name.inspect})
|
36
|
+
|
37
|
+
begin
|
38
|
+
log.progname = "client <starting in \#{log.progname}>"
|
39
|
+
client = client_class.new(
|
40
|
+
seq: seqd, cseq: cseqd, arc: arcd, log: log)
|
41
|
+
client.start do
|
42
|
+
log.progname = "client \#{client.client_id}"
|
43
|
+
end
|
44
|
+
client.instance_eval #{opts[:eval].inspect}
|
45
|
+
ensure
|
46
|
+
client.stop if client
|
47
|
+
end
|
48
|
+
}
|
49
|
+
|
50
|
+
elsif block_given?
|
51
|
+
block = Proc.new
|
52
|
+
ez.remote *snames, host: host, **opts do |seqd, cseqd, arcd|
|
53
|
+
run_client client_class,
|
54
|
+
seq: seqd, cseq: cseqd, arc: arcd, log: log do |client|
|
55
|
+
if block.arity == 0
|
56
|
+
client.instance_eval &block
|
57
|
+
else
|
58
|
+
yield client
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
else
|
64
|
+
raise ArgumentError, "cannot select remote mode based on arguments"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/tupelo/app.rb
CHANGED
@@ -75,6 +75,16 @@ module Tupelo
|
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
|
+
# same as application, but with tcp sockets the default
|
79
|
+
def self.tcp_application argv: ARGV,
|
80
|
+
servers_file: nil, blob_type: nil,
|
81
|
+
seqd_addr: [:tcp, nil, 0],
|
82
|
+
cseqd_addr: [:tcp, nil, 0],
|
83
|
+
arcd_addr: [:tcp, nil, 0], &block
|
84
|
+
application argv: ARGV, servers_file: servers_file, blob_type: blob_type,
|
85
|
+
seqd_addr: seqd_addr, cseqd_addr: cseqd_addr, arcd_addr: arcd_addr, &block
|
86
|
+
end
|
87
|
+
|
78
88
|
#blob_type: 'msgpack' # the default
|
79
89
|
#blob_type: 'marshal' # if you need to pass general ruby objects
|
80
90
|
#blob_type: 'yaml' # less general ruby objects, but cross-language
|
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.6'
|
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-
|
11
|
+
date: 2013-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: atdo
|
@@ -79,6 +79,7 @@ files:
|
|
79
79
|
- README.md
|
80
80
|
- COPYING
|
81
81
|
- Rakefile
|
82
|
+
- lib/tupelo/app/remote.rb
|
82
83
|
- lib/tupelo/app/irb-shell.rb
|
83
84
|
- lib/tupelo/app/trace.rb
|
84
85
|
- lib/tupelo/client.rb
|
@@ -96,9 +97,11 @@ files:
|
|
96
97
|
- bench/pipeline.rb
|
97
98
|
- bugs/write-read.rb
|
98
99
|
- bugs/take-write.rb
|
100
|
+
- example/pubsub.rb
|
99
101
|
- example/timeout-trans.rb
|
100
102
|
- example/tiny-client.rb
|
101
103
|
- example/add.rb
|
104
|
+
- example/parallel.rb
|
102
105
|
- example/app-and-tup.rb
|
103
106
|
- example/small.rb
|
104
107
|
- example/bounded-retry.rb
|
@@ -124,7 +127,10 @@ files:
|
|
124
127
|
- example/fail-and-retry.rb
|
125
128
|
- example/dphil-optimistic-v2.rb
|
126
129
|
- example/broker-optimistic-v2.rb
|
130
|
+
- example/remote.rb
|
131
|
+
- example/take-nowait.rb
|
127
132
|
- example/optimist.rb
|
133
|
+
- example/message-bus.rb
|
128
134
|
- example/balance-xfer-locking.rb
|
129
135
|
- example/increment.rb
|
130
136
|
- example/custom-class.rb
|
@@ -133,8 +139,11 @@ files:
|
|
133
139
|
- example/broker-optimistic.rb
|
134
140
|
- example/notify.rb
|
135
141
|
- example/small-simplified.rb
|
142
|
+
- example/broker-queue.rb
|
136
143
|
- example/async-transaction.rb
|
137
144
|
- example/boolean-match.rb
|
145
|
+
- example/load-balancer.rb
|
146
|
+
- example/take-many.rb
|
138
147
|
- example/dphil.rb
|
139
148
|
- test/lib/testable-worker.rb
|
140
149
|
- test/lib/mock-seq.rb
|