--- AnyEvent-MP/MP.pm 2009/08/04 18:46:16 1.23 +++ AnyEvent-MP/MP.pm 2009/08/08 21:56:29 1.41 @@ -10,21 +10,36 @@ 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 + # 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 DESCRIPTION This module (-family) implements a simple message passing framework. @@ -45,31 +60,43 @@ =item port -A port is something you can send messages to with the C function, and -you can register C handlers with. All C handlers will receive -messages they match, messages will not be queued. +A port is something you can send messages to (with the C function). + +Some ports allow you to register C handlers that can match specific +messages. All C handlers will receive messages they match, messages +will not be queued. =item port id - C -A port id is always the noderef, a hash-mark (C<#>) as separator, followed -by a port name (a printable string of unspecified format). +A port id is normaly the concatenation of a noderef, a hash-mark (C<#>) as +separator, and a port name (a printable string of unspecified format). An +exception is the the node port, whose ID is identical to its node +reference. =item node A node is a single process containing at least one port - the node -port. You can send messages to node ports to let them create new ports, -among other things. +port. You can send messages to node ports to find existing ports or to +create new ports, among other things. -Initially, nodes are either private (single-process only) or hidden -(connected to a master node only). Only when they epxlicitly "become -public" can you send them messages from unrelated other nodes. +Nodes are either private (single-process only), slaves (connected to a +master node only) or public nodes (connectable from unrelated nodes). =item noderef - C, C, C -A noderef is a string that either uniquely identifies a given node (for -private and hidden nodes), or contains a recipe on how to reach a given +A node reference is a string that either simply identifies the node (for +private and slave nodes), or contains a recipe on how to reach a given node (for public nodes). +This recipe is simply a comma-separated list of C pairs (for +TCP/IP, other protocols might look different). + +Node references come in two flavours: resolved (containing only numerical +addresses) or unresolved (where hostnames are used instead of addresses). + +Before using an unresolved node reference in a message you first have to +resolve it. + =back =head1 VARIABLES/FUNCTIONS @@ -90,11 +117,11 @@ use base "Exporter"; -our $VERSION = '0.02'; +our $VERSION = '0.1'; our @EXPORT = qw( NODE $NODE *SELF node_of _any_ - become_slave become_public - snd rcv mon kil reg psub + resolve_node initialise_node + snd rcv mon kil reg psub spawn port ); @@ -113,148 +140,147 @@ to C or C, after which all local port identifiers become invalid. -=item $noderef = node_of $portid +=item $noderef = node_of $port Extracts and returns the noderef from a portid or a noderef. -=item $SELF +=item initialise_node $noderef, $seednode, $seednode... -Contains the current port id while executing C callbacks or C -blocks. +=item initialise_node "slave/", $master, $master... -=item SELF, %SELF, @SELF... +Before a node can talk to other nodes on the network it has to initialise +itself - the minimum a node needs to know is it's own name, and optionally +it should know the noderefs of some other nodes in the network. -Due to some quirks in how perl exports variables, it is impossible to -just export C<$SELF>, all the symbols called C are exported by this -module, but only C<$SELF> is currently used. +This function initialises a node - it must be called exactly once (or +never) before calling other AnyEvent::MP functions. -=item snd $portid, type => @data +All arguments are noderefs, which can be either resolved or unresolved. -=item snd $portid, @msg +There are two types of networked nodes, public nodes and slave nodes: -Send the given message to the given port ID, which can identify either -a local or a remote port, and can be either a string or soemthignt hat -stringifies a sa port ID (such as a port object :). +=over 4 -While the message can be about anything, it is highly recommended to use a -string as first element (a portid, or some word that indicates a request -type etc.). +=item public nodes -The message data effectively becomes read-only after a call to this -function: modifying any argument is not allowed and can cause many -problems. +For public nodes, C<$noderef> must either be a (possibly unresolved) +noderef, in which case it will be resolved, or C (or missing), in +which case the noderef will be guessed. -The type of data you can transfer depends on the transport protocol: when -JSON is used, then only strings, numbers and arrays and hashes consisting -of those are allowed (no objects). When Storable is used, then anything -that Storable can serialise and deserialise is allowed, and for the local -node, anything can be passed. +Afterwards, the node will bind itself on all endpoints and try to connect +to all additional C<$seednodes> that are specified. Seednodes are optional +and can be used to quickly bootstrap the node into an existing network. -=item kil $portid[, @reason] +=item slave nodes -Kill the specified port with the given C<@reason>. +When the C<$noderef> is the special string C, then the node will +become a slave node. Slave nodes cannot be contacted from outside and will +route most of their traffic to the master node that they attach to. -If no C<@reason> is specified, then the port is killed "normally" (linked -ports will not be kileld, or even notified). +At least one additional noderef is required: The node will try to connect +to all of them and will become a slave attached to the first node it can +successfully connect to. -Otherwise, linked ports get killed with the same reason (second form of -C, see below). +=back -Runtime errors while evaluating C callbacks or inside C blocks -will be reported as reason C<< die => $@ >>. +This function will block until all nodes have been resolved and, for slave +nodes, until it has successfully established a connection to a master +server. -Transport/communication errors are reported as C<< transport_error => -$message >>. +Example: become a public node listening on the default node. -=item $guard = mon $portid, $cb->(@reason) + initialise_node; -=item $guard = mon $portid, $otherport +Example: become a public node, and try to contact some well-known master +servers to become part of the network. -=item $guard = mon $portid, $otherport, @msg + initialise_node undef, "master1", "master2"; -Monitor the given port and do something when the port is killed. +Example: become a public node listening on port C<4041>. -In the first form, 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. + initialise_node 4041; -In the second form, the other port 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. +Example: become a public node, only visible on localhost port 4044. -In the last form, a message of the form C<@msg, @reason> will be C. + initialise_node "locahost:4044"; -Example: call a given callback when C<$port> is killed. +Example: become a slave node to any of the specified master servers. - mon $port, sub { warn "port died because of <@_>\n" }; + initialise_node "slave/", "master1", "192.168.13.17", "mp.example.net"; -Example: kill ourselves when C<$port> is killed abnormally. +=item $cv = resolve_node $noderef - mon $port, $self; +Takes an unresolved node reference that may contain hostnames and +abbreviated IDs, resolves all of them and returns a resolved node +reference. -Example: send us a restart message another C<$port> is killed. +In addition to C pairs allowed in resolved noderefs, the +following forms are supported: - mon $port, $self => "restart"; +=over 4 -=cut +=item the empty string -sub mon { - my ($noderef, $port, $cb) = ((split /#/, shift, 2), shift); +An empty-string component gets resolved as if the default port (4040) was +specified. - my $node = $NODE{$noderef} || add_node $noderef; +=item naked port numbers (e.g. C<1234>) - #TODO: ports must not be references - if (!ref $cb or "AnyEvent::MP::Port" eq ref $cb) { - if (@_) { - # send a kill info message - my (@msg) = ($cb, @_); - $cb = sub { snd @msg, @_ }; - } else { - # simply kill other port - my $port = $cb; - $cb = sub { kil $port, @_ if @_ }; - } - } +These are resolved by prepending the local nodename and a colon, to be +further resolved. - $node->monitor ($port, $cb); +=item hostnames (e.g. C, C) - defined wantarray - and AnyEvent::Util::guard { $node->unmonitor ($port, $cb) } -} +These are resolved by using AnyEvent::DNS to resolve them, optionally +looking up SRV records for the C port, if no port was +specified. -=item $guard = mon_guard $port, $ref, $ref... +=back -Monitors the given C<$port> and keeps the passed references. When the port -is killed, the references will be freed. +=item $SELF -Optionally returns a guard that will stop the monitoring. +Contains the current port id while executing C callbacks or C +blocks. -This function is useful when you create e.g. timers or other watchers and -want to free them when the port gets killed: +=item SELF, %SELF, @SELF... - $port->rcv (start => sub { - my $timer; $timer = mon_guard $port, AE::timer 1, 1, sub { - undef $timer if 0.9 < rand; - }); - }); +Due to some quirks in how perl exports variables, it is impossible to +just export C<$SELF>, all the symbols called C are exported by this +module, but only C<$SELF> is currently used. -=cut +=item snd $port, type => @data -sub mon_guard { - my ($port, @refs) = @_; +=item snd $port, @msg - mon $port, sub { 0 && @refs } -} +Send the given message to the given port ID, which can identify either +a local or a remote port, and can be either a string or soemthignt hat +stringifies a sa port ID (such as a port object :). -=item $local_port = port +While the message can be about anything, it is highly recommended to use a +string as first element (a portid, or some word that indicates a request +type etc.). + +The message data effectively becomes read-only after a call to this +function: modifying any argument is not allowed and can cause many +problems. -Create a new local port object that supports message matching. +The type of data you can transfer depends on the transport protocol: when +JSON is used, then only strings, numbers and arrays and hashes consisting +of those are allowed (no objects). When Storable is used, then anything +that Storable can serialise and deserialise is allowed, and for the local +node, anything can be passed. -=item $portid = port { my @msg = @_; $finished } +=item $local_port = port -Creates a "mini port", that is, a very lightweight port without any -pattern matching behind it, and returns its ID. +Create a new local port object that can be used either as a pattern +matching port ("full port") or a single-callback port ("miniport"), +depending on how C callbacks are bound to the object. + +=item $port = port { my @msg = @_; $finished } + +Creates a "miniport", that is, a very lightweight port without any pattern +matching behind it, and returns its ID. Semantically the same as creating +a port and calling C on it. The block will be called for every message received on the port. When the callback returns a true value its job is considered "done" and the port @@ -265,64 +291,33 @@ If you need the local port id in the callback, this works nicely: - my $port; $port = miniport { + my $port; $port = port { snd $otherport, reply => $port; }; =cut +sub rcv($@); + sub port(;&) { my $id = "$UNIQ." . $ID++; my $port = "$NODE#$id"; if (@_) { - my $cb = shift; - $PORT{$id} = sub { - local $SELF = $port; - eval { - &$cb - and kil $id; - }; - _self_die if $@; - }; + rcv $port, shift; } else { - my $self = bless { - id => "$NODE#$id", - }, "AnyEvent::MP::Port"; - - $PORT_DATA{$id} = $self; - $PORT{$id} = sub { - local $SELF = $port; - - eval { - for (@{ $self->{rc0}{$_[0]} }) { - $_ && &{$_->[0]} - && undef $_; - } - - for (@{ $self->{rcv}{$_[0]} }) { - $_ && [@_[1 .. @{$_->[1]}]] ~~ $_->[1] - && &{$_->[0]} - && undef $_; - } - - for (@{ $self->{any} }) { - $_ && [@_[0 .. $#{$_->[1]}]] ~~ $_->[1] - && &{$_->[0]} - && undef $_; - } - }; - _self_die if $@; - }; + $PORT{$id} = sub { }; # nop } $port } -=item reg $portid, $name +=item reg $port, $name -Registers the given port under the name C<$name>. If the name already -exists it is replaced. +=item reg $name + +Registers the given port (or C<$SELF><<< if missing) under the name +C<$name>. If the name already exists it is replaced. A port can only be registered under one well known name. @@ -331,27 +326,33 @@ =cut sub reg(@) { - my ($portid, $name) = @_; + my $port = @_ > 1 ? shift : $SELF || Carp::croak 'reg: called with one argument only, but $SELF not set,'; - $REG{$name} = $portid; + $REG{$_[0]} = $port; } -=item rcv $portid, tagstring => $callback->(@msg), ... +=item rcv $port, $callback->(@msg) + +Replaces the callback on the specified miniport (after converting it to +one if required). -=item rcv $portid, $smartmatch => $callback->(@msg), ... +=item rcv $port, tagstring => $callback->(@msg), ... -=item rcv $portid, [$smartmatch...] => $callback->(@msg), ... +=item rcv $port, $smartmatch => $callback->(@msg), ... -Register callbacks to be called on matching messages on the given port. +=item rcv $port, [$smartmatch...] => $callback->(@msg), ... + +Register callbacks to be called on matching messages on the given full +port (after converting it to one if required) and return the port. The callback has to return a true value when its work is done, after which is will be removed, or a false value in which case it will stay registered. -The global C<$SELF> (exported by this module) contains C<$portid> while +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 @@ -365,34 +366,95 @@ element is a string identifying the message. The one-string-only match is also the most efficient match (by far). +Example: create a port and bind receivers on it in one go. + + my $port = rcv port, + msg1 => sub { ...; 0 }, + msg2 => sub { ...; 0 }, + ; + +Example: create a port, bind receivers and send it in a message elsewhere +in one go: + + snd $otherport, reply => + rcv port, + msg1 => sub { ...; 0 }, + ... + ; + =cut sub rcv($@) { - my ($noderef, $port) = split /#/, shift, 2; + my $port = shift; + my ($noderef, $portid) = split /#/, $port, 2; ($NODE{$noderef} || add_node $noderef) == $NODE{""} - or Carp::croak "$noderef#$port: rcv can only be called on local ports, caught"; + or Carp::croak "$port: rcv can only be called on local ports, caught"; - my $self = $PORT_DATA{$port} - or Carp::croak "$noderef#$port: rcv can only be called on message matching ports, caught"; + if (@_ == 1) { + my $cb = shift; + delete $PORT_DATA{$portid}; + $PORT{$portid} = sub { + local $SELF = $port; + eval { + &$cb + and kil $port; + }; + _self_die if $@; + }; + } else { + my $self = $PORT_DATA{$portid} ||= do { + my $self = bless { + id => $port, + }, "AnyEvent::MP::Port"; + + $PORT{$portid} = sub { + local $SELF = $port; + + eval { + for (@{ $self->{rc0}{$_[0]} }) { + $_ && &{$_->[0]} + && undef $_; + } + + for (@{ $self->{rcv}{$_[0]} }) { + $_ && [@_[1 .. @{$_->[1]}]] ~~ $_->[1] + && &{$_->[0]} + && undef $_; + } + + for (@{ $self->{any} }) { + $_ && [@_[0 .. $#{$_->[1]}]] ~~ $_->[1] + && &{$_->[0]} + && undef $_; + } + }; + _self_die if $@; + }; - "AnyEvent::MP::Port" eq ref $self - or Carp::croak "$noderef#$port: rcv can only be called on message matching ports, caught"; + $self + }; - while (@_) { - my ($match, $cb) = splice @_, 0, 2; - - if (!ref $match) { - push @{ $self->{rc0}{$match} }, [$cb]; - } elsif (("ARRAY" eq ref $match && !ref $match->[0])) { - my ($type, @match) = @$match; - @match - ? push @{ $self->{rcv}{$match->[0]} }, [$cb, \@match] - : push @{ $self->{rc0}{$match->[0]} }, [$cb]; - } else { - push @{ $self->{any} }, [$cb, $match]; + "AnyEvent::MP::Port" eq ref $self + or Carp::croak "$port: rcv can only be called on message matching ports, caught"; + + while (@_) { + my ($match, $cb) = splice @_, 0, 2; + + if (!ref $match) { + push @{ $self->{rc0}{$match} }, [$cb]; + } elsif (("ARRAY" eq ref $match && !ref $match->[0])) { + my ($type, @match) = @$match; + @match + ? push @{ $self->{rcv}{$match->[0]} }, [$cb, \@match] + : push @{ $self->{rc0}{$match->[0]} }, [$cb]; + } else { + push @{ $self->{any} }, [$cb, $match]; + } } } + + $port } =item $closure = psub { BLOCK } @@ -433,27 +495,192 @@ } } -=back +=item $guard = mon $port, $cb->(@reason) -=head1 FUNCTIONS FOR NODES +=item $guard = mon $port, $rcvport -=over 4 +=item $guard = mon $port + +=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. + +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) +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. + +The third form (kill self) is the same as the second form, except that +C<$rvport> defaults to C<$SELF>. + +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. -=item become_public endpoint... + mon $port, sub { warn "port died because of <@_>\n" }; -Tells the node to become a public node, i.e. reachable from other nodes. +Example: kill ourselves when C<$port> is killed abnormally. -If no arguments are given, or the first argument is C, then -AnyEvent::MP tries to bind on port C<4040> on all IP addresses that the -local nodename resolves to. - -Otherwise the first argument must be an array-reference with transport -endpoints ("ip:port", "hostname:port") or port numbers (in which case the -local nodename is used as hostname). The endpoints are all resolved and -will become the node reference. + mon $port; + +Example: send us a restart message when another C<$port> is killed. + + mon $port, $self => "restart"; =cut +sub mon { + my ($noderef, $port) = split /#/, shift, 2; + + my $node = $NODE{$noderef} || add_node $noderef; + + 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) = ($cb, @_); + $cb = sub { snd @msg, @_ }; + } else { + # simply kill other port + my $port = $cb; + $cb = sub { kil $port, @_ if @_ }; + } + } + + $node->monitor ($port, $cb); + + defined wantarray + and AnyEvent::Util::guard { $node->unmonitor ($port, $cb) } +} + +=item $guard = mon_guard $port, $ref, $ref... + +Monitors the given C<$port> and keeps the passed references. When the port +is killed, the references will be freed. + +Optionally returns a guard that will stop the monitoring. + +This function is useful when you create e.g. timers or other watchers and +want to free them when the port gets killed: + + $port->rcv (start => sub { + my $timer; $timer = mon_guard $port, AE::timer 1, 1, sub { + undef $timer if 0.9 < rand; + }); + }); + +=cut + +sub mon_guard { + my ($port, @refs) = @_; + + #TODO: mon-less form? + + mon $port, sub { 0 && @refs } +} + +=item kil $port[, @reason] + +Kill the specified port with the given C<@reason>. + +If no C<@reason> is specified, then the port is killed "normally" (linked +ports will not be kileld, or even notified). + +Otherwise, linked ports get killed with the same reason (second form of +C, see below). + +Runtime errors while evaluating C callbacks or inside C blocks +will be reported as reason C<< die => $@ >>. + +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 @@ -463,6 +690,8 @@ message - C<$reply[0]> is the port to reply to, C<$reply[1]> the type and the remaining arguments are simply the message data. +While other messages exist, they are not public and subject to change. + =over 4 =cut @@ -500,6 +729,115 @@ =back +=head1 AnyEvent::MP vs. Distributed Erlang + +AnyEvent::MP got lots of its ideas from distributed Erlang (Erlang node +== aemp node, Erlang process == aemp port), so many of the documents and +programming techniques employed by Erlang apply to AnyEvent::MP. Here is a +sample: + + http://www.Erlang.se/doc/programming_rules.shtml + http://Erlang.org/doc/getting_started/part_frame.html # chapters 3 and 4 + http://Erlang.org/download/Erlang-book-part1.pdf # chapters 5 and 6 + http://Erlang.org/download/armstrong_thesis_2003.pdf # chapters 4 and 5 + +Despite the similarities, there are also some important differences: + +=over 4 + +=item * Node references contain the recipe on how to contact them. + +Erlang relies on special naming and DNS to work everywhere in the +same way. AEMP relies on each node knowing it's own address(es), with +convenience functionality. + +This means that AEMP requires a less tightly controlled environment at the +cost of longer node references and a slightly higher management overhead. + +=item * Erlang uses processes and a mailbox, AEMP does not queue. + +Erlang uses processes that selctively receive messages, and therefore +needs a queue. AEMP is event based, queuing messages would serve no useful +purpose. + +(But see L for a more Erlang-like process model on top of AEMP). + +=item * Erlang sends are synchronous, AEMP sends are asynchronous. + +Sending messages in Erlang is synchronous and blocks the process. AEMP +sends are immediate, connection establishment is handled in the +background. + +=item * Erlang can silently lose messages, AEMP cannot. + +Erlang makes few guarantees on messages delivery - messages can get lost +without any of the processes realising it (i.e. you send messages a, b, +and c, and the other side only receives messages a and c). + +AEMP guarantees correct ordering, and the guarantee that there are no +holes in the message sequence. + +=item * In Erlang, processes can be declared dead and later be found to be +alive. + +In Erlang it can happen that a monitored process is declared dead and +linked processes get killed, but later it turns out that the process is +still alive - and can receive messages. + +In AEMP, when port monitoring detects a port as dead, then that port will +eventually be killed - it cannot happen that a node detects a port as dead +and then later sends messages to it, finding it is still alive. + +=item * Erlang can send messages to the wrong port, AEMP does not. + +In Erlang it is quite possible that a node that restarts reuses a process +ID known to other nodes for a completely different process, causing +messages destined for that process to end up in an unrelated process. + +AEMP never reuses port IDs, so old messages or old port IDs floating +around in the network will not be sent to an unrelated port. + +=item * Erlang uses unprotected connections, AEMP uses secure +authentication and can use TLS. + +AEMP can use a proven protocol - SSL/TLS - to protect connections and +securely authenticate nodes. + +=item * The AEMP protocol is optimised for both text-based and binary +communications. + +The AEMP protocol, unlike the Erlang protocol, supports both +language-independent text-only protocols (good for debugging) and binary, +language-specific serialisers (e.g. Storable). + +It has also been carefully designed to be implementable in other languages +with a minimum of work while gracefully degrading fucntionality to make the +protocol simple. + +=item * AEMP has more flexible monitoring options than Erlang. + +In Erlang, you can chose to receive I exit signals as messages +or I, there is no in-between, so monitoring single processes is +difficult to implement. Monitoring in AEMP is more flexible than in +Erlang, as one can choose between automatic kill, exit message or callback +on a per-process basis. + +=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, 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 + =head1 SEE ALSO L.