dimecadmium has asked for the wisdom of the Perl Monks concerning the following question:

I need to know how YOU would recommend I run code (for my IRC bot) after x time. I need to (a) implement message queueing, (and b) implement some checks for database status, connection status, request list, etc.

Alarm wasn't working before. I've got no clue why not. I need to go back in and try again, but basically it got out of the handler and died immediately. Any comments on that would also be extremely welcome.

  • Comment on Timers/running (something) after (sometime)

Replies are listed 'Best First'.
Re: Timers/running (something) after (sometime)
by zentara (Cardinal) on Feb 07, 2009 at 15:32 UTC

      The bonus with POE is that you get to use POE::Component::IRC or Bot::BasicBot. These already implement message priorities and queuing for flood protection. They also support multiple timers through POE. Each has their own plug-in system, and CPAN has a bunch of plug-ins for them. You'll see some of them at the previous links.

Re: Timers/running (something) after (sometime)
by dimecadmium (Initiate) on Feb 07, 2009 at 02:50 UTC
    FYI, I'm thinking of something like the following:

    sub after { my ($time, $anonsub) = @_; push @{ $alrmsubs{$time} }, $anonsub; return 1; }

    Except that this will make me check for time/time()-1 (due to the "give-or-take" of, iirc, 1s doc'd in perlfunc) in the alarm, run the alarm sub every second, etc. So, I need improvements on this; as in a way to store anonymous subs in a hash or array or whatever and execute them after the specified time. (Figure out the time until the soonest one, and alarm time-until somehow?)

    Once again, I reaaaally need suggestions.

      Do you need to do these things asynchronously, or do you have a main loop somewhere dispatching things ?

      Time::HiRes provides a higher resolution time() and sleep(). I also discovered that Time::HiRes::alarm() doesn't work, but Time::HiRes::ualarm() does -- at least on by Linux machine. Neither alarm nor ualarm work on Winders :-(

      I would be nervous about doing a lot of work in a signal handler -- the documentation does not inspire confidence. However, the code below appears to work. I've included a mechanism for temporarily disabling the signal handler, which can be wrapped round any code found to be critical.

      If your program can be converted into a select loop, then something like:

      while (1) { my $timeout = 5 ; while (@events) { $timeout = $events[-1]->[0] - Time::HiRes::time() ; last if ($timeout > 0.001) ; my ($time, $rsub, @args) = @{pop @events} ; $rsub->(@args) ; $timeout = 5 ; } ; # use IO::Select recommended, but this is just for illustration... select(my $rout = $rin, my $wout = $win, my $eout = $ein, $timeout +) ; # ..... } ; # add_event($delay, $rsub, @args) # # schedule call of $rsub->(@args) in $delay (float) seconds in the + future sub add_event { my ($delay, $rsub, @args) = @_ ; my $when = $delay + Time::HiRes::time() ; push @events, [$when, $rsub, @args] ; if ((@events > 1) && ($events[-2]->[0] < $when)) { @events = sort { $b->[0] <=> $a->[0] } @events ; } ; } ;
      will do the trick... but you're into non-blocking I/O bigtime here.

      Alternatively, you could poll an event loop on a regular basis. So scattering calls to something like:

      sub events { while (@events) { my $timeout = $events[-1]->[0] - Time::HiRes::time() ; return $timeout if ($timeout > 0.001) ; my ($time, $rsub, @args) = @{pop @events} ; $rsub->(@args) ; } ; return 5 ; } ;
      far and wide... This is straightforward, and finesses a lot of worries about access to shared variables. But you have to be careful to ensure that the events() subroutine is called "often enough" given the required timing accuracy.

Re: Timers/running (something) after (sometime)
by dimecadmium (Initiate) on Feb 07, 2009 at 23:36 UTC
    Yes, yes, I know there are modules ;) but at this point, it'd be a major pain to rewrite it, and I don't like most of the modules.

    I'm using a while in my main code (on an IO::Select) to get the input.

    I've got some code which works GREAT by itself... but with small adaptations to make it fit in my bot code it just plain doesn't work; it quits when it gets out of the SIGALRM handler.

    #!/usr/bin/perl use warnings; sub after { my ($time, $anonsub) = @_; do { $id = int(rand(10000)); } while (defined($times{$id})); $times{$id} = $time; $subs{$id} = $anonsub; } sub Alrm { for (keys(%times)) { if (time() >= $times{$_}) { ${subs{$_}}(); delete $times{$_}; } elsif (time() < $times{$_}) { next; } } alarm 1; } $SIG{ALRM} = \&Alrm; alarm 1; print gmtime(time()).chr(10); sub DoStuff { print gmtime(time()).chr(10); after(time+1, \&DoStuff); } after(time+1, \&DoStuff); 1 while 1;

    So, I don't know. I may just trash this. And when I do my major rewrite I've already got planned I may switch to a module. But... not yet.