tupelo 0.4 → 0.5

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.
@@ -1,23 +1,8 @@
1
1
  require 'tupelo/app'
2
2
 
3
3
  N = 2 # how many cpus do you want to use for mappers?
4
- VERBOSE = ARGV.delete "-v"
5
4
 
6
5
  Tupelo.application do |app|
7
- if VERBOSE
8
- app.child do |client| # a debugger client, to see what's happening
9
- note = client.notifier
10
- puts "%4s %4s %10s %s" % %w{ tick cid status operation }
11
- loop do
12
- status, tick, cid, op = note.wait
13
- unless status == :attempt
14
- s = status == :failure ? "FAILED" : ""
15
- puts "%4d %4d %10s %p" % [tick, cid, s, op]
16
- end
17
- end
18
- end
19
- end
20
-
21
6
  app.child do |client|
22
7
  document = "I will not map reduce in class\n" * 10
23
8
  lineno = 0
@@ -51,12 +36,11 @@ Tupelo.application do |app|
51
36
  end
52
37
  end
53
38
 
54
- client.log "DONE. results = #{results}"
55
- client.log "Press ^C to exit"
39
+ client.log "results = #{results}"
56
40
  end
57
41
 
58
42
  N.times do |i|
59
- app.child do |client|
43
+ app.child passive: true do |client|
60
44
  client.log.progname = "mapper #{i}"
61
45
 
62
46
  loop do
@@ -2,17 +2,17 @@
2
2
 
3
3
  require 'tupelo/app'
4
4
 
5
- Tupelo.application do |app|
6
- app.child do |client|
7
- client.write [2, 3, "frogs"]
8
- _, s = client.take ["animals", nil]
5
+ Tupelo.application do
6
+ child do
7
+ write [2, 3, "frogs"]
8
+ _, s = take ["animals", nil]
9
9
  puts s
10
10
  end
11
11
 
12
- app.child do |client|
13
- x, y, s = client.take [Numeric, Numeric, String]
12
+ child do
13
+ x, y, s = take [Numeric, Numeric, String]
14
14
  s2 = ([s] * (x + y)).join(" ")
15
- client.write ["animals", s2]
15
+ write ["animals", s2]
16
16
  end
17
17
  end
18
18
 
data/example/small.rb CHANGED
@@ -39,8 +39,8 @@ EasyServe.start(servers_file: "small-servers.yaml") do |ez|
39
39
 
40
40
  ez.server :arcd do |svr|
41
41
  require 'tupelo/archiver'
42
- arc = Archiver.new svr, seq: arc_to_seq_sock, cseq: arc_to_cseq_sock,
43
- log: log
42
+ arc = Tupelo::Archiver.new svr,
43
+ seq: arc_to_seq_sock, cseq: arc_to_cseq_sock, log: log
44
44
  arc.start
45
45
  end
46
46
  end
@@ -49,7 +49,7 @@ EasyServe.start(servers_file: "small-servers.yaml") do |ez|
49
49
  log = opts[:log]
50
50
  log.progname = "client <starting in #{log.progname}>"
51
51
  require 'tupelo/client'
52
- client = Client.new opts
52
+ client = Tupelo::Client.new opts
53
53
  client.start do
54
54
  log.progname = "client #{client.client_id}"
55
55
  end
data/example/tcp.rb CHANGED
@@ -5,6 +5,10 @@
5
5
  #
6
6
  # ruby tcp.rb --info
7
7
  #
8
+ # or
9
+ #
10
+ # ruby tcp.rb --trace
11
+ #
8
12
  # You can test with a local client:
9
13
  #
10
14
  # ../bin/tup tcp.yaml
@@ -27,7 +31,10 @@ Tupelo.application servers_file: svr,
27
31
  arcd_addr: [:tcp, '0.0.0.0', port + 2] do |app|
28
32
  if app.owns_servers
29
33
  puts "server started; ^C to stop"
30
- puts "(run with --info to see events)" if app.log.level > Logger::INFO
34
+ puts "run in another terminal: ../bin/tup tcp.yaml"
35
+ if app.log.level > Logger::INFO
36
+ puts "(run with --info or --trace to see events)"
37
+ end
31
38
  sleep
32
39
  else
33
40
  abort "server seems to be running already; check file #{svr.inspect}"
