The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

POE::Session::Multiplex - POE session with object multiplexing

SYNOPSIS

    use POE;
    use POE::Session::Multiplex;

    My::Component->spawn( @args );
    $poe_kernel->run;

    package My::Component;

    sub spawn {
        my( $package, @args ) = @_;
        POE::Session::Multiplex->create(
                        package_states => [
                                $package => [ qw( _start ) ]
                                'My::Component::Object' => 
                                        [qw( fetch decode back )] 
                            ]
                        args => [ @args ]
                    );
    }

    ##### To add an object to the session:
    my $obj = My::Component::Object->new();
    $_[SESSION]->object( $name, $obj );
        # or
    $_[SESSION]->object_register( name => $name, object => $obj );

    ##### To remove an object
    $_[SESSION]->object_unregister( $name );


    ##### Address an event to the current object:
    my $state = ev"state";

    ##### To build a session/event tuple addressed to the current object
    my $rsvp = rsvp "state";
    
    # this tuple would be useful for getting a response from another session
    $poe_kernel->post( $session=>'fetch', $params, rsvp"fetch_back" );
    
    # and in the 'fetch' handler, you could reply with:
    my $rsvp = $_[ARG1];
    $poe_kernel->post( @$rsvp, $reply );

    ##### Posting to a specific object from inside the session
    $poe_kernel->yield( evo( $name, $state ), @args );

    ##### Posting to a specific object from outside the session
    $poe_kernel->post( evos( $session, $name, $state ), @args );
    $poe_kernel->post( $session, evo( $name, $state ), @args );

DESCRIPTION

POE::Session::Multiplex allows you to have multiple objects handling events from a single POE::Session.

A standard POE design is to have one POE::Session per object and to address each object using session IDs or aliases. POE::Session::Multiplex takes the oposite approach; there is only one session and each object is addressed by manipulating the event name.

The advantage is that you save the overhead of multiple sessions. While session creation is very fast, the POE kernel garbage collection must continually verify that each session should still be alive. For a system with many sessions this could represent a non-trivial task.

Overview

Each object has a name associated with it. Events are addressed to the object by including the object's name in the event name. When invoked POE::Session::Multiplex then seperates the object name from the event name and calls an event handler on the object.

Objects are made available for multiplexing with "object_register". They are removed with "object_unregister".

POE::Session::Multiplex provides handy routines to do the event name manipulation. See "HELPER FUNCTIONS".

Event handlers for a class (aka package) must be defined before hand. This is done with either "package_register" or POE::Session's package_states.

POE::Session::Multiplex keeps a reference to all objects. This means that DESTROY will not be called until you unregister the object. It also means that you don't have to keep track of your objects. See "object_get" if you want to retrieve an object.

Objects passed to the session via object_states are currently not multiplexed, though their events are available to objects of the same class. This could change in the future.

Event methods

POE::Session::Multiplex makes sure that a given event handler method has been set up for a given object. That is, if you define an event for a certain class, that event is not available for objects of other classes, unless they are descendents of the first class.

For example, a session is created with the following.

    POE::Session::Multiplex->create(
                    # ...
                    package_states => [
                            Class1 => [ qw( load save ) ],
                            Class2 => [ qw( marshall demarshall ) ],
                        ],
                    # ...
                );

Objects of Class1 are only accessible via the load and save events and objects of Class2 are only accessible via marshall and demarshall. Unless Class2 is a sub-class of Class1 in which case all 4 events are available.

POE::Session::Multiplex does the same thing with object_states. UNIVERSAL::isa is used to verify that 2 objects are of the same class.

_start and _stop vs _psm_begin and _psm_end

