Can I use a AnyEvent->timer() in a AnyEvent::Fork ? - perl

Let's say I work with a number N of account object.
I would like to create for N Account, several forks, and independently include an event AnyEvent-> timer ().
here is what my code looks like:
for my $num_account (1..2) {
my $fork_1 = AnyEvent::Fork
->new
->require ("TweetFactory")
->fork
->run ("TweetFactory::worker",sub {
my ($master_filehandle) =#_;
my $wait1 = AnyEvent->timer(after => 0, interval => 100 ,cb => sub {
my $account = UsersPool::get_account($num_account);
my $tf = new TweetFactory ({account => $account, topic => $topic});
%search_article = $tf->search_articles_from_topic_list($dbh,\$db_access,#rh_website);
$tf->save_url_to_process($dbh,\$db_access,%search_article);
#url_to_process = $tf->get_url_to_process(100,$dbh,\$db_access);
%articles_urls_titles = $tf->get_infos_url($mech,#url_to_process);
$tf->save_url_processed($dbh,\$db_access,%articles_urls_titles);
});
});
my $fork_2 = AnyEvent::Fork
->new
->require ("TargetUsers")
->fork
->run ("TargetUsers::worker",sub {
my ($master_filehandle) =#_;
my $wait2 = AnyEvent->timer(after => 0, interval => 80, cb => sub {
my $account = UsersPool::get_account($num_account);
TargetUsers::save_all_messages($account,$dbh,\$db_access);
});
});
my $fork_3 = AnyEvent::Fork
->new
->require ("TargetUsers")
->fork
->run ("TargetUsers::worker",sub {
my ($master_filehandle) =#_;
my $wait3 = AnyEvent->timer(after => 0 , interval => 80, cb => sub {
my $account = UsersPool::get_account($num_account);
TargetUsers::save_followers($dbh,\$db_access,$account);
});
});
AnyEvent::Loop::run;
}
But during the execution, the timers does not start.
I have, on the contrary, tried to launch an event AnyEvent-> timer in which I create my fork :
my $wait2 = AnyEvent->timer(after => 0, interval => 10, cb => sub {
my $fork_2 = AnyEvent::Fork
->new
->require ("TargetUsers")
->fork
->run ("TargetUsers::worker",sub {
my ($master_filehandle) =#_;
my $account = UsersPool::get_account($num_account);
TargetUsers::save_all_messages($account,$dbh,\$db_access);
});
});
At this moment, the event was well launched, but I had to wait for the execution of the last event to create the next fork.
Have you some idea please ? Thanks

First some observations: in your example, you do not need to call ->fork. You also don't show the code running in the forked process, you only show how you create timers in the parent process, and these should certainly work. Lastly, you don't seem to do anything with the forked process (you do nothing to the $master_filehandle).
More importantly, your example creates and instantly destroys the fork objects, they never survive the loop, and you actually call the loop function inside your for loop, so probably you don't loop more than once
Maybe there is some misunderstanding involved - the callback you pass to run is executed in the parent, the same process whjere you execute AnyEvent::Fork->new, The code that runs in the child would be the TargetUsers::worker function for example.
To make timers work in the newly created processes, you would need to run an event loop in them.
Maybe AnyEvent::Fork::RPC with the async backend would be more suited for your case: it runs an event loop for you, it has a simpler request/response usage and it can pass data to and from the newly created process.

Related

Setting the "finish" event on a Mojolicious/Minion::Job

I am trying to have Minion jobs feed back to the Mojolicious web app upon completion (probably by posting a message to an API). The underlying idea here is then for the web app to feed back to the client that has uploaded/started the job.
I have tried doing this:
get '/' => sub {
my $c = shift;
my $id = $c->minion->enqueue('thing', [ qw/a b 1/, { foo => 'bar' } ]);
my $job = $c->minion->job($id);
$job->on(finish => sub ($job) {
my $id = $job->id;
my $task = $job->task;
$job->app->log->info(qq{Job "$id" was performed with task "$task"});
});
$c->render(template => 'index');
};
which doesn't work - I guess because the event is only emitted in the process performing the job, and the event does not get serialized and queued.
If I do this:
app->minion->add_task
(thing => sub ($job, $c, $sub, #args) {
$job->on(finish => sub ($job) {
my $id = $job->id;
my $task = $job->task;
$job->app->log->info(qq{Job "$id" was performed with task "$task"});
});
sleep 2;
});
it works ok, but it means I have to add the event handling to every task - which adds complexity to the code.
Is there a way to avoid having to do this?
I am thinking of:
being able to set a default class for jobs (so that jobs are all of a subclass of Mojo::Job - for example Minion::Job::WithFeedback)
better yet, being able to inject roles into task creation (so that you can do $c->minion->enqueue('thing', [ qw/a b 1/, { foo => 'bar' } ], { roles => qw/+WithFeedback +WithTimeout/);
I know I could poll all jobs regularly and see what changed status - this is what the Minion::Admin plugin does - but I would like to see if there is a different way that doesn't require polling the database.
Is this possible? and while we're at it - is this a bad idea in and of itself?

Perl Dancer2 infinite-loop on get when calling a method

I made a photobooth with Dancer some years ago and it worked fine.
Now I try to move this to Dancer2. However, It's not working anymore, because I have some infinite-loop.
Let's say my app looks like this:
package App;
use Dancer2;
# Photobox is my pm file with all the magic
use Photobox;
my $photobox = photobox->new();
get '/photo' => sub {
my $photo;
# Trigger cam and download photo. It returns me the filename of the photo.
$photo = $photobox->takePicture();
# Template photo is to show the photo
template 'photo',
{
'photo_filename' => $photo,
'redirect_uri' => "someURI"
};
}
takePicture() looks like this:
sub takePicture {
my $Objekt = shift;
my $return;
my $capture;
$return = `gphoto2 --auto-detect`;
if ($return =~ m/usb:/) {
$capture = `gphoto2 --capture-image-and-download --filename=$photoPath$filename`;
if (!-e $photoPath.$filename) {
return "no-photo-error.png";
}
else {
return $filename;
}
} else {
die "Camera not found: $return";
}
}
When I now call /photo, it'll result in an infinite loop. The browser is "frefreshing" all the time and my cam is shooting one photo after the other. But it never redirects to /showphoto.
It was working with Dancer(1) when I run the application just by perl app.pl from the bin directory. How I use Dancer2 und run it by using plackup app.psgi
I tried to put it into a before hook, but it changed nothing.
Update:
I figured out a way to work around this issue.
First I refactored my code a bit. Basic idea was to split the take photo and show photo operations into two different routes. This makes it easier to see what happens.
get '/takesinglephoto' => sub {
my $photo;
$photo = takePicture();
$single_photo=$photo;
redirect '/showsinglephoto';
;
get '/showsinglephoto' => sub {
set 'layout' => 'fotobox-main';
template 'fotobox_fotostrip',
{
'foto_filename' => $single_photo,
'redirect_uri' => "fotostrip",
'timer' => $timer,
'number' => 'blank'
};
};
And I moved the takePicture method just into my Dancer main App.pm.
Now I recognized from the log output, that the browser does not load the '/takesinglephoto' page once, but refreshes it every some secons. I think the reason is, that takePicture() takes some seconds to run and to return the output. And Dancer does not wait until it ends. With every reload, it triggers the takePicture() again and that causes the infinite-loop.
I worked around this by implementing a simple check to run takePicture() just once.
# define avariable set to 1 / true
my $do_stuff_once = 1;
get '/takesinglephoto' => sub {
my $photo;
# check if variable is true
if ($do_stuff_once == 1) {
$photo = takePicture();
$single_photo=$photo;
# set variable to false
$do_stuff_once = 0;
}
redirect '/showsinglephoto';
};
get '/showsinglephoto' => sub {
# set variable back to true
$do_stuff_once = 1;
set 'layout' => 'fotobox-main';
template 'fotobox_fotostrip',
{
'foto_filename' => $single_photo,
'redirect_uri' => "fotostrip",
'timer' => $timer,
'number' => 'blank'
};
};
Now it still refreshes /takesinglephoto, but it does not trigger takePicture() again and again and finally, when the method returns the photo filename, it redirects to /showsinglephoto.
I would call this a workaround. Is there a better way to solve this?
BR
Arne

Adding a periodic timeout to select from a sub-class of Net::Server::HTTP

I'm trying to write a small service that responds to a couple commands (to check/report on status), but every 15 seconds or so breaks out of its polling status to check a database for messages and fork off a child to do some processing.
I'm sub-classing Net::Server::HTTP, for example:
my $service = MyService->new("port" => 8080);
$service->run(app => {
"/ping" => sub { print_client("Pong"); },
"/status" => sub { print_client("Good, thanks"); },
});
but I can't figure out how to get a timeout through the class hierarchy to reach the socket's select call. I was hoping I could pass a timeout through, or use a while(1) with an alarm to break out of the run method, but neither is working.
What I was /hoping/ for was to do:
while (1) {
$service->run(
timeout => 15,
app => { ... }
});
check_database();
}
Is there a better way to do this while keeping the code simple?

Mojo IOLoop blocks app when ran

I use Mojo::IOLoop to perform background tasks that should be run every so often, and am doing this using Mojo::IOLoop::recurring. I do this within the Mojo app itself:
sub startup {
my $self = shift;
$self->setup_routes();
... more setup
my $sleep_time = $self->config()->{sleep_time};
Mojo::IOLoop->recurring($sleep_time => sub {
my $sync = My::BackgroundTask->new(
sleep_time => $sleep_time,
);
$sync->run();
});
local $SIG{TERM} = sub {
Mojo::IOLoop->stop_gracefully;
};
}
When the time comes for the above loop to run, when trying to view the actual app the site times out, and when it's finished the app is available again. Not sure why this is happening, would someone be able to explain?
EDIT:
My::BackgroundTask::run
sub run {
my ($self, $data) = #_;
while ( scalar(#{$data}) > 0 ) {
my #batch = splice(#{$data}, 0, 100);
$self->schema->update_batch_of_data( \#batch );
# sleep for a while to not be rude :P
sleep ($self->sleep_time);
}
return 1;
}
Are you saying that it sleeps for $sleep_time seconds? Because that's not how it's supposed to work. It should exit and continue the process next time the recurring starts it up. As it is, you're trying to start another copy of the task of the task each time recurring kicks in, and maybe your task is hanging because it's unable to start more than one task? Just a guess, Does it ever return from $sync->run()? And what sort of thing is $sync?
– Borodin
yeah seems to have been because of the sleep inbetween, thanks! Any suggestions on how to be friendly when calling external sources and not milking them in one go? :P – a7omiton
Yes, you need a similar recurring timer, but only do a fraction of the work at each step
– Borodin

Pop Up in perl that goes away automatically after pause

I'm writing a script to assist people who'll scan a barcode and get a response to keep or dispose the scanned sample. I want to have a message, similar to tk's messagebox or Win32::MsgBox but one that requires no user interaction to go away after three seconds.
My thought was to create the messages in a child process, using alarm to kill the process after a delay. In Tk:
sub tmpMsgBox {
my ($message,$delay) = #_;
if (fork() == 0) {
my $topWin = MainWindow->new;
my $label = $topWin->Label();
my $ok = $topWin->Button();
$label->pack(-side => 'top');
$ok->pack(-side => 'bottom');
$label->configure(-text => $message);
$ok->configure(-text => 'Ok', -command => sub {exit});
$SIG{ALRM} = sub {exit};
alarm $delay || 1;
$topWin->MainLoop;
}
}
for (3..10) {
tmpMsgBox("This window will disappear in $_ seconds", $_);
}
I don't think Tk plays nicely with fork, though, so this idea probably won't work so well if you are also using Tk in your main process.
Desktop::Notify is the standard-compliant interface to the desktop's passive notification pop-ups.
perl -MDesktop::Notify -e'
Desktop::Notify
->new
->create(
body => q{why hello there},
timeout => 3000
)->show'
What you want to do is to send a destroy message to the window after your timeout (remembering to cancel the sending of the message if the user does choose something!) Tk's certainly capable of doing this.
# Make the timeout something like this...
$id = $widget->after(3000, sub {
$widget->destroy;
});
# To cancel, just do...
$id->cancel;
You also need to make sure that you don't block when the widget is forced to go away, of course. This also prevents trouble if someone kills the widget by other means too, so it's a double-bonus.