bluepill 0.0.46 → 0.0.47

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