Running a blocking recurring call that exits & renders correctly

151 views
Skip to first unread message

hammondos

unread,
Apr 21, 2015, 3:39:52 PM4/21/15
to mojol...@googlegroups.com
Hi,

I'm trying to run an AJAX call that needs to check an external API queue for a response every second, then generate a chart based on that data using JSON.

The code sample from the module I'm using to connect to the API suggests using a for...sleep loop, however clearly this doesn't work with Mojolicious.

I've tried several different permutations of using a recurring loop with varying degrees of success, but just can't get it to work - latest example below. This example never exits, even when there is data returned.

I'm aware that the $success parameter is probably not helping the situation, but would really appreciate any guidance on where to go next to stop the loop once a successful response has been received, and render a JSON response.

my $success = 0;
my $id = Mojo::IOLoop->recurring(
   
1 => sub {
        $self
->finish if $success == 1;
       
if (my $results = $sc_api->check_queue($report_id)) {
           
my $rows = $sc_api->process_os($results);
            $log
->info(Dumper($rows));
           
&_insert_data({domain => $domain, path => $path, data => {os => $rows}});
            $success
= 1;
         
}
   
}
);

# need to wait
$self
->on(finish => sub {
   
# remove ioloop
   
Mojo::IOLoop->remove($id);

   
my $db = $self->db;
   
my $analytics = $db->get_collection('analytics');
   
my $rows = $analytics->find({id => $url});
   
my $doc = $rows->next;
    $self
->render(json => $doc->{'analytics'}->{'os'});
});


Dan Book

unread,
Apr 21, 2015, 4:41:48 PM4/21/15
to mojol...@googlegroups.com
The finish event of the controller is not really appropriate here. It will not fire until the transaction is finished, which doesn't happen until you render a response. So once a success happens you want to call something that will render a response from there. You probably want to use the delay helper which will turn off automatic rendering, keep a reference to the transaction, and handle exceptions.

--
You received this message because you are subscribed to the Google Groups "Mojolicious" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mojolicious...@googlegroups.com.
To post to this group, send email to mojol...@googlegroups.com.
Visit this group at http://groups.google.com/group/mojolicious.
For more options, visit https://groups.google.com/d/optout.

hammondos

unread,
Apr 21, 2015, 4:58:04 PM4/21/15
to mojol...@googlegroups.com
Great, thanks - so something like the code example from here? Not sure how to replace the timer with recurring logic though

# Synchronize multiple events
my $delay = Mojo::IOLoop->delay(sub { say 'BOOM!' });
for my $i (1 .. 10) {
  my $end = $delay->begin;
  Mojo::IOLoop->timer($i => sub {
    say 10 - $i;
    $end->();
  });
}
$delay->wait;

Dan Book

unread,
Apr 21, 2015, 5:10:36 PM4/21/15
to mojol...@googlegroups.com
Sort of. You want the delay helper here: https://metacpan.org/pod/Mojolicious::Plugin::DefaultHelpers#delay

That creates a Mojo::IOLoop delay as well, but also does all the things I described before relevant to rendering. You won't be able to do a recurring timer exactly the same way, as you can't set up begin() callbacks in advance since you don't know how many there will be. You will want to just create a recurring timer in the first step, get the callback from $delay->begin, and you can have that call $end->() to proceed to the next step when you get a success.

hammondos

unread,
Apr 21, 2015, 5:17:35 PM4/21/15
to mojol...@googlegroups.com
Ok cool, thanks for pointing me in the right direction

hammondos

unread,
Apr 22, 2015, 3:10:49 AM4/22/15
to mojol...@googlegroups.com
Ok so I've adapted the script to use the delay helper, and based on the documentation it seems like this should work, but still can't seem to exit from the recurring loop.

Feels like the placement of $delay->begin is wrong or I'm not using the right signal to say the recurring loop should be stopped - return doesn't seem right but have also tried $loop->stop & $loop->reset with no luck. Any ideas?

$self->delay(
   
sub {
       
my $delay = shift;
        $delay
->begin;

       
Mojo::IOLoop->recurring(
           
1 => sub {

               
if (my $results = $sc_api->check_queue($report_id)) {
                   
my $rows = $sc_api->process_os($results);
                    $log
->info(Dumper($rows));
                   
&_insert_data({domain => $domain, path => $path, data => {os => $rows}});

                    $delay
->data(rows => $rows);
                   
return;
               
}
           
}
       
);
   
},
   
sub {
       
my $delay = shift;
        $self
->render(json => $delay->data('rows'));
   
}
);

Dan Book

unread,
Apr 22, 2015, 10:14:06 AM4/22/15
to mojol...@googlegroups.com
You need to save the return value of $delay->begin, that returns a callback that you need to call in order to proceed to the next step, like in the timer example you were looking at before.

Dan Book

unread,
Apr 22, 2015, 10:15:22 AM4/22/15
to mojol...@googlegroups.com
Also, to stop the recurring timer, save the returned id (you can store it in the delay as well) and then remove it from the loop after a success (for example in the next step).

hammondos

unread,
Apr 22, 2015, 12:32:56 PM4/22/15
to mojol...@googlegroups.com
Thank you! For the record, this appears to do the job:

my $r_id;

$self
->delay(
   
sub {
       
my $delay = shift;

       
my $end = $delay->begin;

        $r_id
= Mojo::IOLoop->recurring(
           
1 => sub {

               
if (my $results = $sc_api->check_queue($report_id)) {
                   
my $rows = $sc_api->process_os($results);

                   
&_insert_data({domain => $domain, path => $path, data => {os => $rows}});
                    $delay
->data(rows => $rows);

                    $end
->();

               
}
           
}
       
);
   
},
   
sub {
       
my $delay = shift;

       
Mojo::IOLoop->remove($r_id);

        $self
->render(json => $delay->data('rows'));
   
}
);

Dan Book

unread,
Apr 22, 2015, 2:29:22 PM4/22/15
to mojol...@googlegroups.com
You can also pass a "return value" to the next step via the delay callback (e.g. $end->($rows) => my ($delay, $rows) = @_). But the first argument is left out unless you pass a 0 to $delay->begin, this is because most non-blocking methods will pass their object as the first parameter. I tend to use the delay data() more for things like timer IDs, or data not specific to one step; you could store the timer ID this way after creating the recurring timer. This is all just stylistic and for clarity of course.
Reply all
Reply to author
Forward
0 new messages