Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

Re: Mixing asynchronous data feed with synchronous program flow control

by haukex (Bishop)
on Jan 02, 2020 at 08:29 UTC ( #11110864=note: print w/replies, xml ) Need Help??


in reply to Mixing asynchronous data feed with synchronous program flow control

If the client is as simple as shown, I think the code is ok - there are a couple of chances for race conditions in regards to $TX not being in the correct state when the timer fires, but for a command-line script that may be acceptable (I'd just add some $SIG{INT} handlers to server and client). If this is part of a larger app, here's how I would have coded it:

  • Tie the lifetime of the timer to the lifetime of the WebSocket client
  • Don't exit from inside the handler, allow for graceful shutdown
my $ua = Mojo::UserAgent->new; my $conn = $ua->websocket("ws://localhost:8080/" => sub { my ($ua, $tx) = @_; say "WebSocket handshake failed!" and return unless $tx->is_websocket; my $id = Mojo::IOLoop->recurring(5 => sub { my @command = qw/ RUN STOP /; my $command = $command[rand @command]; say "Client > $command"; $tx->send($command); }); $tx->on(finish => sub { my ($tx, $code, $reason) = @_; say "WebSocket closed with status $code."; Mojo::IOLoop->remove($id); Mojo::IOLoop->stop_gracefully; }); $tx->on(text => sub { my ($tx, $bytes) = @_; say "Server > ", $bytes; }); }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running;

Replies are listed 'Best First'.
Re^2: Mixing asynchronous data feed with synchronous program flow control
by jcb (Priest) on Jan 02, 2020 at 23:13 UTC
    race conditions in regards to $TX not being in the correct state when the timer fires

    In a program using a single event loop, that cannot happen — timers only produce a delay until some time, but do not interrupt other event handlers and timer events are not processed until other callbacks return. There are no races provided that all callbacks return all resources to "safe states" before returning.

      In a program using a single event loop, that cannot happen timers only produce a delay until some time, but do not interrupt other event handlers and timer events are not processed until other callbacks return. There are no races provided that all callbacks return all resources to "safe states" before returning.

      Ummm what? Sorry, but no. Take the code exactly as posted in the root node, but in the server code, insert a "sleep 5;" as the first line in on_connect to simulate bad network delay, and then the client will, on many runs, throw the error "Mojo::Reactor::EV: Timer failed: Can't call method "send" on an undefined value", because $TX is still undef at that point. That demonstrates exactly the issue that I was talking about. The code I posted doesn't suffer from that particular issue, because it doesn't start the timer until the connection object is actually available.

      Update: I admit my wording "there are a couple of chances for race conditions" may have been a stretch, I initially thought I saw more than the one I described above, but I'm not so sure anymore. However, your claim "that cannot happen" is still incorrect.

        I admit my precondition was incomplete as well. The actual requirement is that, upon entering or returning to the event loop, all resources must be in some usable idle state. I still maintain that race conditions cannot occur in a program with a single event loop because the program is implicitly serialized on the event loop itself. The error that you cite is "use of uninitialized value" and the bug is obscured by an asynchronous environment, but it is not a result of undefined timing. It is very well defined: an attempt is made to use $TX some time later, even if the I/O required to initialize $TX is still pending.

        I am most familiar with writing Tk callbacks where getting back to the event loop quickly is very important, but Tk ensures that all initialization is complete before the first user callback runs. The error you have found occurs when the event loop is started but some resource used in a callback has not yet been fully initialized. If initializing a connection requires the event loop to run, things get more interesting and you must track the "initialization pending" state somewhere and either drop or requeue timer events until the connection is ready. So the "idle state" for the connection includes a "connected" flag that must remain clear until the connection is actually established.

        ... and so the pristine model starts to get hairy when put into practice ...

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://11110864]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others scrutinizing the Monastery: (13)
As of 2020-07-06 16:54 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?