@@ -0,0 +1,23 @@
1
+ require 'tupelo/app'
2
+
3
+ # displays every transaction in sequence, specially marking failed ones,
4
+ # until INT signal
5
+ class Tupelo::AppBuilder
6
+ def start_trace
7
+ child passive: true do |client|
8
+ trap :INT do
9
+ exit!
10
+ end
11
+
12
+ note = client.notifier
13
+ log << ( "%6s %6s %6s %s\n" % %w{ tick cid status operation } )
14
+ loop do
15
+ status, tick, cid, op = note.wait
16
+ unless status == :attempt
17
+ s = status == :failure ? "FAIL" : ""
18
+ log << ( "%6d %6d %6s %p\n" % [tick, cid, s, op] )
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
data/lib/tupelo/app.rb CHANGED
@@ -22,21 +22,42 @@ module Tupelo
22
22
  end
23
23
 
24
24
  # Yields a client that runs in this process.
25
- def local client_class = Client
25
+ def local client_class = Client, &block
26
26
  ez.local :seqd, :cseqd, :arcd do |seqd, cseqd, arcd|
27
27
  run_client client_class,
28
28
  seq: seqd, cseq: cseqd, arc: arcd, log: log do |client|
29
- yield client
29
+ if block
30
+ if block.arity == 0
31
+ client.instance_eval &block
32
+ else
33
+ yield client
34
+ end
35
+ else
36
+ client
37
+ end
30
38
  end
31
39
  end
32
40
  end
33
41
 
34
42
  # Yields a client that runs in a subprocess.
35
- def child client_class = Client
36
- ez.client :seqd, :cseqd, :arcd do |seqd, cseqd, arcd|
43
+ #
44
+ # A passive client will be forced to stop after all active clients exit. Use
45
+ # the passive flag for processes that wait for tuples and respond in some
46
+ # way. Then you do not have to manually interrupt the whole application when
47
+ # the active processes are done. See examples.
48
+ def child client_class = Client, passive: false, &block
49
+ ez.client :seqd, :cseqd, :arcd, passive: passive do |seqd, cseqd, arcd|
37
50
  run_client client_class,
38
51
  seq: seqd, cseq: cseqd, arc: arcd, log: log do |client|
39
- yield client
52
+ if block
53
+ if block.arity == 0
54
+ client.instance_eval &block
55
+ else
56
+ yield client
57
+ end
58
+ else
59
+ client
60
+ end
40
61
  end
41
62
  end
42
63
  end
@@ -61,7 +82,7 @@ module Tupelo
61
82
 
62
83
  def self.application argv: ARGV,
63
84
  servers_file: nil, blob_type: nil,
64
- seqd_addr: [], cseqd_addr: [], arcd_addr: []
85
+ seqd_addr: [], cseqd_addr: [], arcd_addr: [], &block
65
86
 
66
87
  log_level = case
67
88
  when argv.delete("--debug"); Logger::DEBUG
@@ -79,6 +100,8 @@ module Tupelo
79
100
  end
80
101
  blob_type ||= "msgpack"
81
102
  end
103
+
104
+ enable_trace = ARGV.delete("--trace")
82
105
 
83
106
  svrs = servers_file || argv.shift || "servers-#$$.yaml"
84
107
 
@@ -115,7 +138,22 @@ module Tupelo
115
138
  end
116
139
  end
117
140
 
118
- yield AppBuilder.new(ez, owns_servers: owns_servers)
141
+ app = AppBuilder.new(ez, owns_servers: owns_servers)
142
+
143
+ if enable_trace
144
+ require 'tupelo/app/trace'
145
+ app.start_trace
146
+ end
147
+
148
+ if block
149
+ if block.arity == 0
150
+ app.instance_eval &block
151
+ else
152
+ yield app
153
+ end
154
+ else
155
+ app
156
+ end
119
157
  end
120
158
  end
121
159
  end
@@ -3,6 +3,11 @@ require 'funl/history-worker'
3
3
  class Tupelo::Archiver
4
4
  class Worker < Tupelo::Client::Worker
5
5
  include Funl::HistoryWorker
6
+
7
+ def initialize *args
8
+ super
9
+ @scheduled_actions = Hash.new {|h,k| h[k] = []}
10
+ end
6
11
 
7
12
  def handle_client_request req
8
13
  case req
@@ -17,7 +22,7 @@ class Tupelo::Archiver
17
22
  stream = client.arc_server_stream_for req.io
18
23
 
19
24
  begin
20
- op, args = stream.read
25
+ op, tags, tick = stream.read
21
26
  rescue EOFError
22
27
  log.debug {"#{stream.peer_name} disconnected from archiver"}
23
28
  return
@@ -26,9 +31,19 @@ class Tupelo::Archiver
26
31
  end
27
32
 
