tupelo 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/COPYING +22 -0
- data/README.md +422 -0
- data/Rakefile +77 -0
- data/bench/pipeline.rb +25 -0
- data/bugs/take-write.rb +19 -0
- data/bugs/write-read.rb +15 -0
- data/example/add.rb +19 -0
- data/example/app-and-tup.rb +30 -0
- data/example/async-transaction.rb +16 -0
- data/example/balance-xfer-locking.rb +50 -0
- data/example/balance-xfer-retry.rb +55 -0
- data/example/balance-xfer.rb +33 -0
- data/example/boolean-match.rb +32 -0
- data/example/bounded-retry.rb +35 -0
- data/example/broker-locking.rb +43 -0
- data/example/broker-optimistic-async.rb +33 -0
- data/example/broker-optimistic.rb +41 -0
- data/example/broker-queue.rb +2 -0
- data/example/cancel.rb +17 -0
- data/example/concurrent-transactions.rb +39 -0
- data/example/custom-class.rb +29 -0
- data/example/custom-search.rb +27 -0
- data/example/fail-and-retry.rb +29 -0
- data/example/hash-tuples.rb +53 -0
- data/example/increment.rb +21 -0
- data/example/lock-mgr-with-queue.rb +75 -0
- data/example/lock-mgr.rb +62 -0
- data/example/map-reduce-v2.rb +96 -0
- data/example/map-reduce.rb +77 -0
- data/example/matching.rb +9 -0
- data/example/notify.rb +35 -0
- data/example/optimist.rb +20 -0
- data/example/pulse.rb +24 -0
- data/example/read-in-trans.rb +56 -0
- data/example/small-simplified.rb +18 -0
- data/example/small.rb +76 -0
- data/example/tcp.rb +35 -0
- data/example/timeout-trans.rb +21 -0
- data/example/timeout.rb +27 -0
- data/example/tiny-client.rb +14 -0
- data/example/tiny-server.rb +12 -0
- data/example/transaction-logic.rb +40 -0
- data/example/write-wait.rb +17 -0
- data/lib/tupelo/app.rb +121 -0
- data/lib/tupelo/archiver/tuplespace.rb +68 -0
- data/lib/tupelo/archiver/worker.rb +87 -0
- data/lib/tupelo/archiver.rb +86 -0
- data/lib/tupelo/client/common.rb +10 -0
- data/lib/tupelo/client/reader.rb +124 -0
- data/lib/tupelo/client/transaction.rb +455 -0
- data/lib/tupelo/client/tuplespace.rb +50 -0
- data/lib/tupelo/client/worker.rb +493 -0
- data/lib/tupelo/client.rb +44 -0
- data/lib/tupelo/version.rb +3 -0
- data/test/lib/mock-client.rb +38 -0
- data/test/lib/mock-msg.rb +47 -0
- data/test/lib/mock-queue.rb +42 -0
- data/test/lib/mock-seq.rb +50 -0
- data/test/lib/testable-worker.rb +24 -0
- data/test/stress/concurrent-transactions.rb +42 -0
- data/test/system/test-archiver.rb +35 -0
- data/test/unit/test-mock-queue.rb +93 -0
- data/test/unit/test-mock-seq.rb +39 -0
- data/test/unit/test-ops.rb +222 -0
- metadata +134 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c7b7dc03d833db67770e720b9bea6ec44cecd57e
|
4
|
+
data.tar.gz: 86bda705a11cd4802746fe77d9f14e7c74537685
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: baa626f2c08643a301200896da84b68e2b6ed0d3d30fa9c296a36c514df660c035608eec8b77ace273093fed35673038dd273d996c8158a868455b70ee8ef850
|
7
|
+
data.tar.gz: 1b18b2f49961bb0741331dc493024f5643db0cf49ed2c0d0029ff05f8b2a2a44b5e98a2be61b6d6034fa49fbc6a901b811d00380106fcd43e83950de95340df9
|
data/COPYING
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013, Joel VanderWerf
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
|
13
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
14
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
15
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
16
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
17
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
18
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
19
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
20
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
21
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
22
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,422 @@
|
|
1
|
+
tupelo
|
2
|
+
==
|
3
|
+
|
4
|
+
A tuplespace that is fast, scalable, and language agnostic.
|
5
|
+
|
6
|
+
This is the reference implementation in ruby. It should be able to communicate with implementations in other languages. Planned implementation languages include C, Python, and Go.
|
7
|
+
|
8
|
+
Tupelo differs from other spaces in several ways:
|
9
|
+
|
10
|
+
* minimal central storage: the only state in the server is a counter and socket connections
|
11
|
+
|
12
|
+
* minimal central computation: just counter increment, message dispatch, and connection management
|
13
|
+
|
14
|
+
* clients do all the tuple work: registering and checking waiters, matching, searching, notifying, storing, inserting, deleting, persisting, etc. Each client is free to to decide how to do these things (application code is insulated from this, however). Special-purpose clients may use specialized algorithms and stores for the subspaces they manage.
|
15
|
+
|
16
|
+
* replication is inherent in the design (in fact it is unavoidable), for better or worse.
|
17
|
+
|
18
|
+
|
19
|
+
Getting started
|
20
|
+
==========
|
21
|
+
|
22
|
+
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).
|
23
|
+
|
24
|
+
2. Install the gem and its dependencies:
|
25
|
+
|
26
|
+
gem install tupelo
|
27
|
+
|
28
|
+
3. Try running tup:
|
29
|
+
|
30
|
+
$ tup
|
31
|
+
>> w ["hello", "world"]
|
32
|
+
>> ra
|
33
|
+
=> [["hello", "world"]]
|
34
|
+
>> t [nil, nil]
|
35
|
+
=> ["hello", "world"]
|
36
|
+
|
37
|
+
If you run tup with the --info switch it will tell you the aliases to the tuple API (and also tell you much about what is happening in your transactions). Briefly:
|
38
|
+
|
39
|
+
Write one or more tuples (and wait for the transaction to be recorded):
|
40
|
+
|
41
|
+
w <tuple>,...
|
42
|
+
write_wait <tuple>,...
|
43
|
+
|
44
|
+
Write without waiting:
|
45
|
+
|
46
|
+
write <tuple>,...
|
47
|
+
|
48
|
+
Write and then wait, under user control:
|
49
|
+
|
50
|
+
write(...).wait
|
51
|
+
|
52
|
+
Pulse a tuple or several (write but immediately delete it, like pubsub):
|
53
|
+
|
54
|
+
pl <tuple>,...
|
55
|
+
pulse_wait ...
|
56
|
+
|
57
|
+
Pulse without waiting:
|
58
|
+
|
59
|
+
pulse_nowait ...
|
60
|
+
|
61
|
+
Read tuple matching a template, waiting for a match to exist:
|
62
|
+
|
63
|
+
r <template>
|
64
|
+
read_wait <template>
|
65
|
+
|
66
|
+
Read tuple matching a template, without waiting for a match to exist:
|
67
|
+
|
68
|
+
read_nowait <template>
|
69
|
+
|
70
|
+
Read all tuples matching a template, no waiting:
|
71
|
+
|
72
|
+
ra <template>
|
73
|
+
read_all <template>
|
74
|
+
|
75
|
+
If the template is omitted, reads everything (careful, you get what you ask for!). The template can be a standard template as discussed below or anything with a #=== method. Hence
|
76
|
+
|
77
|
+
ra Hash
|
78
|
+
|
79
|
+
reads all hash tuples (and ignore array tuples), and
|
80
|
+
|
81
|
+
ra proc {|t| t.size==2}
|
82
|
+
|
83
|
+
reads all 2-tuples.
|
84
|
+
|
85
|
+
Take a tuple
|
86
|
+
|
87
|
+
t <tuple>
|
88
|
+
take <tuple>
|
89
|
+
|
90
|
+
Take a tuple and optimistically use the local value before the transaction is
|
91
|
+
complete:
|
92
|
+
|
93
|
+
x_final = take <tuple> do |x_optimistic|
|
94
|
+
...
|
95
|
+
end
|
96
|
+
|
97
|
+
It's possible that the block will be called with a value different from the eventual return value. It's also possible for the block to be called more than once.
|
98
|
+
|
99
|
+
Perform a general transaction:
|
100
|
+
|
101
|
+
result =
|
102
|
+
tr do |t| # tr is alias for transaction
|
103
|
+
rval = t.read ... # optimistic value
|
104
|
+
t.write ...
|
105
|
+
t.pulse ...
|
106
|
+
tval = t.take ... # optimistic value
|
107
|
+
[rval, tval] # pass out result
|
108
|
+
end
|
109
|
+
|
110
|
+
Note that the block may get executed more than once, if there is competition for the tuples that you are trying to #take. When the block exits, however, the transaction is final and universally accepted by all clients.
|
111
|
+
|
112
|
+
4. Run tup with a server file so that two sessions can interact. Do this in two terminals in the same dir:
|
113
|
+
|
114
|
+
$ tup svr
|
115
|
+
|
116
|
+
(The 'svr' argument names a file that the first instance of tup uses to store information like socket addresses and the second instance uses to connect. The first instance starts the servers as child processes. However, both instances appear in the terminal as interactive shells.)
|
117
|
+
|
118
|
+
5. Look at the examples. You may need to dig a bit to find the gem installation. For example:
|
119
|
+
|
120
|
+
ls /usr/local/lib/ruby/gems/2.0.0/gems
|
121
|
+
|
122
|
+
Note that all bin and example programs accept blob type (e.g., --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.
|
123
|
+
|
124
|
+
6. Deugging: in addition to --info, bin/tspy is also really useful, and see the debugger client in example/lock-mgr.rb.
|
125
|
+
|
126
|
+
|
127
|
+
What is a tuplespace?
|
128
|
+
=====================
|
129
|
+
|
130
|
+
A tuplespace is a service for coordination, configuration, and control of concurrent and distributed systems. The model it provides to processes is a shared space that they can use to communicate in a deterministic and sequential manner. (Deterministic in that all clients see the same, consistent view of the data.) The space contains tuples. The operations on the space are few, but powerful. It's not a database, but it might be a front-end for one or more databases.
|
131
|
+
|
132
|
+
See https://en.wikipedia.org/wiki/Tuple_space for general information and history. This project is strongly influenced by Masatoshi Seki's Rinda implementation, part of the Ruby standard library.
|
133
|
+
|
134
|
+
What is a tuple?
|
135
|
+
----------------
|
136
|
+
|
137
|
+
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.
|
138
|
+
|
139
|
+
A tuple is either an array:
|
140
|
+
|
141
|
+
["hello", 7]
|
142
|
+
[nil, true, false]
|
143
|
+
["foo", 3.2, [6,5,4], {"bar" => 3}]
|
144
|
+
|
145
|
+
... or a hash:
|
146
|
+
|
147
|
+
{name: "Myrtle", location: [100,200]}
|
148
|
+
{ [1,2] => 3, [5,7] => 12 }
|
149
|
+
|
150
|
+
In other words, a tuple is a fairly general object, though this depends on the serializer--see below. More or less, a tuple is anything that can be built out of:
|
151
|
+
|
152
|
+
* strings
|
153
|
+
|
154
|
+
* numbers
|
155
|
+
|
156
|
+
* nil, true, false
|
157
|
+
|
158
|
+
* arrays
|
159
|
+
|
160
|
+
* hashes
|
161
|
+
|
162
|
+
It's kind of like a "JSON object", except that in the json blob case, the hash keys can only be strings. In the case of the marshal and yaml modes, tuples can contain many other kinds of objects.
|
163
|
+
|
164
|
+
What is a template?
|
165
|
+
-------------------
|
166
|
+
|
167
|
+
A template an object that matches (or does not match) tuples. It's used for querying a tuplespace. Typically, a template looks just like a tuple, but possibly with wildcards of some sort. The template:
|
168
|
+
|
169
|
+
[3..5, Integer, /foo/, nil]
|
170
|
+
|
171
|
+
would match the tuple:
|
172
|
+
|
173
|
+
[4, 7, "foobar", "xyz"]
|
174
|
+
|
175
|
+
but not these tuples:
|
176
|
+
|
177
|
+
[6, 7, "foobar", "xyz"]
|
178
|
+
[3, 7.2, "foobar", "xyz"]
|
179
|
+
[3, 7, "fobar", "xyz"]
|
180
|
+
|
181
|
+
The nil wildcard matches anything.
|
182
|
+
|
183
|
+
Here's a template for matching some hash tuples:
|
184
|
+
|
185
|
+
{name: String, location: "home"}
|
186
|
+
|
187
|
+
This would match all tuples whose keys are "name" and "location" and whose values for those keys are any string and the string "home", respectively.
|
188
|
+
|
189
|
+
A template doesn't have to be a pattern, though. It can be anything with a #=== method. For example:
|
190
|
+
|
191
|
+
read_all proc {|t| some_predicate(t)}
|
192
|
+
read_all Hash
|
193
|
+
read_all Array
|
194
|
+
read_all Object
|
195
|
+
|
196
|
+
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.
|
197
|
+
|
198
|
+
What are the operations on tuples?
|
199
|
+
--------------------
|
200
|
+
|
201
|
+
* read - search the space for matching tuples, waiting if none found
|
202
|
+
|
203
|
+
* write - insert the tuple into the space
|
204
|
+
|
205
|
+
* take - search the space for matching tuples, waiting if none found, removing the tuple if found
|
206
|
+
|
207
|
+
* pulse - write and take the tuple; readers see it, but it cannot be taken
|
208
|
+
|
209
|
+
These operations have a few variations (wait vs nowait) and options (timeouts).
|
210
|
+
|
211
|
+
Transactions and optimistic concurrency
|
212
|
+
--------------------
|
213
|
+
|
214
|
+
Transactions combine operations into a group that take effect at the same instant in (logical) time, isolated from other transactions. However, it may take some time (both real and logical) to prepare the transaction: to find tuples that match the criteria of the read and take operations. Finding tuples may require searching (locally) for tuples, or waiting for new tuples to be written by others. Also, the transaction may fail even after matching tuples are found (when another process takes tuples of interest). Then the transaction needs to be prepared again. Once prepared, transaction is sent to all clients, where it may either succeed (globally) or fail (for the same reason as before--someone else grabbed our tuples). If it fails, then the preparation can begin again. A transaction guarantees that, when it completes, all the operations were performed on the tuples at the same logical time. It does not guarantee that the world stands still while one process is inside the `transaction {...}` block.
|
215
|
+
|
216
|
+
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.
|
217
|
+
|
218
|
+
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).
|
219
|
+
|
220
|
+
If you prefer classical tuplespace locking, you can simply take / write lock tuples. See the examples. If you have a lot of contention and want to avoid the thundering herd, see example/lock-mgr-with-queue.rb.
|
221
|
+
|
222
|
+
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...
|
223
|
+
|
224
|
+
ACID -- Atomic and Isolated are enforced by the transactions; Consistency is enforced by the 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.
|
225
|
+
|
226
|
+
On the CAP spectrum, tupelo tends towards consistency.
|
227
|
+
|
228
|
+
These 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 imposes a limitation on transactions over subspaces...
|
229
|
+
|
230
|
+
|
231
|
+
Advantages
|
232
|
+
==========
|
233
|
+
|
234
|
+
Tupelo can be used to impose a unified transactional structure and distributed access model on a mixture of programs and stores. ("Polyglot persistence".) Need examples....
|
235
|
+
|
236
|
+
Speed (latency, throughput):
|
237
|
+
|
238
|
+
* minimal system-wide bottlenecks
|
239
|
+
|
240
|
+
* read -- local and hence very fast
|
241
|
+
|
242
|
+
* write -- fast, pipelined (waiting for acknowledgement is optional);
|
243
|
+
|
244
|
+
* transactions -- combine several takes and writes, reducing latency and avoiding locking
|
245
|
+
|
246
|
+
Can use optimal data structure for each subspace of tuplespace.
|
247
|
+
|
248
|
+
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.
|
249
|
+
|
250
|
+
Data replication is easy--hard to avoid in fact.
|
251
|
+
|
252
|
+
Limitations
|
253
|
+
===========
|
254
|
+
|
255
|
+
Better for small messages, because they tend to propagate widely.
|
256
|
+
|
257
|
+
May stress network and local memory (but subspaces can help).
|
258
|
+
|
259
|
+
Worker thread has cpu cost (but subspaces can help).
|
260
|
+
|
261
|
+
What other potential problems and how does tupelo solve them?
|
262
|
+
|
263
|
+
|
264
|
+
Future
|
265
|
+
======
|
266
|
+
|
267
|
+
- Subspaces. Redundancy, for read-heavy systems (redundant array of in-memory sqlite, for example). Clients managing different subspaces may benefit by using different stores and algorithms.
|
268
|
+
|
269
|
+
- More persistence options.
|
270
|
+
|
271
|
+
- Fail-over. Robustness.
|
272
|
+
|
273
|
+
- Investigate nio4r for faster networking.
|
274
|
+
|
275
|
+
- Interoperable client and server implementations in C, Python, Go, ....
|
276
|
+
|
277
|
+
- UDP multicast.
|
278
|
+
|
279
|
+
- Tupelo as a service; specialized and replicated subspace managers as services.
|
280
|
+
|
281
|
+
|
282
|
+
|
283
|
+
Comparisons
|
284
|
+
===========
|
285
|
+
|
286
|
+
Redis
|
287
|
+
-----
|
288
|
+
|
289
|
+
Unlike redis, computations are not a centralized bottleneck. Set intersection, for example.
|
290
|
+
|
291
|
+
Pushing data to client eliminates need for polling, makes reads faster.
|
292
|
+
|
293
|
+
However, tupelo is not a substitute for the caching functionality of redis and memcache.
|
294
|
+
|
295
|
+
|
296
|
+
Rinda
|
297
|
+
-----
|
298
|
+
|
299
|
+
Very similar api.
|
300
|
+
|
301
|
+
No central bottleneck.
|
302
|
+
|
303
|
+
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.
|
304
|
+
|
305
|
+
Tupelo also supports custom classes in tuples, but only with marshal / yaml; must define #==; see example/custom-class.rb
|
306
|
+
|
307
|
+
Both: tuples can be arrays or hashes.
|
308
|
+
|
309
|
+
|
310
|
+
To compare
|
311
|
+
----------
|
312
|
+
|
313
|
+
* beanstalkd
|
314
|
+
|
315
|
+
* resque
|
316
|
+
|
317
|
+
* zookeeper -- totally ordered updates
|
318
|
+
|
319
|
+
* chubby
|
320
|
+
|
321
|
+
* doozer
|
322
|
+
|
323
|
+
* hazelcast
|
324
|
+
|
325
|
+
* lmax -- minimal spof
|
326
|
+
|
327
|
+
* datomic -- similar distribution of "facts", but not tuplespace
|
328
|
+
|
329
|
+
|
330
|
+
Architecture
|
331
|
+
============
|
332
|
+
|
333
|
+
Two central processes:
|
334
|
+
|
335
|
+
* message sequencer -- assigns unique increasing IDs to each message (a message is essentially a transaction containing operations on the tuplespace). This is the key to the whole design. By sequencing all transactions in a way that all clients agree with, the transactions can be applied (or rejected) by all clients without further negotiation.
|
336
|
+
|
337
|
+
* client sequencer -- assigns unique increasing IDs to clients when they join the distributed system
|
338
|
+
|
339
|
+
Specialized clients:
|
340
|
+
|
341
|
+
* archiver -- dumps tuplespace state to clients joining the system later than t=0
|
342
|
+
|
343
|
+
* tup -- command line shell for accessing (and creating) tuplespaces
|
344
|
+
|
345
|
+
* tspy -- uses the notification API to watch all events in the space
|
346
|
+
|
347
|
+
* queue / lock / lease managers (see examples)
|
348
|
+
|
349
|
+
General application clients:
|
350
|
+
|
351
|
+
* contain a worker thread and any number of application-level client threads
|
352
|
+
|
353
|
+
* worker thread manages local tuplespace state and requests to modify or access it
|
354
|
+
|
355
|
+
* client threads construct transactions and wait for results (communicating with the worker thread over queues)
|
356
|
+
|
357
|
+
Protocol
|
358
|
+
--------
|
359
|
+
|
360
|
+
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.
|
361
|
+
|
362
|
+
The protocol has two layers. The outer (message) layer is 6 fields, managed by the funl gem, using msgpack for serialization.
|
363
|
+
|
364
|
+
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.
|
365
|
+
|
366
|
+
Each inner serialization method ("blobber") has its own advantages and drawbacks:
|
367
|
+
|
368
|
+
* marshal is ruby only, but can contain the widest variation of objects
|
369
|
+
|
370
|
+
* yaml is portable and humanly readable, and still fairly diverse, but very inefficient
|
371
|
+
|
372
|
+
* msgpack and json (yajl) are both relatively efficient (in terms of packet size, as well as parse/emit time)
|
373
|
+
|
374
|
+
* msgpack and json both support non-blocking (buffered) reads, which can avoid bottlenecks due to slow senders or bad networks.
|
375
|
+
|
376
|
+
* msgpack and json support the least diversity of objects (just "JSON objects"), but msgpack also supports hash keys that are objects rather than just strings.
|
377
|
+
|
378
|
+
For most purposes, msgpack is the right default choice.
|
379
|
+
|
380
|
+
|
381
|
+
Development
|
382
|
+
===========
|
383
|
+
|
384
|
+
Patches and bug reports are most welcome.
|
385
|
+
|
386
|
+
This project is hosted at
|
387
|
+
|
388
|
+
https://github.com/vjoel/tupelo
|
389
|
+
|
390
|
+
Dependencies
|
391
|
+
------------
|
392
|
+
|
393
|
+
Gems that were developed to support this project:
|
394
|
+
|
395
|
+
* https://github.com/vjoel/atdo
|
396
|
+
|
397
|
+
* https://github.com/vjoel/easy-serve
|
398
|
+
|
399
|
+
* https://github.com/vjoel/funl
|
400
|
+
|
401
|
+
* https://github.com/vjoel/object-stream
|
402
|
+
|
403
|
+
* https://github.com/vjoel/object-template
|
404
|
+
|
405
|
+
Other gems:
|
406
|
+
|
407
|
+
* msgpack
|
408
|
+
|
409
|
+
* yajl-ruby (only used to support --json option)
|
410
|
+
|
411
|
+
|
412
|
+
Contact
|
413
|
+
=======
|
414
|
+
|
415
|
+
Joel VanderWerf, vjoel@users.sourceforge.net.
|
416
|
+
|
417
|
+
License and Copyright
|
418
|
+
========
|
419
|
+
|
420
|
+
Copyright (c) 2013, Joel VanderWerf
|
421
|
+
|
422
|
+
License for this project is BSD. See the COPYING file for the standard BSD license. The supporting gems developed for this project are similarly licensed.
|
data/Rakefile
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
PRJ = "tupelo"
|
5
|
+
|
6
|
+
def version
|
7
|
+
@version ||= begin
|
8
|
+
require 'tupelo/version'
|
9
|
+
warn "Tupelo::VERSION not a string" unless Tupelo::VERSION.kind_of? String
|
10
|
+
Tupelo::VERSION
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def tag
|
15
|
+
@tag ||= "#{PRJ}-#{version}"
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Run all tests"
|
19
|
+
task :test => %w{ test:unit test:system test:stress }
|
20
|
+
|
21
|
+
namespace :test do
|
22
|
+
desc "Run unit tests"
|
23
|
+
Rake::TestTask.new :unit do |t|
|
24
|
+
t.libs << "lib"
|
25
|
+
t.libs << "test/lib"
|
26
|
+
t.test_files = FileList["test/unit/*.rb"]
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "Run system tests"
|
30
|
+
task :system do |t|
|
31
|
+
FileList["test/system/*.rb"].each do |f|
|
32
|
+
ruby f
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "Run stress tests"
|
37
|
+
task :stress do |t|
|
38
|
+
FileList["test/stress/*.rb"].each do |f|
|
39
|
+
ruby f
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
desc "Commit, tag, and push repo; build and push gem"
|
45
|
+
task :release => "release:is_new_version" do
|
46
|
+
require 'tempfile'
|
47
|
+
|
48
|
+
sh "gem build #{PRJ}.gemspec"
|
49
|
+
|
50
|
+
file = Tempfile.new "template"
|
51
|
+
begin
|
52
|
+
file.puts "release #{version}"
|
53
|
+
file.close
|
54
|
+
sh "git commit --allow-empty -a -v -t #{file.path}"
|
55
|
+
ensure
|
56
|
+
file.close unless file.closed?
|
57
|
+
file.unlink
|
58
|
+
end
|
59
|
+
|
60
|
+
sh "git tag #{tag}"
|
61
|
+
sh "git push"
|
62
|
+
sh "git push --tags"
|
63
|
+
|
64
|
+
sh "gem push #{tag}.gem"
|
65
|
+
end
|
66
|
+
|
67
|
+
namespace :release do
|
68
|
+
desc "Diff to latest release"
|
69
|
+
task :diff do
|
70
|
+
latest = `git describe --abbrev=0 --tags --match '#{PRJ}-*'`
|
71
|
+
sh "git diff #{latest}"
|
72
|
+
end
|
73
|
+
|
74
|
+
task :is_new_version do
|
75
|
+
abort "#{tag} exists; update version!" unless `git tag -l #{tag}`.empty?
|
76
|
+
end
|
77
|
+
end
|
data/bench/pipeline.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'tupelo/app'
|
2
|
+
require 'benchmark'
|
3
|
+
|
4
|
+
N = 1000
|
5
|
+
|
6
|
+
Tupelo.application do |app|
|
7
|
+
app.local do |client|
|
8
|
+
Benchmark.bmbm(20) do |b|
|
9
|
+
GC.start
|
10
|
+
b.report('nowait') do
|
11
|
+
1000.times do
|
12
|
+
client.pulse_nowait [0]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
GC.start
|
17
|
+
b.report('wait') do
|
18
|
+
1000.times do
|
19
|
+
client.pulse_wait [0]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
data/bugs/take-write.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'tupelo/app'
|
2
|
+
|
3
|
+
Tupelo.application do |app|
|
4
|
+
app.local do |client|
|
5
|
+
client.write_wait [1]
|
6
|
+
|
7
|
+
note = client.notifier
|
8
|
+
|
9
|
+
client.transaction do |t|
|
10
|
+
x = t.take [1]
|
11
|
+
t.write x
|
12
|
+
end
|
13
|
+
|
14
|
+
note.wait
|
15
|
+
status, tick, cid, op = note.wait
|
16
|
+
p op # should "read [1]", not "write [1]; take [1]"
|
17
|
+
# this is just an optimization, not really a bug
|
18
|
+
end
|
19
|
+
end
|
data/bugs/write-read.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'tupelo/app'
|
2
|
+
|
3
|
+
Tupelo.application do |app|
|
4
|
+
app.local do |client|
|
5
|
+
begin
|
6
|
+
val = client.transaction timeout: 0.1 do |t|
|
7
|
+
t.write [1]
|
8
|
+
t.read [1] # similarly for take
|
9
|
+
end
|
10
|
+
p val # should be [1]
|
11
|
+
rescue TimeoutError => ex
|
12
|
+
puts ex
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/example/add.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'tupelo/app'
|
2
|
+
|
3
|
+
Tupelo.application do |app|
|
4
|
+
app.child do |client|
|
5
|
+
client.write ['x', 1]
|
6
|
+
client.write ['y', 2]
|
7
|
+
end
|
8
|
+
|
9
|
+
app.child do |client|
|
10
|
+
sum =
|
11
|
+
client.transaction do |t|
|
12
|
+
_, x = t.take ['x', Numeric]
|
13
|
+
_, y = t.take ['y', Numeric]
|
14
|
+
x + y
|
15
|
+
end
|
16
|
+
|
17
|
+
client.log "sum = #{sum}"
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# It's very easy to connect tup to an existing app.
|
2
|
+
# Just run this file, and then do (in another terminal):
|
3
|
+
#
|
4
|
+
# ../bin/tup servers-nnnn.yaml
|
5
|
+
#
|
6
|
+
# where nnnn is determined by looking in this dir. You can also
|
7
|
+
# set the filename explicitly (first ARGV), rather than let it be generated
|
8
|
+
# based on PID.
|
9
|
+
#
|
10
|
+
# Then, in tup, you can write [Numeric] tuples and they get summed:
|
11
|
+
#
|
12
|
+
# w [2]
|
13
|
+
# w [3]
|
14
|
+
# ra [nil] # => [[5]]
|
15
|
+
# w [7.4]
|
16
|
+
# ra [nil] # => [[12.4]]
|
17
|
+
|
18
|
+
require 'tupelo/app'
|
19
|
+
|
20
|
+
Tupelo.application do |app|
|
21
|
+
app.child do |client|
|
22
|
+
loop do
|
23
|
+
client.transaction do |t|
|
24
|
+
x, = t.take [Numeric]
|
25
|
+
y, = t.take [Numeric]
|
26
|
+
t.write [x + y]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'tupelo/app'
|
2
|
+
|
3
|
+
# see also cancel.rb
|
4
|
+
|
5
|
+
Tupelo.application do |app|
|
6
|
+
app.child do |client|
|
7
|
+
t = client.transaction.async do |t|
|
8
|
+
t.write ["pong"]
|
9
|
+
t.take ["ping"]
|
10
|
+
end
|
11
|
+
|
12
|
+
client.write ["ping"]
|
13
|
+
puts client.take ["pong"]
|
14
|
+
puts t.value
|
15
|
+
end
|
16
|
+
end
|