scgi 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README +42 -21
  2. data/bin/scgi_ctrl +214 -110
  3. metadata +5 -4
data/README CHANGED
@@ -38,6 +38,7 @@ Subversion access is available at svn://code.jeremyevans.net/ruby-scgi/.
38
38
  help keep your site alive under heavy load.
39
39
  * Simple to configure with your web server. Even if you use clustering you'll
40
40
  be able to manage your webserver and Rails application independently.
41
+ * Supports supervisor mode for rock solid upgrades without losing connections
41
42
  * Reasonable defaults for almost everything based on user feedback.
42
43
  * Completely free code licensed under Rails's own license.
43
44
  * No external dependencies other than Ruby
@@ -60,13 +61,14 @@ have to request open ports. Sorry, no UNIX Domain sockets in SCGI.
60
61
 
61
62
  == Comparison With WEBrick
62
63
 
63
- In theory WEBrick should be able to run just as fast as SRR. They are both
64
- written in pure Ruby. They both do similar processing (although WEBrick's are
65
- a little more complicated). They both return about the same amount of data.
64
+ In theory WEBrick should be able to run just as fast as ruby-scgi. They are
65
+ both written in pure Ruby. They both do similar processing (although WEBrick's
66
+ are a little more complicated). They both return about the same amount of
67
+ data.
66
68
 
67
- In practice WEBrick in production mode runs much slower than SRR in production
68
- mode. The (dis)advantage (depending on your point of view) is that you have to
69
- manage your webserver differently than you manage your application.
69
+ In practice WEBrick in production mode runs much slower than ruby-scgi in
70
+ production mode. The (dis)advantage (depending on your point of view) is that
71
+ you have to manage your webserver differently than you manage your application.
70
72
 
71
73
  == Comparison With CGI
72
74
 
@@ -76,10 +78,10 @@ framework is loaded. This is very slow, but it's easy to install.
76
78
  An alternative is to use the cgi2scgi program distributed with the SCGI source
77
79
  available from http://www.mems-exchange.org/software/scgi/ along with the
78
80
  Apache modules. This program basically is a small little C program that runs
79
- quickly as a CGI, but passes it's requests to your SRR backend. It's not all
80
- that fast, but if you're stuck with cgi-bin only access then this might be just
81
- the way to go. Since SCGI runs over TCP/IP you can even host your SRR on a
82
- totally different machine with this.
81
+ quickly as a CGI, but passes it's requests to your ruby-scgi backend. It's not
82
+ all that fast, but if you're stuck with cgi-bin only access then this might be
83
+ just the way to go. Since SCGI runs over TCP/IP you can even host your
84
+ ruby-scgi on a totally different machine with this.
83
85
 
84
86
  == Running and Configuration
85
87
 
@@ -93,7 +95,7 @@ To stop the application:
93
95
 
94
96
  To restart the application:
95
97
 
96
- scgi_ctrl -d /path/to/rails/app start # start actually restarts
98
+ scgi_ctrl -d /path/to/rails/app restart
97
99
 
98
100
  Note that restarting/stopping is controlled by a pid file (the location is
99
101
  configurable). If the pidfile exists, it is read and the pids in it are
@@ -110,12 +112,14 @@ scgi_ctrl [option=value, ...] (start|stop)
110
112
  -d, --directory Working directory [.]
111
113
  -e, --environment Environment (for Rails) [production]
112
114
  -f, --fork Number of listners on each port [1]
115
+ -k, --killtime Number of seconds to wait when killing children [2]
113
116
  -l, --logfile Location of log file [log/scgi.log]
114
117
  -m, --maxconns Maximum number of concurrent users [2**30-1]
115
118
  -n, --number Number of ports to bind to [1]
116
119
  -p, --port Starting port to bind to [9999]
117
120
  -P, --pidfile Location of pid file [log/scgi.pid]
118
121
  -r, --processor Type of processor to use [Rails]
122
+ -s, --supervise Whether to use a supervisor [No]
119
123
 