The _start event is invoked directly by POE. This means that no object will be associated with the event and that the helper functions will not work. However, when an object is registered, its "_psm_begin" handler is called and when it is unregistered, its "_psm_end" handler is called. The _start handler then becomes a place to register an alias, create and register one or more objects. Furthur initialisation can happen in "_psm_begin".

    sub _start {
        my( $package, $session, @args ) = @_[OBJECT,SESSION,ARG0..$#_];
        $poe_kernel->alias_set( 'multiplex' );
        $session->object( main => $package->new( @args ) );
    }

    sub _psm_begin {
        my( $self, @args ) = @_[OBJECT,ARG0..$#_];
        $poe_kernel->sig( CHLD => ev"sig_CHLD" );
        $poe_kernel->sig( INT  => ev"sig_INT" );
        # ....
    }

Examples

When creating a socket factory, we use "ev" to create an event name addressed to the current object:

    package Example;
    use strict;
    use warnings;
    use POE;
    use POE::Session::Multiplex;
    use POE::Wheel::SocketFactory;
    
    sub spawn {
        my( $package, $params ) = @_;
        POE::Session::Multiplex->create(
                args => [ $params ],
                package_states => [
                    $package => [ qw( _start _psm_begin connected error ) ]
                ] );
    }

    sub _start {
        my( $package, $session, $params ) = @_[OBJECT,SESSION,ARG0];
        # We can't call call open_connection(), because ev() won't
        # have a current object.
        # So we create an object
        my $obj = $package->new( $params );
        # And register it.
        $session->object( listener => $obj );
        # This will cause _psm_begin to be invoked
    }

    sub new {
        my( $package, $params ) = @_;
        return bless { params=>$params }, $package;
    }

    # we now have a 'current' object, so open_connection() may call ev() without
    # worries
    sub _psm_begin {
        my( $self ) = @_;
        $self->open_connection( $self->{params} );
    }

    sub open_connection {
        my( $self, $params ) = @_[OBJECT, ARG0];
        $self->{wheel} = POE::Wheel::SocketFactory->new(
                            %$params,
                            SuccessEvent => ev "connected",
                            FailureEvent => ev "error"
                        );
    }

When sending a request to another session, we use "rsvp" to create an event that is addressed tot he current object and session:

    $poe_kernel->post( $session, 'sum', 
                        [ 1, 2, 3, 4 ], rsvp "reply" 
                     );

$session's sum event handler would then be:

    sub sum_handler {
        my( $array, $reply ) = @_[ ARG0, ARG1 ];
        my $tot = 0;
        foreach ( @$array ) { $tot += $_ }
        $_[KERNEL]->post( @$reply, $tot );
    }

This could also have been implemented as:

    $poe_kernel->post( $session, 'sum', 
                        [ 1, 2, 3, 4 ], ev "reply" 
                     );

    sub sum_handler {
        my( $array, $reply ) = @_[ ARG0, ARG1 ];
        # ...
        $_[KERNEL]->post( $_[SENDER], $reply, $tot );
    }

Limits

It is impossible to multiplex events that are sent from the POE kernel. Specifically, _start, _stop, _child and _parent can not be multiplexed. Use "_being" and "_psm_end" or _start and _stop. For _child and _parent, use a call to the right object:

    sub _child {
        my( $self, $session, @args ) = @_[OBJECT,SESSION,ARG0..$#_];
        my $call = evo $self->{name}, "poe_child";
        $poe_kernel->call( $session, $call, @args );
    }

    sub poe_child {
        my( $self, $reason, $child, $retval ) = @_[OBJECT,ARG0,ARG1,ARG2];
        # Do the work ...
    }

Object Names

POE::Session::Multiplex requires each object to have a name. If you do not supply one when registering an object, the method __name is called to fetch the name. This is a crude form of meta-object protocol. If your object does not implement the __name method, a name is generated from the stringised object reference.

Note

This documentation tries to consistently use the proper term 'event' to refer to POE's confusingly named 'state'.

Event Names

Currently POE::Session::Multiplex uses event names of the form NAME->EVENT to address EVENT to the object named NAME. BUT YOU MUST NOT DEPEND ON THIS BEHAVIOUR. It could very well change to EVENT@NAME or anything else in the future. Please use the event helper functions provided.

EVENTS

POE::Session::Multiplex provides 2 object management events: _psm_begin and _psm_end. They are invoked synchronously whenever an object is registered or unregistered.

_psm_begin

_psm_begin is invoked when an object is registered. This is roughly equivalent to POE's _start. Helper functions like "ev" will have a default object to work with.

_psm_end

_psm_end is invoked when an object is registered. This is roughly equivalent to POE's _stop. However, there is no guarantee that _psm_end will be called; if a session is destroyed before an object is unregistering _psm_end won't be called. If _psm_end is necessary, you must explicitly unregister the object:

    sub _stop {
        my $session = $_[SESSION];
        foreach my $name ( $session->object_list ) {
            $session->object_unregister( $name );
        }
    }

METHODS

create

    POE::Session::Multiplex->create( @lots_of_stuff );

Creates a new multiplexed POE::Session. No new parameters are defined by POE::Session::Multiplex. Parameters of interest to this module are package_states and object_states; they define event -> object method mappings that are also used by POE::Session::Multiplex. Objects referenced in ojbect_states are currently not multiplexed.

object_register

    $_[SESSION]->object_register( $object );
    $_[SESSION]->object_register( name => $name,
                                  object => $object,
                                  events => $events
                                );

Register a new $object named $name with the session. Optionally creating POE states in $events.

object

The object to be registered with the session. Required.

name

The name of the object being registered. If omitted, "object_register" will attempt to get an object name via a __name method call. If this method isn't available, a stringised object reference is used.

If an object with the same name has already registered, that object is unregistered.

events

Optional hashref or arrayref of POE events to create. If it is a hashref, keys are POE event names, values are the names of event handler methods.

    events => { load => 'load_handler', save => 'save_handler }

If you create POE events with an object, they are available to other objects of the same class. However, they will be removed when this object is unregistered. If you do not want this, use "package_register".

If defined, the "_psm_begin" event handler is invoked when an object is registered.

object_get

    my $obj = $_[SESSION]->object_get( $name );

Returns the object named $name.

object_list

    my @list = $_[SESSION]->object_list;

Returns a list of names of all the currently registered objects.

object_unregister

    $_[SESSION]->object_unregister( $name );
    $_[SESSION]->object_unregister( $self );

Unregisters an object. This makes the object unavailable for events. Any POE events created when the object was registered are removed.

If defined, the "_psm_end" event handler is invoked.

object

    # Register an object
    $_[SESSION]->object( $name => $self[, $events] );
    $_[SESSION]->object( $self, $events );

    # Unregister an object
    $_[SESSION]->object( $name );
    $_[SESSION]->object( $self );

Syntactic sugar for "object_register" or "object_unregister".

package_register

    $_[SESSION]->package_register( $package, $events );

Creates the POE events defined in $events as package methods of $package. This also makes the events available to all objects of class $package.

It is not currently possible to unregister a package.

HELPER FUNCTIONS

POE::Session::Multiplex exports a few helper functions for manipulating the event names.

ev

    $event = ev "handler";
    $poe_kernel->yield( ev"handler" );

Returns an event name that is addressed to a handler of the current object. Obviously may only be called from within a multiplexed event.

evo

    $state = evo( $name, "handler" );
    $poe_kernel->yield( ev"handler" );

Returns an event name addressed to a handler of the $nameed object. Used when you want to address a specific object.

evs

    $poe_kernel->post( evs "handler", @args );

Returns session/event tuple addressed to handler of the current object. Obviously may only be called from within a multiplexed event.

evos

    $poe_kernel->post( evos( $session, $name, "handler" ), @args );

Returns session/event tuple addressed to a handler of the $nameed object in $session. Currently syntatic sugar for:

    $poe_kernel->post( $session, evo( $name, "handler" ), @args );

rsvp

    my $rsvp = rsvp "handler";

Returns an opaque object that may be used to post an event addressed to a handler of the current object. Obviously may only be called from within a multiplexed event.

rsvp is used by objects to create postbacks. You may pass the rsvp to other objects or sessions. They reply with:

    $poe_kernel->post( @$rsvp, @answer );

FYI, rsvp is from the French Repondez, s'il vous plais. That is, Answer, please in English.

POE::Session::PlainCall

It is unfortunately impossible to have clean multiple inheritance of POE::Session. However, POE::Session::Multiplex is compatible with POE::Session::PlainCall. It does this by checking its inheritance and implementing a few of POE::Session::PlainCall's methods.

If you wish to use both, create a session class as follows:

    package My::Session;
    use base qw( POE::Session::Multiplex POE::Session::PlainCall );

Then use that class to create your sessions:

    My::Session->create( 
                package_states => [],
                args           => \@args
            );

SEE ALSO

POE and POE::Session for details of POE.

Reflex for the final solution.

AUTHOR

Philip Gwyn, <gwyn-at-cpan.org>

COPYRIGHT AND LICENSE

Copyright (C) 2009,2010,2011 by Philip Gwyn

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.8 or, at your option, any later version of Perl 5 you may have available.