POE::Session - an event driven abstract state machine |
POE::Session - an event driven abstract state machine
# Import POE::Session constants. use POE::Session;
POE::Session->create(
# Inline or coderef states. inline_states => { state_one => \&coderef_one, state_two => sub { ... }, },
# Plain and mapped object states. object_states => [ $object_one => [ 'state_three', 'state_four', 'state_five' ], $object_two => { state_nine => 'method_nine' }, ],
# Plain and mapped package states. package_states => [ $package_one => [ 'state_six', 'state_seven', 'state_eight' ], $package_two => { state_ten => 'method_ten' }, ],
# Parameters for the session's _start state. args => [ argument_zero, argument_one, ... ],
# Initial options. See the option() method. options => \%options,
# Change the session's heap representation. heap => [ ], );
Other methods:
# Retrieve a session's unique identifier. $session_id = $session->ID;
# Retrieve a reference to the session's heap. $session_heap = $session->get_heap();
# Set or clear session options. $session->option( trace => 1, default => 1 ); $session->option( trace );
# Create a postback, then invoke it and pass back additional # information. $postback_coderef = $session->postback( $state_name, @state_args ); $postback_coderef->( @additional_args );
# Or do the same thing synchronously $callback_coderef = $session->callback( $state_name, @state_args ); $retval = $callback_coderef->( @additional_args );
POE::Session combines a runtime context with an event driven state machine. Together they implement a simple cooperatively timesliced thread.
Sessions receive their timeslices as events from POE::Kernel. Each event has two fields, a state name and a session identifier. These fields describe the code to run and the context to run it in, respectively. Events carry several other fields which will be discussed in the ``Predefined Event Fields'' section.
States are re-entrant since they are invoked with their runtime contexts. Although it's not usually necessary, this re-entrancy allows a single function to be bound to several different sessions, under several different state names.
As sessions run, they post new events through the Kernel. These events may be for themselves or other sessions, in which case they act as a form of inter-session communications. The Kernel can also generate events based on external conditions such as file activity or the passage of time.
POE provides some convenient built-in states with special meanings. They will be covered later on in the ``Predefined States'' section.
ID()
returns the session instance's unique identifier. This is a
number that starts with 1 and counts up forever, or until something
causes the number to wrap. It's theoretically possible that session
IDs may collide after about 4.29 billion sessions have been created.
create()
is the recommended Session constructor. It binds states to
their corresponding event names, initializes other parts of the
session, and then fires off its _start
state, possibly with some
parameters.
create's parameters look like a hash of name/value pairs, but it's
really just a list. create()
is preferred over the older, more DWIMmy
new()
constructor because each kind of parameter is explicitly named.
This makes it easier for maintainers to understand the constructor
call, and it lets the constructor unambiguously recognize and validate
parameters.
create()
returns a reference to the newly created session but it
is recommended not to save this. POE::Kernel manages sessions and
will ensure timely destruction of them as long as extra references to
them aren't hanging around.
args
parameter accepts a reference to a list of parameters that
will be passed to the machine's _start
state. They are passed in
the _start
event's ARG0..$#_
fields.
args => [ 'arg0', 'arg1', 'etc.' ],
sub _start { my @args = @_[ARG0..#$_]; print "I received these parameters from create()'s args: @args\n"; }
heap
parameter defines a session's heap. The heap is passed
into states as the $_[HEAP] field. Heaps are anonymous hash
references by default.
POE::Session->create( ..., heap => { runstate_variable => 1 }, ... );
sub state_function { my $heap = $_[HEAP]; print "runstate variable is $heap->{runstate_variable}\n"; }
It's also possible to use create's heap
parameter to change the
heap into something completely different, such as a list reference or
even an object.
sub RUNSTATE_VARIABLE () { 0 } # offset into the heap POE::Session->create( ..., heap => [ 1 ], ... );
sub state_function { my $heap = $_[HEAP]; print "runstate variable is ", $heap->[RUNSTATE_VARIABLE], "\n"; }
inline_states
maps events names to the plain coderefs which will
handle them. Its value is a reference to a hash of event names and
corresponding coderefs.
inline_states => { _start => sub { print "arg0=$_[ARG0], arg1=$_[ARG1], etc.=$_[ARG2]\n"; } _stop => \&stop_handler, },
These states are called ``inline'' because they can be inline anonymous subs.
object_states
maps event names to the object methods which will
handle them. Its value is a arrayref of object references and the
methods to use. It's a arrayref because using a hashref would
stringify its keys, and the object references would become unusable.
The object's methods can be specified in two ways.
The first form associates a arrayref to each object reference. This
form maps each event to an object method with the same name. In this
example, event_one
is handled by $object
's event_one()
method.
object_states => [ $object => [ 'event_one', 'event_two' ], ];
The second form associates a hashref to each object reference. In
turn, the hashref maps each event name to a method in the object. In
this form, the object's method names needn't match the event names
they'll handle. For example, event_four
is handled by $object's
handler_four()
method.
object_states => [ $object => { event_three => 'handler_three', event_four => 'handler_four', } ];
options
contains a new session's initial options. It's equivalent
to creating the session and then calling its option()
method to set
them. HASHREF contains a set of option/value pairs.
These two statements are equivalent:
POE::Session->create( ..., options => { trace => 1, debug => 1 }, ..., );
POE::Session->create( ..., )->option( trace => 1, debug => 1 );
See the option()
method for a list of options and values.
package_states
maps event names to the package methods which will
handle them. It's very similar to object_states
.
package_states
' value is a arrayref of package names and the
methods to use. It's a arrayref for consistency with object_states
.
The package's methods can be specified in two ways.
The first form associates a arrayref to each package name. This form
maps each event to a package method with the same name. In this
example, event_ten
is handled by Package
's event_ten()
method.
package_states => [ Package => [ 'event_ten', 'event_eleven' ], ];
The second form associates a hashref to each package name. In turn,
the hashref maps each event name to a method in the package. In this
form, the package's method names needn't match the event names they'll
handle. For example, event_twelve
is handled by Package
's
handler_twelve()
method.
package_states => [ Package => { event_twelve => 'handler_twelve', event_thirteen => 'handler_thirteen', } ];
new()
is deprecated as per POE's roadmap.
new()
was Session's older constructor. Its design was clever at the
time, but it didn't expand well.
new()
returned a reference to the newly created session but it is
recommended not to save this. POE::Kernel manages sessions and will
ensure timely destruction of them as long as extra references to them
aren't hanging around.
Inline states, object states, package states, and _start arguments were all inferred by their contexts. This context sensitivity made it harder for maintainers to understand what's going on, and it allowed errors to be interpreted as different behavior.
Inline states were specified as a scalar mapped to a coderef.
event_one => \&state_one, event_two => sub { ... },
The equivalent for create()
is
inline_states => { event_one => \&state_one, event_two => sub { ... }, },
Object states were specified as object references mapped to list or hash references. Objects that are mapped to arrayrefs will handle events with identically named methods.
$object_one => [ 'event_one', 'event_two' ],
Objects that are mapped to hashrefs can handle events with differently named methods.
$object_two => { event_ten => 'method_foo', event_eleven => 'method_bar' },
the equivalent of these for create is as follows
object_states => [ $object_one => [ 'event_one', 'event_two' ], $object_two => { event_ten => 'method_foo', event_eleven => 'method_bar' }, ],
Package states were specified as package names mapped to list or hash references. Package names that are mapped to arrayrefs will handle events with identically named methods.
PackageOne => [ 'event_five', 'event_six' ],
Package names that are mapped to hashrefs can handle events with differently named methods.
PackageTwo => { event_seven => 'method_baz', event_eight => 'method_quux' },
For these, the equivalent create()
syntax is
package_states => [ PackageOne => [ 'event_five', 'event_six' ], PackageTwo => { event_seven => 'method_baz', event_eight => 'method_quux' }, ], Arguments for the C<_start> state were specified as arrayrefs.
[ 'arg0', 'arg1', ... ],
And this is how to do it for create()
args => [ 'arg0', 'arg1', ... ],
option()
sets and/or retrieves options' values.
The first form returns the value of a single option, OPTION_NAME, without changing it.
my $trace_value = $_[SESSION]->option( 'trace' );
The second form sets OPTION_NAME to OPTION_VALUE, returning the previous value of OPTION_NAME.
my $old_trace_value = $_[SESSION]->option( trace => $new_trace_value );
The final form sets several options, returning a hashref containing pairs of option names and their previous values.
my $old_values = $_[SESSION]->option( trace => $new_trace_value, debug => $new_debug_value, ); print "Old option values:\n"; while (my ($option, $old_value) = each %$old_values) { print "$option = $old_value\n"; }
postback()
and callback()
create anonymous coderefs that may be used
as callbacks for other libraries. A contrived example:
my $postback = $session->postback( event_one => 1, 2, 3 ); my $callback = $session->callback( event_two => 5, 6, 7 );
use File::Find; find( $callback, @directories_to_search );
$poe_main_window->Button( -text => 'Begin Slow and Fast Alarm Counters', -command => $postback, )->pack;
When called, postbacks and callbacks fire POE events. Postbacks use
$kernel->post(), and callbacks use $kernel->call(). See POE::Kernel
for post()
and call()
documentation.
Each takes an EVENT_NAME, which is the name of the event to fire when called. Any other EVENT_PARAMETERS are passed to the event's handler as a list reference in ARG0.
Calling <$postback-
(``a'', ``b'', ``c'')>> results in event_one's handler
being called with the following arguments:
sub handle_event_one { my $passed_through = $_[ARG0]; # [ 1, 2, 3 ] my $passed_back = $_[ARG1]; # [ "a", "b", "c" ] }
Calling <$callback-
(``m'', ``n'', ``o'')>> does the same:
sub handle_event_two { my $passed_through = $_[ARG0]; # [ 5, 6, 7 ] my $passed_back = $_[ARG1]; # [ "m", "n", "o" ] }
Therefore you can use ARG0 to pass state through a callback, while ARG1 contains information provided by the external library to its callbacks.
Postbacks and callbacks use reference counts to keep the sessions they are called upon alive. This prevents sessions from disappearing while other code expects them to handle callbacks. The Tk Button code above is an example of this in action. The session will not stop until the widget releases its postback.
The difference between postback()
and callback()
is subtle but can
cause all manner of grief if you are not aware of it. Postback
handlers are not called right away since they are triggered by events
posted through the queue. Callback handlers are invoked immediately
since they are triggered by call().
Some libraries expect their callbacks to be invoked immediately. They
may go so far as to set up global variables for the duration of the
callback. File::Find is such a library. Each callback receives a new
filename in $_. Delaying these callbacks until later means that $_
will not contain expected values. It is necessary to use callback()
in these cases.
Most libraries pass state to their callbacks as parameters.
Generally, postback()
is the way to go.
Since postback()
and callback()
are Session methods, they may be
called on $_[SESSION] or $_[SENDER], depending on particular needs.
There are usually better ways to interact between sessions, however.
get_heap()
returns a reference to a session's heap. It's the same
value that's passed to every state via the HEAP
field, so it's not
necessary within states.
Combined with the Kernel's get_active_session()
method,
get_heap()
lets libraries access a Session's heap without having to
be given it. It's convenient, for example, to write a function like
this:
sub put_stuff { my @stuff_to_put = @_; $poe_kernel->get_active_session()->get_heap()->{wheel}->put( @stuff_to_put ); }
sub some_state { ...; &put_stuff( @stuff_to_put ); }
While it's more efficient to pass HEAP
along, it's also less
convenient.
sub put_stuff { my ($heap, @stuff_to_put) = @_; $heap->{wheel}->put( @stuff_to_put ); }
sub some_state { ...; &put_stuff( $_[HEAP], @stuff_to_put ); }
Although if you expect to have a lot of calls to &put_a_wheel() in your program, you may want to optimize for programmer efficiency by using the first form.
There are a few methods available to help people trying to subclass the POE::Session manpage.
The easiest way to do this is by overriding the instantiate method, which creates an empty object for you, and is passed a reference to the hash of parameters passed to create().
When overriding it, be sure to first call the parent classes instantiate
method, so you have a reference to the empty object. Then you should remove
all the extra parameters from the hash of parameters you get passed, so
the POE::Session manpage's create()
doesn't croak when it encounters parameters it
doesn't know.
Also, don't forget to return the reference to the object (optionally already filled with your data; try to keep out of the places where the POE::Session manpage stores its stuff, or it'll get overwritten)
create()
method, try_alloc()
is called.
This tells the POE Kernel to allocate an actual session with the object
just created.
If you want to fiddle with the object the constructor just created, to
modify parameters that already exist in the base the POE::Session manpage class,
based on your extra parameters for example, this is the place to do it.
override the try_alloc()
method, do your evil, and end with calling
the parent try_alloc(), returning its return value.
try_alloc()
is passed the arguments for the _start state (the contents of
the listref passed in the 'args' parameter for create()). Make sure to pass
this on to the parent method (after maybe fiddling with that too).
Each session maintains its unique runtime context. Sessions pass their contexts on to their states through a series of standard parameters. These parameters tell each state about its Kernel, its Session, itself, and the events that invoke it.
State parameters' offsets into @_ are never used directly. Instead they're referenced by symbolic constant. This lets POE to change their order without breaking programs, since the constants will always be correct.
These are the @_ fields that make up a session's runtime context.
ARG0..ARG9
are a state's first ten custom parameters. They will
always be at the end of @_
, so it's possible to access more than
ten parameters with $_[ARG9+1]
or even this:
my @args = @_[ARG0..$#_];
The custom parameters often correspond to PARAMETER_LIST in many of
the Kernel's methods. This passes the words ``zero'' through ``four'' to
some_state
as @_[ARG0..ARG4]
:
$_[KERNEL]->yield( some_state => qw( zero one two three four ) );
HEAP
is a session's unique runtime storage space. It's separate
from everything else so that Session authors don't need to worry about
namespace collisions.
States that store their runtime values in the HEAP
will always be
saving it in the correct session. This makes them re-entrant, which
will be a factor when Perl's threading stops being experimental.
sub _start { $_[HEAP]->{start_time} = time(); }
sub _stop { my $elapsed_runtime = time() - $_[HEAP]->{start_time}; print 'Session ', $_[SESSION]->ID, " elapsed runtime: $elapsed_runtime\n"; }
KERNEL
is a reference to the Kernel. It's used to access the
Kernel's methods from within states.
# Fire a "time_is_up" event in ten seconds. $_[KERNEL]->delay( time_is_up => 10 );
It can also be used with SENDER
to make sure Kernel events have
actually come from the Kernel.
OBJECT
is only meaningful in object and package states.
In object states, it contains a reference to the object whose method is being invoked. This is useful for invoking plain object methods once an event has arrived.
sub ui_update_everything { my $object = $_[OBJECT]; $object->update_menu(); $object->update_main_window(); $object->update_status_line(); }
In package states, it contains the name of the package whose method is being invoked. Again, it's useful for invoking plain package methods once an event has arrived.
sub Package::_stop { $_[PACKAGE]->shutdown(); }
OBJECT
is undef in inline states.
SENDER
is a reference to the session that sent an event. It can be
used as a return address for service requests. It can also be used to
validate events and ignore them if they've come from unexpected
places.
This example shows both common uses. It posts a copy of an event back to its sender unless the sender happens to be itself. The condition is important in preventing infinite loops.
sub echo_event { $_[KERNEL]->post( $_[SENDER], $_[STATE], @_[ARG0..$#_] ) unless $_[SENDER] == $_[SESSION]; }
SESSION
is a reference to the current session. This lets states
access their own session's methods, and it's a convenient way to
determine whether SENDER
is the same session.
sub enable_trace { $_[SESSION]->option( trace => 1 ); print "Session ", $_[SESSION]->ID, ": dispatch trace is now on.\n"; }
STATE
contains the event name that invoked a state. This is useful
in cases where a single state handles several different events.
sub some_state { print( "some_state in session ", $_[SESSION]-ID, " was invoked as ", $_[STATE], "\n" ); }
POE::Session->create( inline_states => { one => \&some_state, two => \&some_state, six => \&some_state, ten => \&some_state, } );
my ($caller_file, $caller_line, $caller_state) = @_[CALLER_FILE, CALLER_LINE, CALLER_STATE];
The file, line number, and state from which this state was called.
POE contains helpers which, in order to help, need to emit predefined events. These events all being with a single leading underscore, and it's recommended that sessions not post leading-underscore events unless they know what they're doing.
Predefined events generally have serious side effects. The _start
event, for example, performs a lot of internal session initialization.
Posting a redundant _start
event may try to allocate a session that
already exists, which in turn would do terrible, horrible things to
the Kernel's internal data structures. Such things would normally be
outlawed outright, but the extra overhead to check for them would slow
everything down all the time. Please be careful! The clock cycles
you save may be your own.
These are the predefined events, why they're emitted, and what their parameters mean.
_child
is a job-control event. It notifies a parent session when
its set of child sessions changes.
ARG0
contains one of three strings describing what is happening to
the child session.
ARG1
is a reference to the child session. It will still be valid,
even if the child is in its death throes, but it won't last long
enough to receive posted events. If the parent must interact with
this child, it should do so with call()
or some other means.
ARG2
is only valid when a new session has been created or an old
one destroyed. It holds the return value from the child session's
_start
or _stop
state when ARG0
is 'create' or 'lose',
respectively.
_default
is the event that's delivered whenever an event isn't
handled. The unhandled event becomes parameters for _default
.
It's perfectly okay to post events to a session that can't handle
them. When this occurs, the session's _default
handler is invoked
instead. If the session doesn't have a _default
handler, then the
event is quietly discarded.
Quietly discarding events is a feature, but it makes catching mistyped
event names kind of hard. There are a couple ways around this: One is
to define event names as symbolic constants. Perl will catch typos at
compile time. The second way around it is to turn on a session's
debug
option (see Session's option()
method). This makes
unhandled events hard runtime errors.
As was previously mentioned, unhandled events become _default
's
parameters. The original state's name is preserved in ARG0
while
its custom parameter list is preserved as a reference in ARG1
.
sub _default { print "Default caught an unhandled $_[ARG0] event.\n"; print "The $_[ARG0] event was given these parameters: @{$_[ARG1]}\n"; }
All the other _default
parameters are the same as the unhandled
event's, with the exception of STATE
, which becomes _default
.
the POE::Kernel manpage discusses signal handlers in ``Signal Watcher Methods''.
It also covers the pitfalls of _default
states in more detail
_parent
It notifies child sessions that their parent sessions are
in the process of changing. It is the complement to _child
.
ARG0
contains the session's previous parent, and ARG1
contains
its new parent.
_start
is a session's initialization event. It tells a session
that the Kernel has allocated and initialized resources for it, and it
may now start doing things. A session's constructors invokes the
_start
handler before it returns, so it's possible for some
sessions' _start
states to run before $poe_kernel->run()
is called.
Every session must have a _start
handler. Its parameters are
slightly different from normal ones.
SENDER
contains a reference to the new session's parent. Sessions
created before $poe_kernel->run()
is called will have KERNEL
as
their parents.
ARG0..$#_
contain the parameters passed into the Session's
constructor. See Session's new()
and create()
methods for more
information on passing parameters to new sessions.
_stop
is sent to a session when it's about to stop. This usually
occurs when a session has run out of events to handle and resources to
generate new events.
The _stop
handler is used to perform shutdown tasks, such as
releasing custom resources and breaking circular references so that
Perl's garbage collection will properly destroy things.
Because a session is destroyed after a _stop
handler returns, any
POE things done from a _stop
handler may not work. For example,
posting events from _stop
will be ineffective since part of the
Session cleanup is removing posted events.
ARG0
contains the signal's name as it appears in Perl's %SIG hash.
That is, it is the root name of the signal without the SIG prefix.
POE::Kernel discusses exceptions to this, namely that CLD will be
presented as CHLD.
The ``Signal Watcher Methods'' section in the POE::Kernel manpage is recommended reading before using signal events. It discusses the different signal levels and the mechanics of signal propagation.
States are always evaluated in a scalar context. States that must return more than one value should therefore return them as a reference to something bigger.
States may not return references to objects in the ``POE'' namespace. The Kernel will stringify these references to prevent them from lingering and breaking its own garbage collection.
POE::Kernel tracks resources on behalf of its active sessions. It generates events corresponding to these resources' activity, notifying sessions when it's time to do things.
The conversation goes something like this.
Session: Be a dear, Kernel, and let me know when someone clicks on this widget. Thanks so much!
[TIME PASSES] [SFX: MOUSE CLICK]
Kernel: Right, then. Someone's clicked on your widget. Here you go.
Furthermore, since the Kernel keeps track of everything sessions do,
it knows when a session has run out of tasks to perform. When this
happens, the Kernel emits a _stop
event at the dead session so it
can clean up and shutdown.
Kernel: Please switch off the lights and lock up; it's time to go.
Likewise, if a session stops on its own and there still are opened resource watchers, the Kernel knows about them and cleans them up on the session's behalf. POE excels at long-running services because it so meticulously tracks and cleans up its resources.
While time's passing, however, the Kernel may be telling Session other things are happening. Or it may be telling other Sessions about things they're interested in. Or everything could be quiet... perhaps a little too quiet. Such is the nature of non-blocking, cooperative timeslicing, which makes up the heart of POE's threading.
Some resources must be serviced right away, or they'll faithfully continue reporting their readiness. These reports would appear as a stream of duplicate events, which would be bad. These are ``synchronous'' events because they're handled right away.
The other kind of event is called ``asynchronous'' because they're posted and dispatched through a queue. There's no telling just when they'll arrive.
Synchronous event handlers should perform simple tasks limited to handling the resources that invoked them. They are very much like device drivers in this regard.
Synchronous events that need to do more than just service a resource should pass the resource's information to an asynchronous handler. Otherwise synchronous operations will occur out of order in relation to asynchronous events. It's very easy to have race conditions or break causality this way, so try to avoid it unless you're okay with the consequences.
Many external libraries expect plain coderef callbacks, but sometimes
programs could use asynchronous events instead. POE::Session's
postback()
method was created to fill this need.
postback()
creates coderefs suitable to be used in traditional
callbacks. When invoked as callbacks, these coderefs post their
parameters as POE events. This lets POE interact with nearly every
callback currently in existing, and most future ones.
Sessions are resources, too. The Kernel watches sessions come and go,
maintains parent/child relationships, and notifies sessions when these
relationships change. These events, _parent
and _child
, are
useful for job control and managing pools of worker sessions.
Parent/child relationships are maintained automatically. ``Child'' sessions simply are ones which have been created from an existing session. The existing session which created a child becomes its ``parent''.
A session with children will not spontaneously stop. In other words, the presence of child sessions will keep a parent alive.
POE traps exceptions that happen within an event. When an exception
occurs, POE sends the DIE
signal to the session that caused the
exception. This is a terminal signal and will shutdown the POE
environment unless the session handles the signal and calls
sig_handled()
.
This behavior can be turned off by setting the CATCH_EXCEPTIONS
constant subroutine in POE::Kernel
to 0 like so:
sub POE::Kernel::CATCH_EXCEPTIONS () { 0 }
The signal handler will be passed a single argument, a hashref, containing the following data.
$@
, which contains the error string created by the
exception.
POE::Session contains a two debugging assertions, for now.
Session's ASSERT_DEFAULT inherits Kernel's ASSERT_DEFAULT value unless overridden.
option()
function earlier in this document for details
about the ``default'' option.
POE::Kernel.
The SEE ALSO section in POE contains a table of contents covering the entire POE distribution.
There is a chance that session IDs may collide after Perl's integer value wraps. This can occur after as few as 4.29 billion sessions.
Please see POE for more information about authors and contributors.
POE::Session - an event driven abstract state machine |