120
124
  Note that the -d (--directory) option changes the working directory of the
121
125
  process, so the -c, -l, and -P options are relative to that.
@@ -160,6 +164,12 @@ Here's a longer explanation of the options:
160
164
  it is not stable, switch to multiple port clustering. It is possible to use
161
165
  both as once.
162
166
 
167
+ -k, --killtime Number of seconds to wait when killing children [2]
168
+
169
+ This sets the time that ruby-scgi will wait when stopping or restarting
170
+ child processes. The time can actually be twice as long as this, if the
171
+ child processes are not shutting down cleanly.
172
+
163
173
  -l, --logfile Location of log file [log/scgi.log]
164
174
 
165
175
  This is the location of the log file, relative to the working directory.
@@ -201,14 +211,29 @@ Here's a longer explanation of the options:
201
211
  path (the RUBYLIB environment variable). See RailsSCGIProcessor.rb for an
202
212
  example.
203
213
 
214
+ -s, --supervise Whether to use a supervisor [No]
215
+
216
+ This starts a supervisor process in addition to the worker processes. The
217
+ supervisor process can then add and remove children using the same SCGI
218
+ listening socket, which means that there will be no downtime when you have
219
+ to upgrade your application. Using a supervisor restricts some of the
220
+ variables that will be updated when you use restart. You can change the
221
+ environment, fork, killtime, logfile, maxconns, and processor variables.
222
+ Note that if you use a supervisor, you should specify those variables in
223
+ the config file and not on the command line, otherwise it won't be
224
+ possible to update them. This is the only option that does not take an
225
+ argument on the command line. To set it in the config file, see below.
226
+
204
227
  Each of the options can also be specified in the config file as a symbol. An
205
228
  example config file would be:
206
229
 
207
230
  ---
208
231
  :port: 4000
209
232
  :fork: 3
233
+ :supervise: true
210
234
 
211
- This sets up a single-port cluster on port 4000 with 3 listening processes.
235
+ This sets up a supervised single-port cluster on port 4000 with 3 listening
236
+ processes.
212
237
 
213
238
  == Example configurations
214
239
 
@@ -301,9 +326,9 @@ main attack vectors you should be aware of when running this:
301
326
  * scgi_ctrl is fully configurable on the command line
302
327
  * Clustering and processing are now built into scgi_ctrl
303
328
  * DRb, Win32, and throttling are no longer supported
304
- * Soft reconfiguration is no longer supported (no SIGUSR1)
305
- * Restarting via SIGHUP is no longer supported (SIGHUP is the same as SIGINT)
306
- * The only commands available to scgi_ctrl are start and stop
329
+ * Soft reconfiguration has changed (no SIGUSR1)
330
+ * Restarting via SIGHUP is only supported in supervise mode
331
+ * The only commands available to scgi_ctrl are start, stop, and restart
307
332
 
308
333
  == FAQ
309
334
 
@@ -323,9 +348,5 @@ A: I haven't tried. If you have luck, let me know.
323
348
  Q: Is there an easy way to reload? I don't want to take the whole thing down
324
349
  just to deploy new code.
325
350
 
326
- A: scgi_ctrl always uses SIGINT to stop processes, which allows existing
327
- clients to finish. Restarting may have problems if a lot of clients are
328
- connected and the processes can't shutdown before the new listening socket is
329
- bound. This hasn't been a problem for me yet, but it is a possible race
330
- condition. In case it affects you, you may have to run scgi_ctrl start again
331
- if you get an error binding to a port.
351
+ A: Use supervise mode, which ensures that no connections will be lost when
352
+ updating your apps.
@@ -1,132 +1,236 @@
1
1
  #!/usr/local/bin/ruby
2
2
  require 'getoptlong'
3
3
  require 'yaml'
