--- AnyEvent-MP/MP.pm 2009/08/02 19:29:41 1.16 +++ AnyEvent-MP/MP.pm 2009/08/04 22:16:54 1.28 @@ -6,11 +6,14 @@ 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 snd $port, type => data...; + $SELF # receiving/own port id in rcv callbacks + rcv $port, smartmatch => $cb->($port, @msg); # examples: @@ -29,9 +32,12 @@ Despite its simplicity, you can securely message other processes running on the same or other hosts. -At the moment, this module family is severly brokena nd underdocumented, -so do not use. This was uploaded mainly to resreve the CPAN namespace - -stay tuned! +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! The basic API should be finished, however. =head1 CONCEPTS @@ -84,19 +90,43 @@ use base "Exporter"; -our $VERSION = '0.02'; +our $VERSION = '0.1'; our @EXPORT = qw( - NODE $NODE $PORT snd rcv _any_ - create_port create_port_on - create_miniport + NODE $NODE *SELF node_of _any_ become_slave become_public + snd rcv mon kil reg psub + port ); -=item NODE / $NODE +our $SELF; + +sub _self_die() { + my $msg = $@; + $msg =~ s/\n+$// unless ref $msg; + kil $SELF, die => $msg; +} + +=item $thisnode = NODE / $NODE + +The C function returns, and the C<$NODE> variable contains +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 $noderef = node_of $portid -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. +Extracts and returns the noderef from a portid or a noderef. + +=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 $portid, type => @data @@ -120,45 +150,118 @@ that Storable can serialise and deserialise is allowed, and for the local node, anything can be passed. -=item $local_port = create_port +=item kil $portid[, @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 >>. + +=item $guard = mon $portid, $cb->(@reason) + +=item $guard = mon $portid, $otherport + +=item $guard = mon $portid, $otherport, @msg + +Monitor the given port and do something when the port is killed. + +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. + +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. + +In the last form, a message of the form C<@msg, @reason> will be C. + +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, $self; + +Example: send us a restart message another C<$port> is killed. -Create a new local port object. See the next section for allowed methods. + mon $port, $self => "restart"; =cut -sub create_port { - my $id = "$AnyEvent::MP::Base::UNIQ." . $AnyEvent::MP::Base::ID++; +sub mon { + my ($noderef, $port, $cb) = ((split /#/, shift, 2), shift); - my $self = bless { - id => "$NODE#$id", - names => [$id], - }, "AnyEvent::MP::Port"; - - $AnyEvent::MP::Base::PORT{$id} = sub { - unshift @_, $self; - - for (@{ $self->{rc0}{$_[1]} }) { - $_ && &{$_->[0]} - && undef $_; - } + my $node = $NODE{$noderef} || add_node $noderef; - for (@{ $self->{rcv}{$_[1]} }) { - $_ && [@_[1 .. @{$_->[1]}]] ~~ $_->[1] - && &{$_->[0]} - && undef $_; + #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 @_ }; } + } - for (@{ $self->{any} }) { - $_ && [@_[0 .. $#{$_->[1]}]] ~~ $_->[1] - && &{$_->[0]} - && undef $_; - } - }; + $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) = @_; - $self + mon $port, sub { 0 && @refs } } -=item $portid = miniport { my @msg = @_; $finished } +=item lnk $port1, $port2 + +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. + +=item $local_port = port + +Create a new local port object that supports message matching. + +=item $portid = port { my @msg = @_; $finished } Creates a "mini port", that is, a very lightweight port without any pattern matching behind it, and returns its ID. @@ -167,7 +270,7 @@ callback returns a true value its job is considered "done" and the port will be destroyed. Otherwise it will stay alive. -The message will be passed as-is, no extra argument (ie.. no port id) will +The message will be passed as-is, no extra argument (i.e. no port id) will be passed to the callback. If you need the local port id in the callback, this works nicely: @@ -178,49 +281,89 @@ =cut -sub miniport(&) { - my $cb = shift; - my $id = "$AnyEvent::MP::Base::UNIQ." . $AnyEvent::MP::Base::ID++; - - $AnyEvent::MP::Base::PORT{$id} = sub { - &$cb - and delete $AnyEvent::MP::Base::PORT{$id}; - }; +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 $@; + }; + } 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 $@; + }; + } - "$NODE#$id" + $port } -package AnyEvent::MP::Port; - -=back - -=head1 METHODS FOR PORT OBJECTS +=item reg $portid, $name -=over 4 +Registers the given port under the name C<$name>. If the name already +exists it is replaced. -=item "$port" +A port can only be registered under one well known name. -A port object stringifies to its port ID, so can be used directly for -C operations. +A port automatically becomes unregistered when it is killed. =cut -use overload - '""' => sub { $_[0]{id} }, - fallback => 1; +sub reg(@) { + my ($portid, $name) = @_; -=item $port->rcv (type => $callback->($port, @msg)) + $REG{$name} = $portid; +} -=item $port->rcv ($smartmatch => $callback->($port, @msg)) +=item rcv $portid, tagstring => $callback->(@msg), ... -=item $port->rcv ([$smartmatch...] => $callback->($port, @msg)) +=item rcv $portid, $smartmatch => $callback->(@msg), ... -Register a callback on the given port. +=item rcv $portid, [$smartmatch...] => $callback->(@msg), ... + +Register callbacks to be called on matching messages on the given 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 +executing the callback. + +Runtime errors wdurign callback execution will result in the port being +Ced. + 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. @@ -235,52 +378,69 @@ =cut sub rcv($@) { - my ($self, $match, $cb) = @_; - - 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]; - } -} - -=item $port->register ($name) - -Registers the given port under the well known name C<$name>. If the name -already exists it is replaced. - -A port can only be registered under one well known name. + my ($noderef, $port) = split /#/, shift, 2; -=cut + ($NODE{$noderef} || add_node $noderef) == $NODE{""} + or Carp::croak "$noderef#$port: rcv can only be called on local ports, caught"; -sub register { - my ($self, $name) = @_; + my $self = $PORT_DATA{$port} + or Carp::croak "$noderef#$port: rcv can only be called on message matching ports, caught"; - $self->{wkname} = $name; - $AnyEvent::MP::Base::WKP{$name} = "$self"; + "AnyEvent::MP::Port" eq ref $self + or Carp::croak "$noderef#$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]; + } + } } -=item $port->destroy +=item $closure = psub { BLOCK } -Explicitly destroy/remove/nuke/vaporise the port. - -Ports are normally kept alive by there mere existance alone, and need to -be destroyed explicitly. +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; + }; + }; =cut -sub destroy { - my ($self) = @_; +sub psub(&) { + my $cb = shift; + + my $port = $SELF + or Carp::croak "psub can only be called from within rcv or psub callbacks, not"; - delete $AnyEvent::MP::Base::WKP{ $self->{wkname} }; + sub { + local $SELF = $port; - delete $AnyEvent::MP::Base::PORT{$_} - for @{ $self->{names} }; + if (wantarray) { + my @res = eval { &$cb }; + _self_die if $@; + @res + } else { + my $res = eval { &$cb }; + _self_die if $@; + $res + } + } } =back @@ -289,10 +449,6 @@ =over 4 -=item mon $noderef, $callback->($noderef, $status, $) - -Monitors the given noderef. - =item become_public endpoint... Tells the node to become a public node, i.e. reachable from other nodes. @@ -321,7 +477,7 @@ =cut -=item wkp => $name, @reply +=item lookup => $name, @reply Replies with the port ID of the specified well-known port, or C. @@ -354,6 +510,93 @@ =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. + +=back + =head1 SEE ALSO L.