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
@@ -23,14 +23,14 @@ Tupelo.application do
23
23
  when "find box"
24
24
  arg = req[:arg] ## should validate args
25
25
  lat = arg[:lat]; lng = arg[:lng]
26
- template = PoiTemplate.new(poi_template: poispace,
26
+ template = PoiTemplate.new(poispace,
27
27
  lat: lat[0]..lat[1], lng: lng[0]..lng[1])
28
28
  write id: req[:id], result: read_all(template)
29
29
 
30
30
  when "delete box"
31
31
  arg = req[:arg]
32
32
  lat = arg[:lat]; lng = arg[:lng]
33
- template = PoiTemplate.new(poi_template: poispace,
33
+ template = PoiTemplate.new(poispace,
34
34
  lat: lat[0]..lat[1], lng: lng[0]..lng[1])
35
35
 
36
36
  deleted = []
@@ -1,6 +1,7 @@
1
1
  # Read-atomic multipartition transactions, as per:
2
2
  # http://www.youtube.com/watch?v=_rAdJkAbGls (around minutes 28-30)
3
3
  # http://www.bailis.org/blog/non-blocking-transactional-atomicity
4
+ # http://www.bailis.org/blog/scalable-atomic-visibility-with-ramp-transactions
4
5
  #
5
6
  # Example of transacting separately on two subspaces (i.e. shardable subsets of
6
7
  # the tuplespace), but hiding intermediate tuples so that the results show up
@@ -9,7 +10,7 @@
9
10
  # tupelo transactions.)
10
11
  #
11
12
  # In tupelo, we could use the classic tuplespace technique of taking a lock
12
- # tuple to protect the sequence ot two transactions on the two subspaces, but
13
+ # tuple to protect the sequence of two transactions on the two subspaces, but
13
14
  # that would reduce concurrency and require a lease mechanism in case the lock
14
15
  # holder dies. That's possible, but not scalable. So we use transactions with a
15
16
  # trick...
@@ -33,7 +34,7 @@ X_REPLICATIONS = 1 # number of copies of the shard of X data
33
34
  Y_REPLICATIONS = 1 # number of copies of the shard of Y data
34
35
 
35
36
  def next_local_id
36
- @counter = 0
37
+ @counter ||= 0
37
38
  @counter += 1
38
39
  # Protect this with a mutex or queue if other threads need it, or
39
40
  # use the atomic gem. It's ok in a multiprocess app without mutex,
@@ -128,6 +129,12 @@ Tupelo.application do
128
129
  # This doesn't test that RAMP is working -- it will always see a consistent
129
130
  # view because of tupelo, even without the pending/ack trick. It is more
130
131
  # informative to look at the log output from the x and y clients.
132
+ #
133
+ # The key point of this example is that we could write a reader process that
134
+ # doesn't use tupelo at all, but accesses the data stores directly (assuming
135
+ # we're using a client-server store like postgres or a concurrent key-value
136
+ # store like leveldb or lmdb). This non-tupelo process would only need to be
137
+ # aware of the RAMP semantics of pending and id fields.
131
138
  child subscribe: ["x", "y"], passive: true do
132
139
  log.progname = "reader"
133
140
  read do |t|
@@ -18,6 +18,11 @@
18
18
  # Then run a client like this:
19
19
  #
20
20
  # bin/tup remote-copy-of-tcp.yaml
21
+ #
22
+ # If you have ssh set up, you don't even need to copy the file. Just reference
23
+ # it in the same way you would with scp:
24
+ #
25
+ # bin/tup host:tcp.yaml
21
26
 
22
27
  require 'tupelo/app'
23
28
 
@@ -0,0 +1,15 @@
1
+ # See tiny-tcp-service.rb
2
+
3
+ require 'tupelo/app'
4
+
5
+ Tupelo.application do
6
+ if owns_services
7
+ abort "service not running"
8
+ end
9
+
10
+ local do
11
+ x = rand(0..100); y = rand(0..100)
12
+ write [x, y]
13
+ log "%p + %p = %p" % take([x, y, Numeric])
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ # Run this with a specified filename:
2
+ #
3
+ # ruby tiny-tcp-service.rb srv.yaml
4
+ #
5
+ # Then you can run the client locally:
6
+ #
7
+ # ruby tiny-tcp-client.rb srv.yaml
8
+ #
9
+ # or from a remote host that can ssh back to the server
10
+ #
11
+ # ruby tiny-tcp-client.rb serverhost:path/to/srv.yaml
12
+ #
13
+ # (Use the --tunnel switch to send the data over ssh, as well.)
14
+ #
15
+ # You can also access the service using tup:
16
+ #
17
+ # tup srv.yaml
18
+ # >> w [12, 34]
19
+ # >> r [12, 34, nil]
20
+ # => [12, 34, 46]
21
+
22
+ require 'tupelo/app'
23
+
24
+ Tupelo.tcp_application do
25
+ puts "service started"
26
+ local do
27
+ loop do
28
+ x, y = take [Numeric, Numeric]
29
+ write [x, y, x+y]
30
+ end
31
+ end
32
+ end
@@ -34,7 +34,7 @@ module Tupelo
34
34
 
35
35
  opts[:trace] = argv.delete("--trace")
36
36
  opts[:tunnel] = argv.delete("--tunnel")
37
-
37
+
38
38
  [argv, opts]
39
39
  end
40
40
 
@@ -54,7 +54,7 @@ module Tupelo
54
54
  def self.application argv: nil,
55
55
  services_file: nil, blob_type: nil,
56
56
  seqd_addr: {}, cseqd_addr: {}, arcd_addr: {}, **opts, &block
57
-
57
+
58
58
  unless argv
59
59
  argv, h = parse_args(ARGV)
60
60
  opts.merge! h
@@ -121,7 +121,7 @@ module Tupelo
121
121
 
122
122
  app = AppBuilder.new(ez, argv: argv.dup,
123
123
  owns_services: owns_services, tunnel_default: tunnel_default)
124
-
124
+
125
125
  if enable_trace
126
126
  require 'tupelo/app/trace'
127
127
  app.start_trace
@@ -137,7 +137,7 @@ module Tupelo
137
137
 
138
138
  if block
139
139
  if block.arity == 0
140
- app.instance_eval &block
140
+ app.instance_eval(&block)
141
141
  else
142
142
  yield app
143
143
  end
@@ -53,7 +53,7 @@ module Tupelo
53
53
  run_client client_class, **opts do |client|
54
54
  if block
55
55
  if block.arity == 0
56
- client.instance_eval &block
56
+ client.instance_eval(&block)
57
57
  else
58
58
  yield client
59
59
  end
@@ -76,7 +76,7 @@ module Tupelo
76
76
  run_client client_class, **opts do |client|
77
77
  if block
78
78
  if block.arity == 0
79
- client.instance_eval &block
79
+ client.instance_eval(&block)
80
80
  else
81
81
  yield client
82
82
  end
@@ -7,7 +7,7 @@ module IRB
7
7
  def IRB.parse_opts
8
8
  # Don't touch ARGV, which belongs to the app which called this module.
9
9
  end
10
-
10
+
11
11
  def IRB.start_session(*args)
12
12
  unless $irb
13
13
  IRB.setup nil
@@ -34,7 +34,7 @@ module IRB
34
34
  trap 'INT' do
35
35
  $irb.signal_handle
36
36
  end
37
-
37
+
38
38
  custom_configuration if defined?(IRB.custom_configuration)
39
39
 
40
40
  begin
@@ -44,7 +44,7 @@ module IRB
44
44
  ensure
45
45
  IRB.irb_at_exit
46
46
  end
47
-
47
+
48
48
  ## might want to reset your app's interrupt handler here
49
49
  end
50
50
  end
@@ -6,8 +6,6 @@ require 'funl/history-client'
6
6
  ## should manipulate tuples as strings (at least in msgpack/json cases) instead
7
7
  ## of objects -- use msgpack extension for #hash and #== on packed objects
8
8
 
9
- class Tupelo::Archiver < Tupelo::Client; end
10
-
11
9
  require 'tupelo/archiver/worker'
12
10
  require 'tupelo/archiver/tuplestore' ## unless persistent?
13
11
 
@@ -1,4 +1,4 @@
1
- class Tupelo::Archiver
1
+ class Tupelo::Archiver < Tupelo::Client
2
2
  # Faster than the default tuplestore, but does not support some things
3
3
  # that are not needed in the Archiver: template searches, for example.
4
4
  class TupleStore
@@ -1,11 +1,11 @@
1
1
  require 'funl/history-worker'
2
2
 
3
- class Tupelo::Archiver
3
+ class Tupelo::Archiver < Tupelo::Client
4
4
  class Worker < Tupelo::Client::Worker
5
5
  include Funl::HistoryWorker
6
-
6
+
7
7
  def initialize *args, **opts
8
- super *args
8
+ super(*args)
9
9
  @scheduled_actions = Hash.new {|h,k| h[k] = []}
10
10
  @opts = opts
11
11
  end
@@ -13,7 +13,7 @@ class Tupelo::Archiver
13
13
  def tuplestore
14
14
  @tuplestore ||= begin
15
15
  if client.tuplestore.respond_to? :new
16
- client.tuplestore.new **@opts
16
+ client.tuplestore.new(**@opts)
17
17
  else
18
18
  client.tuplestore
19
19
  end
@@ -81,7 +81,7 @@ class Tupelo::Archiver
81
81
  ensure
82
82
  req.io.close
83
83
  end
84
-
84
+
85
85
  def at_tick tick, &action
86
86
  @scheduled_actions[tick] << action
87
87
  end
@@ -99,7 +99,7 @@ class Tupelo::Archiver
99
99
  "send_tuplestore to #{stream.peer_name} " +
100
100
  "at tick #{global_tick.inspect} " +
101
101
  (sub_delta ? " with sub_delta #{sub_delta.inspect}" : "")}
102
-
102
+
103
103
  stream << [global_tick]
104
104
 
105
105
  ## better: make use of sub_delta["subscribed_*"] to reduce what
@@ -13,7 +13,7 @@ module Tupelo
13
13
  attr_reader :tuplestore
14
14
 
15
15
  def initialize(tuplestore: SimpleTupleStore, subscribe: :all, **opts)
16
- super **opts
16
+ super(**opts)
17
17
  @tuplestore = tuplestore
18
18
  @worker = make_worker
19
19
  @initial_subscriptions = subscribe || []
@@ -57,7 +57,7 @@ module Tupelo
57
57
  if args.empty?
58
58
  super()
59
59
  else
60
- super().unknown *args
60
+ super().unknown(*args)
61
61
  end
62
62
  end
63
63
  end
@@ -85,7 +85,7 @@ class Tupelo::Client
85
85
  false
86
86
  end
87
87
  end
88
-
88
+
89
89
  def peek tuple
90
90
  queue << tuple
91
91
  once
@@ -102,10 +102,10 @@ class Tupelo::Client
102
102
  "<#{self.class}: #{template.inspect}>"
103
103
  end
104
104
  end
105
-
105
+
106
106
  class Waiter < WaiterBase
107
107
  end
108
-
108
+
109
109
  class Matcher < WaiterBase
110
110
  attr_reader :all # this is only cosmetic -- see #inspect
111
111
 
@@ -14,7 +14,7 @@ module Tupelo
14
14
  # Instead of calling this method, call Client#make_scheduler.
15
15
  def initialize client, **opts
16
16
  @client = client
17
- super **opts
17
+ super(**opts)
18
18
  end
19
19
 
20
20
  # Accepts numeric +time+ or Time instance. Logs errors that occur
@@ -34,10 +34,10 @@ class Tupelo::Client
34
34
  })
