Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask
 
PerlMonks  

PSGI, Plack, Twiggy, AnyEvent and SockJS... I need help

by xtpu2 (Acolyte)
on Apr 08, 2014 at 20:48 UTC ( #1081564=perlquestion: print w/ replies, xml ) Need Help??
xtpu2 has asked for the wisdom of the Perl Monks concerning the following question:

**UPDATE: The issue has been resolved. I've preserved the original post, but added the final, working code to the bottom. I sincerely hope this benefits others!**

I'm very new to event-based programming in Perl. I'm currently trying to get to grips with a Plack / Twiggy / AnyEvent / SockJS combination (details below). Unfortunately, I'm not doing so well.

I'm trying to write a program that will send the client a message whenever a certain event happens (in the production version, the event will be a database update, but for the purposes of this test, I'm trying to use an AnyEvent timer). However, I have no clue from which end to approach this. Here is the code I have so far (this is heavily based on the example code from the SockJS-perl docs):

#!/usr/bin/perl use strict; use warnings; use Plack::Builder; use SockJS; use AnyEvent; builder { mount '/echo' => SockJS->new( handler => sub { my ($session) = @_; $session->on( 'data' => sub { my $session = shift; $session->write('got your message'); } ); $session->write('connected'); my $w_cond = AnyEvent->condvar; my $w; $w = AnyEvent->timer( after => 0, interval => 5, cb => sub { undef $w; # cleanup $session->write('5 seconds have passed'); $w_cond->send(); } ); $w_cond->recv; }; ); };

This code is executed with:

plackup --server Twiggy::TLS --port 5000 --tks-key /path/to/key --tls-cert /path/to/cert app.psgi

This code doesn't work! Console.log outputs the following:

open connected close

It is obvious that $w->recv is the culprit because if I comment out that line, the server works and outputs 'got your message' when I send a message to it. However, in that case the timer doesn't work.

I know that SockJS is a module in very early development, and I have contacted the author for more information, but I have the feeling that my problem has nothing to do with SockJS, but with the way Plack, Twiggy and AnyEvent work. Hence my posting the question here on perlmonks. Any input / insight would be greatly appreciated (because not understanding this is driving me up the wall!!!)

For completeness, here is the client-side JavaScript code (copied 95% from the example on the SockJS module page):

<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script> <script> var sock = new SockJS("https://0.0.0.0:5000/echo"); sock.onopen = function() { console.log("open"); }; sock.onmessage = function(e) { console.log(e.data); }; sock.onclose = function() { console.log("close"); }; </script>

**Update: The issue has been resolved!**

Final, working code:

#!/usr/bin/perl use strict; use warnings; use Plack::Builder; use SockJS; use AnyEvent; builder { mount '/echo' => SockJS->new( handler => sub { my ($session) = @_; $session->on( 'data' => sub { my $session = shift; $session->write('got your message'); } ); $session->write('connected'); my $w_cond = AnyEvent->condvar; $w_cond->cb( sub { warn $_[0]->recv } ); my $w; $w = AnyEvent->timer( after => 0, interval => 5, cb => sub { undef $w if (0); $session->write('5 seconds have passed'); $w_cond->send(); } ); }; ); };

