tupelo 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +22 -0
  3. data/README.md +422 -0
  4. data/Rakefile +77 -0
  5. data/bench/pipeline.rb +25 -0
  6. data/bugs/take-write.rb +19 -0
  7. data/bugs/write-read.rb +15 -0
  8. data/example/add.rb +19 -0
  9. data/example/app-and-tup.rb +30 -0
  10. data/example/async-transaction.rb +16 -0
  11. data/example/balance-xfer-locking.rb +50 -0
  12. data/example/balance-xfer-retry.rb +55 -0
  13. data/example/balance-xfer.rb +33 -0
  14. data/example/boolean-match.rb +32 -0
  15. data/example/bounded-retry.rb +35 -0
  16. data/example/broker-locking.rb +43 -0
  17. data/example/broker-optimistic-async.rb +33 -0
  18. data/example/broker-optimistic.rb +41 -0
  19. data/example/broker-queue.rb +2 -0
  20. data/example/cancel.rb +17 -0
  21. data/example/concurrent-transactions.rb +39 -0
  22. data/example/custom-class.rb +29 -0
  23. data/example/custom-search.rb +27 -0
  24. data/example/fail-and-retry.rb +29 -0
  25. data/example/hash-tuples.rb +53 -0
  26. data/example/increment.rb +21 -0
  27. data/example/lock-mgr-with-queue.rb +75 -0
  28. data/example/lock-mgr.rb +62 -0
  29. data/example/map-reduce-v2.rb +96 -0
  30. data/example/map-reduce.rb +77 -0
  31. data/example/matching.rb +9 -0
  32. data/example/notify.rb +35 -0
  33. data/example/optimist.rb +20 -0
  34. data/example/pulse.rb +24 -0
  35. data/example/read-in-trans.rb +56 -0
  36. data/example/small-simplified.rb +18 -0
  37. data/example/small.rb +76 -0
  38. data/example/tcp.rb +35 -0
  39. data/example/timeout-trans.rb +21 -0
  40. data/example/timeout.rb +27 -0
  41. data/example/tiny-client.rb +14 -0
  42. data/example/tiny-server.rb +12 -0
  43. data/example/transaction-logic.rb +40 -0
  44. data/example/write-wait.rb +17 -0
  45. data/lib/tupelo/app.rb +121 -0
  46. data/lib/tupelo/archiver/tuplespace.rb +68 -0
  47. data/lib/tupelo/archiver/worker.rb +87 -0
  48. data/lib/tupelo/archiver.rb +86 -0
  49. data/lib/tupelo/client/common.rb +10 -0
  50. data/lib/tupelo/client/reader.rb +124 -0
  51. data/lib/tupelo/client/transaction.rb +455 -0
  52. data/lib/tupelo/client/tuplespace.rb +50 -0
  53. data/lib/tupelo/client/worker.rb +493 -0
  54. data/lib/tupelo/client.rb +44 -0
  55. data/lib/tupelo/version.rb +3 -0
  56. data/test/lib/mock-client.rb +38 -0
  57. data/test/lib/mock-msg.rb +47 -0
  58. data/test/lib/mock-queue.rb +42 -0
  59. data/test/lib/mock-seq.rb +50 -0
  60. data/test/lib/testable-worker.rb +24 -0
  61. data/test/stress/concurrent-transactions.rb +42 -0
  62. data/test/system/test-archiver.rb +35 -0
  63. data/test/unit/test-mock-queue.rb +93 -0
  64. data/test/unit/test-mock-seq.rb +39 -0
  65. data/test/unit/test-ops.rb +222 -0
  66. 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
+
@@ -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
@@ -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