tupelo 0.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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