bluepill 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -15,6 +15,9 @@ begin
15
15
  gem.add_dependency("daemons", ">= 1.0.9")
16
16
  gem.add_dependency("pluginaweek-state_machine", ">= 0.8.0")
17
17
  gem.add_dependency("activesupport", ">= 2.3.4")
18
+
19
+ gem.files -= ["bin/sample_forking_server"]
20
+ gem.executables = ["bluepill"]
18
21
  end
19
22
  rescue LoadError
20
23
  puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.6
1
+ 0.0.7
@@ -5,11 +5,12 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{bluepill}
8
- s.version = "0.0.6"
8
+ s.version = "0.0.7"
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-10-22}
12
+ s.date = %q{2009-10-23}
13
+ s.default_executable = %q{bluepill}
13
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.}
14
15
  s.email = %q{entombedvirus@gmail.com}
15
16
  s.executables = ["bluepill"]
@@ -21,7 +21,7 @@ module Bluepill
21
21
  start_server
22
22
  rescue StandardError => e
23
23
  logger.err("Got exception: %s `%s`" % [e.class.name, e.message])
24
- logger.err(e.backtrace.join("\n"))
24
+ e.backtrace.each {|l| logger.err(l)}
25
25
  end
26
26
  end
27
27
 
@@ -32,7 +32,7 @@ module Bluepill
32
32
 
33
33
  if self.groups.has_key?(nil)
34
34
  self.groups[nil].processes.each do |p|
35
- buffer << "%s%s: %s" % [" " * depth, p.name, p.state]
35
+ buffer << "%s%s(pid:%s): %s" % [" " * depth, p.name, p.actual_pid.inspect, p.state]
36
36
 
37
37
  if p.monitor_children?
38
38
  depth += 2
@@ -52,7 +52,7 @@ module Bluepill
52
52
  group.processes.each do |p|
53
53
  depth += 2
54
54
 
55
- buffer << "%s%s(pid:%d): %s" % [" " * depth, p.name, p.actual_pid, p.state]
55
+ buffer << "%s%s(pid:%s): %s" % [" " * depth, p.name, p.actual_pid.inspect, p.state]
56
56
 
57
57
  if p.monitor_children?
58
58
  depth += 2
@@ -149,8 +149,8 @@ private
149
149
  client.close
150
150
  end
151
151
  rescue StandardError => e
152
- logger.err(e.inspect)
153
- logger.err(e.backtrace.join("\n"))
152
+ logger.err("Got exception in cmd listener: %s `%s`" % [e.class.name, e.message])
153
+ e.backtrace.each {|l| logger.err(l)}
154
154
  end
155
155
  end
156
156
  end
@@ -230,13 +230,16 @@ module Bluepill
230
230
  logger.warning "Executing start command: #{start_command}"
231
231
 
232
232
  if self.daemonize?
233
- starter = lambda { drop_privileges; ::Kernel.exec(start_command) }
234
- child_pid = Daemonize.call_as_daemon(starter)
233
+ child_pid = System.daemonize(start_command, :uid => self.uid, :gid => self.gid)
235
234
  File.open(pid_file, "w") {|f| f.write(child_pid)}
235
+
236
236
  else
237
237
  # This is a self-daemonizing process
238
- unless System.execute_blocking(start_command)
239
- logger.warning "Start command execution returned non-zero exit code"
238
+ result = System.execute_blocking(start_command, :uid => self.uid, :gid => self.gid, :logger => logger)
239
+
240
+ unless result[:exit_code].zero?
241
+ logger.warning "Start command execution returned non-zero exit code:"
242
+ logger.warning result.inspect
240
243
  end
241
244
  end
242
245
 
@@ -247,9 +250,11 @@ module Bluepill
247
250
  if stop_command
248
251
  cmd = stop_command.to_s.gsub("{{PID}}", actual_pid.to_s)
249
252
  logger.warning "Executing stop command: #{cmd}"
250
-
251
- unless System.execute_blocking(cmd)
252
- logger.warning "Stop command execution returned non-zero exit code"
253
+
254
+ result = System.execute_blocking(cmd, :uid => self.uid, :gid => self.gid)
255
+ unless result[:exit_code].zero?
256
+ logger.warning "Stop command execution returned non-zero exit code:"
257
+ logger.warning result.inspect
253
258
  end
254
259
 
255
260
  else
@@ -265,8 +270,10 @@ module Bluepill
265
270
  if restart_command
266
271
  logger.warning "Executing restart command: #{restart_command}"
267
272
 
