9

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.

jjmerelo
  • 22,578
  • 8
  • 40
  • 86
zentrunix
  • 2,098
  • 12
  • 20
  • I can't test at the moment, but it might be possible the connection doesn't recognize that it's closed when you kill the server with ^C. Does it still show up in `netstat` (or whatever's equivalent on your OS)? – Kaiepi Jul 21 '18 at 07:17
  • 3
    Hi. It is a confirmed bug, but it is yet to be fixed. I hope the fix will be ready soon, starting from working Monday. The ticket you can track progress with is: https://github.com/croservices/cro-websocket/issues/15 Otherwise, I think your code is correct, so stack overflow rules are likely to restrict "not a questions" like this. Thanks for your patience and sorry for inconvenience. – Takao Jul 21 '18 at 22:50
  • 1
    @Takao It can sometimes be difficult to tell where the line is between a question that should be closed and one that shouldn't, particularly for new people. This one provided enough information that I think it is probably safe from being closed. The reason for closing is to keep the quality and usefulness of posts up. – Brad Gilbert Jul 22 '18 at 00:46

1 Answers1

4

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).

Kaiepi
  • 3,230
  • 7
  • 27