You are viewing a plain text version of this content. The canonical link for it is here.
Posted to modperl@perl.apache.org by Peter Valdemar Mørch <pe...@morch.com> on 2012/06/10 20:23:55 UTC

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

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

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

Posted by Peter Valdemar Mørch <pe...@morch.com>.
Thanks for your reply!

On Mon, Jun 11, 2012 at 9:16 AM, Steve Hay <St...@verosoftware.com> wrote:
> it would certainly be worth upgrading to 2.0.6 or 2.0.7 to see if that helps you.

As pr. your suggestion I've installed a fresh debian testing/wheezy
system that runs these versions:

# dpkg-query -W | grep apache2
apache2	2.2.22-6
apache2-mpm-worker	2.2.22-6
apache2-utils	2.2.22-6
apache2.2-bin	2.2.22-6
apache2.2-common	2.2.22-6
libapache2-mod-perl2	2.0.6-2
libapache2-reload-perl	0.11-2

And here the only difference I can see is that $r->rflush doesn't die
(like you write). Instead of testing for whether $r->rflush() dies one
can test for $r->connection->abort().

All the rest is the same. It it only possible to detect 'stop' after
the second print/rflush cycle as described in my original post. I'm
still hoping there is some way to detect 'stop' earlier...

Peter
-- 
Peter Valdemar Mørch
http://www.morch.com

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

Posted by Steve Hay <St...@verosoftware.com>.
Peter Valdemar Mørch wrote on 2012-06-10:
> 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_Co
> nnections - 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.
> 
[...]
> 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


I notice that you're using mod_perl-2.0.4. The behaviour of rflush() was changed in mod_perl-2.0.6 so that it no longer throws an exception on reset/aborted connections, so it would certainly be worth upgrading to 2.0.6 or 2.0.7 to see if that helps you.


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

Posted by Peter Valdemar Mørch <pe...@morch.com>.
On Mon, Jun 11, 2012 at 12:29 PM, André Warnier <aw...@ice-sa.com> wrote:
> Just my two cent, maybe just to dampen your expectations a little bit.
> This topic is probably as old as the WWW itself, and there just is no magic
> bullet here.
...
> But if your ultimate aim is to find a way where instant detection is
> guaranteed, then give it up.  It is just not possible, given how the
> Internet, and TCP/IP and HTTP work.

I was certain that normal cgis under Apache2 reacted immediately to
aborts. I was wrong ( As I discovered in my quest to devise a test
case proving you wrong... ;-) )

Ok, we're doing the best we can then. Fine. At least the next guy that
comes along will have this thread to read.

So pseudocode for the magic incantation then seems to be:

    while (! $done) {
        heavyCalculationBite();
        $r->print($msg);
        # Eval needed to support < 2.0.6 that dies if $r->connection->aborted
        eval {
            $r->rflush();
        };
        if ($r->connection->aborted) {
            # Handle user abort
            last;
        }
    }

Thank you so much for your enlightening replies!

Peter
-- 
Peter Valdemar Mørch
http://www.morch.com

Re: Perlhandler bugs when set up on the root of the domain

Posted by Torsten Förtsch <to...@gmx.net>.
On 06/12/2012 03:01 PM, David Levy wrote:
> Hi everyone,
> 
> i've start working with mod_perl and perlhandler few weeks ago in a
> folder of my domain (mydomain.com/folder/) and everything was fine.
> 
> I've recently tried to set it up on the root of my domain, but now, i've
> got an error 500 when i try to go to mydomain.com and the logs says :
> 
>  Can't locate object method "content_type" via package
> "Apache2::RequestRec" at /usr/lib/perl5/...
> 
> And if i add use Apache2::RequestRec after my package definition the
> error change from content_type to print.
> The test code is really simple :
> package test2::Rules2;
> use Apache2::Const qw(:common);
> 
> sub handler {
> my $r = shift;
> $r->content_type("text/plain");
> $r->print("mod_perl rules!\n");
> return OK;
> }
> 1;
> 
> Any idea on where it comes from?

Don't know why it *had* worked, but

  use Apache2::RequestRec ();     # pulls in content_type()
  use Apache2::RequestIO ();      # pulls in print()

is missing here.

One of the first things I did several years ago when I started with
modperl2 was to define this bash alias:

  alias mp2lookup='perl -MModPerl::MethodLookup -e print_method'

Now, if I don't know which module defines which method I can ask:

$ mp2lookup print
There is more than one class with method 'print'
try one of:
        use Apache2::Filter ();
        use Apache2::RequestIO ();

Torsten

Perlhandler bugs when set up on the root of the domain

Posted by David Levy <da...@gmail.com>.
Hi everyone,

i've start working with mod_perl and perlhandler few weeks ago in a 
folder of my domain (mydomain.com/folder/) and everything was fine.

I've recently tried to set it up on the root of my domain, but now, i've 
got an error 500 when i try to go to mydomain.com and the logs says :

  Can't locate object method "content_type" via package 
"Apache2::RequestRec" at /usr/lib/perl5/...

And if i add use Apache2::RequestRec after my package definition the 
error change from content_type to print.
The test code is really simple :
package test2::Rules2;
use Apache2::Const qw(:common);

sub handler {
my $r = shift;
$r->content_type("text/plain");
$r->print("mod_perl rules!\n");
return OK;
}
1;

Any idea on where it comes from?

Thanks
David


unsubscribe

Posted by Stuart Young <st...@goba.mobi>.
On Jun 11, 2012, at 5:33 PM, Peter Valdemar Mørch wrote:

> On Mon, Jun 11, 2012 at 6:39 PM, Randolf Richardson <ra...@modperl.pl> wrote:
>> Have you've already experimented with the Timeout directive?  If
>> not, then I suggest you start with this directive before using the
>> KeepAlive and KeepAliveTimeout directives:
>> 
>>                http://httpd.apache.org/docs/current/mod/core.html#timeout
> 
> It is my impression that mod_perl doesn't honor the Timeout directive.
> This comes from both reading and testing. We've had to resort to
> http://perl.apache.org/docs/1.0/guide/debug.html#Handling_Server_Timeout_Cases_and_Working_with__SIG_ALRM_
> . Have I missed something? (Using TimeOut would be so much nicer!)
> 
> Peter
> -- 
> Peter Valdemar Mørch
> http://www.morch.com


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

Posted by Peter Valdemar Mørch <pe...@morch.com>.
On Mon, Jun 11, 2012 at 6:39 PM, Randolf Richardson <ra...@modperl.pl> wrote:
> Have you've already experimented with the Timeout directive?  If
> not, then I suggest you start with this directive before using the
> KeepAlive and KeepAliveTimeout directives:
>
>                http://httpd.apache.org/docs/current/mod/core.html#timeout

It is my impression that mod_perl doesn't honor the Timeout directive.
This comes from both reading and testing. We've had to resort to
http://perl.apache.org/docs/1.0/guide/debug.html#Handling_Server_Timeout_Cases_and_Working_with__SIG_ALRM_
. Have I missed something? (Using TimeOut would be so much nicer!)

Peter
-- 
Peter Valdemar Mørch
http://www.morch.com

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

Posted by Jeff Trawick <tr...@gmail.com>.
On Mon, Jun 11, 2012 at 12:39 PM, Randolf Richardson <ra...@modperl.pl> wrote:
>                http://httpd.apache.org/docs/current/mod/core.html#keepalivetimeout
>
>        Note:  The KeepAliveTimeout directive is only available starting
> with Apache HTTPd v2.3.2.

It is ancient.  2.3.2 brought the ability to specify the timeout in
milliseconds.

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

Posted by Randolf Richardson <ra...@modperl.pl>.
> Peter Valdemar Mørch wrote:
> > 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?
> 
> Hi.
> Just my two cent, maybe just to dampen your expectations a little bit.
> This topic is probably as old as the WWW itself, and there just is no magic bullet here.
> Think of the following :
> The whole FIN, ACK, RST etc.. exchange concerns /one/ TCP connection, between your browser 
> and whatever it is talking to directly.  For example, a HTTP proxy server, or a firewall.
> And then, this proxy/firewall has /another/ TCP connection with the next node in the chain 
> (which may be your webserver or still another intermediary, like a load-balancer e.g.).
> And in-between, there are buffers and a lot of piping, and delays.
> So, expecting your mod_perl script to be able to detect instantly when the user at the 
> browser half a planet away presses the "stop" button, is /never/ going to be guaranteed.
> I'm not saying that you cannot try to catch such a thing as early as possible.
> But if your ultimate aim is to find a way where instant detection is guaranteed, then give 
> it up.  It is just not possible, given how the Internet, and TCP/IP and HTTP work.

	In addition to that, there is variation among web browser and smart 
phone vendors in the way they tear down sockets.  Some may close the 
socket immediately (a good practice in my opinion), while others may 
just let the resource get garbage collected or even have an unknown 
bug that results in a temporary or permanent resource leak on the 
client-side.

	The same problems can occur with proxy servers, and for localized 
end-user products that intercept all traffic (e.g., for the purpose 
of scanning for viruses or other suspicious activity) there can be 
problems as well.  I've seen big problems with long timeouts with 
some of these client-side products where the server has lingering 
connections after the QUIT phase with POP3 connections, and removing 
the firewalling software resolved the problem (but this isn't a 
solution that helps you because many users insist on running these 
programs, are working at companies that require it, etc.).

	Making sure you have adequate resources to handle the load on the 
server-side is likely less time-consuming than figuring out how every 
web browser does (and will) handle the user pressing the "Stop" 
button behind-the-scenes, although the KeepAlive and KeepAliveTImeout 
directives may be helpful to you:

		http://httpd.apache.org/docs/current/mod/core.html#keepalive
		http://httpd.apache.org/docs/current/mod/core.html#keepalivetimeout

	Note:  The KeepAliveTimeout directive is only available starting 
with Apache HTTPd v2.3.2.

	Have you've already experimented with the Timeout directive?  If 
not, then I suggest you start with this directive before using the 
KeepAlive and KeepAliveTimeout directives:

		http://httpd.apache.org/docs/current/mod/core.html#timeout

Randolf Richardson - randolf@inter-corporate.com
Inter-Corporate Computer & Network Services, Inc.
Beautiful British Columbia, Canada
http://www.inter-corporate.com/



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

Posted by André Warnier <aw...@ice-sa.com>.
Peter Valdemar Mørch wrote:
> 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?
> 

Hi.
Just my two cent, maybe just to dampen your expectations a little bit.
This topic is probably as old as the WWW itself, and there just is no magic bullet here.
Think of the following :
The whole FIN, ACK, RST etc.. exchange concerns /one/ TCP connection, between your browser 
and whatever it is talking to directly.  For example, a HTTP proxy server, or a firewall.
And then, this proxy/firewall has /another/ TCP connection with the next node in the chain 
(which may be your webserver or still another intermediary, like a load-balancer e.g.).
And in-between, there are buffers and a lot of piping, and delays.
So, expecting your mod_perl script to be able to detect instantly when the user at the 
browser half a planet away presses the "stop" button, is /never/ going to be guaranteed.
I'm not saying that you cannot try to catch such a thing as early as possible.
But if your ultimate aim is to find a way where instant detection is guaranteed, then give 
it up.  It is just not possible, given how the Internet, and TCP/IP and HTTP work.