--- AnyEvent-MP/MP.pm 2009/08/06 10:46:48 1.36 +++ AnyEvent-MP/MP.pm 2009/08/13 01:16:24 1.45 @@ -10,29 +10,45 @@ NODE # returns this node's noderef NODE $port # returns the noderef of the port + $SELF # receiving/own port id in rcv callbacks + + # ports are message endpoints + + # sending messages snd $port, type => data...; + snd $port, @msg; + snd @msg_with_first_element_being_a_port; - $SELF # receiving/own port id in rcv callbacks + # miniports + my $miniport = port { my @msg = @_; 0 }; - rcv $port, smartmatch => $cb->($port, @msg); + # full ports + my $port = port; + rcv $port, smartmatch => $cb->(@msg); + rcv $port, ping => sub { snd $_[0], "pong"; 0 }; + rcv $port, pong => sub { warn "pong received\n"; 0 }; - # examples: - rcv $port2, ping => sub { snd $_[0], "pong"; 0 }; - rcv $port1, pong => sub { warn "pong received\n" }; - snd $port2, ping => $port1; + # remote ports + my $port = spawn $node, $initfunc, @initdata; # more, smarter, matches (_any_ is exported by this module) rcv $port, [child_died => $pid] => sub { ... rcv $port, [_any_, _any_, 3] => sub { .. $_[2] is 3 - # linking two ports, so they both crash together - lnk $port1, $port2; - # monitoring mon $port, $cb->(@msg) # callback is invoked on death mon $port, $otherport # kill otherport on abnormal death mon $port, $otherport, @msg # send message on death +=head1 CURRENT STATUS + + AnyEvent::MP - stable API, should work + AnyEvent::MP::Intro - outdated + AnyEvent::MP::Kernel - WIP + AnyEvent::MP::Transport - mostly stable + + stay tuned. + =head1 DESCRIPTION This module (-family) implements a simple message passing framework. @@ -45,7 +61,7 @@ At the moment, this module family is severly broken and underdocumented, so do not use. This was uploaded mainly to reserve the CPAN namespace - -stay tuned! The basic API should be finished, however. +stay tuned! =head1 CONCEPTS @@ -100,7 +116,7 @@ package AnyEvent::MP; -use AnyEvent::MP::Base; +use AnyEvent::MP::Kernel; use common::sense; @@ -110,11 +126,12 @@ use base "Exporter"; -our $VERSION = '0.1'; +our $VERSION = $AnyEvent::MP::Kernel::VERSION; + our @EXPORT = qw( NODE $NODE *SELF node_of _any_ resolve_node initialise_node - snd rcv mon kil reg psub + snd rcv mon kil reg psub spawn port ); @@ -345,7 +362,7 @@ The global C<$SELF> (exported by this module) contains C<$port> while executing the callback. -Runtime errors wdurign callback execution will result in the port being +Runtime errors during callback execution will result in the port being Ced. If the match is an array reference, then it will be matched against the @@ -496,15 +513,24 @@ =item $guard = mon $port, $rcvport, @msg -Monitor the given port and do something when the port is killed, and -optionally return a guard that can be used to stop monitoring again. +Monitor the given port and do something when the port is killed or +messages to it were lost, and optionally return a guard that can be used +to stop monitoring again. + +C effectively guarantees that, in the absence of hardware failures, +that after starting the monitor, either all messages sent to the port +will arrive, or the monitoring action will be invoked after possible +message loss has been detected. No messages will be lost "in between" +(after the first lost message no further messages will be received by the +port). After the monitoring action was invoked, further messages might get +delivered again. In the first form (callback), the callback is simply called with any number of C<@reason> elements (no @reason means that the port was deleted "normally"). Note also that I<< the callback B never die >>, so use C if unsure. -In the second form (another port given), the other port (C<$rcvport) +In the second form (another port given), the other port (C<$rcvport>) will be C'ed with C<@reason>, iff a @reason was specified, i.e. on "normal" kils nothing happens, while under all other conditions, the other port is killed with the same reason. @@ -515,6 +541,13 @@ In the last form (message), a message of the form C<@msg, @reason> will be C. +As a rule of thumb, monitoring requests should always monitor a port from +a local port (or callback). The reason is that kill messages might get +lost, just like any other message. Another less obvious reason is that +even monitoring requests can get lost (for exmaple, when the connection +to the other node goes down permanently). When monitoring a port locally +these problems do not exist. + Example: call a given callback when C<$port> is killed. mon $port, sub { warn "port died because of <@_>\n" }; @@ -534,12 +567,12 @@ my $node = $NODE{$noderef} || add_node $noderef; - my $cb = @_ ? $_[0] : $SELF || Carp::croak 'mon: called with one argument only, but $SELF not set,'; + my $cb = @_ ? shift : $SELF || Carp::croak 'mon: called with one argument only, but $SELF not set,'; unless (ref $cb) { if (@_) { # send a kill info message - my (@msg) = @_; + my (@msg) = ($cb, @_); $cb = sub { snd @msg, @_ }; } else { # simply kill other port @@ -580,30 +613,6 @@ mon $port, sub { 0 && @refs } } -=item lnk $port1, $port2 - -=item lnk $otherport - -Link two ports. This is simply a shorthand for: - - mon $port1, $port2; - mon $port2, $port1; - -It means that if either one is killed abnormally, the other one gets -killed as well. - -The one-argument form assumes that one port is C<$SELF>. - -=cut - -sub lnk { - my $port1 = shift; - my $port2 = @_ ? shift : $SELF || Carp::croak 'lnk: called with one argument only, but $SELF not set,'; - - mon $port1, $port2; - mon $port2, $port1; -} - =item kil $port[, @reason] Kill the specified port with the given C<@reason>. @@ -620,6 +629,77 @@ Transport/communication errors are reported as C<< transport_error => $message >>. +=cut + +=item $port = spawn $node, $initfunc[, @initdata] + +Creates a port on the node C<$node> (which can also be a port ID, in which +case it's the node where that port resides). + +The port ID of the newly created port is return immediately, and it is +permissible to immediately start sending messages or monitor the port. + +After the port has been created, the init function is +called. This function must be a fully-qualified function name +(e.g. C). To specify a function in the main +program, use C<::name>. + +If the function doesn't exist, then the node tries to C +the package, then the package above the package and so on (e.g. +C, C, C) until the function +exists or it runs out of package names. + +The init function is then called with the newly-created port as context +object (C<$SELF>) and the C<@initdata> values as arguments. + +A common idiom is to pass your own port, monitor the spawned port, and +in the init function, monitor the original port. This two-way monitoring +ensures that both ports get cleaned up when there is a problem. + +Example: spawn a chat server port on C<$othernode>. + + # this node, executed from within a port context: + my $server = spawn $othernode, "MyApp::Chat::Server::connect", $SELF; + mon $server; + + # init function on C<$othernode> + sub connect { + my ($srcport) = @_; + + mon $srcport; + + rcv $SELF, sub { + ... + }; + } + +=cut + +sub _spawn { + my $port = shift; + my $init = shift; + + local $SELF = "$NODE#$port"; + eval { + &{ load_func $init } + }; + _self_die if $@; +} + +sub spawn(@) { + my ($noderef, undef) = split /#/, shift, 2; + + my $id = "$RUNIQ." . $ID++; + + $_[0] =~ /::/ + or Carp::croak "spawn init function must be a fully-qualified name, caught"; + + ($NODE{$noderef} || add_node $noderef) + ->send (["", "AnyEvent::MP::_spawn" => $id, @_]); + + "$noderef#$id" +} + =back =head1 NODE MESSAGES @@ -761,12 +841,19 @@ Erlang, as one can choose between automatic kill, exit message or callback on a per-process basis. -=item * Erlang has different semantics for monitoring and linking, AEMP has the same. +=item * Erlang tries to hide remote/local connections, AEMP does not. Monitoring in Erlang is not an indicator of process death/crashes, -as linking is (except linking is unreliable in Erlang). In AEMP, the -semantics of monitoring and linking are identical, linking is simply -two-way monitoring with automatic kill. +as linking is (except linking is unreliable in Erlang). + +In AEMP, you don't "look up" registered port names or send to named ports +that might or might not be persistent. Instead, you normally spawn a port +on the remote node. The init function monitors the you, and you monitor +the remote port. Since both monitors are local to the node, they are much +more reliable. + +This also saves round-trips and avoids sending messages to the wrong port +(hard to do in Erlang). =back