Guide Home
POD Home

Soft Stop/Restart

The idea behind a soft stop is that rather than just killing the server process, you first make sure existing connections are dealt with in a polite way consistent with the TCP and HTTP protocols.

There are a number of different ways of approaching this. They all revolve around first shutting the server listen socket via $sloop->stopListening. You can't undo this; subsequently, until a new server is started, TCP connections on $sloop->{port} will be refused.

This leaves existing connections, which will continue to be serviced. The ideal pattern here is that perhaps a current request gets processed, then the connection is closed by the client or else times out (see timeout in Sloop::Server SYNOPSIS). However, it is in theory possible for clients to indefinitely pipeline requests. To prevent this, you may want to change the nature of the reply sent to the client.

A simple way of implementing an appropriate mechanism is via a signal handler (if you are not familiar with perl's global %SIG hash, see perlipc).

# Will respond to, e.g., `kill -s USR1 ...`. 
$SIG{USR1} = sub {
    $Sloop->stopListening();
    # Only necessary if you are using virtual hosts.
    $Sloop->{virtualHosts} = undef;
    $Sloop->{handlers} = {
        '/' => sub {
            my $client = shift;
            # Could use $client->generic(503) here instead.
            $client->reply (
                \"<h1>Temporarily out of service</h1>",
                status => 503,
                # The Retry-After parameter is in seconds. Clients are
                # not obligated to act on this, but they are unlikely
                # to try again automatically before that.
                extra => { 'Retry-After' => 30 }
            );
            $client->setAfterReplyHandler (
                sub {
                # Ensures there can be no more requests.
                    my $client = shift;
                    $client->cancel;
                }
            )
        }
    };
    $Sloop->{pollwait} = 1;
    $Sloop->{timeout} = 2;
};

This ensures:

What "stop" means here is that the $sloop->run call which activated the server will return. Do NOT try to restart it. The Sloop::Server object is now defunct.

There is a minor complication here if you have active Sloop::Other connections. They will continue to keep the server loop going unless you also shut those down.

Something else that may be useful when contemplating a restart is $sloop->epollSize. This returns "the number of connections still being actively polled", including the server listen socket. You can implement a simple administrative check with a handler like:

checkConnections => sub {
    my $client = shift;
    if ($client->{ip} ne '127.0.0.1') {
        $client->generic(404);
        return;
    }
    my $num = $sloop->epollSize;
    $client->reply(\$num);
}

Again, this includes the server listen socket. Beware checking this from an actual browser as they may use several simultaneous connections (one of them looking for a favicon). Tools like wget and curl should be fine. If you get '2', there are no other clients and it is safe to just kill the server.

Restarting

Since once the listen socket and all connections are closed the run() call will return, you can do this:

$sloop->run;
exec "$0 ".join(' ', @ARGV);

To re-execute the sloop script exactly as it was first invoked. You can thus make changes to it, then trigger a soft restart.

Likely, though, you'll want to use some kind of condition based on a global flag (if ($Restart)...) which can be toggled in a signal or sloop handler.