bluepill 0.0.68 → 0.0.69
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -2
- data/bin/bluepill +19 -20
- data/bin/sample_forking_server +26 -13
- data/bluepill.gemspec +12 -18
- data/lib/bluepill.rb +12 -12
- data/lib/bluepill/application.rb +64 -71
- data/lib/bluepill/application/client.rb +0 -2
- data/lib/bluepill/application/server.rb +1 -3
- data/lib/bluepill/condition_watch.rb +12 -7
- data/lib/bluepill/controller.rb +37 -42
- data/lib/bluepill/dsl.rb +1 -2
- data/lib/bluepill/dsl/app_proxy.rb +3 -4
- data/lib/bluepill/dsl/process_factory.rb +40 -44
- data/lib/bluepill/dsl/process_proxy.rb +4 -5
- data/lib/bluepill/group.rb +15 -21
- data/lib/bluepill/logger.rb +4 -4
- data/lib/bluepill/process.rb +107 -109
- data/lib/bluepill/process_conditions.rb +1 -3
- data/lib/bluepill/process_conditions/always_true.rb +2 -3
- data/lib/bluepill/process_conditions/cpu_usage.rb +0 -1
- data/lib/bluepill/process_conditions/file_time.rb +5 -6
- data/lib/bluepill/process_conditions/http.rb +11 -9
- data/lib/bluepill/process_conditions/mem_usage.rb +6 -7
- data/lib/bluepill/process_conditions/process_condition.rb +4 -5
- data/lib/bluepill/process_conditions/running_time.rb +1 -2
- data/lib/bluepill/process_conditions/zombie_process.rb +1 -2
- data/lib/bluepill/process_journal.rb +18 -21
- data/lib/bluepill/process_statistics.rb +2 -4
- data/lib/bluepill/socket.rb +13 -16
- data/lib/bluepill/system.rb +57 -63
- data/lib/bluepill/trigger.rb +9 -11
- data/lib/bluepill/triggers/flapping.rb +12 -16
- data/lib/bluepill/util/rotational_array.rb +1 -2
- data/lib/bluepill/version.rb +1 -2
- metadata +4 -28
- data/.gitignore +0 -12
- data/.rspec +0 -1
- data/.travis.yml +0 -17
- data/Gemfile +0 -27
- data/Rakefile +0 -38
- data/examples/example.rb +0 -87
- data/examples/new_example.rb +0 -89
- data/examples/new_runit_example.rb +0 -29
- data/examples/runit_example.rb +0 -26
- data/local-bluepill +0 -130
- data/spec/lib/bluepill/application_spec.rb +0 -51
- data/spec/lib/bluepill/logger_spec.rb +0 -3
- data/spec/lib/bluepill/process_spec.rb +0 -135
- data/spec/lib/bluepill/process_statistics_spec.rb +0 -24
- data/spec/lib/bluepill/system_spec.rb +0 -45
- data/spec/spec_helper.rb +0 -26
data/lib/bluepill/logger.rb
CHANGED
@@ -1,18 +1,17 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
1
|
module Bluepill
|
3
2
|
class Logger
|
4
3
|
LOG_METHODS = [:emerg, :alert, :crit, :err, :warning, :notice, :info, :debug]
|
5
4
|
|
6
5
|
def initialize(options = {})
|
7
6
|
@options = options
|
8
|
-
@logger = options[:logger] ||
|
7
|
+
@logger = options[:logger] || create_logger
|
9
8
|
@prefix = options[:prefix]
|
10
9
|
@stdout = options[:stdout]
|
11
10
|
@prefixes = {}
|
12
11
|
end
|
13
12
|
|
14
13
|
LOG_METHODS.each do |method|
|
15
|
-
|
14
|
+
class_eval <<-END
|
16
15
|
def #{method}(msg, prefix = [])
|
17
16
|
if @logger.is_a?(self.class)
|
18
17
|
@logger.#{method}(msg, [@prefix] + prefix)
|
@@ -40,7 +39,8 @@ module Bluepill
|
|
40
39
|
end
|
41
40
|
end
|
42
41
|
|
43
|
-
|
42
|
+
protected
|
43
|
+
|
44
44
|
def create_logger
|
45
45
|
if @options[:log_file]
|
46
46
|
LoggerAdapter.new(@options[:log_file])
|
data/lib/bluepill/process.rb
CHANGED
@@ -1,12 +1,10 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
1
|
# fixes problem with loading on systems with rubyist-aasm installed
|
4
|
-
gem
|
2
|
+
gem 'state_machine'
|
5
3
|
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
4
|
+
require 'state_machine'
|
5
|
+
require 'daemons'
|
6
|
+
require 'bluepill/system'
|
7
|
+
require 'bluepill/process_journal'
|
10
8
|
|
11
9
|
module Bluepill
|
12
10
|
class Process
|
@@ -49,12 +47,12 @@ module Bluepill
|
|
49
47
|
:group_start_noblock,
|
50
48
|
:group_restart_noblock,
|
51
49
|
:group_stop_noblock,
|
52
|
-
:group_unmonitor_noblock
|
50
|
+
:group_unmonitor_noblock,
|
53
51
|
|
54
52
|
]
|
55
53
|
|
56
54
|
attr_accessor :name, :watches, :triggers, :logger, :skip_ticks_until, :process_running
|
57
|
-
attr_accessor
|
55
|
+
attr_accessor(*CONFIGURABLE_ATTRIBUTES)
|
58
56
|
attr_reader :children, :statistics
|
59
57
|
|
60
58
|
state_machine :initial => :unmonitored do
|
@@ -124,9 +122,9 @@ module Bluepill
|
|
124
122
|
|
125
123
|
checks.each do |name, opts|
|
126
124
|
if Trigger[name]
|
127
|
-
|
125
|
+
add_trigger(name, opts)
|
128
126
|
else
|
129
|
-
|
127
|
+
add_watch(name, opts)
|
130
128
|
end
|
131
129
|
end
|
132
130
|
|
@@ -135,11 +133,11 @@ module Bluepill
|
|
135
133
|
@cache_actual_pid = true
|
136
134
|
@start_grace_time = @stop_grace_time = @restart_grace_time = 3
|
137
135
|
@environment = {}
|
138
|
-
@on_start_timeout =
|
136
|
+
@on_start_timeout = 'start'
|
139
137
|
@group_start_noblock = @group_stop_noblock = @group_restart_noblock = @group_unmonitor_noblock = true
|
140
138
|
|
141
139
|
CONFIGURABLE_ATTRIBUTES.each do |attribute_name|
|
142
|
-
|
140
|
+
send("#{attribute_name}=", options[attribute_name]) if options.key?(attribute_name)
|
143
141
|
end
|
144
142
|
|
145
143
|
# Let state_machine do its initialization stuff
|
@@ -159,64 +157,68 @@ module Bluepill
|
|
159
157
|
# run state machine transitions
|
160
158
|
super
|
161
159
|
|
162
|
-
|
163
|
-
|
160
|
+
return unless self.up?
|
161
|
+
run_watches
|
164
162
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
end
|
169
|
-
end
|
163
|
+
return unless self.monitor_children?
|
164
|
+
refresh_children!
|
165
|
+
children.each(&:tick)
|
170
166
|
end
|
171
167
|
|
172
168
|
def logger=(logger)
|
173
169
|
@logger = logger
|
174
|
-
|
175
|
-
|
170
|
+
watches.each { |w| w.logger = logger }
|
171
|
+
triggers.each { |t| t.logger = logger }
|
176
172
|
end
|
177
173
|
|
178
174
|
# State machine methods
|
179
175
|
def dispatch!(event, reason = nil)
|
180
176
|
@event_mutex.synchronize do
|
181
177
|
@statistics.record_event(event, reason)
|
182
|
-
|
178
|
+
send("#{event}")
|
183
179
|
end
|
184
180
|
end
|
185
181
|
|
186
182
|
def record_transition(transition)
|
187
|
-
|
188
|
-
|
183
|
+
return if transition.loopback?
|
184
|
+
@transitioned = true
|
189
185
|
|
190
|
-
|
191
|
-
|
186
|
+
# When a process changes state, we should clear the memory of all the watches
|
187
|
+
watches.each(&:clear_history!)
|
192
188
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
end
|
198
|
-
logger.info "Going from #{transition.from_name} => #{transition.to_name}"
|
189
|
+
# Also, when a process changes state, we should re-populate its child list
|
190
|
+
if self.monitor_children?
|
191
|
+
logger.warning 'Clearing child list'
|
192
|
+
children.clear
|
199
193
|
end
|
194
|
+
logger.info "Going from #{transition.from_name} => #{transition.to_name}"
|
200
195
|
end
|
201
196
|
|
202
197
|
def notify_triggers(transition)
|
203
|
-
|
198
|
+
triggers.each do |trigger|
|
199
|
+
begin
|
200
|
+
trigger.notify(transition)
|
201
|
+
rescue => e
|
202
|
+
logger.err e.backtrace
|
203
|
+
raise e
|
204
|
+
end
|
205
|
+
end
|
204
206
|
end
|
205
207
|
|
206
208
|
# Watch related methods
|
207
209
|
def add_watch(name, options = {})
|
208
|
-
|
210
|
+
watches << ConditionWatch.new(name, options.merge(:logger => logger))
|
209
211
|
end
|
210
212
|
|
211
213
|
def add_trigger(name, options = {})
|
212
|
-
|
214
|
+
triggers << Trigger[name].new(self, options.merge(:logger => logger))
|
213
215
|
end
|
214
216
|
|
215
217
|
def run_watches
|
216
218
|
now = Time.now.to_i
|
217
219
|
|
218
|
-
threads =
|
219
|
-
[watch, Thread.new { Thread.current[:events] = watch.run(
|
220
|
+
threads = watches.collect do |watch|
|
221
|
+
[watch, Thread.new { Thread.current[:events] = watch.run(actual_pid, now) }]
|
220
222
|
end
|
221
223
|
|
222
224
|
@transitioned = false
|
@@ -246,22 +248,22 @@ module Bluepill
|
|
246
248
|
|
247
249
|
def handle_user_command(cmd)
|
248
250
|
case cmd
|
249
|
-
when
|
251
|
+
when 'start'
|
250
252
|
if self.process_running?(true)
|
251
|
-
logger.warning(
|
253
|
+
logger.warning('Refusing to re-run start command on an already running process.')
|
252
254
|
else
|
253
|
-
dispatch!(:start,
|
255
|
+
dispatch!(:start, 'user initiated')
|
254
256
|
end
|
255
|
-
when
|
257
|
+
when 'stop'
|
256
258
|
stop_process
|
257
|
-
dispatch!(:unmonitor,
|
258
|
-
when
|
259
|
+
dispatch!(:unmonitor, 'user initiated')
|
260
|
+
when 'restart'
|
259
261
|
restart_process
|
260
|
-
when
|
262
|
+
when 'unmonitor'
|
261
263
|
# When the user issues an unmonitor cmd, reset any triggers so that
|
262
264
|
# scheduled events gets cleared
|
263
|
-
triggers.each
|
264
|
-
dispatch!(:unmonitor,
|
265
|
+
triggers.each(&:reset!)
|
266
|
+
dispatch!(:unmonitor, 'user initiated')
|
265
267
|
end
|
266
268
|
end
|
267
269
|
|
@@ -271,7 +273,7 @@ module Bluepill
|
|
271
273
|
|
272
274
|
@process_running ||= signal_process(0)
|
273
275
|
# the process isn't running, so we should clear the PID
|
274
|
-
|
276
|
+
clear_pid unless @process_running
|
275
277
|
@process_running
|
276
278
|
end
|
277
279
|
|
@@ -280,55 +282,54 @@ module Bluepill
|
|
280
282
|
pre_start_process
|
281
283
|
logger.warning "Executing start command: #{start_command}"
|
282
284
|
if self.daemonize?
|
283
|
-
daemon_id = System.daemonize(start_command,
|
285
|
+
daemon_id = System.daemonize(start_command, system_command_options)
|
284
286
|
if daemon_id
|
285
287
|
ProcessJournal.append_pid_to_journal(name, daemon_id)
|
286
|
-
children.each
|
288
|
+
children.each do|child|
|
287
289
|
ProcessJournal.append_pid_to_journal(name, child.actual_id)
|
288
|
-
|
290
|
+
end if self.monitor_children?
|
289
291
|
end
|
290
292
|
daemon_id
|
291
293
|
else
|
292
294
|
# This is a self-daemonizing process
|
293
295
|
with_timeout(start_grace_time, on_start_timeout) do
|
294
|
-
result = System.execute_blocking(start_command,
|
296
|
+
result = System.execute_blocking(start_command, system_command_options)
|
295
297
|
|
296
298
|
unless result[:exit_code].zero?
|
297
|
-
logger.warning
|
299
|
+
logger.warning 'Start command execution returned non-zero exit code:'
|
298
300
|
logger.warning result.inspect
|
299
301
|
end
|
300
302
|
end
|
301
303
|
end
|
302
304
|
|
303
|
-
|
305
|
+
skip_ticks_for(start_grace_time)
|
304
306
|
end
|
305
307
|
|
306
308
|
def pre_start_process
|
307
309
|
return unless pre_start_command
|
308
310
|
logger.warning "Executing pre start command: #{pre_start_command}"
|
309
|
-
result = System.execute_blocking(pre_start_command,
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
end
|
311
|
+
result = System.execute_blocking(pre_start_command, system_command_options)
|
312
|
+
return if result[:exit_code].zero?
|
313
|
+
logger.warning 'Pre start command execution returned non-zero exit code:'
|
314
|
+
logger.warning result.inspect
|
314
315
|
end
|
315
316
|
|
316
317
|
def stop_process
|
317
318
|
if monitor_children
|
318
|
-
System.get_children(
|
319
|
+
System.get_children(actual_pid).each do |child_pid|
|
319
320
|
ProcessJournal.append_pid_to_journal(name, child_pid)
|
320
321
|
end
|
321
322
|
end
|
322
323
|
|
323
324
|
if stop_command
|
324
|
-
cmd =
|
325
|
+
cmd = prepare_command(stop_command)
|
325
326
|
logger.warning "Executing stop command: #{cmd}"
|
326
327
|
|
327
|
-
with_timeout(stop_grace_time,
|
328
|
-
result = System.execute_blocking(cmd,
|
328
|
+
with_timeout(stop_grace_time, 'stop') do
|
329
|
+
result = System.execute_blocking(cmd, system_command_options)
|
329
330
|
|
330
331
|
unless result[:exit_code].zero?
|
331
|
-
logger.warning
|
332
|
+
logger.warning 'Stop command execution returned non-zero exit code:'
|
332
333
|
logger.warning result.inspect
|
333
334
|
end
|
334
335
|
end
|
@@ -348,9 +349,9 @@ module Bluepill
|
|
348
349
|
|
349
350
|
logger.debug "Sleeping for #{delay} seconds"
|
350
351
|
sleep delay
|
351
|
-
#break unless signal_process(0) #break unless the process can be reached
|
352
|
+
# break unless signal_process(0) #break unless the process can be reached
|
352
353
|
unless process.signal_process(0)
|
353
|
-
logger.debug
|
354
|
+
logger.debug 'Process has terminated.'
|
354
355
|
break
|
355
356
|
end
|
356
357
|
logger.info "Sending signal #{signal} to #{process.actual_pid}"
|
@@ -359,55 +360,55 @@ module Bluepill
|
|
359
360
|
end
|
360
361
|
else
|
361
362
|
logger.warning "Executing default stop command. Sending TERM signal to #{actual_pid}"
|
362
|
-
signal_process(
|
363
|
+
signal_process('TERM')
|
363
364
|
end
|
364
365
|
ProcessJournal.kill_all_from_journal(name) # finish cleanup
|
365
|
-
|
366
|
+
unlink_pid # TODO: we only write the pid file if we daemonize, should we only unlink it if we daemonize?
|
366
367
|
|
367
|
-
|
368
|
+
skip_ticks_for(stop_grace_time)
|
368
369
|
end
|
369
370
|
|
370
371
|
def restart_process
|
371
372
|
if restart_command
|
372
|
-
cmd =
|
373
|
+
cmd = prepare_command(restart_command)
|
373
374
|
|
374
375
|
logger.warning "Executing restart command: #{cmd}"
|
375
376
|
|
376
|
-
with_timeout(restart_grace_time,
|
377
|
-
result = System.execute_blocking(cmd,
|
377
|
+
with_timeout(restart_grace_time, 'restart') do
|
378
|
+
result = System.execute_blocking(cmd, system_command_options)
|
378
379
|
|
379
380
|
unless result[:exit_code].zero?
|
380
|
-
logger.warning
|
381
|
+
logger.warning 'Restart command execution returned non-zero exit code:'
|
381
382
|
logger.warning result.inspect
|
382
383
|
end
|
383
384
|
end
|
384
385
|
|
385
|
-
|
386
|
+
skip_ticks_for(restart_grace_time)
|
386
387
|
else
|
387
|
-
logger.warning
|
388
|
-
|
389
|
-
|
388
|
+
logger.warning 'No restart_command specified. Must stop and start to restart'
|
389
|
+
stop_process
|
390
|
+
start_process
|
390
391
|
end
|
391
392
|
end
|
392
393
|
|
393
394
|
def clean_threads
|
394
|
-
@threads.each
|
395
|
+
@threads.each(&:kill)
|
395
396
|
@threads.clear
|
396
397
|
end
|
397
398
|
|
398
399
|
def daemonize?
|
399
|
-
!!
|
400
|
+
!!daemonize
|
400
401
|
end
|
401
402
|
|
402
403
|
def monitor_children?
|
403
|
-
!!
|
404
|
+
!!monitor_children
|
404
405
|
end
|
405
406
|
|
406
407
|
def signal_process(code)
|
407
408
|
code = code.to_s.upcase if code.is_a?(String) || code.is_a?(Symbol)
|
408
409
|
::Process.kill(code, actual_pid)
|
409
410
|
true
|
410
|
-
rescue
|
411
|
+
rescue => e
|
411
412
|
logger.err "Failed to signal process #{actual_pid} with code #{code}: #{e}"
|
412
413
|
false
|
413
414
|
end
|
@@ -424,7 +425,7 @@ module Bluepill
|
|
424
425
|
return @actual_pid if cache_actual_pid? && @actual_pid
|
425
426
|
@actual_pid = begin
|
426
427
|
if pid_file
|
427
|
-
if File.
|
428
|
+
if File.exist?(pid_file)
|
428
429
|
str = File.read(pid_file)
|
429
430
|
str.to_i if str.size > 0
|
430
431
|
else
|
@@ -436,7 +437,7 @@ module Bluepill
|
|
436
437
|
end
|
437
438
|
|
438
439
|
def pid_from_command
|
439
|
-
pid =
|
440
|
+
pid = `#{pid_command}`.strip
|
440
441
|
(pid =~ /\A\d+\z/) ? pid.to_i : nil
|
441
442
|
end
|
442
443
|
|
@@ -453,26 +454,26 @@ module Bluepill
|
|
453
454
|
System.delete_if_exists(pid_file)
|
454
455
|
end
|
455
456
|
|
456
|
-
|
457
|
+
# Internal State Methods
|
457
458
|
def skip_ticks_for(seconds)
|
458
459
|
# TODO: should this be addative or longest wins?
|
459
460
|
# i.e. if two calls for skip_ticks_for come in for 5 and 10, should it skip for 10 or 15?
|
460
|
-
self.skip_ticks_until = (
|
461
|
+
self.skip_ticks_until = (skip_ticks_until || Time.now.to_i) + seconds.to_i
|
461
462
|
end
|
462
463
|
|
463
464
|
def skipping_ticks?
|
464
|
-
|
465
|
+
skip_ticks_until && skip_ticks_until > Time.now.to_i
|
465
466
|
end
|
466
467
|
|
467
468
|
def refresh_children!
|
468
469
|
# First prune the list of dead children
|
469
|
-
@children.delete_if {|child| !child.process_running?(true) }
|
470
|
+
@children.delete_if { |child| !child.process_running?(true) }
|
470
471
|
|
471
472
|
# Add new found children to the list
|
472
|
-
new_children_pids = System.get_children(
|
473
|
+
new_children_pids = System.get_children(actual_pid) - @children.map(&:actual_pid)
|
473
474
|
|
474
475
|
unless new_children_pids.empty?
|
475
|
-
logger.info "Existing children: #{@children.collect
|
476
|
+
logger.info "Existing children: #{@children.collect(&:actual_pid).join(',')}. Got new children: #{new_children_pids.inspect} for #{actual_pid}"
|
476
477
|
end
|
477
478
|
|
478
479
|
# Construct a new process wrapper for each new found children
|
@@ -481,41 +482,38 @@ module Bluepill
|
|
481
482
|
child_name = "<child(pid:#{child_pid})>"
|
482
483
|
logger = self.logger.prefix_with(child_name)
|
483
484
|
|
484
|
-
child =
|
485
|
+
child = child_process_factory.create_child_process(child_name, child_pid, logger)
|
485
486
|
@children << child
|
486
487
|
end
|
487
488
|
end
|
488
489
|
|
489
490
|
def prepare_command(command)
|
490
|
-
command.to_s.gsub(
|
491
|
+
command.to_s.gsub('{{PID}}', actual_pid.to_s)
|
491
492
|
end
|
492
493
|
|
493
494
|
def system_command_options
|
494
495
|
{
|
495
|
-
:uid =>
|
496
|
-
:gid =>
|
497
|
-
:working_dir =>
|
498
|
-
:environment =>
|
499
|
-
:pid_file =>
|
500
|
-
:logger =>
|
501
|
-
:stdin =>
|
502
|
-
:stdout =>
|
503
|
-
:stderr =>
|
504
|
-
:supplementary_groups =>
|
496
|
+
:uid => uid,
|
497
|
+
:gid => gid,
|
498
|
+
:working_dir => working_dir,
|
499
|
+
:environment => environment,
|
500
|
+
:pid_file => pid_file,
|
501
|
+
:logger => logger,
|
502
|
+
:stdin => stdin,
|
503
|
+
:stdout => stdout,
|
504
|
+
:stderr => stderr,
|
505
|
+
:supplementary_groups => supplementary_groups,
|
505
506
|
}
|
506
507
|
end
|
507
508
|
|
508
509
|
def with_timeout(secs, next_state = nil, &blk)
|
509
510
|
# Attempt to execute the passed block. If the block takes
|
510
511
|
# too long, transition to the indicated next state.
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
dispatch!(next_state)
|
517
|
-
end
|
512
|
+
Timeout.timeout(secs.to_f, &blk)
|
513
|
+
rescue Timeout::Error
|
514
|
+
logger.err 'Execution is taking longer than expected.'
|
515
|
+
logger.err 'Did you forget to tell bluepill to daemonize this process?'
|
516
|
+
dispatch!(next_state)
|
518
517
|
end
|
519
518
|
end
|
520
519
|
end
|
521
|
-
|