=head1 Goal The primary goal of IKC is to propagate a message from it's source (a session/state in kernel A) to it's destination (a session/state in kernel B). Kernel A and B could in fact be the same kernel. Note that we assume one kernel per process. This requires : - A connection - Serialisation - Access control - Naming, routing And an interface to tie all this together. A secondary goal is to make IKC as transparent as possible, so that constituant parts do not realise that an event came from a local or remote kernel. IKC2 should be modular. Things like reconnect should be built on top of as simple a core as possible. Note that IKC2 will not include process management. =head2 Connection Connections can be TCP/IP, unix sockets, or 2 way pipes. The first 2 assume that one kernel is listening to one end point and that another kernel connects to the other end. Connections can come and go. User code needs to be able to monitor this and act accordingly. User code will want to monitor the connection, if only to find out when a remote session session is available / ceases to be avaible / other changes in state. =head2 Serialisation The event destination (ie, session and state name) and its data needs to be serialised to be past over the connection. The serialisation mechanism should be configurable negociatable per connection. This would allow IKC to be extented to other languages. Some data can't be serialised, such as coderefs, filehandles, etc. IKC2 could have some marshalling mechanism that prepares the parameters before they are serialised. An example would be a POE::Wheel::ReadWrite would be turned into an object, that would be turned into some special Whell on the remote side so that events generated on the wheel's native side get propagated to the remote side. =head2 Access control The lowest for of access control is "none". This could be selectable. Next step up, not all session/events should be accessable remotely. A session chooses which events are accessable and another session looks for sessions that allow access to the events it wants. IKC1 uses publish/subscribe, which is misnamed. Session/events should be 'exposed'. Other sessions aren't nessecairly interested in 'session/events', but rather in "interfaces". (Need a verb for "looking for a given interface". Using gank for the moment.) Another abstraction is controling who can access a connection point, either via the firewall, unix access control, SSL certs, SSH keys, etc. Higher up, certain sessions might only want allow certain other sessions to access them. This requires authentification and authorisation. I propose that hooks should provided to enable this, but it not be part of core IKC2. =head2 Naming IKC needs a way of designating Kernel/Session/Event tuples. This allows us to write better documentation. A Kernel/Session/Event tuple shall be called a "destination". POE uses the array (Session, Event). IKC1 uses a scalar which can take 2 forms : string: "poe://Kernel/Session/Event" hash: {kernel=>'Kernel', session=>'Session', event=>'Event'} array: ['Kernel', 'Session', 'Event'] And an array form : ('poe://Kernel/Session', 'Event') IKC2 could allow other string forms LDAP: /K=Kernel/S=Session/E=Event E-mail: Event@Session.Kernel using some sort of plug-in scheme for translating from string form to the hash form. =head2 Routing Routing is deciding with kernel/session gets a message. Should I send it over a connection? Is it in the local kernel? Assume 3 kernels, A, B, C. A and B are connected to C. Imagine C transparently routing messages from A to B. This could be done by having C propagate B's exposed interfaces to A (and vice versa) or by having C say "oh, by the way, you might want to know about B" and then A would connect to B directly. Broadcast messages? Multicast messages? =head1 Complications =head2 automagic In my experience, all forms of automagic and DWIM should be viewed as fraught with peril. Implicit behaviour can lead to suprises. =head2 keepalive What if the connection closes? Should we throw away all the state we have set up regarding that session (proxy sessions, etc). Or do we try to reconnect, saving up messages that are routed to that kernel? (with timeout, of course) =head2 Postbacks By postback, I mean something like # source $poe_kernel->post(session2=>'something', {TellMe=>'favorite colour', Answer=>'query'}); # at the destination $heap->{$id}={event=>$_[ARG0]{Answer}, session=>$_[SENDER]->ID}; #... then eventually $poe_kernel->post($heap->{$id}{session}, $heap->{$id}{event}); Because POE uses a simple string for Event names, and sessions assume they postback to $_[SENDER] and that they can keep $_[SENDER]->ID around, it is next to impossible to have transparent IKC post backs! IKC1 attempts to remedy this by creating a temporary proxy session for $_[SENDER], but it needs to be GCed at some point. IKC1 does it after the destination state is finished. In practice, this isn't long enough. Also, creating a session per message is wasteful, though all messages from a given session could they could be aglomerated. =over 4 =item * $_[SENDER]->ID would return a tied scalar. In it's DESTROY, you shutdown the proxy session. We have to track when the source session disapears and shutdown the proxy session. This is transparent, but can fail in hard to diagnose ways. Say if ID is then only used as key to a hash. =item * $_[SENDER]->ID is a tied scalar. It contains information that will allow POE (via our hooks into POE) to resolve to the remote sender. But then, what about $ses=$poe_kernel->ID_id_to_session($_[SENDER]->ID); and other, related functions? =item * Source session declares itself as "postback-able" to the the destination kernel. That kernel then creates a proxy session and this is used as $_[SENDER]. This requires extra coding at the source end. =item * Source session has to create a special "postback" object. This postback object acts as the 'event' that destination will hold onto. We then have hooks in the kernel which allow us to resolve that event into it's proper destination. This requires extra coding at the source end. If we can't control the source end, we could do an additional trick. We would have to keep a list of all event parameters that are events and then turn those parameters into the special postback object when we marshall the parameters. This last requires extra coding on either side. In fact, I think I like this last idea best. =back =head1 Proposed interface use POE::Component::IKC2; POE::Component::IKC2->spawn({ Name=>[qw(list of aliases)], Listen=>['/path/to/unix/socket', 'localhost:1234', {Host=>'someting.com', Port=>1234}, 1234 # a port ], Connect=>[ # autodiscover kernel names '/path/to/mutter/bump', 'somewhere.org', # assumes port 903 'appserv.localdomain:1234', {Host=>'name', Port=>1234} ], Kernels=>{ Name1=>{Host=>'HONKEROONIE.nu'}, Zitzit=>'somewhere.org:4321' }, AutoGank=>1, # grab all remote interfaces }); Knowing a kernel's name before hand allows us to deffer connection until the a messages is actually posted to that kernel. Deal with connections later IKC2->connect({Host=>'name'}); IKC2->disconnect('Kernel-name'); IKC2->disconnect({'somewhere.org'}); # all connection to this host IKC2->kernel_remove('name'); # kill that kernel IKC2->kernel_add(name=>{how to reach it}); IKC2->kernel_modify(name=>{how to reach it}); or $poe_kernel->post(IKC2 => connect => {Host=>'name'}); $poe_kernel->post(IKC2 => disconnect => 'Kernel-name'); $poe_kernel->post(IKC2 => kernel_remove => 'name'); $poe_kernel->post(IKC2 => kernel_add => {name=>{how to reach it}}); $poe_kernel->post(IKC2 => kernel_modify => {name=>{how to reach it}}); Kernel_add and Kernel_modify differ as follows: Kernel_add disconnects from a remote kernel and reconnects using the new information if it differs. Kernel_modify keeps the current connection, but if it gets disconnected, reconnects with the new information. Expose/Gank IKC2->expose({ session=>[qw(list of aliases we want remote kernels to call us)], name=>'unique name of this interface', version=>1, events=>[qw(....)] }); # gank by name IKC2->gank({name=>'unique name'}); # gank by interface contents IKC2->gank({session=>'name', events=>[qw(list)]}); Posting IKC2->post({kernel=>'Kernel', session=>'Session', event=>'Event'}, @args); If Kernel is "host:port", we could auto-vivify the connection. $poe_kernel->post(RemoteSession => 'Event', @args); This works most of the time; either RemoteSession has been ganked already or we can tell that RemoteSession belongs to remote kernel that we know about. This last means we are connected to that kernel, or it was specified with Kernels() Postbacks When we control caller code my $event=IKC2->postback('eventName'); $poe_kernel->post(RemoteSession => 'event', Do=>'work', OnReady=>$event); When we control destination code $event=IKC2->remoteback($event); When we control neither # args are hash or hashref IKC2->marshall_postback('session', 'event', hash => [qw(....)]); # args are array or arrayref IKC2->marshall_postback('session', 'event', array => [qw(....)]); # args are complicated IKC2->marshall_postback('session', 'event', complex => sub()); Of course, getting 'session' right in the above code is dificult, seeing as there are many synonyms for remote sessions. Monitoring + Registry