4
+ require 'socket'
4
5
  SCGI_DEFAULT_CONFIG = {:pidfile=>'log/scgi.pid', :number=>1, :port=>9999,
5
6
  :processor=>'Rails', :fork=>1, :logfile=>'log/scgi.log', :maxconns=>2**30-1,
6
- :bind=>'127.0.0.1', :command=>''}
7
+ :bind=>'127.0.0.1', :cliconfig=>{}, :killtime=>2, :config=>'config/scgi.yaml'}
7
8
  SCGI_CONFIG = {}
8
9
 
9
- def config_file_options(filename)
10
- begin
11
- config = YAML.load(File.read(filename))
12
- config.is_a?(Hash) ? config : Hash.new
13
- rescue
14
- Hash.new
15
- end
16
- end
10
+ module SCGI
11
+ class Starter
12
+ def initialize
13
+ super
14
+ parse_options
15
+ end
16
+
17
+ def config_file_options(filename)
18
+ begin
19
+ config = YAML.load(File.read(filename))
20
+ config.is_a?(Hash) ? config : Hash.new
21
+ rescue
22
+ Hash.new
23
+ end
24
+ end
17
25
 
18
- def get_processor(string)
19
- raise NameError, "#{string} is not a valid processor!" unless string =~ /\A[A-Z][A-ZA-z]*\z/
20
- processor = "#{string}SCGIProcessor"
21
- require processor
22
- eval(processor)
23
- end
26
+ def get_processor(string)
27
+ raise NameError, "#{string} is not a valid processor!" unless string =~ /\A[A-Z][A-ZA-z]*\z/
28
+ processor = "#{string}SCGIProcessor"
29
+ require processor
30
+ eval(processor)
31
+ end
32
+
33
+ def kill_children_forcefully(pids)
34
+ kill_pids('TERM', *pids)
35
+ sleep(SCGI_CONFIG[:killtime])
36
+ kill_pids('KILL', *pids)
37
+ end
38
+
39
+ def kill_children_gently(pids)
40
+ kill_pids('INT', *pids)
41
+ sleep(SCGI_CONFIG[:killtime])
42
+ sleep(SCGI_CONFIG[:killtime]) if kill_pids('TERM', *pids)
43
+ kill_pids('KILL', *pids)
44
+ end
24
45
 
25
- def parse_options
26
- config = {:config => 'config/scgi.yaml'}
27
- opts = GetoptLong.new(
28
- [ '--bind', '-b', GetoptLong::REQUIRED_ARGUMENT ],
29
- [ '--config', '-c', GetoptLong::REQUIRED_ARGUMENT ],
30
- [ '--directory', '-d', GetoptLong::REQUIRED_ARGUMENT ],
31
- [ '--environment', '-e', GetoptLong::REQUIRED_ARGUMENT ],
32
- [ '--fork', '-f', GetoptLong::REQUIRED_ARGUMENT ],
33
- [ '--logfile', '-l', GetoptLong::REQUIRED_ARGUMENT ],
34
- [ '--maxconns', '-m', GetoptLong::REQUIRED_ARGUMENT ],
35
- [ '--number', '-n', GetoptLong::REQUIRED_ARGUMENT ],
36
- [ '--port', '-p', GetoptLong::REQUIRED_ARGUMENT ],
37
- [ '--pidfile', '-P', GetoptLong::REQUIRED_ARGUMENT ],
38
- [ '--processor', '-r', GetoptLong::REQUIRED_ARGUMENT ]
39
- )
40
- opts.each do |opt, arg|
41
- case opt
42
- when '--bind'
43
- config[:bind] = arg
44
- when '--config'
45
- config[:config] = arg
46
- when '--directory'
47
- Dir.chdir(arg)
48
- when '--environment'
49
- config[:environment] = arg
50
- when '--fork'
51
- config[:fork] = arg.to_i
52
- when '--logfile'
53
- config[:logile] = arg
54
- when '--maxconns'
55
- config[:maxconns] = arg.to_i
56
- when '--number'
57
- config[:number] = arg.to_i
58
- when '--port'
59
- config[:port] = arg.to_i
60
- when '--pidfile'
61
- config[:pidfile] = arg
62
- when '--processor'
63
- config[:processor] = arg
46
+ def kill_pids(signal, *pids)
47
+ Process.kill(signal, *pids) rescue nil
64
48
  end
