tupelo 0.21 → 0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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