--- AnyEvent-MP/MP.pm 2009/08/07 22:55:18 1.38 +++ AnyEvent-MP/MP.pm 2009/08/14 14:01:05 1.50 @@ -12,6 +12,11 @@ $SELF # receiving/own port id in rcv callbacks + # initialise the node so it can send/receive messages + initialise_node; # -OR- + initialise_node "localhost:4040"; # -OR- + initialise_node "slave/", "localhost:4040" + # ports are message endpoints # sending messages @@ -19,27 +24,31 @@ snd $port, @msg; snd @msg_with_first_element_being_a_port; - # miniports - my $miniport = port { my @msg = @_; 0 }; + # creating/using ports, the simple way + my $somple_port = port { my @msg = @_; 0 }; - # full ports + # creating/using ports, type matching 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 }; - # remote ports + # create a port on another node 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 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. @@ -52,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 @@ -107,7 +116,7 @@ package AnyEvent::MP; -use AnyEvent::MP::Base; +use AnyEvent::MP::Kernel; use common::sense; @@ -117,7 +126,8 @@ 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 @@ -155,7 +165,14 @@ This function initialises a node - it must be called exactly once (or never) before calling other AnyEvent::MP functions. -All arguments are noderefs, which can be either resolved or unresolved. +All arguments (optionally except for the first) are noderefs, which can be +either resolved or unresolved. + +The first argument will be looked up in the configuration database first +(if it is C then the current nodename will be used instead) to find +the relevant configuration profile (see L). If none is found then +the default configuration is used. The configuration supplies additional +seed/master nodes and can override the actual noderef. There are two types of networked nodes, public nodes and slave nodes: @@ -163,23 +180,26 @@ =item public nodes -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. - -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. +For public nodes, C<$noderef> (supplied either directly to +C or indirectly via a profile or the nodename) must be a +noderef (possibly unresolved, in which case it will be resolved). + +After resolving, 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 slave nodes -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. - -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. +When the C<$noderef> (either as given or overriden by the config file) +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. + +At least one additional noderef is required (either by specifying it +directly or because it is part of the configuration profile): 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. =back @@ -187,10 +207,22 @@ nodes, until it has successfully established a connection to a master server. -Example: become a public node listening on the default node. +Example: become a public 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; +Example: become a slave node to any of the the seednodes specified via +C. This form is often used for commandline clients. + + initialise_node "slave/"; + +Example: become a slave node to any of the specified master servers. This +form is also often used for commandline clients. + + initialise_node "slave/", "master1", "192.168.13.17", "mp.example.net"; + Example: become a public node, and try to contact some well-known master servers to become part of the network. @@ -202,11 +234,7 @@ Example: become a public node, only visible on localhost port 4044. - initialise_node "locahost:4044"; - -Example: become a slave node to any of the specified master servers. - - initialise_node "slave/", "master1", "192.168.13.17", "mp.example.net"; + initialise_node "localhost:4044"; =item $cv = resolve_node $noderef @@ -272,105 +300,72 @@ =item $local_port = port -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 } +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. -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. +=item $local_port = port { my @msg = @_ } -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 -will be destroyed. Otherwise it will stay alive. +Creates a new local port, and returns its ID. Semantically the same as +creating a port and calling C on it. -The message will be passed as-is, no extra argument (i.e. no port id) will -be passed to the callback. +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 need the local port id in the callback, this works nicely: +If you want to stop/destroy the port, simply C it: - my $port; $port = port { - snd $otherport, reply => $port; + my $port = port { + my @msg = @_; + ... + kil $SELF; }; =cut sub rcv($@); +sub _kilme { + die "received message on port without callback"; +} + sub port(;&) { my $id = "$UNIQ." . $ID++; my $port = "$NODE#$id"; - if (@_) { - rcv $port, shift; - } else { - $PORT{$id} = sub { }; # nop - } + rcv $port, shift || \&_kilme; $port } -=item reg $port, $name - -=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. - -A port automatically becomes unregistered when it is killed. - -=cut - -sub reg(@) { - my $port = @_ > 1 ? shift : $SELF || Carp::croak 'reg: called with one argument only, but $SELF not set,'; - - $REG{$_[0]} = $port; -} +=item rcv $local_port, $callback->(@msg) -=item rcv $port, $callback->(@msg) - -Replaces the callback on the specified miniport (after converting it to -one if required). - -=item rcv $port, tagstring => $callback->(@msg), ... - -=item rcv $port, $smartmatch => $callback->(@msg), ... - -=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. +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. The global C<$SELF> (exported by this module) contains C<$port> while -executing the callback. +executing the callback. Runtime errors during callback execution will +result in the port being Ced. -Runtime errors during callback execution will result in the port being -Ced. +The default callback received all messages not matched by a more specific +C match. -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. +=item rcv $local_port, tag => $callback->(@msg_without_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. +Register 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>). -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). +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). Example: create a port and bind receivers on it in one go. my $port = rcv port, - msg1 => sub { ...; 0 }, - msg2 => sub { ...; 0 }, + msg1 => sub { ... }, + msg2 => sub { ... }, ; Example: create a port, bind receivers and send it in a message elsewhere @@ -378,7 +373,7 @@ snd $otherport, reply => rcv port, - msg1 => sub { ...; 0 }, + msg1 => sub { ... }, ... ; @@ -391,65 +386,47 @@ ($NODE{$noderef} || add_node $noderef) == $NODE{""} or Carp::croak "$port: rcv can only be called on local 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 $_; - } + 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"; - for (@{ $self->{any} }) { - $_ && [@_[0 .. $#{$_->[1]}]] ~~ $_->[1] - && &{$_->[0]} - && undef $_; + $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] }; } }; - _self_die if $@; - }; - $self - }; + $self + }; - "AnyEvent::MP::Port" eq ref $self - or Carp::croak "$port: rcv can only be called on message matching ports, caught"; + "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; + my ($tag, $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]; + if (defined $cb) { + $self->[1]{$tag} = $cb; } else { - push @{ $self->{any} }, [$cb, $match]; + delete $self->[1]{$tag}; } } } @@ -503,15 +480,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. @@ -548,12 +534,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 @@ -621,8 +607,9 @@ permissible to immediately start sending messages or monitor the port. After the port has been created, the init function is -called. This fucntion must be a fully-qualified function name -(e.g. C). +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. @@ -671,6 +658,9 @@ 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, @_]); @@ -834,6 +824,41 @@ =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. + +Keeping your messages simple, concentrating on data structures rather than +objects, will keep your messages clean, tidy and efficient. + +=back + =head1 SEE ALSO L.