bluepill 0.0.46 → 0.0.47

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.
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Bluepill
3
+ class ProcessProxy
4
+ attr_reader :attributes, :watches, :name
5
+ def initialize(process_name, attributes, process_block)
6
+ @name = process_name
7
+ @attributes = attributes
8
+ @watches = {}
9
+
10
+ process_block.call(self)
11
+ end
12
+
13
+ def method_missing(name, *args)
14
+ if args.size == 1 && name.to_s =~ /^(.*)=$/
15
+ @attributes[$1.to_sym] = args.first
16
+ elsif args.empty? && @attributes.key?(name.to_sym)
17
+ @attributes[name.to_sym]
18
+ else
19
+ super
20
+ end
21
+ end
22
+
23
+ def checks(name, options = {})
24
+ @watches[name] = options
25
+ end
26
+
27
+ def monitor_children(&child_process_block)
28
+ @attributes[:monitor_children] = true
29
+ @attributes[:child_process_block] = child_process_block
30
+ end
31
+
32
+ def to_process
33
+ Process.new(@name, @watches, @attributes)
34
+ end
35
+ end
36
+ end
@@ -1,25 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  module Bluepill
2
3
  class Group
3
4
  attr_accessor :name, :processes, :logger
4
5
  attr_accessor :process_logger
5
-
6
+
6
7
  def initialize(name, options = {})
7
8
  self.name = name
8
9
  self.processes = []
9
10
  self.logger = options[:logger]
10
11
  end
11
-
12
+
12
13
  def add_process(process)
13
14
  process.logger = self.logger.prefix_with(process.name)
14
15
  self.processes << process
15
16
  end
16
-
17
+
17
18
  def tick
18
19
  self.processes.each do |process|
19
20
  process.tick
20
21
  end
21
22
  end
22
-
23
+
23
24
  def determine_initial_state
24
25
  self.processes.each do |process|
25
26
  process.determine_initial_state
@@ -39,16 +40,16 @@ module Bluepill
39
40
  end
40
41
  threads.each { |t| t.join }
41
42
  affected
42
- end
43
+ end
43
44
  END
44
45
  end
45
-
46
+
46
47
  def status(process_name = nil)
47
48
  lines = []
48
49
  if process_name.nil?
49
50
  prefix = self.name ? " " : ""
50
51
  lines << "#{self.name}:" if self.name
51
-
52
+
52
53
  self.processes.each do |process|
53
54
  lines << "%s%s(pid:%s): %s" % [prefix, process.name, process.actual_pid, process.state]
54
55
  if process.monitor_children?
@@ -66,6 +67,6 @@ module Bluepill
66
67
  end
67
68
  lines << ""
68
69
  end
69
-
70
+
70
71
  end
71
72
  end
@@ -1,7 +1,8 @@
1
- module Bluepill
1
+ # -*- encoding: utf-8 -*-
2
+ module Bluepill
2
3
  class Logger
3
4
  LOG_METHODS = [:emerg, :alert, :crit, :err, :warning, :notice, :info, :debug]
4
-
5
+
5
6
  def initialize(options = {})
6
7
  @options = options
7
8
  @logger = options[:logger] || self.create_logger
@@ -9,7 +10,7 @@ module Bluepill
9
10
  @stdout = options[:stdout]
10
11
  @prefixes = {}
11
12
  end
12
-
13
+
13
14
  LOG_METHODS.each do |method|
14
15
  eval <<-END
15
16
  def #{method}(msg, prefix = [])
@@ -26,11 +27,11 @@ module Bluepill
26
27
  end
27
28
  END
28
29
  end
29
-
30
+
30
31
  def prefix_with(prefix)
31
32
  @prefixes[prefix] ||= self.class.new(:logger => self, :prefix => prefix)
32
33
  end
33
-
34
+
34
35
  def reopen
35
36
  if @logger.is_a?(self.class)
