bluepill 0.0.68 → 0.0.69
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.
- 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
|
-
|