28
33
  log.info {
29
- "#{stream.peer_name} requested #{op.inspect}" +
30
- (args ? " on #{args.inspect}" : "")}
34
+ "#{stream.peer_name} requested #{op.inspect} at tick=#{tick}" +
35
+ (tags ? " on #{tags}" : "")}
36
+
37
+ if tick <= global_tick
38
+ fork_for_op op, tags, tick, stream, req
39
+ else
40
+ at_tick tick do
41
+ fork_for_op op, tags, tick, stream, req
42
+ end
43
+ end
44
+ end
31
45
 
46
+ def fork_for_op op, tags, tick, stream, req
32
47
  fork do
33
48
  begin
34
49
  case op
@@ -37,7 +52,7 @@ class Tupelo::Archiver
37
52
  when "get range" ### handle this in Funl::HistoryWorker
38
53
  raise "Unimplemented" ###
39
54
  when GET_TUPLESPACE
40
- send_tuplespace stream, args
55
+ send_tuplespace stream, tags
41
56
  else
42
57
  raise "Unknown operation: #{op.inspect}"
43
58
  end
@@ -50,6 +65,18 @@ class Tupelo::Archiver
50
65
  ensure
51
66
  req.io.close
52
67
  end
68
+
69
+ def at_tick tick, &action
70
+ @scheduled_actions[tick] << action
71
+ end
72
+
73
+ def handle_message msg
74
+ super
75
+ actions = @scheduled_actions.delete(global_tick)
76
+ actions and actions.each do |action|
77
+ action.call
78
+ end
79
+ end
53
80
 
54
81
  def send_tuplespace stream, templates
