sensu 0.9.9.beta.2 → 0.9.9.beta.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -15,6 +15,8 @@ TCP handler socket timeout, which defaults to 10 seconds.
15
15
 
16
16
  Check execution timeout.
17
17
 
18
+ Server extensions (mutators & handlers).
19
+
18
20
  ### Other
19
21
 
20
22
  Server is now using basic AMQP QoS (prefetch), just enough back pressure.
data/lib/sensu/api.rb CHANGED
@@ -21,9 +21,10 @@ module Sensu
21
21
  end
22
22
 
23
23
  def bootstrap(options={})
24
- $logger = Sensu::Logger.get
25
- base = Sensu::Base.new(options)
24
+ base = Base.new(options)
25
+ $logger = base.logger
26
26
  $settings = base.settings
27
+ base.setup_process
27
28
  if $settings[:api][:user] && $settings[:api][:password]
28
29
  use Rack::Auth::Basic do |user, password|
29
30
  user == $settings[:api][:user] && password == $settings[:api][:password]
@@ -35,20 +36,18 @@ module Sensu
35
36
  $logger.debug('connecting to redis', {
36
37
  :settings => $settings[:redis]
37
38
  })
38
- connection_failure = Proc.new do
39
- $logger.fatal('cannot connect to redis', {
40
- :settings => $settings[:redis]
39
+ $redis = Redis.connect($settings[:redis])
40
+ $redis.on_error do |error|
41
+ $logger.fatal('redis connection error', {
42
+ :error => error.to_s
41
43
  })
42
- $logger.fatal('SENSU NOT RUNNING!')
43
- if $rabbitmq
44
- $rabbitmq.close
45
- end
46
- exit 2
44
+ stop
47
45
  end
48
- $redis = Sensu::Redis.connect($settings[:redis], :on_tcp_connection_failure => connection_failure)
49
- $redis.on_tcp_connection_loss do |connection, settings|
46
+ $redis.before_reconnect do
50
47
  $logger.warn('reconnecting to redis')
51
- connection.reconnect(false, 10)
48
+ end
49
+ $redis.after_reconnect do
50
+ $logger.info('reconnected to redis')
52
51
  end
53
52
  end
54
53
 
@@ -56,30 +55,20 @@ module Sensu
56
55
  $logger.debug('connecting to rabbitmq', {
57
56
  :settings => $settings[:rabbitmq]
58
57
  })
59
- connection_failure = Proc.new do
60
- $logger.fatal('cannot connect to rabbitmq', {
61
- :settings => $settings[:rabbitmq]
58
+ $rabbitmq = RabbitMQ.connect($settings[:rabbitmq])
59
+ $rabbitmq.on_error do |error|
60
+ $logger.fatal('rabbitmq connection error', {
61
+ :error => error.to_s
62
62
  })
63
- $logger.fatal('SENSU NOT RUNNING!')
64
- $redis.close
65
- exit 2
63
+ stop
66
64
  end
67
- $rabbitmq = AMQP.connect($settings[:rabbitmq], {
68
- :on_tcp_connection_failure => connection_failure,
69
- :on_possible_authentication_failure => connection_failure
70
- })
71
- $rabbitmq.logger = Sensu::NullLogger.get
72
- $rabbitmq.on_tcp_connection_loss do |connection, settings|
73
- unless connection.reconnecting?
74
- $logger.warn('reconnecting to rabbitmq')
75
- connection.periodically_reconnect(5)
76
- end
65
+ $rabbitmq.before_reconnect do
66
+ $logger.warn('reconnecting to rabbitmq')
77
67
  end
78
- $rabbitmq.on_skipped_heartbeats do
79
- $logger.warn('skipped rabbitmq heartbeat')
68
+ $rabbitmq.after_reconnect do
69
+ $logger.info('reconnected to rabbitmq')
80
70
  end
81
- $amq = AMQP::Channel.new($rabbitmq)
82
- $amq.auto_recovery = true
71
+ $amq = $rabbitmq.channel
83
72
  end
84
73
 
85
74
  def start
@@ -108,29 +97,9 @@ module Sensu
108
97
  end
109
98
  end
110
99
 
111
- def run_test(options={}, &block)
100
+ def test(options={})
112
101
  bootstrap(options)
113
102
  start
114
- $settings[:client][:timestamp] = Time.now.to_i
115
- $redis.set('client:' + $settings[:client][:name], $settings[:client].to_json).callback do
116
- $redis.sadd('clients', $settings[:client][:name]).callback do
117
- $redis.hset('events:' + $settings[:client][:name], 'test', {
118
- :output => 'CRITICAL',
119
- :status => 2,
120
- :issued => Time.now.to_i,
121
- :flapping => false,
122
- :occurrences => 1
123
- }.to_json).callback do
124
- $redis.set('stash:test/test', {:key => 'value'}.to_json).callback do
125
- $redis.sadd('stashes', 'test/test').callback do
126
- EM::Timer.new(0.5) do
127
- block.call
128
- end
129
- end
130
- end
131
- end
132
- end
133
- end
134
103
  end
135
104
  end
136
105
 
@@ -227,7 +196,7 @@ module Sensu
227
196
  aget '/info' do
228
197
  response = {
229
198
  :sensu => {
230
- :version => Sensu::VERSION
199
+ :version => VERSION
231
200
  },
232
201
  :rabbitmq => {
233
202
  :keepalives => {
@@ -261,10 +230,10 @@ module Sensu
261
230
 
262
231
  aget '/clients' do
263
232
  response = Array.new
264
- $redis.smembers('clients').callback do |clients|
233
+ $redis.smembers('clients') do |clients|
265
234
  unless clients.empty?
266
235
  clients.each_with_index do |client_name, index|
267
- $redis.get('client:' + client_name).callback do |client_json|
236
+ $redis.get('client:' + client_name) do |client_json|
268
237
  response.push(JSON.parse(client_json))
269
238
  if index == clients.size - 1
270
239
  body response.to_json
@@ -278,7 +247,7 @@ module Sensu
278
247
  end
279
248
 
280
249
  aget %r{/clients?/([\w\.-]+)$} do |client_name|
281
- $redis.get('client:' + client_name).callback do |client_json|
250
+ $redis.get('client:' + client_name) do |client_json|
282
251
  unless client_json.nil?
283
252
  body client_json
284
253
  else
@@ -288,9 +257,9 @@ module Sensu
288
257
  end
289
258
 
290
259
  adelete %r{/clients?/([\w\.-]+)$} do |client_name|
291
- $redis.get('client:' + client_name).callback do |client_json|
260
+ $redis.get('client:' + client_name) do |client_json|
292
261
  unless client_json.nil?
293
- $redis.hgetall('events:' + client_name).callback do |events|
262
+ $redis.hgetall('events:' + client_name) do |events|
294
263
  events.each do |check_name, event_json|
295
264
  resolve_event(event_hash(event_json, client_name, check_name))
296
265
  end
@@ -302,7 +271,7 @@ module Sensu
302
271
  $redis.srem('clients', client_name)
303
272
  $redis.del('events:' + client_name)
304
273
  $redis.del('client:' + client_name)
305
- $redis.smembers('history:' + client_name).callback do |checks|
274
+ $redis.smembers('history:' + client_name) do |checks|
306
275
  checks.each do |check_name|
307
276
  $redis.del('history:' + client_name + ':' + check_name)
308
277
  end
@@ -367,10 +336,10 @@ module Sensu
367
336
 
368
337
  aget '/events' do
369
338
  response = Array.new
370
- $redis.smembers('clients').callback do |clients|
339
+ $redis.smembers('clients') do |clients|
371
340
  unless clients.empty?
372
341
  clients.each_with_index do |client_name, index|
373
- $redis.hgetall('events:' + client_name).callback do |events|
342
+ $redis.hgetall('events:' + client_name) do |events|
374
343
  events.each do |check_name, event_json|
375
344
  response.push(event_hash(event_json, client_name, check_name))
376
345
  end
@@ -387,7 +356,7 @@ module Sensu
387
356
 
388
357
  aget %r{/events/([\w\.-]+)$} do |client_name|
389
358
  response = Array.new
390
- $redis.hgetall('events:' + client_name).callback do |events|
359
+ $redis.hgetall('events:' + client_name) do |events|
391
360
  events.each do |check_name, event_json|
392
361
  response.push(event_hash(event_json, client_name, check_name))
393
362
  end
@@ -396,7 +365,7 @@ module Sensu
396
365
  end
397
366
 
398
367
  aget %r{/events?/([\w\.-]+)/([\w\.-]+)$} do |client_name, check_name|
399
- $redis.hgetall('events:' + client_name).callback do |events|
368
+ $redis.hgetall('events:' + client_name) do |events|
400
369
  event_json = events[check_name]
401
370
  unless event_json.nil?
402
371
  body event_hash(event_json, client_name, check_name).to_json
@@ -407,7 +376,7 @@ module Sensu
407
376
  end
408
377
 
409
378
  adelete %r{/events?/([\w\.-]+)/([\w\.-]+)$} do |client_name, check_name|
410
- $redis.hgetall('events:' + client_name).callback do |events|
379
+ $redis.hgetall('events:' + client_name) do |events|
411
380
  if events.include?(check_name)
412
381
  resolve_event(event_hash(events[check_name], client_name, check_name))
413
382
  accepted!
@@ -423,7 +392,7 @@ module Sensu
423
392
  client_name = post_body[:client]
424
393
  check_name = post_body[:check]
425
394
  if client_name.is_a?(String) && check_name.is_a?(String)
426
- $redis.hgetall('events:' + client_name).callback do |events|
395
+ $redis.hgetall('events:' + client_name) do |events|
427
396
  if events.include?(check_name)
428
397
  resolve_event(event_hash(events[check_name], client_name, check_name))
429
398
  accepted!
@@ -441,7 +410,7 @@ module Sensu
441
410
 
442
411
  aget '/aggregates' do
443
412
  response = Array.new
444
- $redis.smembers('aggregates').callback do |checks|
413
+ $redis.smembers('aggregates') do |checks|
445
414
  unless checks.empty?
446
415
  limit = 10
447
416
  if params[:limit]
@@ -449,7 +418,7 @@ module Sensu
449
418
  end
450
419
  unless limit.nil?
451
420
  checks.each_with_index do |check_name, index|
452
- $redis.smembers('aggregates:' + check_name).callback do |aggregates|
421
+ $redis.smembers('aggregates:' + check_name) do |aggregates|
453
422
  collection = {
454
423
  :check => check_name,
455
424
  :issued => aggregates.sort.reverse.take(limit)
@@ -470,7 +439,7 @@ module Sensu
470
439
  end
471
440
 
472
441
  aget %r{/aggregates/([\w\.-]+)$} do |check_name|
473
- $redis.smembers('aggregates:' + check_name).callback do |aggregates|
442
+ $redis.smembers('aggregates:' + check_name) do |aggregates|
474
443
  unless aggregates.empty?
475
444
  limit = 10
476
445
  if params[:limit]
@@ -488,15 +457,15 @@ module Sensu
488
457
  end
489
458
 
490
459
  adelete %r{/aggregates/([\w\.-]+)$} do |check_name|
491
- $redis.smembers('aggregates:' + check_name).callback do |aggregates|
460
+ $redis.smembers('aggregates:' + check_name) do |aggregates|
492
461
  unless aggregates.empty?
493
462
  aggregates.each do |check_issued|
494
463
  result_set = check_name + ':' + check_issued
495
464
  $redis.del('aggregation:' + result_set)
496
465
  $redis.del('aggregate:' + result_set)
497
466
  end
498
- $redis.del('aggregates:' + check_name).callback do
499
- $redis.srem('aggregates', check_name).callback do
467
+ $redis.del('aggregates:' + check_name) do
468
+ $redis.srem('aggregates', check_name) do
500
469
  no_content!
501
470
  end
502
471
  end
@@ -508,13 +477,13 @@ module Sensu
508
477
 
509
478
  aget %r{/aggregates?/([\w\.-]+)/([\w\.-]+)$} do |check_name, check_issued|
510
479
  result_set = check_name + ':' + check_issued
511
- $redis.hgetall('aggregate:' + result_set).callback do |aggregate|
480
+ $redis.hgetall('aggregate:' + result_set) do |aggregate|
512
481
  unless aggregate.empty?
513
482
  response = aggregate.inject(Hash.new) do |totals, (status, count)|
514
483
  totals[status] = Integer(count)
515
484
  totals
516
485
  end
517
- $redis.hgetall('aggregation:' + result_set).callback do |results|
486
+ $redis.hgetall('aggregation:' + result_set) do |results|
518
487
  parsed_results = results.inject(Array.new) do |parsed, (client_name, check_json)|
519
488
  check = JSON.parse(check_json, :symbolize_names => true)
520
489
  parsed.push(check.merge(:client => client_name))
@@ -543,8 +512,8 @@ module Sensu
543
512
  apost %r{/stash(?:es)?/(.*)} do |path|
544
513
  begin
545
514
  post_body = JSON.parse(request.body.read)
546
- $redis.set('stash:' + path, post_body.to_json).callback do
547
- $redis.sadd('stashes', path).callback do
515
+ $redis.set('stash:' + path, post_body.to_json) do
516
+ $redis.sadd('stashes', path) do
548
517
  created!
549
518
  end
550
519
  end
@@ -554,7 +523,7 @@ module Sensu
554
523
  end
555
524
 
556
525
  aget %r{/stash(?:es)?/(.*)} do |path|
557
- $redis.get('stash:' + path).callback do |stash_json|
526
+ $redis.get('stash:' + path) do |stash_json|
558
527
  unless stash_json.nil?
559
528
  body stash_json
560
529
  else
@@ -564,10 +533,10 @@ module Sensu
564
533
  end
565
534
 
566
535
  adelete %r{/stash(?:es)?/(.*)} do |path|
567
- $redis.exists('stash:' + path).callback do |stash_exists|
536
+ $redis.exists('stash:' + path) do |stash_exists|
568
537
  if stash_exists
569
- $redis.srem('stashes', path).callback do
570
- $redis.del('stash:' + path).callback do
538
+ $redis.srem('stashes', path) do
539
+ $redis.del('stash:' + path) do
571
540
  no_content!
572
541
  end
573
542
  end
@@ -589,7 +558,7 @@ module Sensu
589
558
  if post_body.is_a?(Array) && post_body.size > 0
590
559
  response = Hash.new
591
560
  post_body.each_with_index do |path, index|
592
- $redis.get('stash:' + path).callback do |stash_json|
561
+ $redis.get('stash:' + path) do |stash_json|
593
562
  unless stash_json.nil?
594
563
  response[path] = JSON.parse(stash_json)
595
564
  end
data/lib/sensu/base.rb CHANGED
@@ -1,52 +1,58 @@
1
1
  require 'rubygems'
2
2
 
3
3
  gem 'eventmachine', '1.0.0'
4
- gem 'amqp', '0.9.7'
5
4
 
6
5
  require 'json'
7
- require 'timeout'
8
6
  require 'time'
9
7
  require 'uri'
10
- require 'amqp'
11
8
 
12
9
  require File.join(File.dirname(__FILE__), 'constants')
10
+ require File.join(File.dirname(__FILE__), 'utilities')
13
11
  require File.join(File.dirname(__FILE__), 'cli')
14
- require File.join(File.dirname(__FILE__), 'logger')
12
+ require File.join(File.dirname(__FILE__), 'logstream')
15
13
  require File.join(File.dirname(__FILE__), 'settings')
14
+ require File.join(File.dirname(__FILE__), 'extensions')
16
15
  require File.join(File.dirname(__FILE__), 'process')
17
16
  require File.join(File.dirname(__FILE__), 'io')
17
+ require File.join(File.dirname(__FILE__), 'rabbitmq')
18
18
 
19
19
  module Sensu
20
20
  class Base
21
- attr_reader :options, :settings
22
-
23
21
  def initialize(options={})
24
- @options = Sensu::DEFAULT_OPTIONS.merge(options)
25
- setup_logging
26
- setup_settings
27
- setup_process
22
+ @options = DEFAULT_OPTIONS.merge(options)
23
+ end
24
+
25
+ def logger
26
+ stream = LogStream.new
27
+ stream.level = @options[:log_level]
28
+ if @options[:log_file]
29
+ stream.reopen(@options[:log_file])
30
+ end
31
+ stream.setup_traps
32
+ stream.logger
28
33
  end
29
34
 
30
- def setup_logging
31
- logger = Sensu::Logger.new
32
- logger.level = @options[:verbose] ? :debug : @options[:log_level] || :info
33
- logger.reopen(@options[:log_file])
34
- logger.setup_traps
35
+ def settings
36
+ settings = Settings.new
37
+ settings.load_env
38
+ settings.load_file(@options[:config_file])
39
+ settings.load_directory(@options[:config_dir])
40
+ settings.validate
41
+ settings.set_env
42
+ settings
35
43
  end
36
44
 
37
- def setup_settings
38
- @settings = Sensu::Settings.new
39
- @settings.load_env
40
- @settings.load_file(@options[:config_file])
41
- Dir[@options[:config_dir] + '/**/*.json'].each do |file|
42
- @settings.load_file(file)
45
+ def extensions
46
+ extensions = Extensions.new
47
+ if @options[:extension_dir]
48
+ extensions.require_directory(@options[:extension_dir])
43
49
  end
44
- @settings.validate
45
- @settings.set_env
50
+ extensions.load_all
51
+ extensions
46
52
  end
47
53
 
48
54
  def setup_process
49
- process = Sensu::Process.new
55
+ process = Process.new
50
56
  if @options[:daemonize]
51
57
  process.daemonize
52
58
  end
data/lib/sensu/cli.rb CHANGED
@@ -10,20 +10,23 @@ module Sensu
10
10
  exit
11
11
  end
12
12
  opts.on('-V', '--version', 'Display version') do
13
- puts Sensu::VERSION
13
+ puts VERSION
14
14
  exit
15
15
  end
16
- opts.on('-c', '--config FILE', 'Sensu JSON config FILE. Default is /etc/sensu/config.json') do |file|
16
+ opts.on('-c', '--config FILE', 'Sensu JSON config FILE. Default: /etc/sensu/config.json') do |file|
17
17
  options[:config_file] = file
18
18
  end
19
- opts.on('-d', '--config_dir DIR', 'DIR for supplemental Sensu JSON config files. Default is /etc/sensu/conf.d/') do |dir|
19
+ opts.on('-d', '--config_dir DIR', 'DIR for supplemental Sensu JSON config files. Default: /etc/sensu/conf.d/') do |dir|
20
20
  options[:config_dir] = dir
21
21
  end
22
- opts.on('-l', '--log FILE', 'Log to a given FILE. Default is to log to stdout') do |file|
22
+ opts.on('-e', '--extension_dir DIR', 'DIR for Sensu extensions (experimental)') do |dir|
23
+ options[:extension_dir] = dir
24
+ end
25
+ opts.on('-l', '--log FILE', 'Log to a given FILE. Default: STDOUT') do |file|
23
26
  options[:log_file] = file
24
27
  end
25
28
  opts.on('-v', '--verbose', 'Enable verbose logging') do
26
- options[:verbose] = true
29
+ options[:log_level] = :debug
27
30
  end
28
31
  opts.on('-b', '--background', 'Fork into the background') do
29
32
  options[:daemonize] = true
@@ -33,7 +36,7 @@ module Sensu
33
36
  end
34
37
  end
35
38
  optparse.parse!(arguments)
36
- Sensu::DEFAULT_OPTIONS.merge(options)
39
+ DEFAULT_OPTIONS.merge(options)
37
40
  end
38
41
  end
39
42
  end
data/lib/sensu/client.rb CHANGED
@@ -3,6 +3,10 @@ require File.join(File.dirname(__FILE__), 'socket')
3
3
 
4
4
  module Sensu
5
5
  class Client
6
+ include Utilities
7
+
8
+ attr_accessor :safe_mode
9
+
6
10
  def self.run(options={})
7
11
  client = self.new(options)
8
12
  EM::run do
@@ -12,41 +16,33 @@ module Sensu
12
16
  end
13
17
 
14
18
  def initialize(options={})
15
- @logger = Sensu::Logger.get
16
- base = Sensu::Base.new(options)
19
+ base = Base.new(options)
20
+ @logger = base.logger
17
21
  @settings = base.settings
22
+ base.setup_process
18
23
  @timers = Array.new
19
24
  @checks_in_progress = Array.new
25
+ @safe_mode = @settings[:client][:safe_mode] || false
20
26
  end
21
27
 
22
28
  def setup_rabbitmq
23
29
  @logger.debug('connecting to rabbitmq', {
24
30
  :settings => @settings[:rabbitmq]
25
31
  })
26
- connection_failure = Proc.new do
27
- @logger.fatal('cannot connect to rabbitmq', {
28
- :settings => @settings[:rabbitmq]
32
+ @rabbitmq = RabbitMQ.connect(@settings[:rabbitmq])
33
+ @rabbitmq.on_error do |error|
34
+ @logger.fatal('rabbitmq connection error', {
35
+ :error => error.to_s
29
36
  })
30
- @logger.fatal('SENSU NOT RUNNING!')
31
- exit 2
37
+ stop
32
38
  end
33
- @rabbitmq = AMQP.connect(@settings[:rabbitmq], {
34
- :on_tcp_connection_failure => connection_failure,
35
- :on_possible_authentication_failure => connection_failure
36
- })
37
- @rabbitmq.logger = Sensu::NullLogger.get
38
- @rabbitmq.on_tcp_connection_loss do |connection, settings|
39
- unless connection.reconnecting?
40
- @logger.warn('reconnecting to rabbitmq')
41
- connection.periodically_reconnect(5)
42
- end
39
+ @rabbitmq.before_reconnect do
40
+ @logger.warn('reconnecting to rabbitmq')
43
41
  end
44
- @rabbitmq.on_skipped_heartbeats do
45
- @logger.warn('skipped rabbitmq heartbeat')
46
- @logger.warn('rabbitmq heartbeats are not recommended for clients')
42
+ @rabbitmq.after_reconnect do
43
+ @logger.info('reconnected to rabbitmq')
47
44
  end
48
- @amq = AMQP::Channel.new(@rabbitmq)
49
- @amq.auto_recovery = true
45
+ @amq = @rabbitmq.channel
50
46
  end
51
47
 
52
48
  def publish_keepalive
@@ -102,7 +98,7 @@ module Sensu
102
98
  execute = Proc.new do
103
99
  started = Time.now.to_f
104
100
  begin
105
- check[:output], check[:status] = Sensu::IO.popen(command, 'r', check[:timeout])
101
+ check[:output], check[:status] = IO.popen(command, 'r', check[:timeout])
106
102
  rescue => error
107
103
  @logger.warn('unexpected error', {
108
104
  :error => error.to_s
@@ -158,7 +154,7 @@ module Sensu
158
154
  if @settings.check_exists?(check[:name])
159
155
  check.merge!(@settings[:checks][check[:name]])
160
156
  execute_check(check)
161
- elsif @settings[:client][:safe_mode]
157
+ elsif @safe_mode
162
158
  @logger.warn('check is not defined', {
163
159
  :check => check
164
160
  })
@@ -184,8 +180,9 @@ module Sensu
184
180
  stagger = testing? ? 0 : 2
185
181
  @settings.checks.each do |check|
186
182
  if check[:standalone]
187
- standalone_check_count = (standalone_check_count + 1) % 30
188
- @timers << EM::Timer.new(stagger * standalone_check_count) do
183
+ standalone_check_count += 1
184
+ scheduling_delay = stagger * standalone_check_count % 30
185
+ @timers << EM::Timer.new(scheduling_delay) do
189
186
  interval = testing? ? 0.5 : check[:interval]
190
187
  @timers << EM::PeriodicTimer.new(interval) do
191
188
  if @rabbitmq.connected?
@@ -200,14 +197,14 @@ module Sensu
200
197
 
201
198
  def setup_sockets
202
199
  @logger.debug('binding client tcp socket')
203
- EM::start_server('127.0.0.1', 3030, Sensu::Socket) do |socket|
200
+ EM::start_server('127.0.0.1', 3030, Socket) do |socket|
204
201
  socket.protocol = :tcp
205
202
  socket.logger = @logger
206
203
  socket.settings = @settings
207
204
  socket.amq = @amq
208
205
  end
209
206
  @logger.debug('binding client udp socket')
210
- EM::open_datagram_socket('127.0.0.1', 3030, Sensu::Socket) do |socket|
207
+ EM::open_datagram_socket('127.0.0.1', 3030, Socket) do |socket|
211
208
  socket.protocol = :udp
212
209
  socket.logger = @logger
213
210
  socket.settings = @settings
@@ -265,19 +262,5 @@ module Sensu
265
262
  end
266
263
  end
267
264
  end
268
-
269
- private
270
-
271
- def testing?
272
- File.basename($0) == 'rake'
273
- end
274
-
275
- def retry_until_true(wait=0.5, &block)
276
- EM::Timer.new(wait) do
277
- unless block.call
278
- retry_until_true(wait, &block)
279
- end
280
- end
281
- end
282
265
  end
283
266
  end