tupelo 0.21 → 0.22

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +171 -45
  3. data/bin/tup +51 -0
  4. data/example/counters/merge.rb +23 -3
  5. data/example/multi-tier/multi-sinatras.rb +5 -0
  6. data/example/riemann/event-subspace.rb +1 -4
  7. data/example/riemann/expiration-dbg.rb +2 -0
  8. data/example/riemann/producer.rb +4 -3
  9. data/example/riemann/v1/riemann.rb +2 -2
  10. data/example/riemann/v2/event-template.rb +71 -0
  11. data/example/riemann/v2/expirer.rb +1 -1
  12. data/example/riemann/v2/hash-store.rb +1 -0
  13. data/example/riemann/v2/ordered-event-store.rb +4 -1
  14. data/example/riemann/v2/riemann.rb +15 -8
  15. data/example/riemann/v2/sqlite-event-store.rb +117 -72
  16. data/example/sqlite/poi-store.rb +1 -1
  17. data/example/sqlite/poi-template.rb +2 -2
  18. data/example/sqlite/poi-v2.rb +2 -2
  19. data/example/subspaces/ramp.rb +9 -2
  20. data/example/tcp.rb +5 -0
  21. data/example/tiny-tcp-client.rb +15 -0
  22. data/example/tiny-tcp-service.rb +32 -0
  23. data/lib/tupelo/app.rb +4 -4
  24. data/lib/tupelo/app/builder.rb +2 -2
  25. data/lib/tupelo/app/irb-shell.rb +3 -3
  26. data/lib/tupelo/archiver.rb +0 -2
  27. data/lib/tupelo/archiver/tuplestore.rb +1 -1
  28. data/lib/tupelo/archiver/worker.rb +6 -6
  29. data/lib/tupelo/client.rb +2 -2
  30. data/lib/tupelo/client/reader.rb +3 -3
  31. data/lib/tupelo/client/scheduler.rb +1 -1
  32. data/lib/tupelo/client/subspace.rb +2 -2
  33. data/lib/tupelo/client/transaction.rb +28 -28
  34. data/lib/tupelo/client/tuplestore.rb +2 -2
  35. data/lib/tupelo/client/worker.rb +11 -10
  36. data/lib/tupelo/util/bin-circle.rb +8 -8
  37. data/lib/tupelo/util/boolean.rb +1 -1
  38. data/lib/tupelo/version.rb +1 -1
  39. data/test/lib/mock-client.rb +10 -10
  40. data/test/system/test-archiver.rb +2 -2
  41. data/test/unit/test-ops.rb +21 -21
  42. metadata +10 -20
  43. data/example/bingo/bingo-v2.rb +0 -20
  44. data/example/broker-queue.rb +0 -35
  45. data/example/child-of-child.rb +0 -34
  46. data/example/dataflow.rb +0 -21
  47. data/example/pregel/dist-opt.rb +0 -15
  48. data/example/riemann/v2/event-sql.rb +0 -56
  49. data/example/sqlite/tmp/poi-sqlite.rb +0 -35
  50. data/example/subspaces/addr-book-v1.rb +0 -104
  51. data/example/subspaces/addr-book-v2.rb +0 -16
  52. data/example/subspaces/sorted-set-space-OLD.rb +0 -130
  53. data/lib/tupelo/tuplets/persistent-archiver.rb +0 -86
  54. data/lib/tupelo/tuplets/persistent-archiver/tuplespace.rb +0 -91
  55. data/lib/tupelo/tuplets/persistent-archiver/worker.rb +0 -114
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3ab41264ead7c8233c4e48f9f568ae68fb04e843
4
- data.tar.gz: 01baffe87a234f70b3f2f84de84f38f62924d152
3
+ metadata.gz: 79341ae98464d4246f83672dc84801f0ec5c9979
4
+ data.tar.gz: 128e129fa77dfc86ddd3097a89d1563e9f20b8b6
5
5
  SHA512:
6
- metadata.gz: 6808153d5b48baf8e376a72d4179ca20851da83833f938dfa00120da1ec20fe9f4fe504b4006bd0c88f243d9a445f6856ecc70af6013d27c1f13140b5946b67a
7
- data.tar.gz: e57be7395a7d81c6cde4e3ec8c21f93761e76f1dc8c7d2f70a6331f59e563c5b7e0bd903119f2f86c2e161231f5cdfc4210144bdf223327c5f158d139caf5c2a
6
+ metadata.gz: d88280189aed380cc55f30182fd58a3e66daa58094bc80ca19c444df4719706b0a0ef427910b6b67fe276c5d73eb677159b02c61fab1a6f99af5f10561aa28e8
7
+ data.tar.gz: 1bb0816c7e482a3b6bd15ff42d6542c5a8fad88ce22b51cb36098a0b954501292f8fd8fdce7e59dd955b5d6f705169e67dbe8f607fd12b95bcf6ffb7afd777db
data/README.md CHANGED
@@ -1,12 +1,42 @@
1
1
  Tupelo
2
2
  ==
3
3
 
4
- Tupelo is a language-agnostic tuplespace for coordination of distributed programs. It is designed for distribution of both computation and storage, on disk and in memory, with pluggable storage adapters. Its programming model is small and semantically transparent: there are tuples (built from arrays, hashes, and scalars), a few operations on tuples (read, write, take), and transactions composed of these operations. This data-centric model, unlike RPC and most forms of messaging, decouples application endpoints from each other, not only in space and time, but also in referential structure: processes refer to data rather than to other processes.
4
+ Tupelo is a language-agnostic tuplespace for coordination of distributed programs. It is designed for distribution of both computation and storage, on disk and in memory, with pluggable storage adapters. Its programming model is small and semantically transparent: there are tuples (built from arrays, hashes, and scalars), a few operations on tuples (read, write, take), and transactions composed of these operations. This data-centric model, unlike RPC and most forms of messaging, decouples application endpoints from each other, not only in space and time, but also in referential structure: processes refer to data rather than to other processes or to channels.
5
5
 
6
6
  Tupelo is inspired by Masatoshi Seki's Rinda in the Ruby standard library, which in turn is based on David Gelernter's Linda. The programming models of Tupelo and Rinda are similar, except for the lack of transactions in Rinda. However, the implementations of the two are nearly opposite in architectural approach.
7
7
 
8
8
  This repository contains the reference implementation in Ruby, with documentation, tests, benchmarks, and examples. Implementations in other languages must communicate with this one.
9
9
 
10
+ News
11
+ ====
12
+
13
+ * 2014-June-26: Slides and videos from my talk on Calvin, which has some ideas in common with tupelo (plus a lot more):
14
+ * http://www.meetup.com/papers-we-love-too/events/171291972
15
+ * https://speakerdeck.com/paperswelove/pwlsf-number-4-equals-joel-vanderwerf-on-calvin
16
+ * https://plus.google.com/events/cprsf9edesa0ghm3b8c416ppa7o
17
+
18
+ * 2014-April-7: Want to use these fancy stores inside a client that you run from the command line? See [here](example/sqlite/README.md) and [here](example/riemann/README.md).
19
+
20
+ * 2014-April-1: [Riemann example](example/riemann) uses more stores in various roles:
21
+
22
+ * sqlite store for the consumer replicas, with indexes for
23
+ * service/host/time
24
+ * tags
25
+ * custom data
26
+ * rbtree store for the expirer
27
+ * hash store for the alerter
28
+
29
+ * 2014-March-19: release 0.21
30
+
31
+ * correct use of `store` and `space` terms everywhere
32
+ * sqlite example improvements
33
+ * API additions
34
+ * tuplestores can define #find_all_matches_for
35
+ * for efficient search in #read_all
36
+ * msgpack and json options to deserialize keys as symbols
37
+ * invoke via Client option and tup switch
38
+
39
+ See also https://github.com/vjoel/tupelo/releases.
10
40
 
