bannister 0.0.2 → 0.0.3

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.
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