You are viewing a plain text version of this content. The canonical link for it is here.
Posted to modperl@perl.apache.org by Randy Kobes <ra...@theoryx5.uwinnipeg.ca> on 2005/08/01 07:31:50 UTC

Re: The mod_perl protocol handler sample code have some problem!

On Sun, 31 Jul 2005, Randy Kobes wrote:

> On Sun, 31 Jul 2005, Randy Kobes wrote:
[ ... ]
> Here's a scaled-down version of the problem - I used
> commands with single letters, as my Win32 console sent a \r\n after each 
> letter.
[ ... ]
> sub handler {
>  my $c = shift;
>  $| = 1;
>  my $socket = $c->client_socket;
>  $socket->opt_set(APR::Const::SO_NONBLOCK, 0);
>
>  $socket->send("Welcome to " . __PACKAGE__ .
>                "\r\nAvailable commands: @cmds\r\n");
>
>  while (1) {
>    my $cmd;
>    next unless $cmd = getline($socket);
[ ... ]
I found that if I change that last line to
      last unless $cmd = getline($socket);
then one can interrupt the telnet session with 'CTRL ]'
and close the connection without the Apache process
consuming 100% cpu.

-- 
best regards,
randy

Re: The mod_perl protocol handler sample code have some problem!

Posted by Randy Kobes <ra...@theoryx5.uwinnipeg.ca>.
On Fri, 12 Aug 2005, Stas Bekman wrote:

> OK, I wrote a test case that reproduces the problem.
>
> If you run:
>
> perl Makefile.PL
> make test
>
> things work, but if you do:
>
> t/TEST -start
> t/TEST -run
>
> the process starts spinning in the getline() call, as $sock->recv doesn't 
> fail.
[ ... ]
> Here is a rewrite that doesn't spin. Notice that I've 
> dropped the $c-aborted check, I don't know if it's needed, 
> since recv() should have caught that anyway. But please 
> restore it if needed.

Works great on Win32, without modification - thanks!

-- 
best regards,
randy

Re: The mod_perl protocol handler sample code have some problem!

Posted by Stas Bekman <st...@stason.org>.
Randy Kobes wrote:
> On Sun, 31 Jul 2005, Randy Kobes wrote:
> 
>> On Sun, 31 Jul 2005, Randy Kobes wrote:
> 
> [ ... ]
> 
>> Here's a scaled-down version of the problem - I used
>> commands with single letters, as my Win32 console sent a \r\n after 
>> each letter.
> 
> [ ... ]
> 
>> sub handler {
>>  my $c = shift;
>>  $| = 1;
>>  my $socket = $c->client_socket;
>>  $socket->opt_set(APR::Const::SO_NONBLOCK, 0);
>>
>>  $socket->send("Welcome to " . __PACKAGE__ .
>>                "\r\nAvailable commands: @cmds\r\n");
>>
>>  while (1) {
>>    my $cmd;
>>    next unless $cmd = getline($socket);
> 
> [ ... ]
> I found that if I change that last line to
>      last unless $cmd = getline($socket);
> then one can interrupt the telnet session with 'CTRL ]'
> and close the connection without the Apache process
> consuming 100% cpu.

OK, I wrote a test case that reproduces the problem.

If you run:

perl Makefile.PL
make test

things work, but if you do:

t/TEST -start
t/TEST -run

the process starts spinning in the getline() call, as $sock->recv doesn't 
fail. This is our "bug", well it was supposed to be a feature as the 
internals are going as:

     rc = apr_socket_recv(socket, SvPVX(buffer), &len);

     if (!(rc == APR_SUCCESS || rc == APR_EOF)) {
         modperl_croak(aTHX_ rc, "APR::Socket::recv");
     }

So if recv has returned EOF, the call was always successful. So basically 
we eat the EOF event and user tries to read again and again.

I think as long as we are in the blocking mode that approach is fine, i.e.:

- if $sock->recv was successful:
    * if you received some string, you are good
    * if you received nothing, that means you've got EOF
- otherwise handle the error

and that getline code doesn't seem to do the right thing anyway, since it 
may return an error code but the caller expects a string.

Here is a rewrite that doesn't spin. Notice that I've dropped the 
$c-aborted check, I don't know if it's needed, since recv() should have 
caught that anyway. But please restore it if needed.

package MyTest::Protocol;

use strict;
use warnings FATAL => 'all';

use Apache2::Connection ();
use APR::Socket ();
use APR::Status ();

use Apache2::Const -compile => qw(OK DONE DECLINED);
use APR::Const     -compile => qw(SO_NONBLOCK);

my @cmds = qw(d q);
my %commands = map { $_, \&{$_} } @cmds;

sub handler {
     my $c = shift;
     $| = 1;
     my $socket = $c->client_socket;

     $socket->opt_set(APR::Const::SO_NONBLOCK, 0);

     $socket->send("Welcome to " . __PACKAGE__ .
                   "\r\nAvailable commands: @cmds\r\n");

     while (1) {
         my $cmd;
         eval {
             $cmd = getline($socket);
         };
         if ($@) {
             return Apache2::Const::DONE if APR::Status::is_ECONNABORTED($@);
         }

         last unless defined $cmd; # EOF

         next unless length $cmd;  # new line with no commands

         warn "READ: $cmd\n";

         if (my $sub = $commands{$cmd}) {
             last unless $sub->($socket) == Apache2::Const::OK;
         } else {
             $socket->send("Commands: @cmds\r\n");
         }
     }

     return Apache2::Const::OK;
}

# returns either of:
# - undef on EOF
# - CRLF stripped line on normal read
#
# may throw an exception (via recv())
sub getline {
     my $socket = shift;
     $socket->recv(my $line, 1024);
     return undef unless length $line;
     $line =~ s/[\r\n]*$//;
     return $line;
}

sub d {
     my $socket = shift;
     $socket->send(scalar(localtime) . "\r\n");
     return Apache2::Const::OK;
}

sub q { Apache2::Const::DONE }

1;
__END__

<NoAutoConfig>
<VirtualHost MyTest::Protocol>
     PerlProcessConnectionHandler MyTest::Protocol
     <Location MyTest__Protocol>
         Order Deny,Allow
         Allow from all
     </Location>
</VirtualHost>
</NoAutoConfig>


-- 
__________________________________________________________________
Stas Bekman            JAm_pH ------> Just Another mod_perl Hacker
http://stason.org/     mod_perl Guide ---> http://perl.apache.org
mailto:stas@stason.org http://use.perl.org http://apacheweek.com
http://modperlbook.org http://apache.org   http://ticketmaster.com