Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cro WebSocket client doesn't see when the server goes out

Tags:

raku

cro

The client program below receives messages from a WebSocket server.
It doesn't send any messages.

CLIENT

use v6;
use Cro::WebSocket::Client;

constant WS-URL = 'ws://localhost:20000/status';
constant TIMEOUT-TO-CONNECT = 5; # seconds

my $timeout;
my $connection-attempt;

await Promise.anyof(
  $connection-attempt = Cro::WebSocket::Client.connect(WS-URL),
  $timeout = Promise.in(TIMEOUT-TO-CONNECT));

if $timeout.status == Kept
{
  say "* could not connect to server in ', TIMEOUT-TO-CONNECT, ' seconds";
  exit 1;
}

if $connection-attempt.status != Kept
{
  my $cause = $connection-attempt.cause;
  say '"* error when trying to connect to server';
  say '"* --------------------------------------';
  # $cause is a long string, how do we get a simple numeric code ?
  say $cause;
  say '"* ======================================';
  exit 1;
}

my $connection = $connection-attempt.result;
my $peer = 'localhost:20000';
say '* connected with ', $peer;

react
{
  whenever $connection.messages -> $message
  {
    my $body = await $message.body;
    say '* received message=[' ~ $body ~ '] from server';
    LAST { say '* LAST'; done; }
    QUIT { default { say '* QUIT'; done; }}
  }
  CLOSE { say '* CLOSE: leaving react block';}
} # react

SERVER

use Cro::HTTP::Router;
use Cro::HTTP::Server;
use Cro::HTTP::Router::WebSocket;

my $application =
route
{
  get -> 'status'
  {
    web-socket -> $incoming
    {
      my $counter = 0;
      my $timer = Supply.interval(1);

      supply
      {
        whenever $incoming -> $thing
        {
          LAST { note '* LAST: client connection was closed'; done; }
          QUIT { default { note '* QUIT: error in client connection'; done;  } }
        }
        whenever $timer
        {
          $counter++;
          say '* sending message ', $counter;
          emit $counter.Str;
        }
        CLOSE { say '* CLOSE: leaving supply block'; }
      } # supply
    } #incoming
  } # get -> status
}

my $server = Cro::HTTP::Server.new: :port(20000), :$application;

$server.start;

say '* serving on port 20000';

react whenever signal(SIGINT)
{
  $server.stop;
  exit;
}

Now, when the server goes out (say, by Ctrl+C) the client sees nothing.

Setting CRO_TRACE=1 in the client gives this:

TRACE(anon 2)] Cro::WebSocket::MessageParser EMIT WebSocket Message - Text

* received message=[4] from server
[TRACE(anon 1)] Cro::TCP::Connector DONE
[TRACE(anon 2)] Cro::WebSocket::FrameParser DONE
[TRACE(anon 2)] Cro::WebSocket::MessageParser DONE
[TRACE(anon 1)] Cro::HTTP::ResponseParser DONE
^C  

The client showed nothing more (and then I cancelled it).

So, the question is: how should the client deal with this scenario ?

UPDATE

Edited the question, now showing the server code
Also, I'm in Fedora 28. When I first cancel the server, netstat shows

$ netstat -ant | grep 20000
tcp6       0      0 ::1:20000               ::1:56652               TIME_WAIT  
$

Tcpdump shows

IP6 ::1.20000 > ::1.56652: Flags [F.], seq 145, ack 194, win 350, options [nop,nop,TS val 1476681452 ecr 1476680552], length 0
IP6 ::1.56652 > ::1.20000: Flags [F.], seq 194, ack 146, win 350, options [nop,nop,TS val 1476681453 ecr 1476681452], length 0
IP6 ::1.20000 > ::1.56652: Flags [.], ack 195, win 350, options [nop,nop,TS val 1476681453 ecr 1476681453], length 0

It seems the last ACK from the client to the server is missing, I guess the client didn't close the connection.

Also, I'm curious as to why Cro chooses to work with IPv6 by default.

like image 688
zentrunix Avatar asked Jul 19 '18 22:07

zentrunix


1 Answers

This is a bug that has been fixed since this question was posted, but I'm leaving an answer because of this part of the question that may confuse people when dealing with networking in Raku:

Also, I'm curious as to why Cro chooses to work with IPv6 by default.

localhost will resolve to an IPv6 address first if that's what the first address for localhost in your hosts file is. As of writing, IO::Socket::Async (which Cro uses internally) only allows PF_UNSPEC to be specified as a family, and the only address that will ever used from the results of hostname resolution is the first one in the list of addresses received. This will be changed at some point in the future as part of the work for my IP6NS grant and a problem solving issue to improve how DNS is handled, but for now, if you want to use IPv4/IPv6 only, you should specify 127.0.0.1/::1 instead of using localhost (or whichever addresses your machine resolves it to if they're different).

like image 176
Kaiepi Avatar answered Sep 29 '22 16:09

Kaiepi