36
37
  @logger.reopen
@@ -38,7 +39,7 @@ module Bluepill
38
39
  @logger = create_logger
39
40
  end
40
41
  end
41
-
42
+
42
43
  protected
43
44
  def create_logger
44
45
  if @options[:log_file]
@@ -50,13 +51,13 @@ module Bluepill
50
51
  end
51
52
 
52
53
  class LoggerAdapter < ::Logger
53
- LOGGER_EQUIVALENTS =
54
+ LOGGER_EQUIVALENTS =
54
55
  {:debug => :debug, :err => :error, :warning => :warn, :info => :info, :emerg => :fatal, :alert => :warn, :crit => :fatal, :notice => :info}
55
-
56
+
56
57
  LOG_METHODS.each do |method|
57
58
  next if method == LOGGER_EQUIVALENTS[method]
58
59
  alias_method method, LOGGER_EQUIVALENTS[method]
59
60
  end
60
- end
61
+ end
61
62
  end
62
63
  end
@@ -1,37 +1,38 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  require "state_machine"
2
3
  require "daemons"
3
4
 
4
5
  module Bluepill
5
6
  class Process
6
7
  CONFIGURABLE_ATTRIBUTES = [
7
- :start_command,
8
- :stop_command,
9
- :restart_command,
10
-
8
+ :start_command,
9
+ :stop_command,
10
+ :restart_command,
11
+
11
12
  :stdout,
12
13
  :stderr,
13
14
  :stdin,
14
-
15
- :daemonize,
16
- :pid_file,
15
+
16
+ :daemonize,
17
+ :pid_file,
17
18
  :working_dir,
18
19
  :environment,
19
-
20
- :start_grace_time,
21
- :stop_grace_time,
20
+
21
+ :start_grace_time,
22
+ :stop_grace_time,
22
23
  :restart_grace_time,
23
-
24
+
24
25
  :uid,
25
26
  :gid,
26
-
27
+
27
28
  :monitor_children,
28
- :child_process_template
29
+ :child_process_factory
29
30
  ]
30
-
31
- attr_accessor :name, :watches, :triggers, :logger, :skip_ticks_until
31
+
32
+ attr_accessor :name, :watches, :triggers, :logger, :skip_ticks_until, :process_running
32
33
  attr_accessor *CONFIGURABLE_ATTRIBUTES
33
34
  attr_reader :children, :statistics
34
-
35
+
35
36
  state_machine :initial => :unmonitored do
36
37
  # These are the idle states, i.e. only an event (either external or internal) will trigger a transition.
37
38
  # The distinction between down and unmonitored is that down
@@ -47,15 +48,15 @@ module Bluepill
47
48
 
48
49
  transition :up => :up, :if => :process_running?
49
50
  transition :up => :down, :unless => :process_running?
50
-
51
+
51
52
  # The process failed to die after entering the stopping state. Change the state to reflect
52
53
  # reality.
53
- transition :stopping => :up, :if => :process_running?
54
+ transition :stopping => :up, :if => :process_running?
54
55
  transition :stopping => :down, :unless => :process_running?
55
-
56
+
56
57
  transition :down => :up, :if => :process_running?
57
58
  transition :down => :starting, :unless => :process_running?
58
-
59
+
59
60
  transition :restarting => :up, :if => :process_running?
60
61
  transition :restarting => :down, :unless => :process_running?
61
62
  end
@@ -84,25 +85,34 @@ module Bluepill
84
85
 
85
86
  after_transition any => any, :do => :record_transition
86
87
  end
87
-
88
- def initialize(process_name, options = {})
88
+
89
+ def initialize(process_name, checks, options = {})
89
90
  @name = process_name
90
91
  @event_mutex = Monitor.new
91
- @transition_history = Util::RotationalArray.new(10)
92
92
  @watches = []
93
93
  @triggers = []
94
94
  @children = []
