ruby-style 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2007 Jeremy Evans <code@jeremyevans.net>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,292 @@
1
+ = style (Supervised TCPServer, Yielding Listeners Easily)
2
+
3
+ style is a Ruby program that provides a supervised TCPServer (or TCPServers)
4
+ with multiple listening childing processes. It allows the child processes to
5
+ be killed and respawned while making sure that there are always child processes
6
+ available to listen to requests. It automatically respawns dead
7
+ child processes. It allows for increasing and decreasing the number of child
8
+ processes on the fly.
9
+
10
+ style is distributed as a gem, and can be installed with:
11
+
12
+ sudo gem install ruby-style
13
+
14
+ Feedback/Bugs/Support Requests should be handled through RubyForge at
15
+ http://rubyforge.org/projects/ruby-style/.
16
+
17
+ The RDoc is available at http://code.jeremyevans.net/doc/ruby-style/.
18
+ Subversion access is available at svn://code.jeremyevans.net/ruby-style/.
19
+
20
+ == Highlights
21
+
22
+ * Always has child listeners available, so no connections are lost when
23
+ listeners are restarted
24
+ * Automatically restarts dead child processes
25
+ * Supports both single port and multiple port clustering, with an arbitrary
26
+ number of listeners per port
27
+ * Supports increasing and decreasing the number of child processes per port on
28
+ the fly
29
+ * Supports both command line and yaml config file configuration
30
+ * Is easily extensible to support running other frameworks and protocols other
31
+ than the included ones (RailsMongrel and RailsSCGI)
32
+
33
+ == Running and configuration
34
+
35
+ To see the available commands and possible and default configuration options,
36
+ just run the program without any arguments:
37
+
38
+ style [option value, ...] (decrement|halt|increment|restart|start|stop)
39
+ Options:
40
+ -b, --bind IP address to bind to [127.0.0.1]
41
+ -c, --config Location of config file [config/style.yaml]
42
+ -d, --directory Working directory [.]
43
+ -f, --fork Number of listners on each port [1]
44
+ -k, --killtime Number of seconds to wait when killing each child [2]
45
+ -l, --logfile Where to redirect STDOUT and STDERR [log/style.log]
46
+ -n, --number Number of ports to which to bind [1]
47
+ -p, --port Starting port to which to bind [9999]
48
+ -P, --pidfile Location of pid file [log/style.pid]
49
+ -s, --style Type of style to use [RailsMongrel]
50
+ -u, --unsupervised Whether to run unsupervised [No]
51
+
52
+ Here's what the various commands do:
53
+
54
+ * decrement (USR2) - Decrease the number of listeners per port by 1
55
+ * halt (TERM) - Immediately shutdown the child processes and exit
56
+ * increment (USR1) - Increase the number of listeners per port by 1
57
+ * restart (HUP) - Replace the current listeners with new listeners
58
+ * start - Starts the supervisor process and the listening processes
59
+ * stop (INT) - Gracefully shutdown the child processes and exit
60
+
61
+ All commands except start just send the listed signal to the preexisting
62
+ supervisor process specified in the pid file.
63
+
64
+ Note that the -d (--directory) option changes the working directory of the
65
+ process, so the -c, -l, and -P options are relative to that.
66
+
67
+ Here's a longer explanation of the options:
68
+
69
+ -b, --bind IP address to bind to [127.0.0.1]
70
+
71
+ This is the TCP/IP networking sockets to bind to. It defaults to the
72
+ loopback address because generally the web application runs on the same
73
+ physical server as the web server. If this is not the case, change it to an
74
+ externally available IP, and make sure to lock down access to the port via a
75
+ firewall.
76
+
77
+ -c, --config Location of config file [config/style.yaml]
78
+
79
+ This is the configuration file for style. It is recommended that you use
80
+ this instead of the command line configuration, as it saves typing. This
81
+ path is relative to the working directory, so if it is not inside the working
82
+ directory, make sure you specify an absolute path. This option is not
83
+ configurable from the configuration file.
84
+
85
+ -d, --directory Working directory [.]
86
+
87
+ This is the working directory of the process. It should generally be the
88
+ path to the root of the application. Alternatively, you can change to the
89
+ root of the application before hand and then not use this option. This option
90
+ is not configurable from the configuration file.
91
+
92
+ -f, --fork Number of listners on each port [1]
93
+
94
+ This enables multiple child processes listening on each port. It is
95
+ recommended that you use -f instead of -n for multiple listeners, since it
96
+ simplifies the configuration of the webserver, and can also eliminate
97
+ the need for a proxy such as pound or pen to handle this for you. It
98
+ defaults to one process per port. Note that when restarting processes,
99
+ replacement processes are started before the currently listening processes
100
+ are killed, so it is possibly to have multiple processes listening on a port
101
+ even if this is left at one.
102
+
103
+ -k, --killtime Number of seconds to wait when killing each child [2]
104
+
105
+ This sets the time that style between sending shutdown signals to child
106
+ process, as well as the time between starting a replacement process and
107
+ killing an existing process. When restarting, the amount of time spent
108
+ waiting should be between 2-3 * (killtime * number * fork).
109
+
110
+ -l, --logfile Where to redirect STDOUT and STDERR [log/style.log]
111
+
112
+ This is the location of the log file, relative to the working directory.
113
+ style itself doesn't output anything after it has detached from the listening
114
+ terminal, but child listening processes might output to STDOUT or STDERR.
115
+
116
+ -n, --number Number of ports to which to bind [1]
117
+
118
+ This makes style start up multiple sockets, one per port starting with the
119
+ given port (so port, port+1, port+2, ..., port+n). This makes webserver
120
+ configuration a little more difficult than with just using -f, and might
121
+ also require a separate proxy such as pound or pen, so you should try just
122
+ using -f first.
123
+
124
+ -p, --port Starting port to which to bind [9999]
125
+
126
+ This is the starting (or only) port that style will use. If -n is used, all
127
+ ports will be greater than this one.
128
+
129
+ -P, --pidfile Location of pid file [log/style.pid]
130
+
131
+ This is the pid file, relative to the working directory. The pid file is
132
+ necessary, as it is what is used by all commands other than start. If
133
+ incorrect information is in the pid file, the processes won't be stopped when
134
+ they should be, and you will probably won't be able to start new processes
135
+ (because the ports will still be in use).
136
+
137
+ -s, --style Type of style to use [RailsMongrel]
138
+
139
+ This is the type of style to use. It defaults to RailsMongrel, as that is
140
+ most common configuration these days. The other style included with style is
141
+ RailsSCGI. Adding other styles is fairly easy, just make the style is in a
142
+ file named XXXXXStyle.rb (where XXXXX is in the name of the style), and that
143
+ file is located in ruby's library path ($:, e.g. via the RUBYLIB environment
144
+ variable). See the included styles for an example.
145
+
146
+ -u, --unsupervised Whether to run unsupervised [No]
147
+
148
+ This starts child processes without using a supervisor process. It is not
149
+ as reliable or as featureful as the regular supervised mode, but those may
150
+ not be necessary for smaller sites. In unsupervised mode, only restart,
151
+ start, and stop are valid commands, child processes aren't automatically
152
+ restarted when they die, and you may lose connections during restarts.
153
+
154
+ Every one of the long options can also be specified in the config file. Also,
155
+ the config file if you want to specify any settings specific to the style
156
+ being used (such as modifying the Rails environment setting).
157
+
158
+ == Included styles and config files
159
+
160
+ style current comes with support for the following styles: RailsMongrelStyle
161
+ (the default, which requires rails and mongrel), and RailsSCGIStyle (which
162
+ requires rails and ruby-scgi).
163
+
164
+ The RailsMongrel style supports running Rails on Mongrel, which would usually
165
+ be deployed behind a front end webserver, such as Apache, Lighttpd, or Nginx.
166
+ The RailsSCGI style supports running Rails over SCGI, which is supported by
167
+ both Apache and Lighttpd. Both default to running Rails in production mode,
168
+ as style is designed as a production tool.
169
+
170
+ Example RailsMongrel style.yaml that listens on ports 8912 and 8192 on any
171
+ interface, with three listeners per port. Note how the :style_config entry is
172
+ used to set up settings specific to the RailsMongrel style, such as placing
173
+ Rails in development mode.
174
+
175
+ ---
176
+ :port: 8912
177
+ :bind: ""
178
+ :fork: 3
179
+ :number: 2
180
+ :killtime: 1
181
+ :style: RailsMongrel
182
+ :style_config:
183
+ :environment: development
184
+ :log_file: log/rails-mongrel.log
185
+ :docroot: public
186
+ :num_processors: 99
187
+ :timeout: 100
188
+
189
+ Example RailsSCGI style.yaml that has four listeners on port 8912:
190
+
191
+ ---
192
+ :port: 8912
193
+ :fork: 4
194
+ :number: 1
195
+ :style: RailsSCGI
196
+ :style_config:
197
+ :logfile: log/railsscgi.log
198
+ :maxconns: 10
199
+ :environment: production
200
+
201
+ Very simple configuration that runs RailsMongrel style in unsupervised mode on
202
+ port 3000.
203
+
204
+ ---
205
+ :port: 3000
206
+ :unsupervised: 1
207
+
208
+ == How restarting works
209
+
210
+ Restarting works by starting a new child process, waiting for killtime, and then
211
+ killing the exisiting process. It repeats this for all existing child processes.
212
+
213
+ For example:
214
+
215
+ # style idling with three listeners
216
+ Thu Sep 6 12:01:47 PDT 2007
217
+ 11460 ?? I 0:00.00 ruby: RailsMongrelStyle dir:/home/billg/testrails supervisor (ruby)
218
+ 6540 ?? I 0:02.52 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
219
+ 272 ?? I 0:02.52 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
220
+ 27808 ?? I 0:02.51 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
221
+ # Restart occurs, new process started (1207)
222
+ Thu Sep 6 12:01:49 PDT 2007
223
+ 11460 ?? I 0:00.00 ruby: RailsMongrelStyle dir:/home/billg/testrails supervisor (ruby)
224
+ 27808 ?? I 0:02.51 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
225
+ 272 ?? I 0:02.52 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
226
+ 6540 ?? I 0:02.52 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
227
+ 1207 ?? R 0:00.95 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
228
+ # Old process killed (27808)
229
+ Thu Sep 6 12:01:51 PDT 2007
230
+ 11460 ?? I 0:00.01 ruby: RailsMongrelStyle dir:/home/billg/testrails supervisor (ruby)
231
+ 6540 ?? I 0:02.52 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
232
+ 272 ?? I 0:02.52 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
233
+ 1207 ?? I 0:02.51 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
234
+ # New process started (3204)
235
+ Thu Sep 6 12:01:53 PDT 2007
236
+ 11460 ?? I 0:00.01 ruby: RailsMongrelStyle dir:/home/billg/testrails supervisor (ruby)
237
+ 272 ?? I 0:02.52 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
238
+ 6540 ?? I 0:02.52 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
239
+ 1207 ?? I 0:02.51 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
240
+ 3204 ?? R 0:01.01 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
241
+ # Old process killed (6540)
242
+ Thu Sep 6 12:01:55 PDT 2007
243
+ 11460 ?? I 0:00.01 ruby: RailsMongrelStyle dir:/home/billg/testrails supervisor (ruby)
244
+ 272 ?? I 0:02.52 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
245
+ 1207 ?? I 0:02.51 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
246
+ 3204 ?? I 0:02.49 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
247
+ # New process started (6766)
248
+ Thu Sep 6 12:01:57 PDT 2007
249
+ 11460 ?? I 0:00.01 ruby: RailsMongrelStyle dir:/home/billg/testrails supervisor (ruby)
250
+ 272 ?? I 0:02.52 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
251
+ 1207 ?? I 0:02.51 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
252
+ 3204 ?? I 0:02.49 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
253
+ 6766 ?? R 0:00.99 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
254
+ # Old process killed (272)
255
+ Thu Sep 6 12:01:59 PDT 2007
256
+ 11460 ?? I 0:00.01 ruby: RailsMongrelStyle dir:/home/billg/testrails supervisor (ruby)
257
+ 1207 ?? I 0:02.51 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
258
+ 3204 ?? I 0:02.49 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
259
+ 6766 ?? I 0:02.49 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
260
+
261
+ Result, three brand new listening processes, total time to restart is about 12
262
+ seconds (2 * (2 killtime * 1 number * 3 fork)).
263
+
264
+ == Other frameworks and protocols
265
+
266
+ style can support any ruby server program that supports the following
267
+ interface (which is called a style):
268
+
269
+ # in file XXXXXStyle.rb, which must be in ruby's path ($:)
270
+ # XXXXX is the name of the style (e.g. RubySCGI)
271
+ class XXXXXStyle
272
+ def initialize(options)
273
+ #options is a Hash
274
+ end
275
+
276
+ def listen(socket)
277
+ # socket is a TCPServer
278
+ end
279
+ end
280
+
281
+ Just specify the style with -s or --style on the command line, or :style in the
282
+ config file.
283
+
284
+ == FAQ
285
+
286
+ Q: Does this run on Windows?
287
+
288
+ A: No. Among other things, style uses fork, which is not available on Windows.
289
+
290
+ Q: Does it work with Capistrano yet?
291
+
292
+ A: I haven't tried. If you have luck, let me know.
data/bin/style ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'style'
3
+ Style.new.process(ARGV[0])
@@ -0,0 +1,58 @@
1
+ require 'rubygems'
2
+ require 'yaml'
3
+ require 'mongrel'
4
+ require 'mongrel/rails'
5
+ require 'etc'
6
+ require 'cgi_multipart_eof_fix' rescue nil
7
+
8
+ module Mongrel
9
+ # Patch the Mongrel HttpServer to add a socket= method
10
+ class HttpServer
11
+ attr_writer :socket
12
+
13
+ # Keep the interface the same, but ignore the host and port and don't
14
+ # create a socket (one will be provided later)
15
+ def initialize(host, port, num_processors=2**30-1, timeout=0)
16
+ @classifier = URIClassifier.new
17
+ @workers = ThreadGroup.new
18
+ @timeout = timeout
19
+ @num_processors = num_processors
20
+ @death_time = 60
21
+ end
22
+ end
23
+ end
24
+
25
+ # This hooks the HTTP request into Ruby on Rails and Mongrel.
26
+ class RailsMongrelStyle
27
+ attr_reader :mongrel
28
+
29
+ # Initialzes Rails and Mongrel with the appropriate environment and settings.
30
+ def initialize(settings)
31
+ settings = {:cwd => Dir.pwd, :log_file => 'log/rails-mongrel.log',
32
+ :environment => 'production', :docroot => 'public',
33
+ :mime_map => nil, :debug => false, :includes => ["mongrel"],
34
+ :config_script => nil, :num_processors => 1024, :timeout => 0,
35
+ :user => nil, :group => nil, :prefix => nil}.merge(settings)
36
+ ENV['RAILS_ENV'] = settings[:environment]
37
+ $0 += " environment:#{ENV['RAILS_ENV']}"
38
+ @mongrel = Mongrel::Rails::RailsConfigurator.new(settings) do
39
+ listener do
40
+ mime = defaults[:mime_map] ? load_mime_map(defaults[:mime_map], mime) : {}
41
+ debug "/" if defaults[:debug]
42
+ uri defaults[:prefix] || "/", :handler => rails(:mime => mime, :prefix => defaults[:prefix])
43
+ load_plugins
44
+ run_config(defaults[:config_script]) if defaults[:config_script]
45
+ setup_rails_signals
46
+ end
47
+ end
48
+ end
49
+
50
+ # Set the HttpServer to the correct socket and start running the mongrel event loop.
51
+ def listen(socket)
52
+ mongrel.instance_variable_get('@rails_handler').instance_variable_get('@listener').socket = socket
53
+ mongrel.run
54
+ mongrel.join
55
+ end
56
+ end
57
+
58
+ GemPlugin::Manager.instance.load "mongrel" => GemPlugin::INCLUDE, "rails" => GemPlugin::EXCLUDE
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ require 'scgi'
3
+
4
+ # This SCGI::Processor subclass hooks the SCGI request into Ruby on Rails.
5
+ class RailsSCGIStyle < SCGI::Processor
6
+ # Initialzes Rails with the appropriate environment and settings
7
+ def initialize(settings)
8
+ ENV['RAILS_ENV'] = settings[:environment] || 'production'
9
+ $0 += " environment:#{ENV['RAILS_ENV']}"
10
+ require "config/environment"
11
+ ActiveRecord::Base.threaded_connections = false
12
+ require 'dispatcher'
13
+ super(settings)
14
+ @guard = Mutex.new
15
+ end
16
+
17
+ # Submits requests to Rails in a single threaded fashion
18
+ def process_request(request, body, socket)
19
+ return if socket.closed?
20
+ cgi = SCGI::CGIFixed.new(request, body, socket)
21
+ begin
22
+ @guard.synchronize{Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput)}
23
+ rescue IOError
24
+ @log.error("received IOError #$! when handling client. Your web server doesn't like me.")
25
+ rescue Object => rails_error
26
+ @log.error("calling Dispatcher.dispatch", rails_error)
27
+ end
28
+ end
29
+ end
data/lib/style.rb ADDED
@@ -0,0 +1,423 @@
1
+ #!/usr/local/bin/ruby
2
+ require 'getoptlong'
3
+ require 'socket'
4
+ require 'thread'
5
+ require 'yaml'
6
+
7
+ # Supervised TCPServer, Yielding Listeners Easily
8
+ #
9
+ # Style creates a supervised (or unsupervised) group of processes listening on
10
+ # one or more TCP sockets. It allows for rock solid restarting of processes
11
+ # without modifications to other software, since the same listening socket is
12
+ # reused, and replacement processes are started before the previous processes
13
+ # are killed. The number of processes used can be increased or decreased on
14
+ # the fly by sending signals to the process. Children that die are automatically
15
+ # restarted. The general idea for this was inspired by Erlang/OTP's supervision
16
+ # trees.
17
+ class Style
18
+ attr_reader :config, :mutex
19
+ VALID_STYLE_RE = /\A[A-Z][A-ZA-z0-9]*\z/
20
+
21
+ # Configure style
22
+ def initialize
23
+ super
24
+ @config = {:pidfile=>'log/style.pid', :number=>1, :port=>9999,
25
+ :style=>'RailsMongrel', :fork=>1, :bind=>'127.0.0.1',
26
+ :cliconfig=>{}, :killtime=>2, :config=>'config/style.yaml',
27
+ :logfile=>'log/style.log', :children=>{},:sockets=>{},
28
+ :style_config=>{}, :directory=>'.'}
29
+ @mutex = Mutex.new
30
+ parse_options
31
+ end
32
+
33
+ # Check that the directory of the given filename exists and is a directory, exit otherwise
34
+ def check_dir(filename)
35
+ filename = File.expand_path(filename)
36
+ dirname = File.dirname(filename)
37
+ unless File.directory?(dirname)
38
+ puts "Invalid directory: #{dirname}"
39
+ exit(1)
40
+ end
41
+ filename
42
+ end
43
+
44
+ # Check that the filename given exists and is a file, exit otherwise
45
+ def check_file(filename)
46
+ filename = File.expand_path(filename)
47
+ unless File.file?(filename)
48
+ puts "Invalid file: #{filename}"
49
+ exit(1)
50
+ end
51
+ filename
52
+ end
53
+
54
+ # Return a hash of options from the config file, or an empty hash
55
+ def config_file_options(filename)
56
+ conf = YAML.load(File.read(filename)) rescue (return Hash.new)
57
+ return Hash.new unless conf.is_a?(Hash)
58
+ conf.delete(:directory)
59
+ conf.delete(:config)
60
+ conf
61
+ end
62
+
63
+ # Detach the process from the controlling terminal, exit otherwise
64
+ def detach
65
+ unless Process.setsid
66
+ puts "Cannot detach from controlling terminal"
67
+ exit(1)
68
+ end
69
+ trap(:HUP, 'IGNORE')
70
+ end
71
+
72
+ # Load the revelent style
73
+ def get_style(string)
74
+ raise NameError, "#{string} is not a valid style!" unless VALID_STYLE_RE.match(string)
75
+ style= "#{string}Style"
76
+ require style
77
+ eval(style)
78
+ end
79
+
80
+ # Kill each of the given pids in order
81
+ def kill_children_gently(pids)
82
+ pids.each{|pid| kill_gently(pid)}
83
+ end
84
+
85
+ # Try to allow the child to die gracefully, by trying INT, TERM, and then KILL
86
+ def kill_gently(pid)
87
+ begin
88
+ Process.kill('INT', pid)
89
+ sleep(config[:killtime])
90
+ Process.kill('TERM', pid)
91
+ sleep(config[:killtime])
92
+ Process.kill('KILL', pid)
93
+ rescue
94
+ return nil
95
+ end
96
+ end
97
+
98
+ # Parse the command line options, and merge them with the default options and
99
+ # the config file options. Config file options take precendence over the
100
+ # default options, and command line options take precendence over both.
101
+ def parse_options
102
+ cliconfig = config[:cliconfig]
103
+ GetoptLong.new(
104
+ [ '--bind', '-b', GetoptLong::REQUIRED_ARGUMENT ],
105
+ [ '--config', '-c', GetoptLong::REQUIRED_ARGUMENT ],
106
+ [ '--directory', '-d', GetoptLong::REQUIRED_ARGUMENT ],
107
+ [ '--fork', '-f', GetoptLong::REQUIRED_ARGUMENT ],
108
+ [ '--killtime', '-k', GetoptLong::REQUIRED_ARGUMENT ],
109
+ [ '--logfile', '-l', GetoptLong::REQUIRED_ARGUMENT ],
110
+ [ '--number', '-n', GetoptLong::REQUIRED_ARGUMENT ],
111
+ [ '--port', '-p', GetoptLong::REQUIRED_ARGUMENT ],
112
+ [ '--pidfile', '-P', GetoptLong::REQUIRED_ARGUMENT ],
113
+ [ '--style', '-s', GetoptLong::REQUIRED_ARGUMENT ],
114
+ [ '--unsupervised', '-u', GetoptLong::NO_ARGUMENT]
115
+ ).each do |opt, arg|
116
+ case opt
117
+ when '--bind'
118
+ cliconfig[:bind] = arg
119
+ when '--config'
120
+ config[:config] = cliconfig[:config] = arg
121
+ when '--directory'
122
+ config[:directory] = arg
123
+ when '--fork'
124
+ cliconfig[:fork] = arg.to_i
125
+ when '--killtime'
126
+ cliconfig[:killtime] = arg.to_i
127
+ when '--logfile'
128
+ cliconfig[:logfile] = arg
129
+ when '--number'
130
+ cliconfig[:number] = arg.to_i
131
+ when '--port'
132
+ cliconfig[:port] = arg.to_i
133
+ when '--pidfile'
134
+ cliconfig[:pidfile] = arg
135
+ when '--style'
136
+ cliconfig[:style] = arg
137
+ when '--unsupervised'
138
+ cliconfig[:unsupervised] = true
139
+ end
140
+ end
141
+
142
+ config[:directory] = File.expand_path(config[:directory])
143
+ Dir.chdir(config[:directory]) rescue (puts "Invalid directory: #{arg}"; exit(1))
144
+
145
+ cliconfig[:config] = File.expand_path(check_file(cliconfig[:config])) if cliconfig[:config]
146
+ config[:config] = File.expand_path(config[:config])
147
+ reload_config
148
+
149
+ [:logfile, :pidfile].each do |opt|
150
+ cliconfig[opt] = File.expand_path(cliconfig[opt]) if cliconfig[opt]
151
+ config[opt] = File.expand_path(config[opt])
152
+ config[opt] = check_dir(config[opt])
153
+ end
154
+ unless VALID_STYLE_RE.match(config[:style])
155
+ puts "#{config[:style]} is not a valid style!"
156
+ exit(1)
157
+ end
158
+ end
159
+
160
+ # Process the command given
161
+ def process(command)
162
+ config[:unsupervised] ? process_unsupervised_command(command) : process_supervised_command(command)
163
+ end
164
+
165
+ # Process the command given in supervised mode. All commands except start just send
166
+ # a signal to the pid in the pid file.
167
+ def process_supervised_command(command)
168
+ case command
169
+ when 'decrement'
170
+ signal_supervisor(:USR2)
171
+ when 'halt'
172
+ signal_supervisor(:TERM)
173
+ when 'increment'
174
+ signal_supervisor(:USR1)
175
+ when 'restart'
176
+ signal_supervisor(:HUP)
177
+ when 'start'
178
+ supervisor_start
179
+ when 'stop'
180
+ signal_supervisor(:INT)
181
+ else
182
+ puts usage
183
+ exit(1)
184
+ end
185
+ end
186
+
187
+ # Process the command given in unsupervised mode. Only restart, start, and stop
188
+ # are supported.
189
+ def process_unsupervised_command(command)
190
+ case command
191
+ when /\A(decrement|halt|increment)\z/
192
+ puts "#{$1} not supported in unsupervised mode"
193
+ exit(1)
194
+ when 'restart'
195
+ stop
196
+ start
197
+ when 'start'
198
+ start
199
+ when 'stop'
200
+ stop
201
+ else
202
+ puts usage
203
+ exit(1)
204
+ end
205
+ end
206
+
207
+ # Reset the umask and redirect STDIN to /dev/null and STDOUT and STDERR to
208
+ # the appropriate logfile.
209
+ def redirect_io
210
+ File.umask(0000)
211
+ STDIN.reopen('/dev/null') rescue nil
212
+ begin
213
+ STDOUT.reopen(config[:logfile], "a")
214
+ STDOUT.sync = true
215
+ rescue
216
+ STDOUT.reopen('/dev/null') rescue nil
217
+ end
218
+ STDERR.reopen(STDOUT) rescue nil
219
+ STDERR.sync = true
220
+ end
221
+
222
+ # Reload the configuration, used when restarting. Only the following options
223
+ # take effect when reloading: config, killtime, pidfile, style, and style_config.
224
+ def reload_config
225
+ config.merge!(config_file_options(config[:config]))
226
+ config.merge!(config[:cliconfig])
227
+ end
228
+
229
+ # Restart stopping children by waiting on them. If the children have died and
230
+ # are in the list of children, restart them.
231
+ def restart_stopped_children
232
+ loop do
233
+ break unless pid = Process.wait(-1, Process::WNOHANG)
234
+ #puts "received sigchild, pid #{pid}"
235
+ mutex.synchronize do
236
+ if socket = config[:children].delete(pid)
237
+ start_child(socket)
238
+ end
239
+ end
240
+ Process.detach(pid)
241
+ end rescue nil
242
+ end
243
+
244
+ # Setup the necessary signals used in supervisory mode:
245
+ #
246
+ # * CLD - Restart any dead children
247
+ # * HUP - Reload configuration and restart all children
248
+ # * INT - Gracefully shutdown children and exit
249
+ # * TERM - Immediately shutdown children and exit
250
+ # * USR1 - Increase the number of listeners on each port by 1
251
+ # * USR2 - Decrease the number of listeners on each port by 1
252
+ #
253
+ # Note that these signals should be sent to the supervising process,
254
+ # the child processes are only required to shutdown on INT and TERM, and will
255
+ # respond to other signals differently depending on the style used.
256
+ def setup_supervisor_signals
257
+ trap(:CLD) do
258
+ # Child Died
259
+ restart_stopped_children
260
+ end
261
+ trap(:HUP) do
262
+ # Restart Children
263
+ Dir.chdir(config[:directory]) rescue nil
264
+ reload_config
265
+ supervisor_restart_children
266
+ end
267
+ trap(:INT) do
268
+ # Graceful Shutdown
269
+ supervisor_shutdown
270
+ kill_children_gently(config[:children].keys)
271
+ supervisor_exit
272
+ end
273
+ trap(:TERM) do
274
+ # Fast Shutdown
275
+ supervisor_shutdown
276
+ pids = config[:children].keys
277
+ Process.kill('TERM', *pids) rescue nil
278
+ sleep(config[:killtime])
279
+ Process.kill('KILL', *pids) rescue nil
280
+ supervisor_exit
281
+ end
282
+ trap(:USR1) do
283
+ # Increment number of children
284
+ config[:sockets].keys.each do |socket|
285
+ mutex.synchronize{start_child(socket)}
286
+ end
287
+ end
288
+ trap(:USR2) do
289
+ # Decrement number of children
290
+ config[:children].invert.values.each do |pid|
291
+ mutex.synchronize do
292
+ config[:children].delete(pid)
293
+ kill_gently(pid)
294
+ end
295
+ end
296
+ end
297
+ end
298
+
299
+ # Read the pid file and signal the supervising process, or raise an error
300
+ def signal_supervisor(signal)
301
+ begin
302
+ pid = File.read(config[:pidfile]).to_i
303
+ rescue
304
+ puts "Can't read pidfile (#{config[:pidfile]}) to send signal #{signal}"
305
+ exit(1)
306
+ end
307
+ if pid > 1
308
+ Process.kill(signal, pid)
309
+ else
310
+ puts "Illegal value in pidfile"
311
+ exit(1)
312
+ end
313
+ end
314
+
315
+ # Start a child process. The child process will reset the signals used, as
316
+ # well as close any unused sockets, and then it should listen indefinitely on
317
+ # the provided socket.
318
+ def start_child(socket)
319
+ return if config[:shutdown]
320
+ pid = fork do
321
+ [:HUP, :INT, :TERM, :USR1, :USR2].each{|signal| trap(signal, 'DEFAULT')}
322
+ config[:sockets].keys.each{|sock| sock.close unless sock == socket}
323
+ $0 = "#{config[:style]}Style dir:#{Dir.pwd} port:#{config[:sockets][socket]}"
324
+ get_style(config[:style]).new(config[:style_config]).listen(socket)
325
+ end
326
+ #puts "started pid #{pid}"
327
+ config[:children][pid] = socket
328
+ end
329
+
330
+ # Start an unsupervised group of processes
331
+ def start
332
+ fork do
333
+ detach
334
+ redirect_io
335
+ config[:number].times do |i|
336
+ port = config[:port]+i
337
+ socket = TCPServer.new(config[:bind], port)
338
+ config[:sockets][socket] = port
339
+ config[:fork].times{start_child(socket)}
340
+ end
341
+ File.open(config[:pidfile], 'wb'){|file| file.print("#{config[:children].keys.join(' ')}")}
342
+ end
343
+ end
344
+
345
+ # Stop an unsupervised group of processes
346
+ def stop
347
+ if File.file?(config[:pidfile])
348
+ pids = nil
349
+ File.open(config[:pidfile], 'rb'){|f| pids = f.read.split.collect{|x| x.to_i if x.to_i > 1}.compact}
350
+ if pids.length > 0
351
+ kill_children_gently(pids)
352
+ File.delete(config[:pidfile])
353
+ end
354
+ end
355
+ end
356
+
357
+ # Start all necessary children of the supervisor process (number * fork)
358
+ def supervisor_children_start
359
+ config[:number].times do |i|
360
+ port = config[:port]+i
361
+ socket = TCPServer.new(config[:bind], port)
362
+ config[:sockets][socket] = port
363
+ config[:fork].times{start_child(socket)}
364
+ end
365
+ end
366
+
367
+ # Remove the pid file and exit
368
+ def supervisor_exit
369
+ File.delete(config[:pidfile]) rescue nil
370
+ exit
371
+ end
372
+
373
+ # Do the final setup of the supervisor process, and then loop indefinitely
374
+ def supervisor_loop
375
+ $0 = "#{config[:style]}Style dir:#{Dir.pwd} supervisor"
376
+ redirect_io
377
+ supervisor_children_start
378
+ setup_supervisor_signals
379
+ loop{sleep(10) && restart_stopped_children}
380
+ end
381
+
382
+ # Restart all children of the supervisor process
383
+ def supervisor_restart_children
384
+ config[:children].keys.each do |pid|
385
+ mutex.synchronize{start_child(config[:children].delete(pid))}
386
+ sleep(config[:killtime])
387
+ kill_gently(pid)
388
+ end
389
+ end
390
+
391
+ # Set the internal shutdown signal for the supervisor process. Once this is set,
392
+ # no children will be restarted
393
+ def supervisor_shutdown
394
+ config[:shutdown] = true
395
+ end
396
+
397
+ # Start the supervisor process, detaching it from the controlling terminal
398
+ def supervisor_start
399
+ fork do
400
+ detach
401
+ File.open(config[:pidfile], 'wb'){|file| file.print(fork{supervisor_loop})}
402
+ end
403
+ end
404
+
405
+ # The command line usage of the style program
406
+ def usage
407
+ <<-END
408
+ style [option value, ...] (decrement|halt|increment|restart|start|stop)
409
+ Options:
410
+ -b, --bind IP address to bind to [127.0.0.1]
411
+ -c, --config Location of config file [config/style.yaml]
412
+ -d, --directory Working directory [.]
413
+ -f, --fork Number of listners on each port [1]
414
+ -k, --killtime Number of seconds to wait when killing each child [2]
415
+ -l, --logfile Where to redirect STDOUT and STDERR [log/style.log]
416
+ -n, --number Number of ports to which to bind [1]
417
+ -p, --port Starting port to bind to [9999]
418
+ -P, --pidfile Location of pid file [log/style.pid]
419
+ -s, --style Type of style to use [RailsMongrel]
420
+ -u, --unsupervised Whether to run unsupervised [No]
421
+ END
422
+ end
423
+ end
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: ruby-style
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.0.0
7
+ date: 2007-09-07 00:00:00 -07:00
8
+ summary: Supervised TCPServer, Yielding Listeners Easily
9
+ require_paths:
10
+ - lib
11
+ email: code@jeremyevans.net
12
+ homepage:
13
+ rubyforge_project:
14
+ description:
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Jeremy Evans
31
+ files:
32
+ - LICENSE
33
+ - README
34
+ - lib/style.rb
35
+ - lib/RailsSCGIStyle.rb
36
+ - lib/RailsMongrelStyle.rb
37
+ test_files: []
38
+
39
+ rdoc_options:
40
+ - --inline-source
41
+ - --line-numbers
42
+ extra_rdoc_files: []
43
+
44
+ executables:
45
+ - style
46
+ extensions: []
47
+
48
+ requirements: []
49
+
50
+ dependencies: []
51
+