User abort/stop, modperl 2 and TCP FIN / RST?

Peter Valdemar Mørch <[email protected]>
Newsgroups gmane.comp.apache.mod-perl
Message-ID <CANh5ensAiP04AKhLeb=2JgDyiwjcPi8tUYUqwdDmYxXuaD4qVg@mail.gmail.com>
Hi,

I'm trying to find out how to detect user hitting 'stop' aka 'abort'
in modperl 2. I found documentation on how it works in modperl 1 (
http://perl.apache.org/docs/1.0/guide/debug.html#Detecting_Aborted_Connections
- short version: $r->print returns success and $r->connection->aborted
tells whether user hit abort ).

However, I've tested the situation to be quite different in modperl 2:

When the user hits 'stop', the *second* $r->rflush() generates an exception
> Apache2::RequestIO::rflush: (103) Software caused connection
as long as ~ 100ms has passed between the two rflush-es.

Here is my understanding of what happens:

When the user hits 'stop' in the browser, the browser sends a TCP
packet with the FIN flag set. Apache2/modperl doesn't react to that
and $r->connection->aborted still returns false. A subsequent
$r->print and $r->rflush works fine. Apache now sends whatever was
printed to the browser. The browser now sends RST ("Hey, I really want
to kill this connection!") to Apache and after that, $r->print still
succeeds (returns true), but $r->rflush dies (because now the client
has closed the socket hard). "Second" $r->rflush really means "the
first $r->rflush after Apache received the client's 'RST' ", so if I
issue many $r->print("foo"); $r->rflush() in quick succession they all
pass. Around 100ms needs to pass between $r->rflush-es for the second
$r->rflush to fail. If tested this with Firefox and Chromium.

So: After the user hits 'stop', the second $r->rflush (requiring a
delay) generates an exception that can be used to determine that the
user has hit 'stop'.

Have I understood this correctly? Is there any way I can get Apache to
react to the first TCP FIN, and have $r->connection->aborted return
true at that point? Or otherwise detect reliably when the user has hit
'stop' in modperl 2 without having to wait an additional
server-client-server roundtrip time?

Using standard Debian squeeze packages:
> dpkg-query -W | grep apache2
apache2	2.2.16-6+squeeze7
apache2-mpm-worker	2.2.16-6+squeeze7
apache2-utils	2.2.16-6+squeeze7
apache2.2-bin	2.2.16-6+squeeze7
apache2.2-common	2.2.16-6+squeeze7
libapache2-mod-perl2	2.0.4-7
libapache2-reload-perl	0.10-2

Apache config:
<Directory /var/www/foo>
	Options +ExecCGI
        AddDefaultCharset On
	<Files flush.cgi>
		SetHandler perl-script
		PerlResponseHandler ModPerl::Registry
		PerlOptions +ParseHeaders
	</Files>
</Directory>

flush.cgi:
#!/usr/bin/perl -w
use strict;

use Apache2::ServerUtil;
use Apache2::RequestUtil;
use Apache2::RequestRec;
use Apache2::Connection;

use Time::HiRes qw(sleep);

sub doCGI {
    my $r = Apache2::RequestUtil->request;
    my $c = $r->connection();
    $r->print("Content-type: text/html\n\n");
    $r->print("Starting up $$<br>\n");
    LOOP_I: foreach my $i (0..3) {

        # User hits STOP in browser here for some $i

        foreach my $j (0..1) {
            my $printRetval = $r->print("$i / $j<br>\n");
            $r->log_error(sprintf "%d / %d pre-flush  : %s print %s",
                          $i, $j,
                          ($c->aborted ? "aborted" : "not aborted"),
                          ( $printRetval ? 'succeeded' : 'failed')
                         );

            # This always dies when $j == 1. Why 1 and not 0?
            # This is the first place we notice when the user has hit STOP in
            # the browser.
            eval {
                $r->rflush;
            };

            my $err = $@;
            $r->log_error(sprintf "%d / %d post-flush : %s flush %s",
                          $i, $j,
                          ( $c->aborted ? "aborted" : "not aborted" ),
                          ( $err ? 'failed' : 'succeeded')
                         );
            if ($err) {
                $r->log_error("exiting loops");
                last LOOP_I;
            }

            # Why sleep 0.1? 0.01 is mostly enough, but 0.001 is always too
            # little. What determines this number?
            sleep 0.1;
        }
        sleep 5;
    }
};
doCGI();

A wireshark trace of the traffic between browser and server can be
found here: http://ge.tt/6XGcPvI/v/0?c

Thanks for reading this far.

Peter
-- 
Peter Valdemar Mørch
http://www.morch.com
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.