95
95
  @statistics = ProcessStatistics.new
96
-
96
+ @actual_pid = options[:actual_pid]
97
+ self.logger = options[:logger]
98
+
99
+ checks.each do |name, opts|
100
+ if Trigger[name]
101
+ self.add_trigger(name, opts)
102
+ else
103
+ self.add_watch(name, opts)
104
+ end
105
+ end
106
+
97
107
  # These defaults are overriden below if it's configured to be something else.
98
108
  @monitor_children = false
99
109
  @start_grace_time = @stop_grace_time = @restart_grace_time = 3
100
110
  @environment = {}
101
-
111
+
102
112
  CONFIGURABLE_ATTRIBUTES.each do |attribute_name|
103
113
  self.send("#{attribute_name}=", options[attribute_name]) if options.has_key?(attribute_name)
104
114
  end
105
-
115
+
106
116
  # Let state_machine do its initialization stuff
107
117
  super() # no arguments intentional
108
118
  end
@@ -119,20 +129,20 @@ module Bluepill
119
129
 
120
130
  if self.up?
121
131
  self.run_watches
122
-
132
+
123
133
  if self.monitor_children?
124
134
  refresh_children!
125
135
  children.each {|child| child.tick}
126
136
  end
127
137
  end
128
138
  end
129
-
139
+
130
140
  def logger=(logger)
131
141
  @logger = logger
132
142
  self.watches.each {|w| w.logger = logger }
133
143
  self.triggers.each {|t| t.logger = logger }
134
144
  end
135
-
145
+
136
146
  # State machine methods
137
147
  def dispatch!(event, reason = nil)
138
148
  @event_mutex.synchronize do
@@ -140,14 +150,14 @@ module Bluepill
140
150
  self.send("#{event}")
141
151
  end
142
152
  end
143
-
153
+
144
154
  def record_transition(transition)
145
155
  unless transition.loopback?
146
156
  @transitioned = true
147
-
157
+
148
158
  # When a process changes state, we should clear the memory of all the watches
149
159
  self.watches.each { |w| w.clear_history! }
150
-
160
+
151
161
  # Also, when a process changes state, we should re-populate its child list
152
162
  if self.monitor_children?
153
163
  self.logger.warning "Clearing child list"
@@ -156,16 +166,16 @@ module Bluepill
156
166
  logger.info "Going from #{transition.from_name} => #{transition.to_name}"
157
167
  end
158
168
  end
159
-
169
+
160
170
  def notify_triggers(transition)
161
171
  self.triggers.each {|trigger| trigger.notify(transition)}
162
172
  end
163
-
173
+
164
174
  # Watch related methods
165
175
  def add_watch(name, options = {})
166
176
  self.watches << ConditionWatch.new(name, options.merge(:logger => self.logger))
167
177
  end
168
-
178
+
169
179
  def add_trigger(name, options = {})
170
180
  self.triggers << Trigger[name].new(self, options.merge(:logger => self.logger))
171
181
  end
@@ -176,9 +186,9 @@ module Bluepill
176
186
  threads = self.watches.collect do |watch|
177
187
  [watch, Thread.new { Thread.current[:events] = watch.run(self.actual_pid, now) }]
178
188
  end
179
-
189
+
180
190
  @transitioned = false
181
-
191
+
182
192
  threads.inject([]) do |events, (watch, thread)|
183
193
  thread.join
184
194
  if thread[:events].size > 0
@@ -193,7 +203,7 @@ module Bluepill
193
203
  self.dispatch!(event, reason)
194
204
  end
195
205
  end
196
-
206
+
197
207
  def determine_initial_state
198
208
  if self.process_running?(true)
199
209
  self.state = 'up'
@@ -202,7 +212,7 @@ module Bluepill
202
212
  self.state = 'down'
203
213
  end
204
214
  end
205
-
215
+
206
216
  def handle_user_command(cmd)
207
217
  case cmd
