bannister 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -3,8 +3,6 @@ CLEAN << ".yardoc"
3
3
  CLEAN << FileList["*.gem"]
4
4
  CLEAN << "doc"
5
5
 
6
- DOC_FILES=Dir['*.md']
7
-
8
6
  desc "Build the gem"
9
7
  task :gem do
10
8
  system "gem build bannister.gemspec"
@@ -12,12 +10,12 @@ end
12
10
 
13
11
  desc "Build the YARD docs"
14
12
  task :docs do
15
- system "yardoc --files #{DOC_FILES.join(",")}"
13
+ system "yardoc"
16
14
  end
17
15
 
18
16
  task :doc => :docs
19
17
 
20
18
  desc "Build the YARD developer docs, including private methods"
21
19
  task :dev_docs do
22
- system "yardoc --private --files #{DOC_FILES.join(",")}"
20
+ system "yardoc --private"
23
21
  end
data/bin/bannister CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'bannister'
4
+ require 'bannister/version'
4
5
  include Bannister
5
6
 
6
7
 
@@ -16,6 +17,8 @@ when "stop"
16
17
  when "restart"
17
18
  stop()
18
19
  start()
20
+ when "version", "--version", "-v"
21
+ $stdout.puts ::Bannister::VERSION.to_s
19
22
  else
20
23
  err "usage: #{$0} (run|start|stop|restart)"
21
24
  end
data/lib/bannister.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'fileutils'
2
+ require 'bannister/utils'
2
3
 
3
4
  module Bannister
4
5
 
@@ -20,7 +21,7 @@ module Bannister
20
21
  # Daemonize and launch the managed processes.
21
22
  def start()
22
23
  if running?
23
- err "This app is already running! Run #{$0} stop first."
24
+ err "This app is already running! Run #{$0} stop first (or remove #{PID_FILENAME} if you know better)."
24
25
  else
25
26
  daemonize(){ do_start() }
26
27
  wait_for_all()
@@ -67,7 +68,6 @@ module Bannister
67
68
  def quit
68
69
  kill_all()
69
70
  remove_pid()
70
- exit(0)
71
71
  end
72
72
 
73
73
 
@@ -76,14 +76,12 @@ module Bannister
76
76
  def stop()
77
77
  old_pid = read_pid()
78
78
  if old_pid
79
- begin
80
- Process.kill("TERM", old_pid)
81
- Process.waitpid(old_pid)
82
- rescue SystemCallError
83
- # which gets thrown if the old process is already dead
84
- nil
79
+ Process.kill("TERM", old_pid)
80
+ if ::Bannister::Utils.wait_foreign_pid(old_pid)
81
+ remove_pid()
82
+ else
83
+ err "Bannister (pid=#{old_pid}) failed to die!"
85
84
  end
86
- remove_pid()
87
85
  end
88
86
  end
89
87
 
