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