11
41
  Documentation
12
42
  ============
@@ -22,6 +52,7 @@ In Depth
22
52
  * [Transactions](doc/transactions.md)
23
53
  * [Replication](doc/replication.md)
24
54
  * [Subspaces](doc/subspace.md)
55
+ * [Tuple stores](doc/tuplestores.md)
25
56
  * [Causality](doc/causality.md)
26
57
  * [Concurrency](doc/concurrency.md)
27
58
 
@@ -68,46 +99,7 @@ Tupelo is a flexible base layer for various distributed programming patterns and
68
99
 
69
100
  Tupelo can be used to impose a unified transactional structure and distributed access model on a mixture of programs and languages (polyglot computation) and a mixture of data stores (polyglot persistence), with consistent replication.
70
101
 
71
-
72
- Example
73
- -------
74
-
75
- This program counts prime numbers in an interval by distributing the problem to a set of hosts:
76
-
77
- require 'tupelo/app/remote'
78
-
79
- hosts = %w{itchy scratchy lisa bart} # ssh hosts with key-based auth
80
-
81
- Tupelo.tcp_application do
82
- hosts.each do |host|
83
- remote host: host, passive: true, eval: %{
84
- require 'prime' # ruby stdlib for prime factorization
85
- loop do
86
- _, input = take(["input", Integer])
87
- write ["output", input, input.prime_division]
88
- end
89
- }
90
- end
91
-
92
- local do
93
- inputs = 1_000_000_000_000 .. 1_000_000_000_200
94
-
95
- inputs.each do |input|
96
- write ["input", input]
97
- end
98
-
99
- count = 0
100
- inputs.size.times do |i|
101
- _, input, factors = take ["output", Integer, nil]
102
- count += 1 if factors.size == 1 and factors[0][1] == 1
103
- print "\rChecked #{i}"
104
- end
105
-
106
- puts "\nThere are #{count} primes in #{inputs}"
107
- end
108
- end
109
-
110
- Ssh is used to set up the remote processes. Additionally, with the `--tunnel` command line argument, all tuple communication is tunneled over ssh. More examples like this are in [example/map-reduce](example/map-reduce).
102
+ See the [example section](#examples) below and the [examples](example) directory.
111
103
 
112
104
 
113
105
  Limitations
@@ -180,14 +172,148 @@ Additional benefits (not related to message sequencing) include:
180
172
  Process control and tunneling are available independently of tupelo using the easy-serve gem.
181
173
 
182
174
 
175
+ Examples
176
+ ========
177
+
178
+ Distributed processing
179
+ ----------------------
180
+
181
+ This program counts prime numbers in an interval by distributing the problem to a set of hosts:
182
+
183
+ require 'tupelo/app/remote'
184
+
185
+ hosts = %w{itchy scratchy lisa bart} # ssh hosts with key-based auth
186
+
187
+ Tupelo.tcp_application do
188
+ hosts.each do |host|
189
+ remote host: host, passive: true, eval: %{
190
+ require 'prime' # ruby stdlib for prime factorization
191
+ loop do
192
+ _, input = take(["input", Integer])
193
+ write ["output", input, input.prime_division]
194
+ end
195
+ }
196
+ end
197
+
198
+ local do
199
+ inputs = 1_000_000_000_000 .. 1_000_000_000_200
200
+
201
+ inputs.each do |input|
202
+ write ["input", input]
203
+ end
204
+
205
+ count = 0
206
+ inputs.size.times do |i|
207
+ _, input, factors = take ["output", Integer, nil]
208
+ count += 1 if factors.size == 1 and factors[0][1] == 1
209
+ print "\rChecked #{i}"
210
+ end
211
+
212
+ puts "\nThere are #{count} primes in #{inputs}"
213
+ end
214
+ end
215
+
216
+ Ssh is used to set up the remote processes. Additionally, with the `--tunnel` command line argument, all tuple communication is tunneled over ssh. More examples like this are in [example/map-reduce](example/map-reduce), [example/pregel](example/pregel), and [example/parallel.rb](example/parallel.rb).
217
+
218
+ Distributed storage
219
+ -------------------
220
+
221
+ Here's an example that creates an in-memory sqlite in one client with a table for Points of Interest (POI). A second client populates that table by writing POI tuples and then executes a SQL delete by writing a tuple with the deletion parameters.
222
+
223
+ require 'tupelo/app'
224
+ require_relative 'poi-client' # run this in example/sqlite
225
+
226
+ Tupelo.application do
227
+ local do
228
+ POISPACE = PoiStore.define_poispace(self)
229
+ define_subspace("cmd", {id: nil, cmd: String, arg: nil})
230
+ define_subspace("rsp", {id: nil, result: nil})
231
+ end
232
+
233
+ child PoiClient, poispace: POISPACE, subscribe: "cmd", passive: true do
234
+ loop do
235
+ req = take subspace("cmd")
236
+ case req[:cmd]
237
+ when "delete box"
238
+ lat = req[:arg][:lat]; lng = req[:arg][:lng]
239
+ template = PoiTemplate.new(poi_template: subspace("poi"),
240
+ lat: lat[0]..lat[1], lng: lng[0]..lng[1])
241
+ deleted = []
242
+ transaction do
243
+ while poi = take_nowait(template)
244
+ deleted << poi
245
+ end
246
+ end
247
+ write id: req[:id], result: deleted
248
+ end
249
+ end
250
+ end
251
+
252
+ child subscribe: "rsp" do
253
+ write lat: 1.2, lng: 3.4, desc: "foo"
254
+ write lat: 5.6, lng: 7.8, desc: "bar"
255
+ write lat: 1.3, lng: 3.5, desc: "baz"
256
+
257
+ write id: 1, cmd: "delete box", arg: {lat: [1.0, 1.4], lng: [3.0, 4.0]}
258
+ rsp = take id: 1, result: nil
259
+ log "deleted: #{rsp["result"]}"
260
+ end
261
+ end
262
+
263
+ The output should be something like this:
264
+
265
+ A: client 3: deleted: [{"lat"=>1.2, "lng"=>3.4, "desc"=>"foo"}, {"lat"=>1.3, "lng"=>3.5, "desc"=>"baz"}]
266
+
267
+ See [example/sqlite](example/sqlite) for the complete example. More advanced versions of this example have remote, replicated sqlites for redundancy and load distribution.
268
+
269
+ Web app coordination
270
+ --------------------
271
+
272
+ This example runs several sinatra web apps and uses tupelo to set up a chat network between their users.
273
+
274
+ require 'tupelo/app'
275
+ require 'sinatra/base'
276
+
277
+ Tupelo.application do
278
+ [9001, 9002, 9003].each do |port|
279
+ child do |client|
280
+ Class.new(Sinatra::Base).class_eval do
281
+ post '/send' do
282
+ client.write ["message", params["dest"], params["text"]]
283
+ end
284
+
285
+ get '/recv' do
286
+ "%s for %s: %s\n" %
287
+ (client.take ["message", params["dest"], String])
288
+ end
289
+
290
+ set :port, port
291
+ run!
292
+ end
293
+ end
294
+ end
295
+ end
296
+
297
+ You can use curl to chat:
298
+
299
+ $ curl 'localhost:9001/send?text=hello&dest=fred' -d ''
300
+
301
+ and
302
+
303
+ $ curl 'localhost:9003/recv?dest=fred'
304
+ message for fred: hello
305
+
306
+ Note that the `recv` call waits for a message if none is available.
307
+
308
+ See also [example/multi-tier](example/multi-tier) and the chat server in [example/chat](example/chat).
309
+
310
+
183
311
  Development
184
312
  ===========
185
313
 
186
314
  Patches and bug reports are most welcome.
187
315
 
188
- This project is hosted at
189
-
190
- https://github.com/vjoel/tupelo
316
+ This project is hosted at https://github.com/vjoel/tupelo
191
317
 
192
318
  Dependencies
193
319
  ------------
@@ -219,7 +345,7 @@ Optional gems for some of the examples:
219
345
  Contact
220
346
  =======
221
347
 
222
- Joel VanderWerf, vjoel@users.sourceforge.net, @JoelVanderWerf.
348
+ Joel VanderWerf, vjoel@users.sourceforge.net, [@JoelVanderWerf](https://twitter.com/JoelVanderWerf).
223
349
 
224
350
  License and Copyright
225
351
  ========
data/bin/tup CHANGED
@@ -75,6 +75,17 @@ if ARGV.delete("-h") or ARGV.delete("--help")
75
75
  subscribe to specified subspaces; use "" for none
76
76
  by default, tup client subscribes to everything
77
77
 
78
+ --store class
79
+ --store class,subspace
80
+ Use a store of the specified class for the client's
81
+ local tuplestore. If subspace is given, pass the subspace's
82
+ spec as the first argument to <class>.new.
83
+ (The -I and -r options are useful.)
84
+
85
+ -I path append dir to $LOAD_PATH (the space char is necessary)
86
+
87
+ -r file require file (the space char is necessary)
88
+
78
89
  END
79
90
  exit
80
91
  end
@@ -93,6 +104,24 @@ if i=argv.index("--subscribe") # default is to subscribe to all
93
104
  subscribed_tags = argv.delete_at(i).split(",")
94
105
  end
95
106
 
107
+ __store = nil
108
+ if i=argv.index("--store")
109
+ argv.delete("--store")
110
+ __store = argv.delete_at(i).split(",")
111
+ end
112
+
113
+ _r_files = []
114
+ if i=argv.index("-r")
115
+ argv.delete("-r")
116
+ _r_files << argv.delete_at(i)
117
+ end
118
+
119
+ _I_dirs = []
120
+ if i=argv.index("-I")
121
+ argv.delete("-I")
122
+ _I_dirs << argv.delete_at(i)
123
+ end
124
+
96
125
  services_file = argv.shift
97
126
  proto = (argv.shift || :unix).to_sym
98
127
  addr = {proto: proto}
@@ -113,6 +142,24 @@ Tupelo.application(
113
142
  cseqd_addr: addr, # using same addr causes autoincrement of port/filename
114
143
  arcd_addr: addr) do
115
144
 
145
+ $LOAD_PATH.unshift(*_I_dirs)
146
+ _r_files.each do |f|
147
+ require f
148
+ end
149
+
150
+ if __store
151
+ store_args = [Object.const_get(__store[0])]
152
+ store_subspace = __store[1]
153
+ if store_subspace
154
+ local subscribe: nil do
155
+ log.debug "getting spec for subspace #{store_subspace.inspect}"
156
+ store_args << subspace(store_subspace, wait: true).spec
157
+ end
158
+ end
159
+ else
160
+ store_args = nil
161
+ end
162
+
116
163
  class TupClient < Tupelo::Client
117
164
  alias w write_wait
118
165
  alias pl pulse_wait
@@ -168,6 +215,10 @@ Tupelo.application(
168
215
  client_opts[:subscribe] = subscribed_tags
169
216
  end
170
217
 
218
+ if store_args
219
+ client_opts[:tuplestore] = store_args
220
+ end
221
+
171
222
  local TupClient, **client_opts do
172
223
  log.info {"cpu time: %.2fs" % Process.times.inject {|s,x|s+x}}
173
224
  log.info {"starting shell."}
@@ -17,18 +17,38 @@ Tupelo.application do
17
17
  # note: no need to init counter
18
18
  pids.each {|pid| Process.waitpid pid}
19
19
 
20
- # but we cannot read the counter(s) without first merging them
21
- # see also example/dedup.rb
22
- while transaction do
20
+ # The next block of code reads the "current value" of the counter.
21
+ #
22
+ # "Current" means globally correct as of the last global
23
+ # tick received at the local client.
24
+ #
25
+ # The "value" of the counter is defined to be the sum of all n
26
+ # occurring in some tuple matching {count: nil}.
27
+ #
28
+ # The difficulty is that we cannot read the counter without
29
+ # first merging all the matching count tuples. In a sense, this
30
+ # is like CRDTs in an eventually consistent DB.
31
+ #
32
+ # See also example/dedup.rb.
33
+ log "merging..." while transaction do
23
34
  c1 = take_nowait count: nil
24
35
  c2 = take_nowait count: nil
25
36
  if c1 and c2
26
37
  write count: c1["count"] + c2["count"]
27
38
  true
39
+ # We had to merge, so try again.
28
40
  elsif c1
29
41
  log c1
42
+ # At the tick of the second take_nowait, exactly one count
43
+ # tuple exists, so we can safely report that value as the
44
+ # global count, with the understanding that the count
45
+ # may have changed at a later tick (even by the time the
46
+ # transaction commits).
47
+ false
30
48
  else
31
49
  log count: 0
50
+ # At the time of this txn, no count tuple exists.
51
+ false
32
52
  end
33
53
  end
34
54
  end
@@ -3,6 +3,10 @@
3
3
  # http.rb example and shows how to use tupelo to coordinate multiple sinatra
4
4
  # instances.
5
5
  #
6
+ # This could easily be adapted to run the servers on remote hosts (using
7
+ # #remote instead of #child) and to restrict the network traffic to just
8
+ # the subspaces each client needs (using subscribe).
9
+ #
6
10
  # Depends on the sinatra and http gems.
7
11
 
8
12
  PORTS = [9001, 9002, 9003]
@@ -29,6 +33,7 @@ fork do
29
33
  end
30
34
 
31
35
  get '/recv' do
36
+ ## actually this should nt be a 'get' but a 'post' or 'delete'
32
37
  dest = params["dest"]
33
38
  _, _, text = client.take ["message", dest, String]
34
39
  text
@@ -25,10 +25,7 @@ class Tupelo::Client
25
25
  # event is considered valid for. Expired states
26
26
  # may be removed from the index.
27
27
 
28
- custom: nil # Any data
29
- # (not quite same as riemann's custom event attrs,
30
- # which are just arbitrary key-value pairs;
31
- # tupelo does not permit wildcards in keys)
28
+ custom: Hash # Arbitrary key-value pairs (not just strings).
32
29
  })
33
30
  end
34
31
 
@@ -1,4 +1,6 @@
1
1
  class Tupelo::Client
2
+ # For each newly expired event, check whether it was expired on time,
3
+ # and log the event and the result of the check.
2
4
  def run_expiration_debugger
3
5
  read Tupelo::Client::EXPIRED_EVENT do |event|
4
6
  event_exp = event["time"] + event["ttl"]
@@ -9,7 +9,7 @@ class Tupelo::Client
9
9
  tags: [],
10
10
  metric: 0,
11
11
  ttl: 0,
12
- custom: nil
12
+ custom: {}
13
13
  }.freeze
14
14
  end
15
15
 
@@ -36,11 +36,12 @@ class Tupelo::Client
36
36
  30.times do |ei|
37
37
  e_cpu = base_event.merge(
38
38
  service: "service #{i}",
39
- state: ei==10 ? "critical" : "ok",
39
+ state: (ei%10==0) ? "critical" : "ok",
40
40
  time: Time.now.to_f,
41
41
  ttl: 0.2,
42
42
  tags: ["cpu", "cumulative"],
43
- metric: Process.times.utime + Process.times.stime
43
+ metric: Process.times.utime + Process.times.stime,
44
+ custom: {foo: ["bar", 42*i + ei]}
44
45
  )
45
46
  write_event e_cpu
46
47
  sleep 0.2