@@ -0,0 +1,279 @@
1
+ require 'fileutils'
2
+ require 'bannister/utils'
3
+
4
+ module Bannister
5
+
6
+
7
+ # List of scripts in script/startup sorted asciibetically. This is the order
8
+ # they will be launched in, but there is no further dependency checking
9
+ # between the scripts.
10
+ SCRIPTS = Dir['script/startup/*'].sort
11
+
12
+ # A list of pids of the started scripts. These will be sent TERM signals when
13
+ # bannister is stopped.
14
+ LAUNCHED_PIDS = []
15
+
16
+ # The location of the pidfile for the master process. This will be
17
+ # de-hard-coded in a future release.
18
+ PID_FILENAME = "pids/bannister.pid"
19
+
20
+
21
+ # Daemonize and launch the managed processes.
22
+ def start()
23
+ if running?
24
+ err "This app is already running! Run #{$0} stop first (or remove #{PID_FILENAME} if you know better)."
25
+ else
26
+ daemonize(){ do_start() }
27
+ wait_for_all()
28
+ end
29
+ end
30
+
31
+
32
+ # Launch the managed processes in screen so that they are all foreground
33
+ # processes.
34
+ #
35
+ # If run from inside an existing screen session, it will be reused, and the
36
+ # managed processes will be added to it. Otherwise a new screen session is
37
+ # started.
38
+ #
39
+ # Starting a new screen session is done by writing a temporary screenrc
40
+ # to /tmp, so $HOME/.screenrc is ignored.
41
+ def run()
42
+ screenrc_filename = "/tmp/screenrc.#{$$}"
43
+
44
+ if ENV['STY'] && !ENV['STY'].empty?
45
+ # then we already have a screen, so reuse it
46
+ # by simply running all the commands
47
+
48
+ SCRIPTS.each do |script|
49
+ system( command_for_script(script) )
50
+ end
51
+
52
+ else # create it. The easiest way to do this is to write
53
+ # out a screenrc containing the relevant commands and
54
+ # let screen launch them
55
+ begin
56
+ write_screenrc(screenrc_filename)
57
+
58
+ system "screen","-c",screenrc_filename
59
+ ensure
60
+ FileUtils.rm_f screenrc_filename
61
+ end
62
+
63
+ end
64
+ end
65
+
66
+
67
+ # Shut down all the managed processes, remove the bannister pidfile and exit.
68
+ def quit
69
+ $stderr.reopen("/tmp/bannister.stop.err.log", "wb")
70
+ $stdout.reopen("/tmp/bannister.stop.out.log", "wb")
71
+ kill_all()
72
+ remove_pid()
73
+ end
74
+
75
+
76
+
77
+ # Send a TERM signal to a #start'ed bannister process.
78
+ def stop()
79
+ old_pid = read_pid()
80
+ if old_pid
81
+ if ::Bannister::Utils.foreign_pid_alive?(old_pid)
82
+ Process.kill("TERM", old_pid)
83
+ if ::Bannister::Utils.wait_foreign_pid(old_pid)
84
+ remove_pid()
85
+ else
86
+ err "Bannister (pid=#{old_pid}) failed to die!"
87
+ end
88
+ else
89
+ err "Bannister (pid=#{old_pid}) wasn't alive."
90
+ end
91
+ end
92
+ end
93
+
94
+
95
+ # Used when you have erred. Shows the passed error message and exits
96
+ # with a status of 1.
97
+ #
98
+ # @param [String] msg Message to display.
99
+ def err(msg)
100
+ $stderr.puts(msg)
101
+ exit(1)
102
+ end
103
+
104
+
105
+
106
+ private
107
+ # Method to see whether a bannister process for the current application
108
+ # is running or not.
109
+ #
110
+ # @return [Boolean] true if PID_FILENAME exists, false otherwise.
111
+ def running?
112
+ return File.exists?(PID_FILENAME)
113
+ end
114
+
115
+
116
+ # Generate the screen command for the passed script filename. Since this
117
+ # will only ever be called when running in the foreground, we append -X
118
+ # to the command. A title for the screen window is also generated based
119
+ # on the current directory name and the script filename.
120
+ #
121
+ # @param [String] script the filename of a script to run.
122
+ # @return [String] the screen command to run the script.
123
+ def command_for_script(script)
124
+ title = File.basename(FileUtils.pwd)+':'+File.basename(script)
125
+ return "screen -t '#{title}' #{script} -X"
126
+ end
127
+
128
+
129
+ # Generate a screen config file and save it to the passed screenrc_filename.
130
+ # Screen is set up to allow restarting of dead processes with the 'r' key,
131
+ # and to give each script a meaningful title in the status line.
132
+ #
133
+ # @param [String] screenrc_filename the filename to save the
134
+ # generated config file to.
135
+ # @return [NilClass] nil.
136
+ def write_screenrc(screenrc_filename)
137
+ File.open(screenrc_filename, "wb") do |f|
138
+ f.write <<-SCREENRC
139
+ zombie cr onerror
140
+ hardstatus alwayslastline "%-Lw%{= BW}%50>%n%f* %t%{-}%+Lw%<"
141
+
142
+ SCREENRC
143
+ SCRIPTS.each do |script|
144
+ f.puts( command_for_script(script) )
145
+ end
146
+ end
147
+
148
+ return nil
149
+ end
150
+
151
+
152
+
153
+ # Daemonize the bannister process and yield. Used when running in
154
+ # the background. This is a slightly different daemonize method to
155
+ # the usual, in that we need to be sure that the script filenames in
156
+ # SCRIPT don't lose their relative location, so we chdir to '/'
157
+ # *after* kicking them off. It's each individual script's
158
+ # responsibility to daemonise properly.
159
+ def daemonize()
160
+ exit!(0) if fork
161
+ Process::setsid
162
+ exit!(0) if fork
163
+ File::umask(0)
164
+ STDIN.reopen("/dev/null")
165
+ STDOUT.reopen("/dev/null", "w")
166
+ STDERR.reopen("/dev/null", "w")
167
+
168
+ yield if block_given?
169
+
170
+ # chdir after yield so that the script paths remain correct
171
+ # and if any of them rely on relative paths, they won't get
172
+ # broken
173
+ Dir::chdir("/")
174
+
175
+ return nil
176
+ end
177
+
178
+
179
+ # Set up our environment and launch everything in the background.
180
+ def do_start()
181
+ $stderr.reopen("/tmp/bannister.start.err.log", "wb")
182
+ $stdout.reopen("/tmp/bannister.start.out.log","wb")
183
+
184
+ set_name()
185
+ drop_pid()
186
+ launch_all()
187
+ end
188
+
189
+ # Set the process name to something meaningful. In this case,
190
+ # "something meaningful" means "bannister:" + the name of the
191
+ # directory we're in. This makes it easy to see which bannister pids
192
+ # are responsible for which applications.
193
+ #
194
+ # @return [NilClass] nil.
195
+ def set_name
196
+ $0 = "bannister:"+File.basename(FileUtils.pwd)
197
+
198
+ return nil
199
+ end
200
+
201
+ # Record the current pid somewhere it can be found later
202
+ def drop_pid
203
+ system "mkdir -p #{File.dirname PID_FILENAME}"
204
+ File.open(PID_FILENAME, "w"){|f| f.write $$.to_s}
205
+
206
+ return nil
207
+ end
208
+
209
+
210
+ # Read the pid from where drop_pid left it, or nil if it's not there.
211
+ #
212
+ # @return [Fixnum] pid as integer if it exists
213
+ # @return [NilClass] nil otherwise.
214
+ def read_pid
215
+ if File.exists?(PID_FILENAME)
216
+ return Integer(File.read(PID_FILENAME).strip)
217
+ else
218
+ return nil
219
+ end
220
+ end
221
+
222
+
223
+ # Clear the pid that drop_pid left behind.
224
+ #
225
+ # @return [NilClass] nil.
226
+ def remove_pid
227
+ FileUtils.rm_f(PID_FILENAME)
228
+
229
+ return nil
230
+ end
231
+
232
+
233
+ # Fork and exec each of SCRIPTS.
234
+ #
235
+ # @return [NilClass] nil.
236
+ def launch_all()
237
+ SCRIPTS.each do |script|
238
+ LAUNCHED_PIDS << fork do
239
+ exec script
240
+ end
241
+ end
242
+
243
+ return nil
244
+ end
245
+
246
+
247
+ # Call Process.waitpid on each process launched by launch_all.
248
+ #
249
+ # @return [NilClass] nil.
250
+ def wait_for_all()
251
+ LAUNCHED_PIDS.each do |pid|
252
+ Process.waitpid(pid)
253
+ end
254
+
255
+ return nil
256
+ end
257
+
258
+
259
+ # Send the TERM signal to each process launched by launch_all.
260
+ #
261
+ # @return [NilClass] nil.
262
+ def kill_all()
263
+ LAUNCHED_PIDS.each do |pid|
264
+ if ::Bannister::Utils.foreign_pid_alive?(pid)
265
+ system "kill -15 #{pid}"
266
+ else
267
+ $stderr.puts "Pid #{pid} doesn't seem to be running!"
268
+ end
269
+ end
270
+
271
+ return nil
272
+ end
273
+
274
+
275
+
276
+
277
+
278
+ end
279
+ Connection to alpha.bytemark.co.uk closed.
@@ -0,0 +1,273 @@
1
+ require 'fileutils'
2
+ require 'bannister/utils'
3
+
4
+ module Bannister
5
+
6
+
7
+ # List of scripts in script/startup sorted asciibetically. This is the order
8
+ # they will be launched in, but there is no further dependency checking
9
+ # between the scripts.
10
+ SCRIPTS = Dir['script/startup/*'].sort
11
+
12
+ # A list of pids of the started scripts. These will be sent TERM signals when
13
+ # bannister is stopped.
14
+ LAUNCHED_PIDS = []
15
+
16
+ # The location of the pidfile for the master process. This will be
17
+ # de-hard-coded in a future release.
18
+ PID_FILENAME = "pids/bannister.pid"
19
+
20
+
21
+ # Daemonize and launch the managed processes.
22
+ def start()
23
+ if running?
24
+ err "This app is already running! Run #{$0} stop first (or remove #{PID_FILENAME} if you know better)."
25
+ else
26
+ daemonize(){ do_start() }
27
+ wait_for_all()
28
+ end
29
+ end
30
+
31
+
32
+ # Launch the managed processes in screen so that they are all foreground
33
+ # processes.
34
+ #
35
+ # If run from inside an existing screen session, it will be reused, and the
36
+ # managed processes will be added to it. Otherwise a new screen session is
37
+ # started.
38
+ #
39
+ # Starting a new screen session is done by writing a temporary screenrc
40
+ # to /tmp, so $HOME/.screenrc is ignored.
41
+ def run()
42
+ screenrc_filename = "/tmp/screenrc.#{$$}"
43
+
44
+ if ENV['STY'] && !ENV['STY'].empty?
45
+ # then we already have a screen, so reuse it
46
+ # by simply running all the commands
47
+
48
+ SCRIPTS.each do |script|
49
+ system( command_for_script(script) )
50
+ end
51
+
52
+ else # create it. The easiest way to do this is to write
53
+ # out a screenrc containing the relevant commands and
54
+ # let screen launch them
55
+ begin
56
+ write_screenrc(screenrc_filename)
57
+
58
+ system "screen","-c",screenrc_filename
59
+ ensure
60
+ FileUtils.rm_f screenrc_filename
61
+ end
62
+
63
+ end
64
+ end
65
+
66
+
67
+ # Shut down all the managed processes, remove the bannister pidfile and exit.
68
+ def quit
69
+ kill_all()
70
+ remove_pid()
71
+ end
72
+
73
+
74
+
75
+ # Send a TERM signal to a #start'ed bannister process.
76
+ def stop()
77
+ old_pid = read_pid()
78
+ if old_pid
79
+ if ::Bannister::Utils.foreign_pid_alive?(old_pid)
80
+ Process.kill("TERM", old_pid)
81
+ if ::Bannister::Utils.wait_foreign_pid(old_pid)
82
+ remove_pid()
83
+ else
84
+ err "Bannister (pid=#{old_pid}) failed to die!"
85
+ end
86
+ else
87
+ err "Bannister (pid=#{old_pid}) wasn't alive."
88
+ end
89
+ end
90
+ end
91
+
92
+
93
+ # Used when you have erred. Shows the passed error message and exits
94
+ # with a status of 1.
95
+ #
96
+ # @param [String] msg Message to display.
97
+ def err(msg)
98
+ $stderr.puts(msg)
99
+ exit(1)
100
+ end
101
+
102
+
103
+
104
+ private
105
+ # Method to see whether a bannister process for the current application
106
+ # is running or not.
107
+ #
108
+ # @return [Boolean] true if PID_FILENAME exists, false otherwise.
109
+ def running?
110
+ return File.exists?(PID_FILENAME)
111
+ end
112
+
113
+
114
+ # Generate the screen command for the passed script filename. Since this
115
+ # will only ever be called when running in the foreground, we append -X
116
+ # to the command. A title for the screen window is also generated based
117
+ # on the current directory name and the script filename.
118
+ #
119
+ # @param [String] script the filename of a script to run.
120
+ # @return [String] the screen command to run the script.
121
+ def command_for_script(script)
122
+ title = File.basename(FileUtils.pwd)+':'+File.basename(script)
123
+ return "screen -t '#{title}' #{script} -X"
124
+ end
125
+
126
+
127
+ # Generate a screen config file and save it to the passed screenrc_filename.
128
+ # Screen is set up to allow restarting of dead processes with the 'r' key,
129
+ # and to give each script a meaningful title in the status line.
130
+ #
131
+ # @param [String] screenrc_filename the filename to save the
132
+ # generated config file to.
133
+ # @return [NilClass] nil.
134
+ def write_screenrc(screenrc_filename)
135
+ File.open(screenrc_filename, "wb") do |f|
136
+ f.write <<-SCREENRC
137
+ zombie cr onerror
138
+ hardstatus alwayslastline "%-Lw%{= BW}%50>%n%f* %t%{-}%+Lw%<"
139
+
140
+ SCREENRC
141
+ SCRIPTS.each do |script|
142
+ f.puts( command_for_script(script) )
143
+ end
144
+ end
145
+
146
+ return nil
147
+ end
148
+
149
+
150
+
151
+ # Daemonize the bannister process and yield. Used when running in
152
+ # the background. This is a slightly different daemonize method to
153
+ # the usual, in that we need to be sure that the script filenames in
154
+ # SCRIPT don't lose their relative location, so we chdir to '/'
155
+ # *after* kicking them off. It's each individual script's
156
+ # responsibility to daemonise properly.
157
+ def daemonize()
158
+ exit!(0) if fork
159
+ Process::setsid
160
+ exit!(0) if fork
161
+ File::umask(0)
162
+ STDIN.reopen("/dev/null")
163
+ STDOUT.reopen("/dev/null", "w")
164
+ STDERR.reopen("/dev/null", "w")
165
+
166
+ yield if block_given?
167
+
168
+ # chdir after yield so that the script paths remain correct
169
+ # and if any of them rely on relative paths, they won't get
170
+ # broken
171
+ Dir::chdir("/")
172
+
173
+ return nil
174
+ end
175
+
176
+
177
+ # Set up our environment and launch everything in the background.
178
+ def do_start()
179
+ set_name()
180
+ drop_pid()
181
+ launch_all()
182
+ end
183
+
184
+ # Set the process name to something meaningful. In this case,
185
+ # "something meaningful" means "bannister:" + the name of the
186
+ # directory we're in. This makes it easy to see which bannister pids
187
+ # are responsible for which applications.
188
+ #
189
+ # @return [NilClass] nil.
190
+ def set_name
191
+ $0 = "bannister:"+File.basename(FileUtils.pwd)
192
+
193
+ return nil
194
+ end
195
+
196
+ # Record the current pid somewhere it can be found later
197
+ def drop_pid
198
+ system "mkdir -p #{File.dirname PID_FILENAME}"
199
+ File.open(PID_FILENAME, "w"){|f| f.write $$.to_s}
200
+
201
+ return nil
202
+ end
203
+
204
+
205
+ # Read the pid from where drop_pid left it, or nil if it's not there.
206
+ #
207
+ # @return [Fixnum] pid as integer if it exists
208
+ # @return [NilClass] nil otherwise.
209
+ def read_pid
210
+ if File.exists?(PID_FILENAME)
211
+ return Integer(File.read(PID_FILENAME).strip)
212
+ else
213
+ return nil
214
+ end
215
+ end
216
+
217
+
218
+ # Clear the pid that drop_pid left behind.
219
+ #
220
+ # @return [NilClass] nil.
221
+ def remove_pid
222
+ FileUtils.rm_f(PID_FILENAME)
223
+
224
+ return nil
225
+ end
226
+
227
+
228
+ # Fork and exec each of SCRIPTS.
229
+ #
230
+ # @return [NilClass] nil.
231
+ def launch_all()
232
+ SCRIPTS.each do |script|
233
+ LAUNCHED_PIDS << fork do
234
+ exec script
235
+ end
236
+ end
237
+
238
+ return nil
239
+ end
240
+
241
+
242
+ # Call Process.waitpid on each process launched by launch_all.
243
+ #
244
+ # @return [NilClass] nil.
245
+ def wait_for_all()
246
+ LAUNCHED_PIDS.each do |pid|
247
+ Process.waitpid(pid)
248
+ end
249
+
250
+ return nil
251
+ end
252
+
253
+
254
+ # Send the TERM signal to each process launched by launch_all.
255
+ #
256
+ # @return [NilClass] nil.
257
+ def kill_all()
258
+ LAUNCHED_PIDS.each do |pid|
259
+ if ::Bannister::Utils.foreign_pid_alive?(pid)
260
+ system "kill -15 #{pid}"
261
+ else
262
+ $stderr.puts "Pid #{pid} doesn't seem to be running!"
263
+ end
264
+ end
265
+
266
+ return nil
267
+ end
268
+
269
+
270
+
271
+
272
+
273
+ end
@@ -0,0 +1,278 @@
1
+ require 'fileutils'
2
+ require 'bannister/utils'
3
+
4
+ module Bannister
5
+
6
+
7
+ # List of scripts in script/startup sorted asciibetically. This is the order
8
+ # they will be launched in, but there is no further dependency checking
9
+ # between the scripts.
10
+ SCRIPTS = Dir['script/startup/*'].sort
11
+
12
+ # A list of pids of the started scripts. These will be sent TERM signals when
13
+ # bannister is stopped.
14
+ LAUNCHED_PIDS = []
15
+
16
+ # The location of the pidfile for the master process. This will be
17
+ # de-hard-coded in a future release.
18
+ PID_FILENAME = "pids/bannister.pid"
19
+
20
+
21
+ # Daemonize and launch the managed processes.
22
+ def start()
23
+ if running?
24
+ err "This app is already running! Run #{$0} stop first (or remove #{PID_FILENAME} if you know better)."
25
+ else
26
+ daemonize(){ do_start() }
27
+ wait_for_all()
28
+ end
29
+ end
30
+
31
+
32
+ # Launch the managed processes in screen so that they are all foreground
33
+ # processes.
34
+ #
35
+ # If run from inside an existing screen session, it will be reused, and the
36
+ # managed processes will be added to it. Otherwise a new screen session is
37
+ # started.
38
+ #
39
+ # Starting a new screen session is done by writing a temporary screenrc
40
+ # to /tmp, so $HOME/.screenrc is ignored.
41
+ def run()
42
+ screenrc_filename = "/tmp/screenrc.#{$$}"
43
+
44
+ if ENV['STY'] && !ENV['STY'].empty?
45
+ # then we already have a screen, so reuse it
46
+ # by simply running all the commands
47
+
48
+ SCRIPTS.each do |script|
49
+ system( command_for_script(script) )
50
+ end
51
+
52
+ else # create it. The easiest way to do this is to write
53
+ # out a screenrc containing the relevant commands and
54
+ # let screen launch them
55
+ begin
56
+ write_screenrc(screenrc_filename)
57
+
58
+ system "screen","-c",screenrc_filename
59
+ ensure
60
+ FileUtils.rm_f screenrc_filename
61
+ end
62
+
63
+ end
64
+ end
65
+
66
+
67
+ # Shut down all the managed processes, remove the bannister pidfile and exit.
68
+ def quit
69
+ $stderr.reopen("/tmp/bannister.stop.err.log", "wb")
70
+ $stdout.reopen("/tmp/bannister.stop.out.log", "wb")
71
+ kill_all()
72
+ remove_pid()
73
+ end
74
+
75
+
76
+
77
+ # Send a TERM signal to a #start'ed bannister process.
78
+ def stop()
79
+ old_pid = read_pid()
80
+ if old_pid
81
+ if ::Bannister::Utils.foreign_pid_alive?(old_pid)
82
+ Process.kill("TERM", old_pid)
83
+ if ::Bannister::Utils.wait_foreign_pid(old_pid)
84
+ remove_pid()
85
+ else
86
+ err "Bannister (pid=#{old_pid}) failed to die!"
87
+ end
88
+ else
89
+ err "Bannister (pid=#{old_pid}) wasn't alive."
90
+ end
91
+ end
92
+ end
93
+
94
+
95
+ # Used when you have erred. Shows the passed error message and exits
96
+ # with a status of 1.
97
+ #
98
+ # @param [String] msg Message to display.
99
+ def err(msg)
100
+ $stderr.puts(msg)
101
+ exit(1)
102
+ end
103
+
104
+
105
+
106
+ private
107
+ # Method to see whether a bannister process for the current application
108
+ # is running or not.
109
+ #
110
+ # @return [Boolean] true if PID_FILENAME exists, false otherwise.
111
+ def running?
112
+ return File.exists?(PID_FILENAME)
113
+ end
114
+
115
+
116
+ # Generate the screen command for the passed script filename. Since this
117
+ # will only ever be called when running in the foreground, we append -X
118
+ # to the command. A title for the screen window is also generated based
119
+ # on the current directory name and the script filename.
120
+ #
121
+ # @param [String] script the filename of a script to run.
122
+ # @return [String] the screen command to run the script.
123
+ def command_for_script(script)
124
+ title = File.basename(FileUtils.pwd)+':'+File.basename(script)
125
+ return "screen -t '#{title}' #{script} -X"
126
+ end
127
+
128
+
129
+ # Generate a screen config file and save it to the passed screenrc_filename.
130
+ # Screen is set up to allow restarting of dead processes with the 'r' key,
131
+ # and to give each script a meaningful title in the status line.
132
+ #
133
+ # @param [String] screenrc_filename the filename to save the
134
+ # generated config file to.
135
+ # @return [NilClass] nil.
136
+ def write_screenrc(screenrc_filename)
137
+ File.open(screenrc_filename, "wb") do |f|
138
+ f.write <<-SCREENRC
139
+ zombie cr onerror
140
+ hardstatus alwayslastline "%-Lw%{= BW}%50>%n%f* %t%{-}%+Lw%<"
141
+
142
+ SCREENRC
143
+ SCRIPTS.each do |script|
144
+ f.puts( command_for_script(script) )
145
+ end
146
+ end
147
+
148
+ return nil
149
+ end
150
+
151
+
152
+
153
+ # Daemonize the bannister process and yield. Used when running in
154
+ # the background. This is a slightly different daemonize method to
155
+ # the usual, in that we need to be sure that the script filenames in
156
+ # SCRIPT don't lose their relative location, so we chdir to '/'
157
+ # *after* kicking them off. It's each individual script's
158
+ # responsibility to daemonise properly.
159
+ def daemonize()
160
+ exit!(0) if fork
161
+ Process::setsid
162
+ exit!(0) if fork
163
+ File::umask(0)
164
+ STDIN.reopen("/dev/null")
165
+ STDOUT.reopen("/dev/null", "w")
166
+ STDERR.reopen("/dev/null", "w")
167
+
168
+ yield if block_given?
169
+
170
+ # chdir after yield so that the script paths remain correct
171
+ # and if any of them rely on relative paths, they won't get
172
+ # broken
173
+ Dir::chdir("/")
174
+
175
+ return nil
176
+ end
177
+
178
+
179
+ # Set up our environment and launch everything in the background.
180
+ def do_start()
181
+ $stderr.reopen("/tmp/bannister.start.err.log", "wb")
182
+ $stdout.reopen("/tmp/bannister.start.out.log","wb")
183
+
184
+ set_name()
185
+ drop_pid()
186
+ launch_all()
187
+ end
188
+
189
+ # Set the process name to something meaningful. In this case,
190
+ # "something meaningful" means "bannister:" + the name of the
191
+ # directory we're in. This makes it easy to see which bannister pids
192
+ # are responsible for which applications.
193
+ #
194
+ # @return [NilClass] nil.
195
+ def set_name
196
+ $0 = "bannister:"+File.basename(FileUtils.pwd)
197
+
198
+ return nil
199
+ end
200
+
201
+ # Record the current pid somewhere it can be found later
202
+ def drop_pid
203
+ system "mkdir -p #{File.dirname PID_FILENAME}"
204
+ File.open(PID_FILENAME, "w"){|f| f.write $$.to_s}
205
+
206
+ return nil
207
+ end
208
+
209
+
210
+ # Read the pid from where drop_pid left it, or nil if it's not there.
211
+ #
212
+ # @return [Fixnum] pid as integer if it exists
213
+ # @return [NilClass] nil otherwise.
214
+ def read_pid
215
+ if File.exists?(PID_FILENAME)
216
+ return Integer(File.read(PID_FILENAME).strip)
217
+ else
218
+ return nil
219
+ end
220
+ end
221
+
222
+
223
+ # Clear the pid that drop_pid left behind.
224
+ #
225
+ # @return [NilClass] nil.
226
+ def remove_pid
227
+ FileUtils.rm_f(PID_FILENAME)
228
+
229
+ return nil
230
+ end
231
+
232
+
233
+ # Fork and exec each of SCRIPTS.
234
+ #
235
+ # @return [NilClass] nil.
236
+ def launch_all()
237
+ SCRIPTS.each do |script|
238
+ LAUNCHED_PIDS << fork do
239
+ exec script
240
+ end
241
+ end
242
+
243
+ return nil
244
+ end
245
+
246
+
247
+ # Call Process.waitpid on each process launched by launch_all.
248
+ #
249
+ # @return [NilClass] nil.
250
+ def wait_for_all()
251
+ LAUNCHED_PIDS.each do |pid|
252
+ Process.waitpid(pid)
253
+ end
254
+
255
+ return nil
256
+ end
257
+
258
+
259
+ # Send the TERM signal to each process launched by launch_all.
260
+ #
261
+ # @return [NilClass] nil.
262
+ def kill_all()
263
+ LAUNCHED_PIDS.each do |pid|
264
+ if ::Bannister::Utils.foreign_pid_alive?(pid)
265
+ system "kill -15 #{pid}"
266
+ else
267
+ $stderr.puts "Pid #{pid} doesn't seem to be running!"
268
+ end
269
+ end
270
+
271
+ return nil
272
+ end
273
+
274
+
275
+
276
+
277
+
278
+ end
@@ -1,17 +1,36 @@
1
+ require 'bannister/utils'
1
2
 