268
- unless System.execute_blocking(restart_command)
269
- logger.warning "Restart command execution returned non-zero exit code"
273
+ result = System.execute_blocking(restart_command, :uid => self.uid, :gid => self.gid)
274
+ unless result[:exit_code].zero?
275
+ logger.warning "Restart command execution returned non-zero exit code:"
276
+ logger.warning result.inspect
270
277
  end
271
278
 
272
279
  self.skip_ticks_for(restart_grace_time)
@@ -294,7 +301,14 @@ module Bluepill
294
301
 
295
302
  def actual_pid
296
303
  @actual_pid ||= begin
297
- File.read(pid_file).to_i if pid_file && File.exists?(pid_file)
304
+ if pid_file
305
+ if File.exists?(pid_file)
306
+ File.read(pid_file).to_i
307
+ else
308
+ logger.warning("pid_file #{pid_file} does not exist or cannot be read")
309
+ nil
310
+ end
311
+ end
298
312
  end
299
313
  end
300
314
 
@@ -310,24 +324,7 @@ module Bluepill
310
324
  File.unlink(pid_file) if pid_file && File.exists?(pid_file)
311
325
  end
312
326
 
313
- def drop_privileges
314
- begin
315
- require 'etc'
316
-
317
- uid_num = Etc.getpwnam(self.uid).uid if self.uid
318
- gid_num = Etc.getgrnam(self.gid).gid if self.gid
319
-
320
- ::Process.groups = [gid_num] if self.gid
321
- ::Process::Sys.setgid(gid_num) if self.gid
322
- ::Process::Sys.setuid(uid_num) if self.uid
323
- rescue ArgumentError, Errno::EPERM, Errno::ENOENT => e
324
- # TODO: log exceptions elsewhere
325
- File.open("/tmp/exception.log", "w+"){|f| puts e}
326
- end
327
- end
328
-
329
-
330
- # Internal State Methods
327
+ # Internal State Methods
331
328
  def skip_ticks_for(seconds)
332
329
  # TODO: should this be addative or longest wins?
333
330
  # i.e. if two calls for skip_ticks_for come in for 5 and 10, should it skip for 10 or 15?
@@ -1,3 +1,5 @@
1
+ require 'etc'
2
+
1
3
  module Bluepill
2
4
  # This class represents the system that bluepill is running on.. It's mainly used to memoize
3
5
  # results of running ps auxx etc so that every watch in the every process will not result in a fork
@@ -28,21 +30,110 @@ module Bluepill
28
30
  end
29
31
  end
30
32
 
31
- def execute_non_blocking(cmd)
32
- if Daemonize.safefork
33
- # In parent, return immediately
34
- return
33
+ # Returns the pid of the child that executes the cmd
34
+ def daemonize(cmd, options = {})
35
+ rd, wr = IO.pipe
36
+
37
+ if child = Daemonize.safefork
38
+ # we do not wanna create zombies, so detach ourselves from the child exit status
39
+ ::Process.detach(child)
40
+
41
+ # parent
42
+ wr.close
43
+
44
+ daemon_id = rd.read.to_i
45
+ rd.close
46
+
47
+ return daemon_id if daemon_id > 0
35
48
 
36
49
  else
37
- # in child
38
- ::Kernel.exec(cmd)
39
- # execution should not reach here
50
+ # child
51
+ rd.close
52
+
53
+ drop_privileges(options[:uid], options[:gid])
54
+
55
+ daemon_id = Daemonize.call_as_daemon(lambda { ::Kernel.exec(cmd); exit }, nil, cmd)
56
+
57
+ wr.write daemon_id
58
+ wr.close
59
+
40
60
  exit
41
61
  end
42
62
  end
43
63
 