Comment on PSGI, Plack, Twiggy, AnyEvent and SockJS... I need help
Select or Download Code
Re: PSGI, Plack, Twiggy, AnyEvent and SockJS... I need help
by kcott (Abbot) on Apr 09, 2014 at 02:09 UTC

    G'day xtpu2,

    [Disclaimer: I haven't used AnyEvent previously. The following information was gleaned from its (and AnyEvent::Intro's) documentation.]

    "It is obvious that $w->recv is the culprit because if I comment out that line, the server works and outputs 'got your message' when I send a message to it. However, in that case the timer doesn't work."

    This may only be part of the story, but it might help resolve the AnyEvent issue.

    You're calling recv() on a time watcher when it should be called on a condition variable. Also, you'll need a send() method in your timer callback.

    Here's a minimal example intended to roughly mirror the AnyEvent part of the code you posted:

    #!/usr/bin/env perl -l use strict; use warnings; use AnyEvent; use AnyEvent::Strict; print 'Start at: ', scalar localtime; my $w_cond = AnyEvent->condvar; my $w = AnyEvent->timer( after => 5, cb => sub { print 'Callback: ', scalar localtime; $w_cond->send; } ); $w_cond->recv;

    Output:

    Start at: Wed Apr 9 12:06:50 2014 Callback: Wed Apr 9 12:06:55 2014

    -- Ken

      Thanks for those tips! I have modified the code accordingly and modified the OP. Unfortunately, the behavior of my code is the same (while your code snippet works correctly).

        I guess, the module you create is already called from some event-driven system. In other words, your module is a "callback". The "callback" can't run it's own event distribution. You can register one more "callback" with the main system and then simply return control to the caller. The main system is then responsible for activating your new callback at appropriate time.

        The AnyEvent module is event-distribution system. When you call $w->recv you activate this system, which blocks the main one, which in turn might trigger some time-out for killing your callback. Figure out how to add timers to the main framework. I don't know Plack and don't have any desire to study it, so I can't help you with this, sorry.

        At the core, event-driven programming is very simple. The main part is manager looping over all possible events. When event is encountered, the manager checks if there are any "callbacks" waiting for this event. Those callbacks are activated and the looping continues. The callbacks must finish their work quickly and if necessary, they can register more callbacks for different events.

        Everything else, is specific ways, how different managers keep track of callbacks and events.

Re: PSGI, Plack, Twiggy, AnyEvent and SockJS... I need help
by Corion (Pope) on Apr 09, 2014 at 07:54 UTC

    In your code, you create the callback timer and immediately discard it, which prevents the timer from ever getting called:

    sub { ... my $w = AnyEvent->timer( after => 0, interval => 5, cb => sub { $session->write('5 seconds have passed'); $w_cond->send(); } }; }

    You need to keep $w alive until the timer has fired. The easiest way is the following pattern:

    ... my $w; $w = AnyEvent->timer( after => 0, interval => 5, cb => sub { undef $w; # cleanup! $session->write('5 seconds have passed'); $w_cond->send(); } }; ...

    That way, the timer stays alive until it has fired.

      Thanks for that advice also! I've modified the code in the OP, however, the behavior has stayed the same.

        Please don't modify your original code that much, as people then can't learn from seeing your code progress through the stages.

        The next step would be to add (much) more amounts of server-side logging. print or warn at every step of the process. At least add logging statements to the following points:

        mount '/echo' => SockJS->new( handler => sub { warn "Creating new session"; my ($session) = @_; $session->on( 'data' => sub { my $session = shift; warn "got new data"; $session->write('got your message'); warn "Wrote response"; } ); $session->write('connected'); warn "Wrote greeting"; my $w_cond = AnyEvent->condvar; my $w; $w = AnyEvent->timer( after => 0, interval => 5, cb => sub { undef $w; # cleanup $session->write('5 seconds have passed'); warn "Wrote timer message"; $w_cond->send(); } ); $w_cond->recv; warn "Leaving"; }; );
      I believe you meant to say:
      my $w; sub { ... $w = AnyEvent->timer( ... }
      ie: you want $w outside of builder
        On another glance
        my $w; $w = AnyEvent->timer(
        is correct, but
        $w_cond->send();
        within your timer cb is not what you want. From AnyEvent:
        $w->recv; # enters "main loop" till $condvar gets ->send
        You want to ->recv when you are done with things. Adding another timeout timer for example would do the trick allowing your timer to run couple of times and eventually terminating your timer and the main loop after 30 seconds:
        my $w; $w = AnyEvent->timer( after => 0, interval => 5, cb => sub { $session->write('5 seconds have passed'); } ); my $timeout_timer; $timeout_timer = AnyEvent->timer( after => 0, interval => 30, cb => sub { undef $w; undef $timeout_timer; $session->write('Timeout after 30 seconds'); $w_cond->send(); } );

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1081564]
Approved by kcott
Front-paged by kcott
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others taking refuge in the Monastery: (3)
As of 2014-09-21 06:44 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    How do you remember the number of days in each month?











    Results (167 votes), past polls