2
3
  module Bannister
3
4
  # A standardised apache launcher.
4
5
  class Apache
5
6
 
6
- # Location of apache's pidfile. This must agree with apache.conf.
7
- PidFilename = "tmp/apache2.pid"
8
-
9
- # @param [Hash] config A config hash to be merged with ENV for
10
- # substitution into apache.conf.
11
- def initialize(config)
7
+ # @param [Hash] config A config hash to be merged with ENV for
8
+ # substitution into apache.conf.
9
+ # @param [String] apacheconf_filename Location of apache.conf (only used if
10
+ # config['DefaultConfig'] doesn't exist, otherwise
11
+ # config['Apache2Conf'] is used instead
12
+ # @param [String] pid_filename Location where apache will write its pidfile
13
+ # (only used if config['DefaultConfig'] doesn't exist, otherwise
14
+ # config['DefaultConfig']['
15
+ def initialize(config, apacheconf_filename=nil, pid_filename=nil)
12
16
  raise ArgumentError.new("config cannot be nil") if config.nil?
13
17
 
14
- @config = config
18
+
19
+ # FIXME: This is pig-ugly, and must be changed for 1.0.0. We do it
20
+ # this way for compatibility with existing deployments, but the old
21
+ # way hardcodes the pid filename, which is problematic and won't
22
+ # work across what's already actually deployed.
23
+ if config.has_key?("DefaultConfig")
24
+ @config = config['DefaultConfig']
25
+ @apacheconf_filename = config['Apache2Conf']
26
+ @pid_filename = @config['PID_FILE']
27
+ else
28
+ @config = config
29
+ @apacheconf_filename = apacheconf_filename
30
+ @pid_filename = pid_filename
31
+ end
32
+
33
+ @pid = nil
15
34
  end
