bluepill 0.0.24 → 0.0.25

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -211,9 +211,10 @@ By default, bluepill uses syslog local6 facility as described in the installatio
211
211
 
212
212
  Keep in mind that you still need to set up log rotation (described in the installation section) to keep the log file from growing huge.
213
213
 
214
- ## Contribute
214
+ ## Links
215
215
  Code: [http://github.com/arya/bluepill](http://github.com/arya/bluepill)
216
216
  Bugs/Features: [http://github.com/arya/bluepill/issues](http://github.com/arya/bluepill/issues)
217
+ Mailing List: [http://groups.google.com/group/bluepill-rb](http://groups.google.com/group/bluepill-rb)
217
218
 
218
219
 
219
220
  [gemcutter]: http://gemcutter.org
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.24
1
+ 0.0.25
@@ -20,16 +20,16 @@ OptionParser.new do |opts|
20
20
  opts.on('-l', "--logfile LOGFILE", "Path to logfile, defaults to #{options[:log_file]}") do |file|
21
21
  options[:log_file] = file
22
22
  end
23
-
23
+
24
24
  opts.on('-c', "--base-dir DIR", "Directory to store bluepill socket and pid files, defaults to #{options[:base_dir]}") do |base_dir|
25
25
  options[:base_dir] = base_dir
26
26
  end
27
-
27
+
28
28
  opts.on("-v", "--version") do
29
29
  puts "bluepill, version #{Bluepill::VERSION}"
30
30
  exit
31
31
  end
32
-
32
+
33
33
  help = lambda do
34
34
  puts opts
35
35
  puts
@@ -46,18 +46,18 @@ OptionParser.new do |opts|
46
46
  puts "See http://github.com/arya/bluepill for README"
47
47
  exit
48
48
  end
49
-
49
+
50
50
  opts.on_tail('-h','--help', 'Show this message', &help)
51
51
  help.call if ARGV.empty?
52
52
  end.parse!
53
53
 
54
54
  APPLICATION_COMMANDS = %w(status start stop restart unmonitor quit log)
55
55
 
56
- controller = Bluepill::Controller.new(options.slice(:base_dir))
56
+ controller = Bluepill::Controller.new(options.slice(:base_dir, :log_file))
57
57
 
58
58
  if controller.running_applications.include?(ARGV.first)
59
59
  # the first arg is the application name
60
- options[:application] = ARGV.shift
60
+ options[:application] = ARGV.shift
61
61
  elsif APPLICATION_COMMANDS.include?(ARGV.first)
62
62
  if controller.running_applications.length == 1
63
63
  # there is only one, let's just use that
@@ -79,26 +79,22 @@ end
79
79
 
80
80
  options[:command] = ARGV.shift
81
81
 
82
- case options[:command]
83
- when "load"
84
- config_file = ARGV.shift
85
- if File.exists?(config_file)
86
- eval(File.read(config_file))
82
+ if options[:command] == "load"
83
+ file = ARGV.shift
84
+ if File.exists?(file)
85
+ # Restart the ruby interpreter for the config file so that anything loaded here
86
+ # does not stay in memory for the daemon
87
+ require 'rbconfig'
88
+ ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])
89
+ load_path = File.expand_path("#{File.dirname(__FILE__)}/../lib")
90
+ file_path = File.expand_path(file)
91
+
92
+ exec(ruby, "-I#{load_path}", '-rbluepill', file_path)
93
+
87
94
  else
88
95
  $stderr.puts "Can't find file: #{file}"
89
96
  end
90
- when "log"
91
- requested_pattern = ARGV.shift
92
- grep_pattern = controller.send_command_to_application(options[:application], :grep_pattern, requested_pattern)
93
-
94
- tail = "tail -n 100 -f #{options[:log_file]} | grep -E '#{grep_pattern}'"
95
- puts "Tailing log for #{requested_pattern}..."
96
- Kernel.exec(tail)
97
-
98
- when *APPLICATION_COMMANDS
99
- target = ARGV.shift
100
- puts controller.send_command_to_application(options[:application], options[:command], target)
101
-
102
97
  else
103
- puts "Unknown command `%s` (or application `%s` has not been loaded yet)" % [options[:command], options[:command]]
98
+ target = ARGV.shift
99
+ controller.handle_command(options[:application], options[:command], target)
104
100
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{bluepill}
8
- s.version = "0.0.24"
8
+ s.version = "0.0.25"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Arya Asemanfar", "Gary Tsang", "Rohith Ravi"]
12
- s.date = %q{2009-11-24}
12
+ s.date = %q{2009-11-30}
13
13
  s.default_executable = %q{bluepill}
14
14
  s.description = %q{Bluepill keeps your daemons up while taking up as little resources as possible. After all you probably want the resources of your server to be used by whatever daemons you are running rather than the thing that's supposed to make sure they are brought back up, should they die or misbehave.}
15
15
  s.email = %q{entombedvirus@gmail.com}
@@ -1,25 +1,30 @@
1
1
  module Bluepill
2
2
  class Application
3
+ PROCESS_COMMANDS = [:start, :stop, :restart, :unmonitor]
4
+
3
5
  attr_accessor :name, :logger, :base_dir, :socket, :pid_file
4
- attr_accessor :groups, :work_queue, :socket_timeout
6
+ attr_accessor :groups, :work_queue
7
+ attr_accessor :pids_dir, :log_file
5
8
 
6
9
  def initialize(name, options = {})
7
10
  self.name = name
8
- self.base_dir = options[:base_dir] ||= '/var/bluepill'
9
- self.socket_timeout = options[:socket_timeout] ||= 10
10
-
11
- self.logger = Bluepill::Logger.new(options.slice(:log_file)).prefix_with(self.name)
12
-
13
- self.groups = Hash.new
14
11
 
12
+ self.log_file = options[:log_file]
13
+ self.base_dir = options[:base_dir] || '/var/bluepill'
15
14
  self.pid_file = File.join(self.base_dir, 'pids', self.name + ".pid")
15
+ self.pids_dir = File.join(self.base_dir, 'pids', self.name)
16
+
17
+ self.groups = {}
16
18
 
17
- @server = false
19
+ self.logger = Bluepill::Logger.new(:log_file => self.log_file).prefix_with(self.name)
20
+
21
+ self.setup_signal_traps
22
+ self.setup_pids_dir
18
23
  end
19
24
 
20
25
  def load
21
26
  begin
22
- start_server
27
+ self.start_server
23
28
  rescue StandardError => e
24
29
  $stderr.puts "Failed to start bluepill:"
25
30
  $stderr.puts "%s `%s`" % [e.class.name, e.message]
@@ -29,132 +34,88 @@ module Bluepill
29
34
  end
30
35
 
31
36
  def status
32
- if(@server)
33
- buffer = []
34
- depth = 0
35
-
36
- if self.groups.has_key?(nil)
37
- self.groups[nil].processes.each do |p|
38
- buffer << "%s%s(pid:%s): %s" % [" " * depth, p.name, p.actual_pid.inspect, p.state]
39
-
40
- if p.monitor_children?
41
- depth += 2
42
- p.children.each do |c|
43
- buffer << "%s%s: %s" % [" " * depth, c.name, c.state]
44
- end
45
- depth -= 2
46
- end
47
- end
48
- end
37
+ buffer = []
38
+ depth = 0
39
+
40
+ if self.groups.has_key?(nil)
41
+ self.groups[nil].processes.each do |p|
42
+ buffer << "%s%s(pid:%s): %s" % [" " * depth, p.name, p.actual_pid.inspect, p.state]
49
43
 
50
- self.groups.each do |group_name, group|
51
- next if group_name.nil?
52
-
53
- buffer << "\n#{group_name}"
54
-
55
- group.processes.each do |p|
44
+ if p.monitor_children?
56
45
  depth += 2
57
-
58
- buffer << "%s%s(pid:%s): %s" % [" " * depth, p.name, p.actual_pid.inspect, p.state]
59
-
60
- if p.monitor_children?
61
- depth += 2
62
- p.children.each do |c|
63
- buffer << "%s%s: %s" % [" " * depth, c.name, c.state]
64
- end
65
- depth -= 2
46
+ p.children.each do |c|
47
+ buffer << "%s%s: %s" % [" " * depth, c.name, c.state]
66
48
  end
67
-
68
49
  depth -= 2
69
50
  end
70
51
  end
71
-
72
- buffer.join("\n")
73
-
74
- else
75
- send_to_server('status')
76
52
  end
77
- end
78
-
79
- def stop(process_or_group_name)
80
- send_to_process_or_group(:stop, process_or_group_name)
81
- end
82
-
83
- def start(process_or_group_name)
84
- send_to_process_or_group(:start, process_or_group_name)
85
- end
86
53
 
87
- def restart(process_or_group_name)
88
- send_to_process_or_group(:restart, process_or_group_name)
89
- end
54
+ self.groups.each do |group_name, group|
55
+ next if group_name.nil?
90
56
 
91
- def unmonitor(process_or_group_name)
92
- send_to_process_or_group(:unmonitor, process_or_group_name)
93
- end
94
-
95
- def send_to_process_or_group(method, process_or_group_name, async = true)
96
- if(@server)
97
- if async
98
- self.work_queue.push([method, process_or_group_name])
99
- else
100
- group = self.groups[process_or_group_name]
101
- if group
102
- group.send(method)
103
- else
104
- self.groups.values.each do |group|
105
- group.send(method, process_or_group_name)
57
+ buffer << "\n#{group_name}"
58
+
59
+ group.processes.each do |p|
60
+ depth += 2
61
+
62
+ buffer << "%s%s(pid:%s): %s" % [" " * depth, p.name, p.actual_pid.inspect, p.state]
63
+
64
+ if p.monitor_children?
65
+ depth += 2
66
+ p.children.each do |c|
67
+ buffer << "%s%s: %s" % [" " * depth, c.name, c.state]
106
68
  end
69
+ depth -= 2
107
70
  end
71
+
72
+ depth -= 2
108
73
  end
109
- return "ok"
110
- else
111
- send_to_server("#{method}:#{process_or_group_name}")
112
74
  end
75
+
76
+ buffer.join("\n")
113
77
  end
114
78
 
115
- def quit
116
- if @server
117
- ::Process.kill("TERM", 0)
118
- "Terminating bluepill[#{::Process.pid}]"
119
- else
120
- send_to_server("quit")
121
- end
79
+ PROCESS_COMMANDS.each do |command|
80
+ class_eval <<-END
81
+ def #{command}(group_name, process_name = nil)
82
+ puts "got #{command}"
83
+ self.send_to_process_or_group(:#{command}, group_name, process_name)
84
+ end
85
+ END
122
86
  end
123
87
 
124
88
  def add_process(process, group_name = nil)
89
+ group_name = group_name.to_s if group_name
90
+
125
91
  self.groups[group_name] ||= Group.new(group_name, :logger => self.logger.prefix_with(group_name))
126
92
  self.groups[group_name].add_process(process)
127
93
  end
128
-
129
- def send_to_server(method)
130
- buffer = ""
131
- begin
132
- status = Timeout::timeout(self.socket_timeout) do
133
- self.socket = Bluepill::Socket.new(name, base_dir).client # Something that should be interrupted if it takes too much time...
134
- socket.write(method + "\n")
135
- while(line = socket.gets)
136
- buffer << line
137
- end
138
- end
139
- rescue Timeout::Error
140
- abort("Socket Timeout: Server may not be responding")
141
- rescue Errno::ECONNREFUSED
142
- abort("Connection Refused: Server is not running")
94
+
95
+ protected
96
+ def send_to_process_or_group(method, group_name, process_name)
97
+ if self.groups.key?(group_name)
98
+ self.groups[group_name].send(method, process_name)
99
+ elsif process_name.nil?
100
+ # they must be targeting just by process name
101
+ process_name = group_name
102
+ self.groups.values.collect do |group|
103
+ group.send(method, process_name)
104
+ end.flatten
105
+ else
106
+ []
143
107
  end
144
- buffer
145
108
  end
146
109
 
147
- private
148
-
149
110
  def start_listener
150
111
  @listener_thread.kill if @listener_thread
151
- @listener_thread = Thread.new(self) do |app|
112
+ @listener_thread = Thread.new do
152
113
  begin
153
114
  loop do
154
- client = app.socket.accept
155
- cmd = client.readline.strip
156
- response = app.send(*cmd.split(":"))
157
- client.write(response)
115
+ client = self.socket.accept
116
+ command, *args = client.readline.strip.split(":")
117
+ response = self.send(command, *args)
118
+ client.write(Marshal.dump(response))
158
119
  client.close
159
120
  end
160
121
  rescue StandardError => e
@@ -164,93 +125,88 @@ module Bluepill
164
125
  end
165
126
  end
166
127
 
167
- def start_worker
168
- @worker_thread.kill if @worker_thread
169
- @worker_thread = Thread.new(self) do |app|
170
- loop do
171
- begin
172
- job = app.work_queue.pop
173
- send_to_process_or_group(job[0], job[1], false)
174
- rescue StandardError => e
175
- logger.err("Error while trying to execute %s from work_queue" % job.inspect)
176
- logger.err("%s: `%s`" % [e.class.name, e.message])
177
- end
178
- end
179
- end
180
- end
181
-
182
128
  def start_server
183
- if File.exists?(self.pid_file)
184
- previous_pid = File.read(self.pid_file).to_i
185
- begin
186
- ::Process.kill(0, previous_pid)
187
- puts "Killing previous bluepilld[#{previous_pid}]"
188
- ::Process.kill(2, previous_pid)
189
- rescue Exception => e
190
- exit unless e.is_a?(Errno::ESRCH)
191
- else
192
- 5.times do |i|
193
- sleep 0.5
194
- break unless System.pid_alive?(previous_pid)
195
- end
196
-
197
- if System.pid_alive?(previous_pid)
198
- $stderr.puts "Previous bluepilld[#{previous_pid}] didn't die"
199
- exit(4)
200
- end
201
- end
202
- end
129
+ self.kill_previous_bluepill
203
130
 
204
131
  Daemonize.daemonize
132
+
205
133
  self.logger.reopen
206
134
 
207
- @server = true
208
135
  $0 = "bluepilld: #{self.name}"
209
136
 
210
- self.work_queue = Queue.new
211
-
212
- self.socket = Bluepill::Socket.new(name, base_dir).server
213
- File.open(self.pid_file, 'w') { |x| x.write(::Process.pid) }
137
+ self.groups.each {|_, group| group.boot }
138
+
214
139
 
215
- self.groups.each {|_, group| group.boot! }
140
+ self.write_pid_file
141
+ self.socket = Bluepill::Socket.server(self.base_dir, self.name)
142
+ self.start_listener
216
143
 
217
- setup_signal_traps
218
- start_listener
219
- start_worker
220
- run
144
+ self.run
221
145
  end
222
146
 
223
147
  def run
224
- loop do
148
+ @running = true # set to false by signal trap
149
+ while @running
225
150
  System.reset_data
226
-
227
- self.groups.each do |_, group|
228
- group.tick
229
- end
151
+ self.groups.each { |_, group| group.tick }
230
152
  sleep 1
231
153
  end
154
+ cleanup
155
+ end
156
+
157
+ def cleanup
158
+ File.unlink(self.socket.path) if self.socket
159
+ File.unlink(self.pid_file) if File.exists?(self.pid_file)
232
160
  end
233
161
 
234
162
  def setup_signal_traps
235
163
  terminator = lambda do
236
164
  puts "Terminating..."
237
- File.unlink(self.socket.path) if self.socket
238
- File.unlink(self.pid_file) if File.exists?(self.pid_file)
239
- ::Kernel.exit
165
+ @running = false
240
166
  end
241
167
 
242
168
  Signal.trap("TERM", &terminator)
243
169
  Signal.trap("INT", &terminator)
244
170
 
245
171
  Signal.trap("HUP") do
246
- self.logger.reopen
172
+ self.logger.reopen if self.logger
247
173
  end
248
174
  end
249
-
250
- def grep_pattern(query = nil)
251
- bluepilld = 'bluepilld\[[[:digit:]]+\]:[[:space:]]+'
252
- pattern = [self.name, query].compact.join(':')
253
- [bluepilld, '\[.*', Regexp.escape(pattern), '.*'].join
175
+
176
+ def setup_pids_dir
177
+ FileUtils.mkdir_p(self.pids_dir) unless File.exists?(self.pids_dir)
178
+ # we need everybody to be able to write to the pids_dir as processes managed by
179
+ # bluepill will be writing to this dir after they've dropped privileges
180
+ FileUtils.chmod(0777, self.pids_dir)
181
+ end
182
+
183
+ def kill_previous_bluepill
184
+ if File.exists?(self.pid_file)
185
+ previous_pid = File.read(self.pid_file).to_i
186
+ begin
187
+ ::Process.kill(0, previous_pid)
188
+ puts "Killing previous bluepilld[#{previous_pid}]"
189
+ ::Process.kill(2, previous_pid)
190
+ rescue Exception => e
191
+ $stderr.puts "Encountered error trying to kill previous bluepill:"
192
+ $stderr.puts "#{e.class}: #{e.message}"
193
+ exit(4) unless e.is_a?(Errno::ESRCH)
194
+ else
195
+ 10.times do |i|
196
+ sleep 0.5
197
+ break unless System.pid_alive?(previous_pid)
198
+ end
199
+
200
+ if System.pid_alive?(previous_pid)
201
+ $stderr.puts "Previous bluepilld[#{previous_pid}] didn't die"
202
+ exit(4)
203
+ end
204
+ end
205
+ end
206
+ end
207
+
208
+ def write_pid_file
209
+ File.open(self.pid_file, 'w') { |x| x.write(::Process.pid) }
254
210
  end
255
211
  end
256
212
  end
@@ -9,7 +9,7 @@ module Bluepill
9
9
  @logger = options.delete(:logger)
10
10
  @fires = options.has_key?(:fires) ? Array(options.delete(:fires)) : [:restart]
11
11
  @every = options.delete(:every)
12
- @times = options[:times] || [1,1]
12
+ @times = options.delete(:times) || [1,1]
13
13
  @times = [@times, @times] unless @times.is_a?(Array) # handles :times => 5
14
14
 
15
15
  self.clear_history!
@@ -2,14 +2,14 @@ require 'fileutils'
2
2
 
3
3
  module Bluepill
4
4
  class Controller
5
- attr_accessor :base_dir, :sockets_dir, :pids_dir
6
- attr_accessor :applications
5
+ attr_accessor :base_dir, :log_file, :sockets_dir, :pids_dir
7
6
 
8
7
  def initialize(options = {})
8
+ self.log_file = options[:log_file]
9
9
  self.base_dir = options[:base_dir]
10
10
  self.sockets_dir = File.join(base_dir, 'socks')
11
11
  self.pids_dir = File.join(base_dir, 'pids')
12
- self.applications = Hash.new
12
+
13
13
  setup_dir_structure
14
14
  cleanup_bluepill_directory
15
15
  end
@@ -18,11 +18,67 @@ module Bluepill
18
18
  Dir[File.join(sockets_dir, "*.sock")].map{|x| File.basename(x, ".sock")}
19
19
  end
20
20
 
21
- def send_command_to_application(application, command, *args)
22
- applications[application] ||= Application.new(application, {:base_dir => base_dir})
23
- applications[application].send(command.to_sym, *args.compact)
21
+ def handle_command(application, command, *args)
22
+ case command.to_sym
23
+ when *Application::PROCESS_COMMANDS
24
+ # these need to be sent to the daemon and the results printed out
25
+ affected = self.send_to_daemon(application, command, *args)
26
+ if affected.empty?
27
+ puts "No processes effected"
28
+ else
29
+ puts "Sent #{command} to:"
30
+ affected.each do |process|
31
+ puts " #{process}"
32
+ end
33
+ end
34
+ when :status
35
+ puts self.send_to_daemon(application, :status, *args)
36
+ when :quit
37
+ pid = pid_for(application)
38
+ if System.pid_alive?(pid)
39
+ ::Process.kill("TERM", pid)
40
+ puts "Killing bluepilld[#{pid}]"
41
+ else
42
+ puts "bluepilld[#{pid}] not running"
43
+ end
44
+ when :log
45
+ log_file_location = self.send_to_daemon(application, :log_file)
46
+ log_file_location = self.log_file if log_file_location.to_s.strip.empty?
47
+
48
+ requested_pattern = args.first
49
+ grep_pattern = self.grep_pattern(application, requested_pattern)
50
+
51
+ tail = "tail -n 100 -f #{log_file_location} | grep -E '#{grep_pattern}'"
52
+ puts "Tailing log for #{requested_pattern}..."
53
+ Kernel.exec(tail)
54
+ else
55
+ $stderr.puts "Unknown command `%s` (or application `%s` has not been loaded yet)" % [command, command]
56
+ end
24
57
  end
25
58
 
59
+ def send_to_daemon(application, command, *args)
60
+
61
+ begin
62
+ Timeout::timeout(Socket::TIMEOUT) do
63
+ buffer = ""
64
+ socket = Socket.client(base_dir, application) # Something that should be interrupted if it takes too much time...
65
+ socket.puts(([command] + args).join(":"))
66
+ while line = socket.gets
67
+ buffer << line
68
+ end
69
+ Marshal.load(buffer)
70
+ end
71
+ rescue Timeout::Error
72
+ abort("Socket Timeout: Server may not be responding")
73
+ rescue Errno::ECONNREFUSED
74
+ abort("Connection Refused: Server is not running")
75
+ end
76
+ end
77
+
78
+ def grep_pattern(application, query = nil)
79
+ pattern = [application, query].compact.join(':')
80
+ ['\[.*', Regexp.escape(pattern), '.*'].compact.join
81
+ end
26
82
  private
27
83
 
28
84
  def cleanup_bluepill_directory
@@ -58,6 +58,7 @@ module Bluepill
58
58
  process.add_watch(name, opts)
59
59
  end
60
60
  end
61
+
61
62
  process
62
63
  end
63
64
  end
@@ -65,23 +66,38 @@ module Bluepill
65
66
  app_proxy = Class.new do
66
67
  @@app = app
67
68
  @@process_proxy = process_proxy
68
- @@process_names = Hash.new # becuase I don't want to require Set just for validations
69
+ @@process_keys = Hash.new # because I don't want to require Set just for validations
70
+ @@pid_files = Hash.new
69
71
  attr_accessor :working_dir, :uid, :gid
70
72
 
71
73
  def validate_process(process, process_name)
72
- if @@process_names.key?(process_name)
73
- $stderr.puts "Config Error: You have two entries for the process name '#{process_name}'"
74
+ # validate uniqueness of group:process
75
+ process_key = [process.attributes[:group], process_name].join(":")
76
+ if @@process_keys.key?(process_key)
77
+ $stderr.print "Config Error: You have two entries for the process name '#{process_name}'"
78
+ $stderr.print " in the group '#{process.attributes[:group]}'" if process.attributes.key?(:group)
79
+ $stderr.puts
74
80
  exit(6)
75
81
  else
76
- @@process_names[process_name] = 0
82
+ @@process_keys[process_key] = 0
77
83
  end
78
84
 
79
- [:start_command, :pid_file].each do |required_attr|
85
+ # validate required attributes
86
+ [:start_command].each do |required_attr|
80
87
  if !process.attributes.key?(required_attr)
81
88
  $stderr.puts "Config Error: You must specify a #{required_attr} for '#{process_name}'"
82
89
  exit(6)
83
90
  end
84
91
  end
92
+
93
+ # validate uniqueness of pid files
94
+ pid_key = process.pid_file.strip
95
+ if @@pid_files.key?(pid_key)
96
+ $stderr.puts "Config Error: You have two entries with the pid file: #{process.pid_file}"
97
+ exit(6)
98
+ else
99
+ @@pid_files[pid_key] = 0
100
+ end
85
101
  end
86
102
 
87
103
  def process(process_name, &process_block)
@@ -89,11 +105,15 @@ module Bluepill
89
105
  process_block.call(process_proxy)
90
106
  set_app_wide_attributes(process_proxy)
91
107
 
108
+ assign_default_pid_file(process_proxy, process_name)
109
+
92
110
  validate_process(process_proxy, process_name)
93
111
 
94
112
  group = process_proxy.attributes.delete(:group)
95
113
  process = process_proxy.to_process(process_name)
96
114
 
115
+
116
+
97
117
  @@app.add_process(process, group)
98
118
  end
99
119
 
@@ -104,6 +124,14 @@ module Bluepill
104
124
  end
105
125
  end
106
126
  end
127
+
128
+ def assign_default_pid_file(process_proxy, process_name)
129
+ unless process_proxy.attributes.key?(:pid_file)
130
+ group_name = process_proxy.attributes["group"]
131
+ default_pid_name = [group_name, process_name].compact.join('_').gsub(/[^A-Za-z0-9_\-]/, "_")
132
+ process_proxy.pid_file = File.join(@@app.pids_dir, default_pid_name + ".pid")
133
+ end
134
+ end
107
135
  end
108
136
 
109
137
  yield(app_proxy.new)
@@ -21,15 +21,18 @@ module Bluepill
21
21
  end
22
22
 
23
23
  # proxied events
24
- [:start, :unmonitor, :stop, :restart, :boot!].each do |event|
24
+ [:start, :unmonitor, :stop, :restart, :boot].each do |event|
25
25
  class_eval <<-END
26
26
  def #{event}(process_name = nil)
27
27
  threads = []
28
+ affected = []
28
29
  self.processes.each do |process|
29
30
  next if process_name && process_name != process.name
31
+ affected << [self.name, process.name].join(":")
30
32
  threads << Thread.new { process.handle_user_command("#{event}") }
31
33
  end
32
34
  threads.each { |t| t.join }
35
+ affected
33
36
  end
34
37
  END
35
38
  end
@@ -192,7 +192,7 @@ module Bluepill
192
192
 
193
193
  def handle_user_command(cmd)
194
194
  case cmd
195
- when "boot!"
195
+ when "boot"
196
196
  # This is only called when bluepill is initially starting up
197
197
  if process_running?(true)
198
198
  # process was running even before bluepill was
@@ -1,41 +1,35 @@
1
1
  require 'socket'
2
2
 
3
3
  module Bluepill
4
- class Socket
5
- attr_accessor :name, :base_dir, :socket
6
-
7
- def initialize(name, base_dir)
8
- self.name = name
9
- self.base_dir = base_dir
10
- end
4
+ module Socket
5
+ TIMEOUT = 10
6
+ extend self
11
7
 
12
- def client
13
- self.socket = UNIXSocket.open(socket_name)
8
+ def client(base_dir, name)
9
+ UNIXSocket.open(socket_path(base_dir, name))
14
10
  end
15
11
 
16
- def server
12
+ def server(base_dir, name)
13
+ socket_path = self.socket_path(base_dir, name)
17
14
  begin
18
- self.socket = UNIXServer.open(socket_name)
15
+ UNIXServer.open(socket_path)
19
16
  rescue Errno::EADDRINUSE
20
17
  # if sock file has been created. test to see if there is a server
21
- tmp_socket = UNIXSocket.open(socket_name) rescue nil
22
- if tmp_socket.nil?
23
- cleanup
24
- retry
18
+ begin
19
+ return UNIXSocket.open(socket_path)
20
+ rescue Errno::ECONNREFUSED
21
+ File.delete(socket_path)
22
+ return UNIXServer.open(socket_path)
25
23
  else
26
- raise Exception.new("Server is already running")
24
+ logger.err("Server is already running!")
25
+ exit(7)
27
26
  end
28
27
  end
29
28
  end
30
29
 
31
- def cleanup
32
- File.delete(socket_name)
30
+ def socket_path(base_dir, name)
31
+ File.join(base_dir, 'socks', name + ".sock")
33
32
  end
34
-
35
- def socket_name
36
- @socket_name ||= File.join(base_dir, 'socks', name + ".sock")
37
- end
38
-
39
33
  end
40
34
  end
41
35
 
@@ -4,6 +4,7 @@ module Bluepill
4
4
  # This class represents the system that bluepill is running on.. It's mainly used to memoize
5
5
  # results of running ps auxx etc so that every watch in the every process will not result in a fork
6
6
  module System
7
+ APPEND_MODE = "a"
7
8
  extend self
8
9
 
9
10
  # The position of each field in ps output
@@ -209,15 +210,15 @@ module Bluepill
209
210
  end
210
211
 
211
212
  def redirect_io(io_in, io_out, io_err)
212
- $stdin.reopen(streams[io_in]) if io_in
213
+ $stdin.reopen(io_in) if io_in
213
214
 
214
215
  if !io_out.nil? && !io_err.nil? && io_out == io_err
215
- $stdout.reopen(io_out)
216
+ $stdout.reopen(io_out, APPEND_MODE)
216
217
  $stderr.reopen($stdout)
217
218
 
218
219
  else
219
- $stdout.reopen(io_out) if io_out
220
- $stderr.reopen(io_err) if io_err
220
+ $stdout.reopen(io_out, APPEND_MODE) if io_out
221
+ $stderr.reopen(io_err, APPEND_MODE) if io_err
221
222
  end
222
223
  end
223
224
  end
@@ -1,3 +1,3 @@
1
1
  module Bluepill
2
- VERSION = "0.0.24"
2
+ VERSION = "0.0.25"
3
3
  end
@@ -1,12 +1,18 @@
1
- require 'rubygems'
2
- require 'bluepill'
3
- require 'logger'
1
+ module Bluepill
2
+ module ProcessConditions
3
+ class ProcMemUsage < MemUsage
4
+ def run(pid)
5
+ rand(1000)
6
+ end
7
+ end
8
+ end
9
+ end
4
10
 
5
11
  ROOT_DIR = "/tmp/bp"
6
12
 
7
13
  # Watch with
8
14
  # watch -n0.2 'ps axu | egrep "(CPU|forking|bluepill|sleep)" | grep -v grep | sort'
9
- Bluepill.application(:sample_app) do |app|
15
+ Bluepill.application(:sample_app, :log_file => "/Users/arya/Desktop/bp.log") do |app|
10
16
  0.times do |i|
11
17
  app.process("process_#{i}") do |process|
12
18
  process.pid_file = "#{ROOT_DIR}/pids/process_#{i}.pid"
@@ -61,7 +67,7 @@ Bluepill.application(:sample_app) do |app|
61
67
  end
62
68
  end
63
69
 
64
- 1.times do |i|
70
+ 2.times do |i|
65
71
  app.process("group_process_#{i}") do |process|
66
72
  process.uid = "arya"
67
73
  process.gid = "wheel"
@@ -71,10 +77,9 @@ Bluepill.application(:sample_app) do |app|
71
77
 
72
78
 
73
79
  process.group = "grouped"
74
- # process.start_command = %Q{cd /tmp && ruby -e '$stderr.puts("hello stderr");$stdout.puts("hello stdout"); $stdout.flush; $stderr.flush; sleep 10'}
75
- process.start_command = "sleep 10"
80
+ process.start_command = %Q{ruby -e '$stderr.puts("hello stderr");$stdout.puts("hello stdout"); $stdout.flush; $stderr.flush; sleep 10'}
76
81
  process.daemonize = true
77
- process.pid_file = "/tmp/p_#{process.group}_#{i}.pid"
82
+ # process.pid_file = "/tmp/p_#{process.group}#{i}_.pid"
78
83
 
79
84
  # process.checks :always_true, :every => 5
80
85
  process.checks :cpu_usage,
@@ -87,6 +92,11 @@ Bluepill.application(:sample_app) do |app|
87
92
  :below => 600.megabytes,
88
93
  :times => [3, 5],
89
94
  :fires => [:stop]
95
+
96
+ process.checks :proc_mem_usage,
97
+ :every => 3,
98
+ :below => 600.megabytes,
99
+ :times => [3, 5]
90
100
  end
91
101
  end
92
102
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bluepill
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.24
4
+ version: 0.0.25
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arya Asemanfar
@@ -11,7 +11,7 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2009-11-24 00:00:00 -08:00
14
+ date: 2009-11-30 00:00:00 -08:00
15
15
  default_executable: bluepill
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency