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:
Here then is one of the simplest POE programs that does anything.
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.
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.
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. |
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; }