ruby-style 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/README +292 -0
- data/bin/style +3 -0
- data/lib/RailsMongrelStyle.rb +58 -0
- data/lib/RailsSCGIStyle.rb +29 -0
- data/lib/style.rb +423 -0
- metadata +51 -0
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,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
|
+
|