Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
77 lines (61 sloc) 2.93 KB
= Handling of SIGUSR1
When you request a 'graceful' restart of Apache, mod_fastcgi sends a SIGUSR1
to each of the fastcgi worker processes. The intention is that each one
should finish the current request, and then exit, at which point Apache will
restart it. Of course, if the worker isn't doing anything, it should die
immediately.
This is implemented in the fcgi C library as follows:
- a signal handler is installed for SIGUSR1, which just sets a flag
(shutdownPending) and returns
- fcgi sits inside an accept() call waiting for a new request
- if this accept() call terminates with EINTR, and this flag is set, then
it returns with no request
Unfortunately, Ruby defeats this mechanism in at least two ways:
1. Ruby installs its own signal handlers for a host of common signals,
including USR1. The fcgi library will not install its own handler if it
detects that a handler has already been set (i.e. the current handler is not
SIG_DFL)
2. When Ruby installs its own signal handlers, it does so with SA_RESTART
set. This means that the accept() call does not terminate with EINTR; it is
restarted automatically by the OS.
When a signal comes in during the accept(), Ruby's own handler does nothing
except store it in a queue to be processed later. It is only when the
accept() call completes, i.e. when a genuine new request comes in, that Ruby
takes action. Unfortunately it's too late by then, and if that
already-accepted request is not honoured, a 500 Internal Error will be
returned to the client.
The simplest solution to this would be to remove Ruby's SIGUSR1 handler
before initialising the FastCGI library.
However, a cleaner solution is to call rb_thread_select before going into
FastCGI's accept loop. If a signal happens during the select, it can be
handled using Ruby's normal mechanisms. This also gives a very useful
side-benefit, which is that FCGI::accept no longer blocks out other Ruby
threads. The program below demonstrates this problem; its background logging
thread is supposed to write a message every 10 seconds, but under older
versions of ruby-fcgi it does not do so if it is waiting for a new request.
#!/usr/local/bin/ruby
require "fcgi"
Thread.new do
f = File.new("/tmp/fcgi.log","a")
f.sync=true
while true
f.puts "#{Time.now.to_s} pid #{$$}"
sleep 10
end
end
FCGI.each_cgi {|cgi|
name = cgi['name'][0]
puts cgi.header
puts "Hey! You are #{name} " if name
puts "Connecting from #{cgi.remote_addr}"
}
Having protected the accept() with a ruby select(), you can then handle
signals as follows:
- call FCGI::accept (it will raise an exception if USR1 is called)
- install a USR1 handler
- process the request
- remove the USR1 handler
The USR1 handler should set a flag to note if a USR1 signal came in while
the request was being processed; you terminate the loop if it was set. The
overall effect is that USR1 will cause the process to terminate, but without
causing a half-completed request.