16
35
 
17
36
 
@@ -19,22 +38,10 @@ module Bannister
19
38
  # Send a TERM signal to the apache process. If the process
20
39
  # is still running after 2 seconds, a RuntimeError is thrown.
21
40
  def stop
22
- if pid = read_pid()
41
+ if pid = @pid
23
42
  Process.kill("TERM", pid)
24
- # the apache process isn't a child process, so we can't
25
- # use Process.waitpid. Instead we use kill -0, which tests
26
- # whether we can send the given pid a signal.
27
- catch :done do
28
- 20.times do
29
- sleep(0.1)
30
- # No need to trap error messages here, because `kill`
31
- # gives us a meaningful error code
32
- next if system "kill -0 #{pid} 2> /dev/null"
33
- throw :done
34
- end
35
-
36
- raise "Apache2 (pid=#{pid}) failed to stop!"
37
- end
43
+ else
44
+ $stderr.puts "No apache pid found!"
38
45
  end
39
46
  end
40
47
 
@@ -48,48 +55,26 @@ module Bannister
48
55
  #
49
56
  # @param [Boolean] foreground Set true to run in the foreground.
50
57
  def start(foreground=false)
51
- @apache2_conf = @config['Apache2Conf']
52
58
  rewrite_envvars(ENV)
53
- system "mkdir -p tmp"
54
- cmd = foreground ? "/usr/sbin/apache2 -f #{@apache2_conf} -X &" :
55
- "/usr/sbin/apache2 -f #{@apache2_conf}"
56
- if system( cmd )
57
- # runner sends TERM, but screen sends a HUP when it quits.
58
- # This is why we can't just exec apache -X; apache restarts
59
- # on HUP.
60
- trap("TERM"){ stop() }
61
- trap("HUP"){ stop() }
62
-
63
- # Apache2's pidfile is written by the child process
64
- # after the parent process (the one we kick off) terminates.
65
- # This means that we need to wait for it.
66
- catch :done do
67
- 20.times do
68
- sleep(0.1)
69
- next if !File.file?(PidFilename)
70
- throw :done
71
- end
72
- raise "Apache2 failed to write #{PidFilename}"
73
- end
74
-
75
- sleep # Sleep to wait for the TERM signal
76
-
77
- else
78
- raise "Apache2 failed to start!"
79
- end
80
-
59
+ system "mkdir -p tmp" # FIXME: check pid_filename
60
+ cmd = foreground ? "/usr/sbin/apache2 -f #{@apacheconf_filename} -X" :
61
+ "/usr/sbin/apache2 -f #{@apacheconf_filename} -D FOREGROUND"
62
+ trap("TERM"){ stop() }
63
+ trap("HUP"){ stop() }
64
+ @pid = fork { exec cmd }
65
+ Process.waitpid(@pid)
81
66
  end