44
- def execute_blocking(cmd)
45
- ::Kernel.system(cmd)
64
+ # Returns the stdout, stderr and exit code of the cmd
65
+ def execute_blocking(cmd, options = {})
66
+ rd, wr = IO.pipe
67
+
68
+ if child = Daemonize.safefork
69
+ # parent
70
+ wr.close
71
+
72
+ cmd_status = rd.read
73
+ rd.close
74
+
75
+ ::Process.waitpid(child)
76
+
77
+ return Marshal.load(cmd_status)
78
+
79
+ else
80
+ # child
81
+ rd.close
82
+
83
+ # create a child in which we can override the stdin, stdout and stderr
84
+ cmd_out_read, cmd_out_write = IO.pipe
85
+ cmd_err_read, cmd_err_write = IO.pipe
86
+
87
+ pid = fork {
88
+ # grandchild
89
+ drop_privileges(options[:uid], options[:gid])
90
+
91
+ # close unused fds so ancestors wont hang. This line is the only reason we are not
92
+ # using something like popen3. If this fd is not closed, the .read call on the parent
93
+ # will never return because "wr" would still be open in the "exec"-ed cmd
94
+ wr.close
95
+
96
+ # we do not care about stdin of cmd
97
+ STDIN.reopen("/dev/null")
98
+
99
+ # point stdout of cmd to somewhere we can read
100
+ cmd_out_read.close
101
+ STDOUT.reopen(cmd_out_write)
102
+ cmd_out_write.close
103
+
104
+ # same thing for stderr
105
+ cmd_err_read.close
106
+ STDERR.reopen(cmd_err_write)
107
+ cmd_err_write.close
108
+
109
+ # finally, replace grandchild with cmd
110
+ ::Kernel.exec(cmd)
111
+ }
112
+
113
+ # we do not use these ends of the pipes in the child
114
+ cmd_out_write.close
115
+ cmd_err_write.close
116
+
117
+ # wait for the cmd to finish executing and acknowledge it's death
118
+ ::Process.waitpid(pid)
119
+
120
+ # collect stdout, stderr and exitcode
121
+ result = {
122
+ :stdout => cmd_out_read.read,
123
+ :stderr => cmd_err_read.read,
124
+ :exit_code => $?.exitstatus
125
+ }
126
+
127
+ # We're done with these ends of the pipes as well
128
+ cmd_out_read.close
129
+ cmd_err_read.close
130
+
131
+ # Time to tell the parent about what went down
132
+ wr.write Marshal.dump(result)
133
+ wr.close
134
+
135
+ exit
136
+ end
46
137
  end
47
138
 
48
139
  def store
@@ -68,5 +159,16 @@ module Bluepill
68
159
  end
69
160
  end
70
161
  end
162
+
163
+ # be sure to call this from a fork otherwise it will modify the attributes
164
+ # of the bluepill daemon
165
+ def drop_privileges(uid, gid)
166
+ uid_num = Etc.getpwnam(uid).uid if uid
167
+ gid_num = Etc.getgrnam(gid).gid if gid
168
+
169
+ ::Process.groups = [gid_num] if gid
170
+ ::Process::Sys.setgid(gid_num) if gid
171
+ ::Process::Sys.setuid(uid_num) if uid
172
+ end
71
173
  end
72
174
  end
@@ -1,12 +1,13 @@
1
1
  require 'rubygems'
2
2
  require 'bluepill'
3
+ require 'logger'
3
4
 
4
5
  ROOT_DIR = "/tmp/bp"
5
6
 
6
7
  # Watch with
7
8
  # watch -n0.2 'ps axu | egrep "(CPU|forking|bluepill|sleep)" | grep -v grep | sort'
8
9
  Bluepill.application(:sample_app) do |app|
9
- 2.times do |i|
10
+ 0.times do |i|
10
11
  app.process("process_#{i}") do |process|
11
12
  process.pid_file = "#{ROOT_DIR}/pids/process_#{i}.pid"
12
13
 
@@ -21,7 +22,7 @@ Bluepill.application(:sample_app) do |app|
21
22
  process.restart_grace_time = 7.seconds
22
23
  process.stop_grace_time = 7.seconds
23
24
 
24
- process.uid = "admin"
25
+ process.uid = "rohith"
25
26
  process.gid = "staff"
26
27
 
27
28
  # process.checks :cpu_usage, :every => 10, :below => 0.5, :times => [5, 5]
@@ -45,14 +46,18 @@ Bluepill.application(:sample_app) do |app|
45
46
  end
46
47
  end
47
48
 
48
- 0.times do |i|
49
+ 1.times do |i|
49
50
  app.process("group_process_#{i}") do |process|
50
- process.start_command = "sleep #{rand(30) + i}"
51
51
  process.group = "Poopfaced"
52
- process.daemonize = true
53
- process.pid_file = "#{ROOT_DIR}/pids/#{process.group}_process_#{i}.pid"
52
+ process.pid_file = "/Users/rohith/ffs/tmp/pids/mongrel_#{i}.pid"
53
+ process.start_command = "cd ~/ffs && mongrel_rails start -P #{process.pid_file} -p 3000 -d"
54
54
 
55
- process.checks :always_true, :every => 10
55
+ process.start_grace_time = 10.seconds
56
+
57
+ process.uid = "rohith"
58
+ process.gid = "staff"
59
+
60
+ # process.checks :always_true, :every => 10
56
61
  end
57
62
  end
58
63
 
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.6
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arya Asemanfar
@@ -11,8 +11,8 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2009-10-22 00:00:00 -07:00
15
- default_executable:
14
+ date: 2009-10-23 00:00:00 -07:00
15
+ default_executable: bluepill
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: rspec