How to write a POE program

Now that you know the concepts behind POE programming, it's time to dig into a working example and see how it's done.

Simple POE programs contain three main parts:

  1. A preamble where modules are loaded and things are configured,
  2. a main part that instantiates and runs one or more POE::Session objects, and finally
  3. the functions that define the event handlers themselves.

Here then is one of the simplest POE programs that does anything.

The preamble

A program's preamble is fairly straightforward. We write a shebang line and load some modules.

#!/usr/bin/perl

use warnings;
use strict;
use POE;

The POE module hides some magic. It does nothing but load other POE modules, including POE::Kernel and POE::Session whether or not you actually ask for them. It gets away with this because those two modules are required by nearly every POE program.

POE::Kernel includes a little magic of its own. When it's first loaded, it creates the singleton POE::Kernel instance that will be used throughout the program.

POE::Session also performs a little magic when it's used. It exports the constants for event handler parameter offsets: KERNEL, HEAP, ARG0, and so on.

The Session is instantiated and run

We can start creating sessions once everything is set up. At least one session must be started before POE::Kernel is run, otherwise run() will have nothing to do.

In this example, we start a single task that will handle three events: _start, _stop, and count. The POE::Session constructor associates each event with the function that will handle it.

POE::Session->create(
    inline_states => {
        _start => \&session_start,
        _stop  => \&session_stop,
        count  => \&session_count,
      }
);

The first two events are provided by POE::Kernel. They notify a program when a session has just been created or is just about to be destroyed. The last event is a custom one, implemented entirely within our program. We'll discuss it shortly.

You'll notice that the program doesn't save a reference to the new POE::Session object. That's because Session instances automatically register themselves with POE::Kernel. The Kernel singleton will manage them, so programs rarely need to.

In fact, keeping extra references to POE::Session objects can be harmful. Perl will not destroy and reclaim memory for a session if there are outstanding references to it.

Next we start POE::Kernel. This begins the main loop, which detects and dispatches events. Since we're writing a demo, we also announce when POE::Kernel begins and ceases.

print "Starting POE::Kernel.\n";
POE::Kernel->run();
print "POE::Kernel's run() method returned.\n";
exit;

The Kernel's run() method will not return until every session has stopped. We exit shortly after that since the program has effectively ended.

We have kept the redundant exit as a visual reminder that the program won't run beyond run(). It isn't absolutely necessary since the program will automatically stop when its execution falls off the end of the file.

Event handlers are implemented

Appropriately enough, we'll begin covering event handlers with _start. The _start handler is called right after the session is instantiated. Sessions use it to bootstrap themselves within their new contexts. They often initialize values in their heaps and allocate some sort of resources to keep themselves alive.

In this case, we set up an accumulator for counting and queue an event to trigger the next handler.

sub session_start {
    print "Session ", $_[SESSION]->ID, " has started.\n";
    $_[HEAP]->{count} = 0;
    $_[KERNEL]->yield("count");
}

Readers familiar with threads will find the yield() method confusing. Rather than suspending a session's execution, it places a new event near the end of the dispatch queue. That event will trigger another event handler after all the events before it take their turn. This may become clearer when we cover multitasking later on.

We can simulate the classic behavior of yield() by returning immediately after calling it, which we have done here.

Next comes the _stop handler. POE::Kernel dispatches it after a session has gone idle and just before it's destroyed for good. It's impossible to stop a session from being destroyed once _stop is reached.

sub session_stop {
    print "Session ", $_[SESSION]->ID, " has stopped.\n";
}

It's not useful to post events from a _stop handler. Part of a session's destruction involves cleaning up any resources still associated with it. That includes events, so any created in a _stop handler will be destroyed before they can be dispatched. This catches a lot of people off-guard.

Finally we have the count event's handler. This function increments the session's accumulator a few times and prints each value. We could have implemented it as a while loop, but we've avoided it for reasons that should become apparent shortly.

sub session_count {
    my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
    my $session_id = $_[SESSION]->ID;

    my $count = ++$heap->{count};
    print "Session $session_id has counted to $count.\n";

    $kernel->yield("count") if $count < 10;
}

The last line of session_count() posts another count event for as long as the accumulator is less than ten. This perpetuates the session, since each new event triggers session_count() again.

The session stops when yield() is no longer called. POE::Kernel detects that no more event handlers will be triggered, and it destroys the idle session.

Here then is the single counter program's output:

  Session 2 has started.
  Starting POE::Kernel.
  Session 2 has counted to 1.
  Session 2 has counted to 2.
  Session 2 has counted to 3.
  Session 2 has counted to 4.
  Session 2 has counted to 5.
  Session 2 has counted to 6.
  Session 2 has counted to 7.
  Session 2 has counted to 8.
  Session 2 has counted to 9.
  Session 2 has counted to 10.
  Session 2 has stopped.
  POE::Kernel's run() method returned.
And here are some notes about it.

Session IDs begin at 2. POE::Kernel is its own session in many ways, and it is session 1 because it's created first.

Handlers for _start events are called before POE::Kernel->run(). This is a side effect of _start being handled within POE::Session->create().

The first count event, posted by _start's handler, is not handled right away. Rather, it's queued and will be dispatched within POE::Kernel->run().

Sessions stop when they've run out of things to trigger more event handlers. Sessions also stop when they have been destroyed by terminal signals, but we won't see that happen here.

POE::Kernel's run() method returns after the last session has stopped.

This is the full program:

#!/usr/bin/perl
use warnings;
use strict;
use POE;
POE::Session->create(
  inline_states => {
    _start => \&session_start,
    _stop  => \&session_stop,
    count  => \&session_count,
  }
);
print "Starting POE::Kernel.\n";
POE::Kernel->run();
print "POE::Kernel's run() method returned.\n";
exit;

sub session_start {
  print "Session ", $_[SESSION]->ID, " has started.\n";
  $_[HEAP]->{count} = 0;
  $_[KERNEL]->yield("count");
}

sub session_stop {
  print "Session ", $_[SESSION]->ID, " has stopped.\n";
}

sub session_count {
  my ($kernel, $heap) = @_[KERNEL, HEAP];
  my $session_id = $_[SESSION]->ID;
  my $count      = ++$heap->{count};
  print "Session $session_id has counted to $count.\n";
  $kernel->yield("count") if $count < 10;
}



Subsecciones
Casiano Rodríguez León
2012-02-29