208
218
  when "start"
@@ -223,67 +233,67 @@ module Bluepill
223
233
  dispatch!(:unmonitor, "user initiated")
224
234
  end
225
235
  end
226
-
236
+
227
237
  # System Process Methods
228
238
  def process_running?(force = false)
229
239
  @process_running = nil if force # clear existing state if forced
230
-
240
+
231
241
  @process_running ||= signal_process(0)
232
242
  # the process isn't running, so we should clear the PID
233
243
  self.clear_pid unless @process_running
234
244
  @process_running
235
245
  end
236
-
246
+
237
247
  def start_process
238
248
  logger.warning "Executing start command: #{start_command}"
239
-
249
+
240
250
  if self.daemonize?
241
251
  System.daemonize(start_command, self.system_command_options)
242
-
252
+
243
253
  else
244
254
  # This is a self-daemonizing process
245
255
  with_timeout(start_grace_time) do
246
256
  result = System.execute_blocking(start_command, self.system_command_options)
247
-
257
+
248
258
  unless result[:exit_code].zero?
249
259
  logger.warning "Start command execution returned non-zero exit code:"
250
260
  logger.warning result.inspect
251
- end
261
+ end
252
262
  end
253
263
  end
254
-
264
+
255
265
  self.skip_ticks_for(start_grace_time)
256
266
  end
257
-
258
- def stop_process
267
+
268
+ def stop_process
259
269
  if stop_command
260
270
  cmd = self.prepare_command(stop_command)
261
271
  logger.warning "Executing stop command: #{cmd}"
262
-
272
+
263
273
  with_timeout(stop_grace_time) do
264
274
  result = System.execute_blocking(cmd, self.system_command_options)
265
-
275
+
266
276
  unless result[:exit_code].zero?
267
277
  logger.warning "Stop command execution returned non-zero exit code:"
268
278
  logger.warning result.inspect
269
279
  end
270
280
  end
271
-
281
+
272
282
  else
273
283
  logger.warning "Executing default stop command. Sending TERM signal to #{actual_pid}"
274
284
  signal_process("TERM")
275
285
  end
276
286
  self.unlink_pid # TODO: we only write the pid file if we daemonize, should we only unlink it if we daemonize?
277
-
287
+
278
288
  self.skip_ticks_for(stop_grace_time)
279
289
  end
280
-
290
+
281
291
  def restart_process
282
292
  if restart_command
283
293
  cmd = self.prepare_command(restart_command)
284
-
294
+
285
295
  logger.warning "Executing restart command: #{cmd}"
286
-
296
+
287
297
  with_timeout(restart_grace_time) do
288
298
  result = System.execute_blocking(cmd, self.system_command_options)
289
299
 
@@ -292,7 +302,7 @@ module Bluepill
292
302
  logger.warning result.inspect
293
303
  end
294
304
  end
295
-
305
+
296
306
  self.skip_ticks_for(restart_grace_time)
297
307
  else
298
308
  logger.warning "No restart_command specified. Must stop and start to restart"
@@ -300,22 +310,22 @@ module Bluepill
300
310
  # the tick will bring it back.
301
311
  end
302
312
  end
303
-
313
+
304
314
  def daemonize?
305
315
  !!self.daemonize
306
316
  end
307
-
317
+
308
318
  def monitor_children?
309
319
  !!self.monitor_children
310
320
  end
311
-
321
+
312
322
  def signal_process(code)
313
323
  ::Process.kill(code, actual_pid)
314
324
  true
315
325
  rescue
316
326
  false
317
327
  end
318
-
328
+
319
329
  def actual_pid
320
330
  @actual_pid ||= begin
321
331
  if pid_file
@@ -329,77 +339,60 @@ module Bluepill
329
339
  end
330
340
  end
331
341
  end
332
-
342
+
333
343
  def actual_pid=(pid)
334
344
  @actual_pid = pid
