ruby-style 1.0.0

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/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
+