65
- end
66
-
67
- # Configuration Precedence: Command Line > Config File > Default
68
- SCGI_CONFIG.merge!(SCGI_DEFAULT_CONFIG)
69
- SCGI_CONFIG.merge!(config_file_options(config[:config]))
70
- SCGI_CONFIG.merge!(config)
71
- end
72
49
 
73
- def process(command)
74
- case command
75
- when 'start'
76
- stop
77
- start
78
- when 'stop'
79
- stop
80
- else
81
- puts usage
82
- end
83
- end
50
+ def parse_options
51
+ cliconfig = {}
52
+ opts = GetoptLong.new(
53
+ [ '--bind', '-b', GetoptLong::REQUIRED_ARGUMENT ],
54
+ [ '--config', '-c', GetoptLong::REQUIRED_ARGUMENT ],
55
+ [ '--directory', '-d', GetoptLong::REQUIRED_ARGUMENT ],
56
+ [ '--environment', '-e', GetoptLong::REQUIRED_ARGUMENT ],
57
+ [ '--fork', '-f', GetoptLong::REQUIRED_ARGUMENT ],
58
+ [ '--killtime', '-k', GetoptLong::REQUIRED_ARGUMENT ],
59
+ [ '--logfile', '-l', GetoptLong::REQUIRED_ARGUMENT ],
60
+ [ '--maxconns', '-m', GetoptLong::REQUIRED_ARGUMENT ],
61
+ [ '--number', '-n', GetoptLong::REQUIRED_ARGUMENT ],
62
+ [ '--port', '-p', GetoptLong::REQUIRED_ARGUMENT ],
63
+ [ '--pidfile', '-P', GetoptLong::REQUIRED_ARGUMENT ],
64
+ [ '--processor', '-r', GetoptLong::REQUIRED_ARGUMENT ],
65
+ [ '--supervise', '-s', GetoptLong::NO_ARGUMENT]
66
+ )
67
+ opts.each do |opt, arg|
68
+ case opt
69
+ when '--bind'
70
+ cliconfig[:bind] = arg
71
+ when '--config'
72
+ cliconfig[:config] = arg
73
+ when '--directory'
74
+ Dir.chdir(arg)
75
+ when '--environment'
76
+ cliconfig[:environment] = arg
77
+ when '--fork'
78
+ cliconfig[:fork] = arg.to_i
79
+ when '--killtime'
80
+ cliconfig[:killtime] = arg.to_i
81
+ when '--logfile'
82
+ cliconfig[:logile] = arg
83
+ when '--maxconns'
84
+ cliconfig[:maxconns] = arg.to_i
85
+ when '--number'
86
+ cliconfig[:number] = arg.to_i
87
+ when '--port'
88
+ cliconfig[:port] = arg.to_i
89
+ when '--pidfile'
90
+ cliconfig[:pidfile] = arg
91
+ when '--processor'
92
+ cliconfig[:processor] = arg
93
+ when '--supervise'
94
+ cliconfig[:supervise] = true
95
+ end
96
+ end
97
+
98
+ # Configuration Precedence: Command Line > Config File > Default
99
+ SCGI_CONFIG.merge!(SCGI_DEFAULT_CONFIG)
100
+ SCGI_CONFIG.merge!(config_file_options(cliconfig[:config] || SCGI_DEFAULT_CONFIG[:config]))
101
+ SCGI_CONFIG.merge!(cliconfig)
102
+ SCGI_CONFIG[:cliconfig] = cliconfig
103
+ end
104
+
105
+ def process(command)
106
+ return process_supervisor(command) if SCGI_CONFIG[:supervise]
107
+ case command
108
+ when 'restart'
109
+ stop
110
+ start
111
+ when 'start'
112
+ start
113
+ when 'stop'
114
+ stop
115
+ else
116
+ puts usage
117
+ end
118
+ end
119
+
120
+ def process_supervisor(command)
121
+ case command
122
+ when 'restart'
123
+ supervisor_restart
124
+ when 'start'
125
+ supervisor_start
126
+ when 'stop'
127
+ supervisor_stop
128
+ else
129
+ puts usage
130
+ end
131
+ end
132
+
133
+ def reload_config
134
+ SCGI_CONFIG.merge!(config_file_options(SCGI_CONFIG[:config]))
135
+ SCGI_CONFIG.merge!(SCGI_CONFIG[:cliconfig])
136
+ end
84
137
 