335
345
  end
336
-
346
+
337
347
  def clear_pid
338
348
  @actual_pid = nil
339
349
  end
340
-
350
+
341
351
  def unlink_pid
342
352
  File.unlink(pid_file) if pid_file && File.exists?(pid_file)
343
353
  rescue Errno::ENOENT
344
354
  end
345
-
355
+
346
356
  # Internal State Methods
347
357
  def skip_ticks_for(seconds)
348
358
  # TODO: should this be addative or longest wins?
349
359
  # i.e. if two calls for skip_ticks_for come in for 5 and 10, should it skip for 10 or 15?
350
360
  self.skip_ticks_until = (self.skip_ticks_until || Time.now.to_i) + seconds.to_i
351
361
  end
352
-
362
+
353
363
  def skipping_ticks?
354
364
  self.skip_ticks_until && self.skip_ticks_until > Time.now.to_i
355
365
  end
356
-
366
+
357
367
  def refresh_children!
358
368
  # First prune the list of dead children
359
369
  @children.delete_if {|child| !child.process_running?(true) }
360
-
370
+
361
371
  # Add new found children to the list
362
372
  new_children_pids = System.get_children(self.actual_pid) - @children.map {|child| child.actual_pid}
363
-
373
+
364
374
  unless new_children_pids.empty?
365
375
  logger.info "Existing children: #{@children.collect{|c| c.actual_pid}.join(",")}. Got new children: #{new_children_pids.inspect} for #{actual_pid}"
366
376
  end
367
-
377
+
368
378
  # Construct a new process wrapper for each new found children
369
379
  new_children_pids.each do |child_pid|
370
- child = self.child_process_template.deep_copy
371
-
372
- child.name = "<child(pid:#{child_pid})>"
373
- child.actual_pid = child_pid
374
- child.logger = self.logger.prefix_with(child.name)
375
-
376
- child.initialize_state_machines
377
- child.state = "up"
378
-
380
+ name = "<child(pid:#{child_pid})>"
381
+ logger = self.logger.prefix_with(name)
382
+
383
+ child = self.child_process_factory.create_child_process(name, child_pid, logger)
379
384
  @children << child
380
385
  end
381
386
  end
382
387
 
383
- def deep_copy
384
- # TODO: This is a kludge. Ideally, process templates
385
- # would be facotries, and not a template object.
386
- mutex, triggers, @event_mutex, @triggers = @event_mutex, @triggers, nil, nil
387
- clone = Marshal.load(Marshal.dump(self))
388
- clone.instance_variable_set("@event_mutex", Monitor.new)
389
- clone.instance_variable_set("@triggers", triggers.collect{ |t| t.deep_copy })
390
- @event_mutex = mutex
391
- @triggers = triggers
392
- clone
393
- end
394
-
395
388
  def prepare_command(command)
396
389
  command.to_s.gsub("{{PID}}", actual_pid.to_s)
397
390
  end
398
-
391
+
399
392
  def system_command_options
400
393
  {
401
- :uid => self.uid,
402
- :gid => self.gid,
394
+ :uid => self.uid,
395
+ :gid => self.gid,
403
396
  :working_dir => self.working_dir,
404
397
  :environment => self.environment,
405
398
  :pid_file => self.pid_file,
@@ -409,10 +402,10 @@ module Bluepill
409
402
  :stderr => self.stderr
410
403
  }
411
404
  end
412
-
405
+
413
406
  def with_timeout(secs, &blk)
414
407
  Timeout.timeout(secs.to_f, &blk)
415
-
408
+
416
409
  rescue Timeout::Error
417
410
  logger.err "Execution is taking longer than expected. Unmonitoring."
418
411
  logger.err "Did you forget to tell bluepill to daemonize this process?"
@@ -420,4 +413,4 @@ module Bluepill
420
413
  end
421
414
  end
422
415
  end
423
-
416
+