35
35
  end
36
36
 
37
- def subspace tag
37
+ def subspace tag, wait: false
38
38
  tag = tag.to_s
39
39
  find_subspace_by_tag(tag) or begin
40
- if subscribed_tags.include? tag
40
+ if wait or subscribed_tags.include? tag
41
41
  read tupelo_meta_key => "subspace",
42
42
  tag: tag,
43
43
  template: nil,
@@ -23,7 +23,7 @@ class Tupelo::Client
23
23
 
24
24
  val =
25
25
  if block.arity == 0
26
- t.instance_eval &block
26
+ t.instance_eval(&block)
27
27
  else
28
28
  yield t
29
29
  end
@@ -39,7 +39,7 @@ class Tupelo::Client
39
39
  ensure
40
40
  t.cancel if t and t.open? and block_given?
41
41
  end
42
-
42
+
43
43
  def abort
44
44
  raise TransactionAbort
45
45
  end
@@ -47,7 +47,7 @@ class Tupelo::Client
47
47
  # returns an object whose #wait method waits for write to be ack-ed
48
48
  def write_nowait *tuples
49
49
  t = transaction
50
- t.write *tuples
50
+ t.write(*tuples)
51
51
  t.commit
52
52
  end
53
53
  alias write write_nowait
@@ -59,7 +59,7 @@ class Tupelo::Client
59
59
 
