POE Primer

POE is a framework that allows a Perl program to run several tasks (called sessions) concurrently. A POE session can run indefinitely while responding to arbitrary events. POE could be used to develop a network application that can interact with various clients, for example.

use POE;    # This loads POE::Kernel and POE::Session.

POE::Kernel->run();

The script above loads the POE module which in turn loads the Kernel and Session modules. It runs a POE kernel and then exits right away because it doesn't have any tasks. The kernel is like an operating system. It is the environment where all of the sessions run.

Sessions

Sessions handle events. When an event occurs, the session goes into the designated state and runs the corresponding subroutine called a handler. You can assign handlers to states using the inline_states, object_states, or package_states methods. All sessions require a _start handler which is executed when a session initializes. When a session ends, it goes into the _stop state.

# Let's save this to hello.pl

use POE; 

POE::Session->create(inline_states => {_start => sub{print "Hello\n" },
                                       _stop => sub{print "Goodbye\n" } });

POE::Kernel->run();
> perl hello.pl 
Hello
Goodbye
# The same script with named subroutines.

use POE; 

POE::Session->create(inline_states => { _start => \&hello,
                                        _stop  => \&goodbye } );

POE::Kernel->run();

sub hello {
  print "Hello\n"
}

sub goodbye {
  print "Goodbye\n"
}

# And the same script using a "package_states" shortcut.
# If the event names match the handler names,
# then you can just use an array of those names.
# Notice that the handler names here are different from the preceding examples.

use POE;

POE::Session->create(package_states => [main => [qw(_start _stop)]]);

POE::Kernel->run();

sub _start {
  print "Hello\n"
}

sub _stop {
  print "Goodbye\n"
}

You can create as many sessions as you want. You can assign a different task to each session or you can create several sessions that all do the same task.

# Let's save this to sessions.pl

use POE;

POE::Session->create(inline_states => {_start => sub{print "Hello! This is a session.\n"} });

POE::Session->create(inline_states => {_start => sub{print "And this is another session.\n"} });

POE::Session->create(inline_states => {_start => sub{print "And this is yet another session.\n"} });

POE::Kernel->run();
> perl sessions.pl 
Hello! This is a session.
And this is another session.
And this is yet another session.
use POE;

for (1..3) {
  POE::Session->create(inline_states => {_start => sub { print "This is a session. \n" } } );
}

