| home / programming / perl / mod_perl / chap6 / 3 | [previous] |
|
|
When a user presses the Stop or Reload button, the current socket connection is broken (aborted). It would be nice if Apache could always immediately detect this event. Unfortunately, there is no way to tell whether the connection is still valid unless an attempt to read from or write to the connection is made.
Note that no detection technique will work if the connection to the backend mod_perl server is coming from a frontend mod_proxy (as discussed in Chapter 12). This is because mod_proxy doesn't break the connection to the backend when the user has aborted the connection.
If the reading of the request's data is completed and the code
does its processing without writing anything back to the client, the broken
connection won't be noticed. When an attempt is made to send at least one character
to the client, the broken connection will be noticed and the SIGPIPE
signal (Broken Pipe) will be sent to the process. The program can then halt
its execution and perform all its cleanup requirements.
Prior to Apache 1.3.6, SIGPIPE was
handled by Apache. Currently, Apache does not handle SIGPIPE,
but mod_perl takes care of it.
Under mod_perl, $r->print (or
just print( )) returns a true value on success
and a false value on failure. The latter usually happens when the connection
is broken.
If you want behavior similar to the old SIGPIPE
(as it was before Apache version 1.3.6), add the following configuration directive:
PerlFixupHandler Apache::SIG
When Apache's SIGPIPE handler is
used, Perl may be left in the middle of its eval( )
context, causing bizarre errors when subsequent requests are handled by that
child. When Apache::SIG is used, it installs a
different SIGPIPE handler that rewinds the context
to make sure Perl is in a normal state before the new request is served, preventing
these bizarre errors. But in general, you don't need to use Apache::SIG.
If you use Apache::SIG and you would
like to log when a request was canceled by a SIGPIPE
in your Apache access_log, you must define a custom
LogFormat in your httpd.conf.
For example:
PerlFixupHandler Apache::SIGLogFormat "%h %l %u %t \"%r\" %s %b %{SIGPIPE}e"
If the server has noticed that the request was canceled via a
SIGPIPE, the log line will end with 1.
Otherwise, it will just be a dash. For example:
127.0.0.1 - - [09/Jan/2001:10:27:15 +0100]"GET /perl/stopping_detector.pl HTTP/1.0" 200 16 1127.0.0.1 - - [09/Jan/2001:10:28:18 +0100]"GET /perl/test.pl HTTP/1.0" 200 10 -
Now let's use the knowledge we have acquired to trace the execution
of the code and watch all the events as they happen. Let's take a simple Apache::Registry
script that purposely hangs the server process, like the one in Example 6-28.
Example 6-28: stopping_detector.pl
my $r = shift;$r->send_http_header('text/plain');print "PID = $$\n";$r->rflush;while (1) {sleep 1;}
The script gets a request object $r
by shift( )ing it from the @_
argument list (passed by the handler( ) subroutine
that was created on the fly by Apache::Registry).
Then the script sends a Content-Type header telling
the client that we are going to send a plain-text response.
Next, the script prints out a single line telling us the ID of
the process that handled the request, which we need to know in order to run
the tracing utility. Then we flush Apache's STDOUT
buffer. If we don't flush the buffer, we will never see this information printed
(our output is shorter than the buffer size used for print(
), and the script intentionally hangs, so the buffer won't be auto-flushed).[3]
Then we enter an infinite while loop
that does nothing but sleep( ), emulating code
that doesn't generate any output. For example, it might be a long-running mathematical
calculation, a database query, or a search for extraterrestrial life.
Running strace -p PID, where PID is the process ID as printed on the browser, we see the following output printed every second:
rt_sigprocmask(SIG_BLOCK, [CHLD], [ ], 8) = 0rt_sigaction(SIGCHLD, NULL, {SIG_DFL}, 8) = 0rt_sigprocmask(SIG_SETMASK, [ ], NULL, 8) = 0nanosleep({1, 0}, {1, 0}) = 0time([978969822]) = 978969822time([978969822]) = 978969822
Alternatively, we can run the server in single-server mode. In single-server mode, we don't need to print the process ID, since the PID is the process of the single mod_perl process that we're running. When the process is started in the background, the shell program usually prints the PID of the process, as shown here:
panic% httpd -X &[1] 20107
Now we know what process we have to attach to with strace (or a similar utility):
panic% strace -p 20107rt_sigprocmask(SIG_BLOCK, [CHLD], [ ], 8) = 0rt_sigaction(SIGCHLD, NULL, {SIG_DFL}, 8) = 0rt_sigprocmask(SIG_SETMASK, [ ], NULL, 8) = 0nanosleep({1, 0}, {1, 0}) = 0time([978969822]) = 978969822time([978969822]) = 978969822
We see the same output as before.
Let's leave strace running and press the Stop button. Did anything change? No, the same system calls trace is printed every second, which means that Apache didn't detect the broken connection.
Now we are going to write \0
(NULL) characters to the client in an attempt to
detect the broken connection as soon as possible after the Stop button is pressed.
Since these are NULL characters, they won't be
seen in the output. Therefore, we modify the loop code in the following way:
while (1) {$r->print("\0");last if $r->connection->aborted;sleep 1;}
We add a print( ) statement to print
a NULL character, then we check whether the connection
was aborted, with the help of the $r->connection->aborted
method. If the connection is broken, we break out of the loop.
We run this script and run strace on it as before, but we see that it still doesn't work--the script doesn't stop when the Stop button is pressed.
The problem is that we aren't flushing the buffer. The NULL
characters won't be printed until the buffer is full and is autoflushed. Since
we want to try writing to the connection pipe all the time, we add an $r->rflush(
) call. Example 6-29 is a new version of the code.
Example 6-29: stopping_detector2.pl
my $r = shift;$r->send_http_header('text/plain');print "PID = $$\n";$r->rflush;while (1) {$r->print("\0");$r->rflush;last if $r->connection->aborted;sleep 1;}
After starting the strace utility on the running process and pressing the Stop button, we see the following output:
rt_sigprocmask(SIG_BLOCK, [CHLD], [ ], 8) = 0rt_sigaction(SIGCHLD, NULL, {SIG_DFL}, 8) = 0rt_sigprocmask(SIG_SETMASK, [ ], NULL, 8) = 0nanosleep({1, 0}, {1, 0}) = 0time([978970895]) = 978970895alarm(300) = 0alarm(0) = 300write(3, "\0", 1) = -1 EPIPE (Broken pipe)--- SIGPIPE (Broken pipe) ---chdir("/usr/src/httpd_perl") = 0select(4, [3], NULL, NULL, {0, 0}) = 1 (in [3], left {0, 0})time(NULL) = 978970895write(17, "127.0.0.1 - - [08/Jan/2001:19:21"..., 92) = 92gettimeofday({978970895, 554755}, NULL) = 0times({tms_utime=46, tms_stime=5, tms_cutime=0,tms_cstime=0}) = 8425400close(3) = 0rt_sigaction(SIGUSR1, {0x8099524, [ ], SA_INTERRUPT|0x4000000},{SIG_IGN}, 8) = 0alarm(0) = 0rt_sigprocmask(SIG_BLOCK, NULL, [ ], 8) = 0rt_sigaction(SIGALRM, {0x8098168, [ ], SA_RESTART|0x4000000},{0x8098168, [ ], SA_INTERRUPT|0x4000000}, 8) = 0fcntl(18, F_SETLKW, {type=F_WRLCK, whence=SEEK_SET,start=0, len=0}) = 0
Apache detects the broken pipe, as you can see from this snippet:
write(3, "\0", 1) = -1 EPIPE (Broken pipe)--- SIGPIPE (Broken pipe) ---
Then it stops the script and does all the cleanup work, such as access logging:
write(17, "127.0.0.1 - - [08/Jan/2001:19:21"..., 92) = 92
where 17 is a file descriptor of the opened access_log file.
| home / programming / perl / mod_perl / chap6 / 3 | [previous] |
Created: March 11, 2003
Revised: July 23, 2003
URL: http://webreference.com/programming/perl/mod_perl/chap6/3