--- AnyEvent-MP/MP.pm 2009/08/01 07:36:30 1.4 +++ AnyEvent-MP/MP.pm 2009/08/28 01:00:34 1.65 @@ -6,21 +6,46 @@ use AnyEvent::MP; - NODE # returns this node identifier - $NODE # contains this node identifier + $NODE # contains this node's noderef + NODE # returns this node's noderef + NODE $port # returns the noderef of the port + $SELF # receiving/own port id in rcv callbacks + + # initialise the node so it can send/receive messages + initialise_node; + + # ports are message endpoints + + # sending messages snd $port, type => data...; + snd $port, @msg; + snd @msg_with_first_element_being_a_port; + + # creating/using ports, the simple way + my $simple_port = port { my @msg = @_; 0 }; + + # creating/using ports, tagged message matching + my $port = port; + rcv $port, ping => sub { snd $_[0], "pong"; 0 }; + rcv $port, pong => sub { warn "pong received\n"; 0 }; + + # create a port on another node + my $port = spawn $node, $initfunc, @initdata; - rcv $port, smartmatch => $cb->($port, @msg); + # 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 - # examples: - rcv $port2, ping => sub { snd $_[0], "pong"; 0 }; - rcv $port1, pong => sub { warn "pong received\n" }; - snd $port2, ping => $port1; - - # more, smarter, matches (_any_ is exported by this module) - rcv $port, [child_died => $pid] => sub { ... - rcv $port, [_any_, _any_, 3] => sub { .. $_[2] is 3 +=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 @@ -29,36 +54,67 @@ Despite its simplicity, you can securely message other processes running on the same or other hosts. +For an introduction to this module family, see the L +manual page. + +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! + =head1 CONCEPTS =over 4 =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. - -=item port id - C +A port is something you can send messages to (with the C function). -A port id is always the noderef, a hash-mark (C<#>) as separator, followed -by a port name (a printable string of unspecified format). +Ports allow you to register C handlers that can match all or just +some messages. Messages send to ports will not be queued, regardless of +anything was listening for them or not. + +=item port ID - C + +A port ID is 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. - -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. - -=item noderef - 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 -node (for public nodes). +A node is a single process containing at least one port - the node port, +which provides nodes to manage each other remotely, and to create new +ports. + +Nodes are either private (single-process only), slaves (can only talk to +public nodes, but do not need an open port) or public nodes (connectable +from any other node). + +=item node ID - C<[a-za-Z0-9_\-.:]+> + +A node ID is a string that uniquely identifies the node within a +network. Depending on the configuration used, node IDs can look like a +hostname, a hostname and a port, or a random string. AnyEvent::MP itself +doesn't interpret node IDs in any way. + +=item binds - C + +Nodes can only talk to each other by creating some kind of connection to +each other. To do this, nodes should listen on one or more local transport +endpoints - binds. Currently, only standard C specifications can +be used, which specify TCP ports to listen on. + +=item seeds - C + +When a node starts, it knows nothing about the network. To teach the node +about the network it first has to contact some other node within the +network. This node is called a seed. + +Seeds are transport endpoint(s) of as many nodes as one wants. Those nodes +are expected to be long-running, and at least one of those should always +be available. When nodes run out of connections (e.g. due to a network +error), they try to re-establish connections to some seednodes again to +join the network. =back @@ -70,11 +126,8 @@ package AnyEvent::MP; -use AnyEvent::MP::Util (); -use AnyEvent::MP::Node; -use AnyEvent::MP::Transport; +use AnyEvent::MP::Kernel; -use utf8; use common::sense; use Carp (); @@ -83,84 +136,105 @@ use base "Exporter"; -our $VERSION = '0.0'; -our @EXPORT = qw(NODE $NODE $PORT snd rcv _any_); +our $VERSION = $AnyEvent::MP::Kernel::VERSION; -our $DEFAULT_SECRET; -our $DEFAULT_PORT = "4040"; +our @EXPORT = qw( + NODE $NODE *SELF node_of after + resolve_node initialise_node + snd rcv mon mon_guard kil reg psub spawn + port +); + +our $SELF; + +sub _self_die() { + my $msg = $@; + $msg =~ s/\n+$// unless ref $msg; + kil $SELF, die => $msg; +} -our $CONNECT_INTERVAL = 5; # new connect every 5s, at least -our $CONNECT_TIMEOUT = 30; # includes handshake +=item $thisnode = NODE / $NODE -sub default_secret { - unless (defined $DEFAULT_SECRET) { - if (open my $fh, "<$ENV{HOME}/.aemp-secret") { - sysread $fh, $DEFAULT_SECRET, -s $fh; - } else { - $DEFAULT_SECRET = AnyEvent::MP::Util::nonce 32; - } - } +The C function returns, and the C<$NODE> variable contains the node +ID of the node running in the current process. This value is initialised by +a call to C. - $DEFAULT_SECRET -} +=item $nodeid = node_of $port -=item NODE / $NODE +Extracts and returns the node ID part from a port ID or a node ID. -The C function and the C<$NODE> variable contain the noderef of -the local node. The value is initialised by a call to C or -C, after which all local port identifiers become invalid. +=item initialise_node $profile_name -=cut +Before a node can talk to other nodes on the network (i.e. enter +"distributed mode") it has to initialise itself - the minimum a node needs +to know is its own name, and optionally it should know the addresses of +some other nodes in the network to discover other nodes. -our $UNIQ = sprintf "%x.%x", $$, time; # per-process/node unique cookie -our $PUBLIC = 0; -our $NODE; -our $PORT; - -our %NODE; # node id to transport mapping, or "undef", for local node -our %PORT; # local ports -our %LISTENER; # local transports - -sub NODE() { $NODE } - -{ - use POSIX (); - my $nodename = (POSIX::uname)[1]; - $NODE = "$$\@$nodename"; -} +This function initialises a node - it must be called exactly once (or +never) before calling other AnyEvent::MP functions. -sub _ANY_() { 1 } -sub _any_() { \&_ANY_ } +The first argument is a profile name. If it is C or missing, then +the current nodename will be used instead (i.e. F). -sub add_node { - my ($noderef) = @_; +The function then looks up the profile in the aemp configuration (see the +L commandline utility). - return $NODE{$noderef} - if exists $NODE{$noderef}; +If the profile specifies a node ID, then this will become the node ID of +this process. If not, then the profile name will be used as node ID. The +special node ID of C will be replaced by a random node ID. - for (split /,/, $noderef) { - return $NODE{$noderef} = $NODE{$_} - if exists $NODE{$_}; - } +The next step is to look up the binds in the profile, followed by binding +aemp protocol listeners on all binds specified (it is possible and valid +to have no binds, meaning that the node cannot be contacted form the +outside. This means the node cannot talk to other nodes that also have no +binds, but it can still talk to all "normal" nodes). - # for indirect sends, use a different class - my $node = new AnyEvent::MP::Node::Direct $noderef; +If the profile does not specify a binds list, then the node ID will be +treated as if it were of the form C, which will be resolved and +used as binds list. - $NODE{$_} = $node - for $noderef, split /,/, $noderef; +Lastly, the seeds list from the profile is passed to the +L module, which will then use it to keep +connectivity with at least on of those seed nodes at any point in time. - $node -} +Example: become a distributed node listening on the guessed noderef, or +the one specified via C for the current node. This should be the +most common form of invocation for "daemon"-type nodes. + + initialise_node; -=item snd $portid, type => @data +Example: become an anonymous node. This form is often used for commandline +clients. -=item snd $portid, @msg + initialise_node "anon/"; -Send the given message to the given port ID, which can identify either a -local or a remote port. +Example: become a distributed node. If there is no profile of the given +name, or no binds list was specified, resolve C and bind +on the resulting addresses. -While the message can be about anything, it is highly recommended to use -a constant string as first element. + initialise_node "localhost:4044"; + +=item $SELF + +Contains the current port id while executing C callbacks or C +blocks. + +=item SELF, %SELF, @SELF... + +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. + +=item snd $port, type => @data + +=item snd $port, @msg + +Send the given message to the given port ID, which can identify either +a local or a remote port, and must be a port ID. + +While the message can be about anything, it is highly recommended to use a +string as first element (a port ID, 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 @@ -172,253 +246,566 @@ that Storable can serialise and deserialise is allowed, and for the local node, anything can be passed. +=item $local_port = port + +Create a new local port object and returns its port ID. Initially it has +no callbacks set and will throw an error when it receives messages. + +=item $local_port = port { my @msg = @_ } + +Creates a new local port, 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, with the +global variable C<$SELF> set to the port ID. Runtime errors will cause the +port to be Ced. The message will be passed as-is, no extra argument +(i.e. no port ID) will be passed to the callback. + +If you want to stop/destroy the port, simply C it: + + my $port = port { + my @msg = @_; + ... + kil $SELF; + }; + =cut -sub snd(@) { - my ($noderef, $port) = split /#/, shift, 2; +sub rcv($@); + +sub _kilme { + die "received message on port without callback"; +} + +sub port(;&) { + my $id = "$UNIQ." . $ID++; + my $port = "$NODE#$id"; - add_node $noderef - unless exists $NODE{$noderef}; + rcv $port, shift || \&_kilme; - $NODE{$noderef}->send (["$port", [@_]]); + $port } -=item rcv $portid, type => $callback->(@msg) +=item rcv $local_port, $callback->(@msg) -=item rcv $portid, $smartmatch => $callback->(@msg) +Replaces the default callback on the specified port. There is no way to +remove the default callback: use C to disable it, or better +C the port when it is no longer needed. -=item rcv $portid, [$smartmatch...] => $callback->(@msg) +The global C<$SELF> (exported by this module) contains C<$port> while +executing the callback. Runtime errors during callback execution will +result in the port being Ced. -Register a callback on the port identified by C<$portid>, which I be -a local port. +The default callback received all messages not matched by a more specific +C match. -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. +=item rcv $local_port, tag => $callback->(@msg_without_tag), ... -If the match is an array reference, then it will be matched against the -first elements of the message, otherwise only the first element is being -matched. +Register (or replace) callbacks to be called on messages starting with the +given tag on the given port (and return the port), or unregister it (when +C<$callback> is C<$undef> or missing). There can only be one callback +registered for each tag. -Any element in the match that is specified as C<_any_> (a function -exported by this module) matches any single element of the message. +The original message will be passed to the callback, after the first +element (the tag) has been removed. The callback will use the same +environment as the default callback (see above). -While not required, it is highly recommended that the first matching -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 { ... }, + msg2 => sub { ... }, + ; + +Example: create a port, bind receivers and send it in a message elsewhere +in one go: + + snd $otherport, reply => + rcv port, + msg1 => sub { ... }, + ... + ; + +Example: temporarily register a rcv callback for a tag matching some port +(e.g. for a rpc reply) and unregister it after a message was received. + + rcv $port, $otherport => sub { + my @reply = @_; + + rcv $SELF, $otherport; + }; =cut sub rcv($@) { - my ($port, $match, $cb) = @_; + my $port = shift; + my ($noderef, $portid) = split /#/, $port, 2; - my $port = $PORT{$port} - or do { - my ($noderef, $lport) = split /#/, $port; - "AnyEvent::MP::Node::Self" eq ref $NODE{$noderef} - or Carp::croak "$port: can only rcv on local ports"; - - $PORT{$lport} - or Carp::croak "$port: port does not exist"; - - $PORT{$port} = $PORT{$lport} # also return - }; + $NODE{$noderef} == $NODE{""} + or Carp::croak "$port: rcv can only be called on local ports, caught"; + + while (@_) { + if (ref $_[0]) { + if (my $self = $PORT_DATA{$portid}) { + "AnyEvent::MP::Port" eq ref $self + or Carp::croak "$port: rcv can only be called on message matching ports, caught"; + + $self->[2] = shift; + } else { + my $cb = shift; + $PORT{$portid} = sub { + local $SELF = $port; + eval { &$cb }; _self_die if $@; + }; + } + } elsif (defined $_[0]) { + my $self = $PORT_DATA{$portid} ||= do { + my $self = bless [$PORT{$port} || sub { }, { }, $port], "AnyEvent::MP::Port"; + + $PORT{$portid} = sub { + local $SELF = $port; + + if (my $cb = $self->[1]{$_[0]}) { + shift; + eval { &$cb }; _self_die if $@; + } else { + &{ $self->[0] }; + } + }; - if (!ref $match) { - push @{ $port->{rc0}{$match} }, [$cb]; - } elsif (("ARRAY" eq ref $match && !ref $match->[0])) { - my ($type, @match) = @$match; - @match - ? push @{ $port->{rcv}{$match->[0]} }, [$cb, \@match] - : push @{ $port->{rc0}{$match->[0]} }, [$cb]; - } else { - push @{ $port->{any} }, [$cb, $match]; + $self + }; + + "AnyEvent::MP::Port" eq ref $self + or Carp::croak "$port: rcv can only be called on message matching ports, caught"; + + my ($tag, $cb) = splice @_, 0, 2; + + if (defined $cb) { + $self->[1]{$tag} = $cb; + } else { + delete $self->[1]{$tag}; + } + } } + + $port } -sub _inject { - my ($port, $msg) = @{+shift}; +=item $closure = psub { BLOCK } + +Remembers C<$SELF> and creates a closure out of the BLOCK. When the +closure is executed, sets up the environment in the same way as in C +callbacks, i.e. runtime errors will cause the port to get Ced. + +This is useful when you register callbacks from C callbacks: + + rcv delayed_reply => sub { + my ($delay, @reply) = @_; + my $timer = AE::timer $delay, 0, psub { + snd @reply, $SELF; + }; + }; - $port = $PORT{$port} - or return; +=cut - @_ = @$msg; +sub psub(&) { + my $cb = shift; - for (@{ $port->{rc0}{$msg->[0]} }) { - $_ && &{$_->[0]} - && undef $_; - } + my $port = $SELF + or Carp::croak "psub can only be called from within rcv or psub callbacks, not"; - for (@{ $port->{rcv}{$msg->[0]} }) { - $_ && [@_[1..$#{$_->[1]}]] ~~ $_->[1] - && &{$_->[0]} - && undef $_; - } + sub { + local $SELF = $port; - for (@{ $port->{any} }) { - $_ && [@_[0..$#{$_->[1]}]] ~~ $_->[1] - && &{$_->[0]} - && undef $_; + if (wantarray) { + my @res = eval { &$cb }; + _self_die if $@; + @res + } else { + my $res = eval { &$cb }; + _self_die if $@; + $res + } } } -sub normalise_noderef($) { - my ($noderef) = @_; +=item $guard = mon $port, $cb->(@reason) - my $cv = AE::cv; - my @res; +=item $guard = mon $port, $rcvport - $cv->begin (sub { - my %seen; - my @refs; - for (sort { $a->[0] <=> $b->[0] } @res) { - push @refs, $_->[1] unless $seen{$_->[1]}++ - } - shift->send (join ",", @refs); - }); +=item $guard = mon $port - $noderef = $DEFAULT_PORT unless length $noderef; +=item $guard = mon $port, $rcvport, @msg - my $idx; - for my $t (split /,/, $noderef) { - my $pri = ++$idx; - - #TODO: this should be outside normalise_noderef and in become_public - if ($t =~ /^\d*$/) { - my $nodename = (POSIX::uname)[1]; - - $cv->begin; - AnyEvent::Socket::resolve_sockaddr $nodename, $t || "aemp=$DEFAULT_PORT", "tcp", 0, undef, sub { - for (@_) { - my ($service, $host) = AnyEvent::Socket::unpack_sockaddr $_->[3]; - push @res, [ - $pri += 1e-5, - AnyEvent::Socket::format_hostport AnyEvent::Socket::format_address $host, $service - ]; - } - $cv->end; - }; +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. -# my (undef, undef, undef, undef, @ipv4) = gethostbyname $nodename; -# -# for (@ipv4) { -# push @res, [ -# $pri, -# AnyEvent::Socket::format_hostport AnyEvent::Socket::format_address $_, $t || $DEFAULT_PORT, -# ]; -# } - } else { - my ($host, $port) = AnyEvent::Socket::parse_hostport $t, "aemp=$DEFAULT_PORT" - or Carp::croak "$t: unparsable transport descriptor"; +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. - $cv->begin; - AnyEvent::Socket::resolve_sockaddr $host, $port, "tcp", 0, undef, sub { - for (@_) { - my ($service, $host) = AnyEvent::Socket::unpack_sockaddr $_->[3]; - push @res, [ - $pri += 1e-5, - AnyEvent::Socket::format_hostport AnyEvent::Socket::format_address $host, $service - ]; - } - $cv->end; - } +Note that monitoring-actions are one-shot: once released, they are removed +and will not trigger 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. + + mon $port, sub { warn "port died because of <@_>\n" }; + +Example: kill ourselves when C<$port> is killed abnormally. + + 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 @_ }; } } - $cv->end; + $node->monitor ($port, $cb); - $cv + defined wantarray + and AnyEvent::Util::guard { $node->unmonitor ($port, $cb) } } -sub become_public { - return if $PUBLIC; +=item $guard = mon_guard $port, $ref, $ref... - my $noderef = join ",", ref $_[0] ? @{+shift} : shift; - my @args = @_; +Monitors the given C<$port> and keeps the passed references. When the port +is killed, the references will be freed. - $NODE = (normalise_noderef $noderef)->recv; +Optionally returns a guard that will stop the monitoring. - for my $t (split /,/, $NODE) { - $NODE{$t} = $NODE{""}; - - my ($host, $port) = AnyEvent::Socket::parse_hostport $t; - - $LISTENER{$t} = AnyEvent::MP::Transport::mp_server $host, $port, - @args, - on_error => sub { - die "on_error<@_>\n";#d# - }, - on_connect => sub { - my ($tp) = @_; - - $NODE{$tp->{remote_id}} = $_[0]; - }, - sub { - my ($tp) = @_; - - $NODE{"$tp->{peerhost}:$tp->{peerport}"} = $tp; - }, - ; - } +This function is useful when you create e.g. timers or other watchers and +want to free them when the port gets killed: - $PUBLIC = 1; + $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 } } -=back +=item kil $port[, @reason] -=head1 NODE MESSAGES +Kill the specified port with the given C<@reason>. -Nodes understand the following messages sent to them: +If no C<@reason> is specified, then the port is killed "normally" (linked +ports will not be kileld, or even notified). -=over 4 +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 -############################################################################# -# self node code +=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>. -sub _new_port($) { - my ($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. - my ($noderef, $portname) = split /#/, $name; +The init function is then called with the newly-created port as context +object (C<$SELF>) and the C<@initdata> values as arguments. - $PORT{$name} = - $PORT{$portname} = { - names => [$name, $portname], +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 $@; } -$NODE{""} = new AnyEvent::MP::Node::Self noderef => $NODE; -_new_port ""; +sub spawn(@) { + my ($noderef, undef) = split /#/, shift, 2; -=item relay => $port, @msg + my $id = "$RUNIQ." . $ID++; -Simply forwards the message to the given port. + $_[0] =~ /::/ + or Carp::croak "spawn init function must be a fully-qualified name, caught"; -=cut + snd_to_func $noderef, "AnyEvent::MP::_spawn" => $id, @_; + + "$noderef#$id" +} -rcv "", relay => \&snd; +=item after $timeout, @msg -=item eval => $string[ @reply] +=item after $timeout, $callback -Evaluates the given string. If C<@reply> is given, then a message of the -form C<@reply, $@, @evalres> is sent (C<$reply[0]> is the port to reply to). +Either sends the given message, or call the given callback, after the +specified number of seconds. + +This is simply a utility function that come sin handy at times. =cut -rcv "", eval => sub { - my (undef, $string, @reply) = @_; - my @res = eval $string; - snd @reply, "$@", @res if @reply; -}; +sub after($@) { + my ($timeout, @action) = @_; + + my $t; $t = AE::timer $timeout, 0, sub { + undef $t; + ref $action[0] + ? $action[0]() + : snd @action; + }; +} -=item time => @reply +=back -Replies the the current node time to C<@reply>. +=head1 AnyEvent::MP vs. Distributed Erlang -=cut +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 IDs are arbitrary strings in AEMP. + +Erlang relies on special naming and DNS to work everywhere in the same +way. AEMP relies on each node somehow knowing its own address(es) (e.g. by +configuraiton or DNS), but will otherwise discover other odes itself. + +=item * Erlang has a "remote ports are like local ports" philosophy, AEMP +uses "local ports are like remote ports". + +The failure modes for local ports are quite different (runtime errors +only) then for remote ports - when a local port dies, you I it dies, +when a connection to another node dies, you know nothing about the other +port. + +Erlang pretends remote ports are as reliable as local ports, even when +they are not. + +AEMP encourages a "treat remote ports differently" philosophy, with local +ports being the special case/exception, where transport errors cannot +occur. + +=item * Erlang uses processes and a mailbox, AEMP does not queue. + +Erlang uses processes that selectively receive messages, and therefore +needs a queue. AEMP is event based, queuing messages would serve no +useful purpose. For the same reason the pattern-matching abilities of +AnyEvent::MP are more limited, as there is little need to be able to +filter messages without dequeing them. + +(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 (and +so does not need a queue that can overflow). AEMP sends are immediate, +connection establishment is handled in the background. + +=item * Erlang suffers from silent message loss, AEMP does not. + +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 likely 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 RATIONALE + +=over 4 + +=item Why strings for ports and noderefs, why not objects? + +We considered "objects", but found that the actual number of methods +thatc an be called are very low. Since port IDs and noderefs travel over +the network frequently, the serialising/deserialising would add lots of +overhead, as well as having to keep a proxy object. + +Strings can easily be printed, easily serialised etc. and need no special +procedures to be "valid". + +And a a miniport consists of a single closure stored in a global hash - it +can't become much cheaper. + +=item Why favour JSON, why not real serialising format such as Storable? + +In fact, any AnyEvent::MP node will happily accept Storable as framing +format, but currently there is no way to make a node use Storable by +default. + +The default framing protocol is JSON because a) JSON::XS is many times +faster for small messages and b) most importantly, after years of +experience we found that object serialisation is causing more problems +than it gains: Just like function calls, objects simply do not travel +easily over the network, mostly because they will always be a copy, so you +always have to re-think your design. -rcv "", time => sub { shift; snd @_, AE::time }; +Keeping your messages simple, concentrating on data structures rather than +objects, will keep your messages clean, tidy and efficient. =back