85
- def start
86
- processor = get_processor(SCGI_CONFIG[:processor]).new(SCGI_CONFIG)
87
- pids = []
88
- SCGI_CONFIG[:number].times do |i|
89
- socket = TCPServer.new(SCGI_CONFIG[:bind], port = SCGI_CONFIG[:port]+i)
90
- SCGI_CONFIG[:fork].times do
91
- if pid = fork
92
- pids << pid
93
- else
138
+ def start_child(socket, port)
139
+ fork do
94
140
  $0 = "scgi-#{SCGI_CONFIG[:processor]} dir:#{Dir.pwd} port:#{port}"
95
- return processor.listen(socket)
141
+ get_processor(SCGI_CONFIG[:processor]).new(SCGI_CONFIG).listen(socket)
142
+ end
143
+ end
144
+
145
+ def start
146
+ pids = []
147
+ SCGI_CONFIG[:number].times do |i|
148
+ socket = TCPServer.new(SCGI_CONFIG[:bind], port = SCGI_CONFIG[:port]+i)
149
+ SCGI_CONFIG[:fork].times do
150
+ pids << start_child(socket, port)
151
+ end
152
+ end
153
+ File.open(SCGI_CONFIG[:pidfile], 'wb'){|file| file.print("#{pids.join(' ')}")}
154
+ end
155
+
156
+ def stop
157
+ if File.file?(SCGI_CONFIG[:pidfile])
158
+ pids = nil
159
+ File.open(SCGI_CONFIG[:pidfile], 'rb'){|f| pids = f.read.split.collect{|x| x.to_i if x.to_i > 0}.compact}
160
+ if pids.length > 0
161
+ kill_children_gently(pids)
162
+ File.delete(SCGI_CONFIG[:pidfile])
163
+ end
96
164
  end
97
165
  end
98
- end
99
- File.open(SCGI_CONFIG[:pidfile], 'wb'){|file| file.print("#{pids.join(' ')}")}
100
- end
101
166
 
102
- def stop
103
- if File.file?(SCGI_CONFIG[:pidfile])
104
- pids = nil
105
- File.open(SCGI_CONFIG[:pidfile], 'rb'){|f| pids = f.read.split.collect{|x| x.to_i if x.to_i > 0}.compact}
106
- if pids.length > 0
107
- Process.kill("INT", *pids) rescue return nil
167
+ def supervisor_exit
108
168
  File.delete(SCGI_CONFIG[:pidfile])