POE::Kernel->run(

> perl sessions.pl
This is a session. 
This is a session. 
This is a session. 

Parameters

The kernel passes a number of parameters to each handler as an array. You should access the individual parameters using numeric constants that are defined by the POE modules. Some of the constants are OBJECT, SESSION, STATE and SENDER.

An important parameter is a reference to the parent kernel- $_[KERNEL] This allows each session to call methods supplied by the kernel.

use POE;

POE::Session->create(inline_states => { _start => \&hello,
                                        bloob  => \&bloob    }  );

POE::Kernel->run();

sub hello {
  print "Hello\n";
  $_[KERNEL]->yield('bloob');
}

sub bloob {
  print "Bloob\n"
}

This example adds a event handler called bloob which is called by the _start event using the kernel's yield method.

A session's various states share a local namespace called the heap which is accessed via $_[HEAP].

# Let's save this to heap.pl

use POE;

POE::Session->create(inline_states => { _start => \&_start,
                                        bloob  => \&bloob    },

                     heap => {heapvar1 => q(This is heapvar1 which was assigned during the session\'s initialization.)} );

POE::Kernel->run();

sub _start {
    $_[HEAP]->{heapvar2} = 'This is heapvar2 which was assigned in the _start handler.';
    $_[KERNEL]->yield('bloob');
}

sub bloob {
    print "This is the bloob handler.\n",
    'heapvar1 = ', $_[HEAP]->{heapvar1}, "\n",
    'heapvar2 = ', $_[HEAP]->{heapvar2}, "\n";
}
> perl heap.pl
This is the bloob handler.
heapvar1 = This is heapvar1 which was assigned during the session's initialization.
heapvar2 = This is heapvar2 which was assigned in the _start handler.

You can pass your own parameters and access them via ARGx.

# Let's save this to parameters.pl

use POE;

POE::Session->create(inline_states => { _start => \&hello,
                                        bloob  => \&bloob    } );

POE::Kernel->run();

sub hello {
  $_[KERNEL]->yield('bloob', 'This is a parameter.', 'This is another one.');
}

sub bloob {
    printf("%s %s \n", $_[ARG0], $_[ARG1]);
}

> perl parameters.pl 
This is a parameter. This is another one. 

Constantly referring to $_[KERNEL] or $_[HEAP] can be cumbersome. Adding the following line of code to a handler allows you to use simpler labels.

my ($kernel, $heap, $parameter) = @_[KERNEL, HEAP, ARG0];

_default

If you send an event to a session that doesn't have an appropriate handler, then the session silently drops the event.

# This script will print "start..." and report no errors.

use POE;

POE::Session->create(inline_states => {
                          _start => sub{ print "start...\n";
                                         $_[KERNEL]->yield('bloob') } });

POE::Kernel->run();

You can change this behavior by enabling the default option.

# let's save this script to default.pl

use POE;

POE::Session->create(inline_states => {
                           _start => sub{ print "start...\n";
			                  $_[KERNEL]->yield('bloob') }},

                     options => {default=>1} );

POE::Kernel->run();

> perl default.pl
start...
a 'bloob' event was sent from default.pl at 5 to session 2 (POE::Session=ARRAY(0xa17100)) but session 2 (POE::Session=ARRAY(0xa17100)) has neither a handler for it nor one for _default
a _stop event was sent from /usr/lib/perl5/site_perl/5.8.8/POE/Resource/Sessions.pm at 494 to session 2 (POE::Session=ARRAY(0xa17100)) but session 2 (POE::Session=ARRAY(0xa17100)) has neither a handler for it nor one for _default

Not only did the bloob event cause an error, but the lack of an _exit handler did as well.

You can add a _default handler for these situations.

use POE;

POE::Session->create(inline_states => {
                           _start   => sub{ print "start...\n";
			                    $_[KERNEL]->yield('bloob') },

                           _default => sub{ print "Default.\n" } },
             
	      options => {default=>1} );

POE::Kernel->run();

> perl default.pl
start...
Default.
Default.

The _default handler treats your parameters differently. The event name is assigned to ARGO and ARG1 becomes an array for any of your parameters.

# Let's call this default_params.pl

use POE;

POE::Session->create(inline_states => { _start => \&hello,
                                        bloob  => \&bloob,
                                        _default => \&default } );

POE::Kernel->run();

sub hello {
    $_[KERNEL]->yield('bloob', 'This is a parameter.', 'This is another one.');
    $_[KERNEL]->yield('bloob2', 'This is a parameter.', 'This is another one.');
}

sub bloob {
    printf("%s %s \n", $_[ARG0], $_[ARG1]);
}

sub default {
    printf("%s %s \n", $_[ARG0], $_[ARG1]);
}

> perl default_params.pl 
This is a parameter. This is another one. 
bloob2 ARRAY(0xa176b0) 
_stop ARRAY(0xa17af0) 

If we change the default handler, we'll be able to access the parameters.

sub default {
  # With _default, ARG0 is the name of the event and
  # all other parameters are assigned to ARG1 as an array

  print("\nHandling $_[ARG0]...\n");
  if (@{$_[ARG1]}) {
    print("It received the following parameters-\n");
    my $x=0;
    for my $arg (@{$_[ARG1]}) {
      printf("ARG%u = %s \n", $x++, $arg);
    }
  } else {
    print("It didn't receive any parameters.\n");
  }
}

> perl default_params.pl
This is a parameter. This is another one. 

Handling bloob2...
It received the following parameters-
ARG0 = This is a parameter. 
ARG1 = This is another one. 

Handling _stop...
It didn't receive any parameters.

Assigning the parameters to regular variables will make it more readable.

sub default {

  my($event,$args)=@_[ARG0,ARG1];  

  print("\nHandling $event...\n");
  if (@$args) {
    print("It received the following parameters-\n");
    my $x=0;
    for my $arg (@$args) {
      printf("ARG%u = %s \n", $x++, $arg);
    }
  } else {
    print("It didn't receive any parameters.\n");
  }
}