Search code examples
perltimerscopeanyevent

Why must an AnyEvent timer watcher be undef'ed in its callback to fire?


While trying to understand AnyEvent, I create two timers which print to screen each time they're fired. Initially neither worked. But following Joshua Barratt's timer example, I found that if I didn't undef the timer's watcher variable then the timer's callback didn't fire at all. Why is that the case? I suspect it has something to do with how scope works in perl and/or AnyEvent.

Here is my example program:

#!/usr/bin/perl

use AE;

my $cv = AE::cv;

sub func1 {
   my $spoke = 0;
   my $t1; $t1 = AE::timer 0, 1,
      sub { 
         print "Timer 1 Fired\n";
         if($spoke++ > 5) {
            print "Timer 1 Done\n";
            undef $t1;
         }   
      };  
      print "Timer 1 started\n";
}

sub func2 {
   my $spoke = 0;
   my $t2; $t2 = AE::timer 0, 1,
      sub { 
         print "Timer 2 Fired\n";
         if($spoke++ > 5) {
            print "Timer 2 Done\n";
            #undef $t2;
         }   
      };  
      print "Timer 2 started\n";
}

func1();
func2();

$cv->recv;

As is, my code returns:

Timer 1 started
Timer 2 started
Timer 1 Fired
Timer 1 Fired
Timer 1 Fired
Timer 1 Fired
Timer 1 Fired
Timer 1 Fired
Timer 1 Fired
Timer 1 Done

If I uncomment the undef $t2; line, Timer 2's callback is fired and I get this:

Timer 1 started
Timer 2 started
Timer 1 Fired
Timer 2 Fired
Timer 2 Fired
Timer 1 Fired
Timer 1 Fired
Timer 2 Fired
Timer 2 Fired
Timer 1 Fired
Timer 1 Fired
Timer 2 Fired
Timer 2 Fired
Timer 1 Fired
Timer 1 Fired
Timer 1 Done
Timer 2 Fired
Timer 2 Done

Solution

  • You must keep the guard object (the value of $t1) alive. If all references to it are gone, it will get destroyed, and that cancels the event.

    Referencing $t1 in the closure causes the closure to capture it, keeping it alive past it's normal death at then end of func.

    If you want to capture a variable you don't otherwise need, you can use

    $t2 if 0;   # Keep timer alive until process exit.
    

    Here's a simple example of a closure:

    sub make_closure {
       my ($x) = @_;
       return sub {
          print("$x\n");
       };
    }
    
    my $f1 = make_closure("Hello, World!");
    my $f2 = make_closure("Allo, Jeune Renard!");
    
    $f1->();
    $f2->();
    

    Notice how the closure (the anon sub) captures the $x that existed at the time?