169
+ exit
170
+ end
171
+
172
+ def supervisor_loop
173
+ $0 = "scgi-#{SCGI_CONFIG[:processor]} dir:#{Dir.pwd} supervisor"
174
+ trap("HUP") { reload_config; supervisor_restart_children }
175
+ trap("TERM") { kill_children_forcefully(SCGI_CONFIG[:pids].values.flatten); supervisor_exit }
176
+ trap("INT") { kill_children_gently(SCGI_CONFIG[:pids].values.flatten); supervisor_exit }
177
+ loop{sleep(60)}
178
+ end
179
+
180
+ def supervisor_restart
181
+ kill_pids("HUP", File.read(SCGI_CONFIG[:pidfile]).to_i)
182
+ end
183
+
184
+ def supervisor_restart_children
185
+ new_pids = {}
186
+ SCGI_CONFIG[:pids].each do |port, pids|
187
+ SCGI_CONFIG[:fork].times do
188
+ (new_pids[port] ||= []) << start_child(SCGI_CONFIG[:sockets][port], port)
189
+ end
190
+ kill_children_gently(pids)
191
+ pids.each{|pid| Process.detach(pid)}
192
+ end
193
+ SCGI_CONFIG[:pids] = new_pids
194
+ end
195
+
196
+ def supervisor_start
197
+ SCGI_CONFIG[:pids] = {}
198
+ SCGI_CONFIG[:sockets] = {}
199
+ SCGI_CONFIG[:number].times do |i|
200
+ port = SCGI_CONFIG[:port]+i
201
+ SCGI_CONFIG[:sockets][port] = TCPServer.new(SCGI_CONFIG[:bind], port)
202
+ SCGI_CONFIG[:fork].times do
203
+ (SCGI_CONFIG[:pids][port] ||= []) << start_child(SCGI_CONFIG[:sockets][port], port)
204
+ end
205
+ end
206
+ File.open(SCGI_CONFIG[:pidfile], 'wb'){|file| file.print(fork{supervisor_loop})}
207
+ end
208
+
209
+ def supervisor_stop
210
+ kill_pids("INT", File.read(SCGI_CONFIG[:pidfile]).to_i)
211
+ end
212
+
213
+ def usage
214
+ <<-END
215
+ scgi_ctrl [option value, ...] (restart|start|stop)
216
+ Options:
217
+ -b, --bind IP address to bind to [127.0.0.1]
218
+ -c, --config Location of config file [config/scgi.yaml]
219
+ -d, --directory Working directory [.]
220
+ -e, --environment Environment (for Rails) [production]
221
+ -f, --fork Number of listners on each port [1]
222
+ -k, --killtime Number of seconds to wait when killing children [2]
223
+ -l, --logfile Location of log file [log/scgi.log]
224
+ -m, --maxconns Maximum number of concurrent users [2**30-1]
225
+ -n, --number Number of ports to bind to [1]
226
+ -p, --port Starting port to bind to [9999]
227
+ -P, --pidfile Location of pid file [log/scgi.pid]
228
+ -r, --processor Type of processor to use [Rails]
229
+ -s, --supervise Whether to use a supervisor [No]
230
+ END
109
231
  end
110
232
  end
111
233
  end
112
234
 
113
- def usage
114
- <<-END
115
- scgi_ctrl [option=value, ...] (start|stop)
116
- Options:
117
- -b, --bind IP address to bind to [127.0.0.1]
118
- -c, --config Location of config file [config/scgi.yaml]
119
- -d, --directory Working directory [.]
120
- -e, --environment Environment (for Rails) [production]
121
- -f, --fork Number of listners on each port [1]
122
- -l, --logfile Location of log file [log/scgi.log]
123
- -m, --maxconns Maximum number of concurrent users [2**30-1]
124
- -n, --number Number of ports to bind to [1]
125
- -p, --port Starting port to bind to [9999]
126
- -P, --pidfile Location of pid file [log/scgi.pid]
127
- -r, --processor Type of processor to use [Rails]
128
- END
129
- end
235
+ SCGI::Starter.new.process(ARGV[0])
130
236
 
131
- parse_options
132
- process(ARGV[0])
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: scgi
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.6.0
7
- date: 2007-08-13 00:00:00 -07:00
6
+ version: 0.7.0
7
+ date: 2007-08-15 00:00:00 -07:00
8
8
  summary: Simple support for using SCGI in ruby apps, such as Rails
9
9
  require_paths:
10
10
  - lib
@@ -35,8 +35,9 @@ files:
35
35
  - lib/RailsSCGIProcessor.rb
36
36
  test_files: []
37
37
 
38
- rdoc_options: []
39
-
38
+ rdoc_options:
39
+ - --inline-source
40
+ - --line-numbers
40
41
  extra_rdoc_files: []
41
42
 
42
43
  executables: