It may be a simple question for a perl expert -
I am trying to run a script which should logically do this>
Main
{
While (true)
Call Sub A
Call Sub B
Call Sub C
}
Sub A
{
Go execute a method A for whatever it needs to do ---
before next time method A runs it need to wait atleast 60 seconds but i still want to call method B in these 60 seconds.
}
Sub B
{
Go execute a method B for whatever it needs to do ---
before next time method b runs it need to wait atleast 60 seconds but i still want to call method C in these 60 seconds in mean time.
}
Sub C
{
Go execute a method C for whatever it needs to do ---
before next time method C runs it need to wait atleast 60 seconds at this moment control should be back in Main and wait for first 60 seconds of A to expire so that Sub A can be called
}
My question:
Q: what is best and optimized way can i do this -?
Q: If i put sleep 60 in each sub then next sub will not be called even till 60 seconds expire and it will delay overall processing.
Q: I want 3 subs called every 60 seconds in a sequential order
Q Lastly if i need to call 2 subs in every 60 seconds and last sub every hour - how do i do it?
Comment - my thought was to take UTC as a variable and store it in a variable and keep checking time if time expires than call individual subs but not sure if it optimum way of running code.
Inside the subroutine record the time when it last ran, then wait if necessary on the next execution:
use feature 'state';
my $sleep_duration = 60; # seconds
sub foo {
state $last_execution = 0;
my $remaining = $last_execution + $sleep_duration - time;
sleep $remaining if $remaining > 0;
$last_execution = time;
...; # normal processing for this sub
}
You probably want to use threads. The following snippet might give you a starting point. Since you have four Qs instead of one, I won't go into detail for each of them.
#!/usr/bin/perl
use warnings;
use strict;
use threads;
my #t = (
threads->create(\&A),
threads->create(\&B),
threads->create(\&C)
);
$t[0] -> join();
$t[1] -> join();
$t[2] -> join();
sub A {
for my $i (1 .. 10) {
print "A $i\n";
sleep 60;
}
}
sub B {
for my $i (1 .. 20) {
print "B $i\n";
sleep 60;
}
}
sub C {
for my $i (1 .. 3) {
print "C $i\n";
sleep 60;
}
}
Using Amon idea, but with sleeping logic outside functions,
sub get_sleeper {
my ($sleep_duration) = #_;
my $last_execution = 0;
return sub {
my $remaining = $last_execution + $sleep_duration - time();
sleep $remaining if $remaining >0;
$last_execution = time();
};
}
my ($sleepA, $sleepB, $sleepC) = map get_sleeper(60), 1..3;
while (1) {
$sleepA->(); SubA();
$sleepB->(); SubB();
$sleepC->(); SubC();
}
Both answers are good but a sort of middle way would be: (stolen from the answer by amon)
use feature 'state';
my $sleep_duration = 60; # seconds
sub foo {
state $last_execution = 0;
my $remaining = $last_execution + $sleep_duration - time;
if ($remaining > 0)
{
return "Waiting"; # Make sure main checks for this to know whether the sub ran.
}
...; # normal processing for this sub
}
This will allow you to run the next sub while this one wait without needing more than one thread. But of course, it means that this sub will run only after the other two had their chance. This means you mix up the order and depending on subs, take longer than 60 seconds till the next call.
Related
In the below code, while sleeping if the QUIT signal comes the sleep will immediately finish and the last line of code will be executed.
#!/opt/tertio/localperl/bin/perl
use strict;
use warnings;
my $quit = 0;
$SIG{'QUIT'} = sub {
print "Please dont try to take thread dump as i am not java\n";
$quit++;
};
my $sleep = shift || 30;
print "I am going to sleep\n";
sleep($sleep);
print "I woke up so will exit, but someone tried to quit me $quit times\n";
No matter how long I set the sleep time, as soon as I send a QUIT signal sleep ends and the program continues.
Is there a way to handle QUIT without affecting the current run of the program such that sleep continues for as long as it was meant to?
Set up a block around that precious sleep, to ignore the signal within it
IGNORE_QUIT: {
local $SIG{QUIT} = 'IGNORE';
sleep 10;
};
If the sleep happens to already be scoped suitably then you don't need an extra block.
Can use a wrapper if there's more of this
sub sleep_protected {
local $SIG{QUIT} = 'IGNORE';
sleep $_[0];
}
The local is crucial so that $SIG{QUIT} isn't changed globally but only within the block.
If you need a signal handler (and can't just use 'IGNORE') then sleep does get interrupted so that the handler can run. So you need to restart the call
sub sleep_protected {
my $sl = $_[0];
my $sigquit;
local $SIG{QUIT} = sub {
say "Got $_[0], but don't do thread dump";
++$sigquit;
};
$sl -= sleep $sl while $sl > 0;
return $sigquit;
}
The counter is local and returned but you can use a global one instead, declared with our. Or declare it as a state variable and it is remembered across calls (see feature).
The interrupted call returns with error EINTR if interrupted by a signal. This can be checked for using %!, as if ($!{EINTR}) {...}. However, I don't know how sleep can reasonably be interrupted other than by a signal and so didn't use this.
That is, let the event loop process the events currently in queue. In VBA the call is named DoEvents.
I want to do this in Perl. Preferably to do this in AnyEvent, because I have a script written with it. But I couldn't find any function like that in documentation.
I naively tried to implement it with a condvar, because documentation says that recv calls event loop, but it failed, here is sample code:
use strict;
use warnings;
use AnyEvent;
use AnyEvent::Strict;
sub _long_task;
my $t1 = AE::timer 0, 3, sub{print"task 1, with interval 3 seconds\n";};
my $t2 = AE::timer 0, 7, sub{print"task 2, with interval 7 seconds\n";};
my $t3 = AE::timer 0, 11, sub{print"task 3, with interval 11 seconds\n";};
my $t_long = AE::timer 0, 0, \&_long_task;
sub DoEvents()
{
my $cv = AE::cv;
my $t = AE::timer 0, 0, sub { $cv->send; };
$cv->recv;
}
sub _long_task {
print"long task: ENTERING\n";
for(1..5) {
print"long task: doing some work for 2 seconds\n";
sleep 2;
print"long task: calling DoEvents\n";
DoEvents();
}
print"long task: EXITING, resheduling after 10 seconds\n";
$t_long = AE::timer 10, 0, \&_long_task;
}
AE::cv->recv;
The output is:
task 1, with interval 3 seconds
task 2, with interval 7 seconds
task 3, with interval 11 seconds
long task: ENTERING
long task: doing some work for 2 seconds
long task: calling DoEvents
AnyEvent::CondVar: recursive blocking wait attempted at C:\Users\andreyi\Desktop\try_event_loop.pl line 18.
UPDATE:
There are lines in AnyEvent.pm:
$WAITING
and Carp::croak "AnyEvent::CondVar: recursive blocking wait attempted";
If you comment them, the DoEvents() works.
However, it will be better to have solution that does not involve mofidication of this CPAN module.
Every problem has at least one simple solution (sometimes it is a dirty hack).
In my case this one seems to work. I added it to production code.
BEGIN { our $orig_Carp_croak = \&Carp::croak; }
sub DoEvents()
{
my $cv = AE::cv;
my $t = AE::timer 0, 0, $cv;
no warnings 'redefine';
local *Carp::croak = sub{
(caller 1)[3] eq 'AnyEvent::CondVar::Base::recv'
&& $_[0] =~ /recursive blocking wait attempted/
&& return;
goto \&{our $orig_Carp_croak};
};
$cv->recv;
}
UPDATE:
For all callbacks that call DoEvents you will need to ensure that it they are not reentered. Like this:
our $entered_callbacks = {};
# ...
sub some_callback {
$entered_callbacks->{some_callback} && return;
local $entered_callbacks->{some_callback} = 1;
...;
}
The loops EV and AnyEvent::Loop have flaw that the callback is removed from queue only when it returned, not before it is called. It makes event loop not safe for reentry.
I want to write a script in perl that will define a value for a variable say "10".
Then, it will ask user to enter the value through STDIN for that variable.
If user, enters the value within a fixed TIME INTERVAL, then take that value, else continue the program by taking that default value of 10.
I had no idea how to do that. I thought of some thing like this..
$t=120 (for 120 seconds)
decrease value of "$t" with every second, if user enters some value
then come out of loop, and continue, else when $t becomes 0, take default value and continue.
But, i dont have any idea of how can i decrease value of variable with time along with asking user for input.
I can do this, decrease value of variable with time, but within that, i am not able to take input.
Here's a simple example of how you might do it with an alarm signal.
use strict;
use warnings;
my $input = eval {
my $tmp;
# this sub will be called after the timeout set by the alarm below
local $SIG{ALRM} = sub {
print "timeout - using default value 10\n";
$tmp = 10;
};
print "enter input: ";
alarm 10; # wait for 10 secs
$tmp = <>;
alarm 0; # turn off alarm if we got input within 10 secs
$tmp;
};
print "the value is: $input\n";
You can also accomplish this with IO::Select
use strict;
use IO::Select;
my $value = 10;
my $obj = IO::Select->new(\*STDIN);
foreach my $hand ($obj->can_read(2)) {
$value = <$hand> ;
}
print "value is :$value:\n" ;
I think you're looking for Prompt::Timeout.
How can I tell Perl to run some code every 20 seconds?
for (;;) {
my $start = time;
# your code;
if ((my $remaining = 20 - (time - $start)) > 0) {
sleep $remaining;
}
}
while (1) {
sleep 20;
<your code here>;
}
Set up a SIGALRM handler, and send yourself a signal every 20 seconds with alarm (see perldoc -f alarm):
$SIG{ALRM} = sub {
# set up the next signal for 20 seconds from now
alarm 20;
# do whatever needs to be done
# ...
};
This will experience drift over time, as each signal may be delayed by up to a second; if this is important, set up a cron job instead. Additionally, even more drift will happen if your other code takes upwards of 20 seconds to run, as only one timer can be counting at once. You could get around this by spawning off threads, but at this point, I would have already gone back to a simple cron solution.
Choosing the proper solution is dependent on what sort of task you need to execute periodically, which you did not specify.
While the sleep function will work for some uses, if you're trying to do "every 20 seconds, forever", then you're better off using an external utility like cron.
In addition to the possible issue of drift already mentioned, if your sleep script exits (expectedly or otherwise), then it's not going to run again at the next 20 second mark.
#Blrfl is correct, and I feel sheepish. That said, it's easy enough to overcome.
* * * * * /path/to/script.pl
* * * * * sleep 20 && /path/to/script.pl
* * * * * sleep 40 && /path/to/script.pl
You could also take a hybrid approach of putting a limited count sleep loop in the script and using cron to run it every X minutes, covering the case of script death. Anything more frequent than 20 seconds, I would definitely take that approach.
See Schedule::ByClock:
#!/usr/bin/perl
use strict; use warnings;
use Schedule::ByClock;
my $scheduler = Schedule::ByClock->new(0, 20, 40);
while ( defined( my $r = $scheduler->get_control_on_second ) ) {
printf "%02d\n", $r;
}
All the caveats others pointed out still apply, but I think the module is neat.
#!/usr/bin/perl -w
use strict;
# heartbeat
$SIG{ALRM} = sub {
# set up the next signal for 20 second from now
alarm(20);
printf "<heartbeat/>\n";
};
alarm(20); # start timer
my $i = 0;
while(1) {
$i++;
printf "loop" . $i . "\n";
sleep 2;
}
alarm(0);
printf "done!\n";
Output :
loop1
loop2
loop3
loop4
loop5
loop6
loop7
loop8
loop9
loop10
<heartbeat/>
loop11
loop12
loop13
loop14
loop15
loop16
loop17
loop18
loop19
loop20
<heartbeat/>
It is better to use some event library. There are a couple of choices:
IO::Async::Timer::Periodic
use IO::Async::Timer::Periodic;
use IO::Async::Loop;
my $loop = IO::Async::Loop->new;
my $timer = IO::Async::Timer::Periodic->new(
interval => 60,
on_tick => sub {
print "You've had a minute\n";
},
);
$timer->start;
$loop->add( $timer );
$loop->run;
AnyEvent::DateTime::Cron
AnyEvent::DateTime::Cron->new()
->add(
'* * * * *' => sub { warn "Every minute"},
'*/2 * * * *' => sub { warn "Every second minute"},
)
->start
->recv
EV::timer
etc.
Go with ..
while () {
sleep 20;
#Code here
}
I want to display a set of values on screen and update that value every 5 seconds. I don't want to clear the screen.
eg:
hours: 1
mins : 30
sec: 45
here, values should change accordingly.
How should i do that in Perl?
Regards,
Anandan
Are you talking about getting more control over where things are printed on your screen? Then you probably want to check out the Term::Cap module.
A poor man's way to do this on one line is to use \r to keep overwriting the same line.
while ($t>0) {
# note no new line at the end of printf statement
printf "\rHours: %d Minutes: %d Seconds: %d ", $t/3600, ($t/60)%60, $t/60;
sleep 5;
$t -= 5;
}
EDIT Here's something that works on my system. Your terminal's capabilities may vary.
require Term::Cap;
$terminal = Tgetent Term::Cap { TERM => cygwin, OSPEED => 9600 };
$terminal->Trequire("up"); # move cursor up
$UP = $terminal->Tputs("up");
$t = 500;
while ($t > 0) {
printf "Hour: %d \n", $t/3600;
printf "Minute: %d \n", ($t/60)%60;
printf "Second: %d \n", $t%60;
print $UP,$UP,$UP;
sleep 5;
$t -= 5;
}
For this sort of thing I like to use Curses. It's just not for Perl, either. :)
Something like this:
use Term::ANSIScreen qw(cls);
while(1) {
cls;
print "....";
sleep 5;
}
Alternatives of "cls" can be found in this question.