Skip to content

Commit

Permalink
Close listen socket(s) on QUIT
Browse files Browse the repository at this point in the history
* Use a new condvar for listening socket(s) to support two phase-shutdown where
  the listening socket is closed while existing connections are handled
* Expose shutdown/wait accessors for runners who wish to manage lifecycle
  themselves
* Add a test for shutdown behavior
  • Loading branch information
athomason committed Oct 17, 2011
1 parent 54f43e7 commit 59ec4e0
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 3 deletions.
26 changes: 23 additions & 3 deletions lib/Twiggy/Server.pm
Expand Up @@ -52,14 +52,22 @@ sub register_service {

$self->start_listen($app);

$self->{exit_guard} = AE::cv {
$self->{listen_guard} = AE::cv {
# Make sure that we are not listening on a socket anymore, while
# other events are being flushed
delete $self->{listen_guards};
};
$self->{listen_guard}->begin;

$self->{exit_guard} = AE::cv;
$self->{exit_guard}->begin;
}

sub exit_guard {
my $self = shift;
return $self->{exit_guard};
}

sub _create_tcp_server {
my ( $self, $listen, $app ) = @_;

Expand Down Expand Up @@ -579,12 +587,24 @@ sub _write_real_fh {
}
}

sub shutdown {
my $self = shift;
$self->{listen_guard}->end;
$self->{exit_guard}->end;
}

sub wait {
my $self = shift;
$self->{listen_guard}->recv;
$self->{exit_guard}->recv;
}

sub run {
my $self = shift;
$self->register_service(@_);

my $w; $w = AE::signal QUIT => sub { $self->{exit_guard}->end; undef $w };
$self->{exit_guard}->recv;
my $w; $w = AE::signal QUIT => sub { $self->shutdown; undef $w };
$self->wait;
}

package Twiggy::Writer;
Expand Down
54 changes: 54 additions & 0 deletions t/shutdown.t
@@ -0,0 +1,54 @@
use strict;
use warnings;
use AnyEvent;
use Test::More qw(no_diag);
use Test::TCP;
use IO::Socket::INET;
use Plack::Loader;

my $server = Test::TCP->new(code => sub {
my $port = shift;
my $server = Plack::Loader->load('Twiggy', port => $port, host => '127.0.0.1');

$server->run(sub {
return [
200,
[ 'Content-Type' => 'text/plain', ],
[ "ok\n" ],
];
});
exit 0;
});

my $port = $server->port;

my $sock1 = IO::Socket::INET->new(
Proto => 'tcp',
PeerAddr => '127.0.0.1',
PeerPort => $port,
);
ok($sock1, "initial connection succeeds");

kill QUIT => $server->pid;

# soon after telling server to quit, it should no longer be possible to
# connect. keeping attempting to fail to connect for up to a few seconds
my $sock2;
for (1 .. 30) {
$sock2 = IO::Socket::INET->new(
Proto => 'tcp',
PeerAddr => '127.0.0.1',
PeerPort => $port,
);
last if !$sock2;
select undef, undef, undef, 0.1;
}
ok(!$sock2, "post-shutdown connection fails");

# ... but existing connection should still work
$sock1->print("GET / HTTP/1.0\n\n");
my $res = join '', <$sock1>;
ok(length $res, 'got some data');
like($res, qr{^HTTP/1.0 200 OK}, 'got a 200 response');

done_testing();

0 comments on commit 59ec4e0

Please sign in to comment.