60
60
  def pulse_nowait *tuples
61
61
  t = transaction
62
- t.pulse *tuples
62
+ t.pulse(*tuples)
63
63
  t.commit
64
64
  end
65
65
  alias pulse pulse_nowait
@@ -88,7 +88,7 @@ class Tupelo::Client
88
88
  end
89
89
  end
90
90
  end
91
-
91
+
92
92
  class Transaction
93
93
  attr_reader :client
94
94
  attr_reader :worker
@@ -109,7 +109,7 @@ class Tupelo::Client
109
109
  attr_reader :missing
110
110
  attr_reader :tags
111
111
  attr_reader :read_only
112
-
112
+
113
113
  STATES = [
114
114
  OPEN = :open, # initial state
115
115
  CLOSED = :closed, # client thread changes open -> closed
@@ -118,7 +118,7 @@ class Tupelo::Client
118
118
  DONE = :done, # worker thread changes pending -> done (terminal)
119
119
  FAILED = :failed # worker thread changes pending -> failed (terminal)
120
120
  ]
121
-
121
+
122
122
  STATES.each do |s|
123
123
  class_eval %{
124
124
  def #{s}?; @status == #{s.inspect}; end
@@ -151,7 +151,7 @@ class Tupelo::Client
151
151
  @_take_nowait = nil
152
152
  @_read_nowait = nil
153
153
  @read_only = false
154
-
154
+
155
155
  open!
156
156
 
157
157
  if deadline
@@ -160,11 +160,11 @@ class Tupelo::Client
160
160
  end
161
161
  end
162
162
  end
163
-
163
+
164
164
  def client_id
165
165
  client.client_id
166
166
  end
167
-
167
+
168
168
  def subspace tag
169
169
  client.subspace tag
170
170
  end
@@ -173,7 +173,7 @@ class Tupelo::Client
173
173
  if args.empty?
174
174
  @log
175
175
  else
176
- @log.unknown *args
176
+ @log.unknown(*args)
177
177
  end
178
178
  end
179
179
 
@@ -185,9 +185,9 @@ class Tupelo::Client
185
185
  when done?
186
186
  "at global_tick: #{global_tick}"
187
187
  end
188
-
188
+
189
189
  stat = [status, stat_extra].compact.join(" ")
190
-
190
+
191
191
  ops = [ ["write", writes], ["pulse", pulses],
192
192
  ["take", take_templates], ["read", read_templates] ]
193
193
  ## exclude templates that were satisfied locally by writes
@@ -200,10 +200,10 @@ class Tupelo::Client
200
200
  ## show take/read tuples too?
201
201
  ## show current tick, if open or closed
202
202
  ## show nowait
203
-
203
+
204
204
  "<#{self.class} #{stat} #{ops.join('; ')}>"
205
205
  end
206
-
206
+
207
207
  # :section: Client methods
208
208
 
209
209
  def check_tuples tuples
@@ -233,7 +233,7 @@ class Tupelo::Client
233
233
  # to convert symbols to strings (in case of msgpack or json)
234
234
  nil
235
235
  end
236
-
236
+
237
237
  def pulse *tuples
238
238
  check_open
239
239
  check_tuples tuples
@@ -241,7 +241,7 @@ class Tupelo::Client
241
241
  @pulses.concat tuples.map {|t| blobber.load(blobber.dump(t))}
242
242
  nil
243
243
  end
244
-
244
+
245
245
  # raises TransactionFailure
246
246
  def take template_spec
247
247
  check_open
@@ -252,7 +252,7 @@ class Tupelo::Client
252
252
  wait
253
253
  return take_tuples_for_local.last
254
254
  end
255
-
255
+
256
256
  def take_nowait template_spec
257
257
  check_open
258
258
  template = worker.make_template(template_spec)
@@ -265,7 +265,7 @@ class Tupelo::Client
265
265
  wait
266
266
  return take_tuples_for_local[i]
267
267
  end
268
-
268
+
269
269
  # transaction applies only if template has a match
270
270
  def read template_spec
271
271
  if block_given?
@@ -328,7 +328,7 @@ class Tupelo::Client
328
328
  def wait
329
329
  return self if done?
330
330
  raise exception if failed?
331
-
331
+
332
332
  log.debug {"waiting for #{inspect}"}
333
333
  @queue.pop
334
334
  log.debug {"finished waiting for #{inspect}"}
@@ -348,7 +348,7 @@ class Tupelo::Client
348
348
  wait
349
349
  granted_tuples
350
350
  end
351
-
351
+
352
352
  class TransactionThread < Thread
353
353
  def initialize t, *args
354
354
  super(*args)
@@ -365,7 +365,7 @@ class Tupelo::Client
365
365
  begin
366
366
  val =
367
367
  if block.arity == 0
368
- instance_eval &block
368
+ instance_eval(&block)
369
369
  else
370
370
  yield self
371
371
  end
@@ -381,7 +381,7 @@ class Tupelo::Client
381
381
  end
382
382
 
383
383
  # :section: Worker methods
384
-
384
+
385
385
  def in_worker_thread?
386
386
  worker.in_thread?
387
387
  end
@@ -523,7 +523,7 @@ class Tupelo::Client
523
523
  ## redo the conversions etc
524
524
  return true
525
525
  end
526
-
526
+
527
527
  def submit
528
528
  raise TransactionStateError, "must be closed" unless closed?
529
529
  raise unless in_worker_thread?
@@ -531,7 +531,7 @@ class Tupelo::Client
531
531
  @local_tick = worker.send_transaction self
532
532
  pending!
533
533
  end
534
-
534
+
535
535
  def done global_tick, granted_tuples
536
536
  unless pending? or (closed? and read_only)
537
537
  raise TransactionStateError, "must be pending or closed+read_only"
@@ -549,17 +549,17 @@ class Tupelo::Client
549
549
  def fail missing
550
550
  raise unless in_worker_thread?
551
551
  raise if @global_tick or @exception
552
-
552
+
553
553
  @missing = missing
554
554
  @exception = TransactionFailure
555
555
  failed!
556
556
  @queue << false
557
557
  end
558
-
558
+
559
559
  def error ex
560
560
  raise unless in_worker_thread?
561
561
  raise if @global_tick or @exception
562
-
562
+
563
563
  @exception = ex
564
564
  failed!
565
565
  @queue << false