55
82
  log.info {
@@ -3,7 +3,6 @@ require 'tupelo/client/common'
3
3
  class Tupelo::Client
4
4
  # include into class that defines #worker and #log
5
5
  module Api
6
- ## need read with more complex predicates: |, &, etc
7
6
  def read_wait template
8
7
  waiter = Waiter.new(worker.make_template(template), self)
9
8
  worker << waiter
@@ -15,7 +14,6 @@ class Tupelo::Client
15
14
  end
16
15
  alias read read_wait
17
16
 
18
- ## need nonwaiting reader that accepts 2 or more templates
19
17
  def read_nowait template
20
18
  matcher = Matcher.new(worker.make_template(template), self)
21
19
  worker << matcher
@@ -8,24 +8,35 @@ class Tupelo::Client
8
8
  class TransactionFailure < TransactionError; end
9
9
 
10
10
  module Api
11
+ def trans_class
12
+ Transaction
13
+ end
14
+
11
15
  # Transactions are atomic by default, and are always isolated. In the
12
16
  # non-atomic case, a "transaction" is really a batch op. Without a block,
13
17
  # returns the Transaction. In the block form, transaction automatically
14
18
  # waits for successful completion and returns the value of the block.
15
- def transaction atomic: true, timeout: nil
19
+ def transaction atomic: true, timeout: nil, &block
16
20
  deadline = timeout && Time.now + timeout
17
- begin
18
- t = Transaction.new self, atomic: atomic, deadline: deadline
19
- return t unless block_given?
20
- val = yield t
21
- t.commit.wait
22
- return val
23
- rescue TransactionFailure => ex
24
- log.info {"retrying #{t.inspect}: #{ex}"}
25
- retry
26
- rescue TransactionAbort
27
- log.info {"aborting #{t.inspect}"}
28
- end
21
+ t = trans_class.new self, atomic: atomic, deadline: deadline
22
+ return t unless block_given?
23
+
24
+ val =
25
+ if block.arity == 0
26
+ t.instance_eval &block
27
+ else
28
+ yield t
29
+ end
30
+
31
+ t.commit.wait
32
+ return val
33
+ rescue TransactionFailure => ex
34
+ log.info {"retrying #{t.inspect}: #{ex}"}
35
+ retry
36
+ rescue TransactionAbort
37
+ log.info {"aborting #{t.inspect}"}
38
+ ensure
39
+ t.cancel if t and t.open? and block_given?
29
40
  end
30
41
 
31
42
  def batch &bl
@@ -81,7 +92,6 @@ class Tupelo::Client
81
92
  class Transaction
82
93
  attr_reader :client
83
94
  attr_reader :worker
84
- attr_reader :log
85
95
  attr_reader :atomic
86
96
  attr_reader :deadline
87
97
  attr_reader :status
@@ -134,6 +144,7 @@ class Tupelo::Client
134
144
  @granted_tuples = nil
135
145
  @missing = nil
136
146
  @_take_nowait = nil
147
+ @_read_nowait = nil
137
148
 
138
149
  if deadline
139
150
  worker.at deadline do
@@ -144,6 +155,14 @@ class Tupelo::Client
144
155
  open!
145
156
  end
146
157
 
158
+ def log *args
159
+ if args.empty?
160
+ @log
161
+ else
162
+ @log.unknown *args
163
+ end
164
+ end
165
+
147
166
  def inspect
148
167
  stat_extra =
149
168
  case
@@ -207,7 +226,7 @@ class Tupelo::Client
207
226
  template = worker.make_template(template_spec)
208
227
  @take_templates << template
209
228
  log.debug {"asking worker to take #{template_spec.inspect}"}
210
- worker << self
229
+ worker_push self
211
230
  wait
212
231
  return take_tuples.last
213
232
  end
@@ -223,7 +242,7 @@ class Tupelo::Client
223
242
  @_take_nowait[i] = true
224
243
  @take_templates << template
225
244
  log.debug {"asking worker to take_nowait #{template_spec.inspect}"}
226
- worker << self
245
+ worker_push self
227
246
  wait
228
247
  return take_tuples[i]
229
248
  end
@@ -237,23 +256,52 @@ class Tupelo::Client
237
256
  template = worker.make_template(template_spec)
238
257
  @read_templates << template
239
258
  log.debug {"asking worker to read #{template_spec.inspect}"}
240
- worker << self
259
+ worker_push self
241
260
  wait
242
261
  return read_tuples.last
243
262
  end
244
-
263
+
264
+ def read_nowait template_spec
265
+ raise "cannot read in batch" unless atomic
266
+ raise exception if failed?
267
+ raise TransactionStateError, "not open: #{inspect}" unless open? or
268
+ failed?
269
+ template = worker.make_template(template_spec)
270
+ @_read_nowait ||= {}
271
+ i = @read_templates.size
272
+ @_read_nowait[i] = true
273
+ @read_templates << template
274
+ log.debug {"asking worker to read #{template_spec.inspect}"}
275
+ worker_push self
276
+ wait
277
+ return read_tuples[i]
278
+ end
279
+
280
+ # Client may call this before commit. In transaction do...end block,
281
+ # this causes transaction to be re-executed.
282
+ def fail!
283
+ raise if in_worker_thread?
284
+ raise unless open?
285
+ failed!
286
+ raise TransactionFailure
287
+ end
288
+
245
289
  # idempotent
246
290
  def commit
247
291
  if open?
248
292
  closed!
249
293
  log.info {"committing #{inspect}"}
250
- worker << self
294
+ worker_push self
251
295
  else
252
296
  raise exception if failed?
253
297
  end
254
298
  return self
255
299
  end
256
-
300
+
301
+ def worker_push event=Proc.new
302
+ worker << event
303
+ end
304
+
257
305
  def wait
258
306
  return self if done?
259
307
  raise exception if failed?
@@ -268,7 +316,7 @@ class Tupelo::Client
268
316
  raise "bug: #{inspect}"
269
317
 
270
318
  rescue TransactionAbort, Interrupt, TimeoutError => ex ## others?
271
- worker << Unwaiter.new(self)
319
+ worker_push Unwaiter.new(self)
272
320
  raise ex.class,
273
321
  "#{ex.message}: client #{client.client_id} waiting for #{inspect}"
274
322
  end
@@ -353,13 +401,24 @@ class Tupelo::Client
353
401
  @_take_nowait.delete i
354
402
  end
355
403
 
404
+ skip = nil
356
405
  (read_tuples.size...read_templates.size).each do |i|
357
406
  read_tuples[i] = worker.tuplespace.find_match_for(
358
407
  read_templates[i])
359
408
  if read_tuples[i]
360
409
  log.debug {"prepared #{inspect} with #{read_tuples[i]}"}
410
+ else
411
+ if @_read_nowait and @_read_nowait[i]
412
+ (skip ||= []) << i
413
+ end
361
414
  end
362
415
  end
416
+
417
+ skip and skip.reverse_each do |i|
418
+ read_tuples.delete_at i
419
+ read_templates.delete_at i
420
+ @_read_nowait.delete i
421
+ end
363
422
  end
364
423
 
365
424
  ## convert cancelling write/take to pulse
@@ -440,9 +499,9 @@ class Tupelo::Client
440
499
 
441
500
  # Called by another thread to cancel a waiting transaction.
442
501
  def cancel err = TransactionAbort
443
- worker << proc do
502
+ worker_push do
444
503
  raise unless in_worker_thread?
445
- if @global_tick or @exception
504
+ if not open? or @global_tick or @exception
446
505
  log.info {"cancel was applied too late: #{inspect}"}
447
506
  else
448
507
  @exception = err.new
@@ -450,6 +509,7 @@ class Tupelo::Client
450
509
  @queue << false
451
510
  end
452
511
  end
512
+ nil
453
513
  end
454
514
  end
455
515
  end
@@ -9,7 +9,6 @@ class Tupelo::Client
9
9
  attr_reader :client
10
10
  attr_reader :seq
11
11
  attr_reader :arc
12
- attr_reader :log
13
12
  attr_reader :client_id
14
13
  attr_reader :local_tick
15
14
  attr_reader :global_tick
@@ -80,6 +79,14 @@ class Tupelo::Client
80
79
  @stopping = false
81
80
  end
82
81
 
82
+ def log *args
83
+ if args.empty?
84
+ @log
85
+ else
86
+ @log.unknown *args
87
+ end
88
+ end
89
+
83
90
  def start
84
91
  return if @worker_thread
85
92
 
@@ -223,7 +230,7 @@ class Tupelo::Client
223
230
  end
224
231
 
225
232
  log.info "requesting tuplespace from arc"
226
- arc << [GET_TUPLESPACE, nil]
233
+ arc << [GET_TUPLESPACE, nil, tick]
227
234
  ## replace nil with template tuples, if any
228
235
 
229
236
  begin
@@ -231,19 +238,21 @@ class Tupelo::Client
231
238
  log.info "arc says global_tick = #{arc_tick}"
232
239
 
233
240
  done = false
241
+ count = 0
234
242
  arc.each do |tuple|
235
243
  if tuple.nil?
236
244
  done = true
237
245
  else
238
246
  raise "bad object stream from archiver" if done
239
247
  tuplespace.insert tuple
248
+ count += 1
240
249
  end
241
250
  end
242
251
  unless done
243
252
  raise "did not get all of tuplespace from archiver" ## roll back?
244
253
  end
245
254
 
246
- log.info "received tuplespace from arc"
255
+ log.info "received tuplespace from arc: #{count} tuples"
247
256
 
248
257
  @global_tick = arc_tick
249
258
  log.info "global_tick = #{global_tick}"
@@ -262,9 +271,9 @@ class Tupelo::Client
262
271
  # due to archiver timing, for example
263
272
  return
264
273
  elsif msg.global_tick > global_tick + 1
265
- log.error "message out of order: #{msg.inspect}, " +
274
+ log.fatal "message out of order: #{msg.inspect}, " +
266
275
  "received at global_tick=#{global_tick}"
267
- ## exit? wait? in udp case, get history
276
+ raise "fatal error"
268
277
  end
269
278
  end
270
279
 
@@ -0,0 +1,25 @@
1
+ class Tupelo::Client
2
+ class Or
3
+ attr_reader :templates
4
+
5
+ def initialize worker, templates
6
+ @templates = templates.map {|template| worker.make_template(template)}
7
+ end
8
+
9
+ def === obj
10
+ templates.any? {|template| template === obj}
11
+ end
12
+ end
13
+
14
+ def or *templates
15
+ Or.new(worker, templates)
16
+ end
17
+ alias match_any or
18
+ end
19
+
20
+ class Tupelo::Client::Transaction
21
+ def or *templates
22
+ client.or *templates
23
+ end
24
+ alias match_any or
25
+ end
@@ -1,3 +1,3 @@
1
1
  module Tupelo
2
- VERSION = "0.4"
2
+ VERSION = "0.5"
3
3
  end
@@ -0,0 +1,36 @@
1
+ module Tupelo
2
+ module TimeFuzz
3
+ DEFAULT_SLEEP_MAX = 0.01
4
+ @sleep_max = DEFAULT_SLEEP_MAX
5
+
6
+ class << self
7
+ attr_accessor :sleep_max
8
+ end
9
+
10
+ module Api
11
+ def trans_class
12
+ TimeFuzz::Transaction
13
+ end
14
+ end
15
+
16
+ class Transaction < Tupelo::Client::Transaction
17
+ def worker_push event=Proc.new
18
+ sleep sleep_duration
19
+ super
20
+ end
21
+
22
+ def wait
23
+ super
24
+ sleep sleep_duration
25
+ end
26
+
27
+ def sleep_duration
28
+ rand * TimeFuzz.sleep_max
29
+ end
30
+ end
31
+
32
+ class Client < Tupelo::Client
33
+ include TimeFuzz::Api
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,16 @@
1
+ # Stresses the ability of the archiver to handle highl load, and in particular
2
+ # situations where the client asks for data more recent than what the archiver
3
+ # has received.
4
+
5
+ require 'tupelo/app'
6
+
7
+ N = 100
8
+
9
+ Tupelo.application do
10
+ N.times do |i|
11
+ sleep i/1000.0
12
+ child do
13
+ write [1]
14
+ end
15
+ end
16
+ end