82
67
 
83
68
 
84
69
  private
85
70
 
86
- # Read apache2's pid from {PidFilename}.
71
+ # Read apache2's pid from {@pid_filename}.
87
72
  #
88
73
  # @return [Integer] Apache's process id.
89
74
  # @return nil if it's not there.
90
75
  def read_pid()
91
- if File.file?(PidFilename)
92
- return Integer(File.read(PidFilename).strip)
76
+ if File.file?(@pid_filename)
77
+ return Integer(File.read(@pid_filename).strip)
93
78
  else
94
79
  return nil
95
80
  end
@@ -100,7 +85,7 @@ module Bannister
100
85
  #
101
86
  # @param [Hash] vars The config hash
102
87
  def rewrite_envvars(vars={})
103
- merged_opts = @config['DefaultConfig'].merge(vars)
88
+ merged_opts = @config.merge(vars)
104
89
  merged_opts.each_pair do |k,v|
105
90
  ENV[k] = v.to_s
106
91
  end
@@ -0,0 +1,46 @@
1
+ module Bannister
2
+
3
+ module Utils
4
+
5
+
6
+ def self.wait_while(timeout=2.0, attempts=20)
7
+ raise ArgumentError.new("No block given!") unless block_given?
8
+ attempts=20
9
+ sleepy_time = timeout/attempts
10
+
11
+ attempts.times do
12
+ return true unless yield
13
+ sleep sleepy_time
14
+ end
15
+
16
+ return nil
17
+ end
18
+
19
+ # Waits for process with process id `pid` to die. This is needed
20
+ # because Process.waitpid can only wait for child pids.
21
+ #
22
+ # @param [Integer] pid process id to wait for
23
+ # @param [Float] timeout time in seconds to wait for
24
+ # @return [Boolean] true on success, nil otherwise.
25
+ def self.wait_foreign_pid(pid, timeout=2.0)
26
+ raise ArgumentError.new("pid cannot be nil!") unless pid
27
+
28
+ return wait_while(){ foreign_pid_alive?(pid) }
29
+ end
30
+
31
+
32
+ # Check to see if the process with id `pid` responds to signals.
33
+ #
34
+ # @param [Integer] pid process id to check
35
+ # @return [Boolean] true if process `pid` responds.
36
+ def self.foreign_pid_alive?(pid)
37
+ raise ArgumentError.new("pid cannot be nil!") unless pid
38
+
39
+ return system "kill -0 #{pid} 2> /dev/null"
40
+ end
41
+
42
+
43
+ end
44
+
45
+
46
+ end
@@ -1,3 +1,3 @@
1
1
  module Bannister
2
- VERSION="0.0.2"
2
+ VERSION="0.0.3"
3
3
  end
@@ -0,0 +1,3 @@
1
+ module Bannister
2
+ VERSION="0.0.3"
3
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bannister
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 25
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 2
10
- version: 0.0.2
9
+ - 3
10
+ version: 0.0.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Alex Young
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-10-13 00:00:00 +01:00
18
+ date: 2010-10-15 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -47,9 +47,14 @@ extra_rdoc_files: []
47
47
  files:
48
48
  - bin/bannister
49
49
  - lib/bannister/version.rb
50
+ - lib/bannister/version.rb.orig
50
51
  - lib/bannister/recurse.rb
51
52
  - lib/bannister/apache.rb
53
+ - lib/bannister/utils.rb
54
+ - lib/bannister.remote.rb
52
55
  - lib/bannister.rb
56
+ - lib/bannister.remote.rb~
57
+ - lib/bannister.rb.orig
53
58
  - README.md
54
59
  - Rakefile
55
60
  has_rdoc: true