AnyEvent-MP
view release on metacpan or search on metacpan
MP/Intro.pod view on Meta::CPAN
before).
When you change both programs above to simply call
configure;
then AnyEvent::MP tries to look up a profile using the current node name
in its configuration database, falling back to some global default.
You can run "generic" nodes using the F<aemp> utility as well, and we will
exploit this in the following way: we configure a profile "seed" and run
a node using it, whose sole purpose is to be a seed node for our example
programs.
We bind the seed node to port 4040 on all interfaces:
aemp profile seed binds "*:4040"
And we configure all nodes to use this as seed node (this only works when
running on the same host, for multiple machines you would replace the C<*>
by the IP address or hostname of the node running the seed), by changing
the global settings shared between all profiles:
aemp seeds "*:4040"
Then we run the seed node:
aemp run profile seed
After that, we can start as many other nodes as we want, and they will
all use our generic seed node to discover each other. The reason we can
start our existing programs even though they specify "incompatible"
parameters to C<configure> is that the configuration file (by default)
takes precedence over any arguments passed to C<configure>.
That's all for now - next we will teach you about monitoring by writing a
simple chat client and server :)
=head1 PART 2: Monitoring, Supervising, Exception Handling and Recovery
That's a mouthful, so what does it mean? Our previous example is what one
could call "very loosely coupled" - the sender doesn't care about whether
there are any receivers, and the receivers do not care if there is any
sender.
This can work fine for simple services, but most real-world applications
want to ensure that the side they are expecting to be there is actually
there. Going one step further: most bigger real-world applications even
want to ensure that if some component is missing, or has crashed, it will
still be there, by recovering and restarting the service.
AnyEvent::MP supports this by catching exceptions and network problems,
and notifying interested parties of these.
=head2 Exceptions, Port Context, Network Errors and Monitors
=head3 Exceptions
Exceptions are handled on a per-port basis: all receive callbacks are
executed in a special context, the so-called I<port-context>: code
that throws an otherwise uncaught exception will cause the port to be
C<kil>led. Killed ports are destroyed automatically (killing ports is
actually the only way to free ports).
Ports can be monitored, even from a different node and host, and when a
port is killed, any entity monitoring it will be notified.
Here is a simple example:
use AnyEvent::MP;
# create a port, it always dies
my $port = port { die "oops" };
# monitor it
mon $port, sub {
warn "$port was killed (with reason @_)";
};
# now send it some message, causing it to die:
snd $port;
AnyEvent->condvar->recv;
It first creates a port whose only action is to throw an exception,
and the monitors it with the C<mon> function. Afterwards it sends it a
message, causing it to die and call the monitoring callback:
anon/6WmIpj.a was killed (with reason die oops at xxx line 5.) at xxx line 9.
The callback was actually passed two arguments: C<die>, to indicate it
did throw an I<exception> as opposed to, say, a network error, and the
exception message itself.
What happens when a port is killed before we have a chance to monitor
it? Granted, this is highly unlikely in our example, but when you program
in a network this can easily happen due to races between nodes.
use AnyEvent::MP;
my $port = port { die "oops" };
snd $port;
mon $port, sub {
warn "$port was killed (with reason @_)";
};
AnyEvent->condvar->recv;
This time we will get something else:
2012-03-21 00:50:36 <2> unmonitored local port fADb died with reason: die oops at - line 3.
anon/fADb was killed (with reason no_such_port cannot monitor nonexistent port)
The first line is an error message that is printed when a port dies that
isn't being monitored, because that is normally a bug. When later a C<mon>
is attempted, it is immediately killed, because the port is already
gone. The kill reason is now C<no_such_port> with some descriptive (we
hope) error message.
As you probably suspect from these examples, the kill reason is usually
some identifier as first argument and a human-readable error message as
second argument - all kill reasons by AnyEvent::MP itself follow this
pattern. But the kill reason can be anything: it is simply a list of
values you can choose yourself. It can even be nothing (an empty list) -
this is called a "normal" kill.
Apart from die'ing, you can kill ports manually using the C<kil>
function. Using the C<kil> function will be treated like an error when a
non-empty reason is specified:
kil $port, custom_error => "don't like your steenking face";
And a I<normal> kill without any reason arguments:
kil $port;
By now you probably wonder what this "normal" kill business is: A common
idiom is to not specify a callback to C<mon>, but another port, such as
C<$SELF>:
mon $port, $SELF;
This basically means "monitor $port and kill me when it crashes" - and
the thing is, a "normal" kill does not count as a crash. This way you can
easily link ports together and make them crash together on errors, while
allowing you to remove a port silently when it has done it's job properly.
=head3 Port Context
Code runs in the so-called "port context". That means C<$SELF> contains
its own port ID and exceptions that the code throws will be caught.
Since AnyEvent::MP is event-based, it is not uncommon to register
callbacks from within C<rcv> handlers. As example, assume that the
following port receive handler wants to C<die> a second later, using
C<after>:
my $port = port {
after 1, sub { die "oops" };
};
If you try this out, you would find it does not work - when the C<after>
callback is executed, it does not run in the port context anymore, so
exceptions will not be caught.
For these cases, AnyEvent::MP exports a special "closure constructor"
called C<psub>, which works mostly like perl's built-in C<sub>:
my $port = port {
after 1, psub { die "oops" };
};
C<psub> remembers the port context and returns a code reference. When the
code reference is invoked, it will run the code block within the context
that it was created in, so exception handling once more works as expected.
There is even a way to temporarily execute code in the context of some
port, namely C<peval>:
peval $port, sub {
# die'ing here will kil $port
};
The C<peval> function temporarily replaces C<$SELF> by the given C<$port>
and then executes the given sub in a port context.
=head3 Network Errors and the AEMP Guarantee
Earlier we mentioned another important source of monitoring failures:
network problems. When a node loses connection to another node, it will
invoke all monitoring actions, just as if the port was killed, I<even if
it is possible that the port is still happily alive on another node> (not
being able to talk to a node means we have no clue what's going on with
it, it could be crashed, but also still running without knowing we lost
the connection).
So another way to view monitors is: "notify me when some of my messages
couldn't be delivered". AEMP has a guarantee about message delivery to a
port: After starting a monitor, any message sent to a port will either
be delivered, or, when it is lost, any further messages will also be lost
until the monitoring action is invoked. After that, further messages
I<might> get delivered again.
This doesn't sound like a very big guarantee, but it is kind of the best
you can get while staying sane: Specifically, it means that there will be
no "holes" in the message sequence: all messages sent are delivered in
order, without any of them missing in between, and when some were lost,
you I<will> be notified of that, so you can take recovery action.
And, obviously, the guarantee only works in the presence of
correctly-working hardware, and no relevant bugs inside AEMP itself.
MP/Intro.pod view on Meta::CPAN
my $nick = shift;
configure;
$| = 1;
my $port = port;
my ($client, $server);
sub server_connect {
my $servernodes = grp_get "eg_chat_server2"
or return after 1, \&server_connect;
print "\rconnecting...\n";
$client = port { print "\r \r@_\n> " };
mon $client, sub {
print "\rdisconnected @_\n";
&server_connect;
};
$server = spawn $servernodes->[0], "::client_connect", $client, $nick;
mon $server, $client;
}
server_connect;
my $w = AnyEvent->io (fh => 0, poll => 'r', cb => sub {
chomp (my $line = <STDIN>);
print "> ";
snd $server, $line
if $server;
});
print "> ";
AnyEvent->condvar->recv;
The client is quite similar to the previous one, but instead of contacting
the server I<port> (which no longer exists), it C<spawn>s (creates) a new
the server I<port on node>:
$server = spawn $servernodes->[0], "::client_connect", $client, $nick;
mon $server, $client;
And of course the first thing after creating it is monitoring it.
Phew, let's go through this in slow motion: the C<spawn> function creates
a new port on a remote node and returns its port ID. After creating
the port it calls a function on the remote node, passing any remaining
arguments to it, and - most importantly - executes the function within
the context of the new port, so it can be manipulated by referring to
C<$SELF>. The init function can reside in a module (actually it normally
I<should> reside in a module) - AnyEvent::MP will automatically load the
module if the function isn't defined.
The C<spawn> function returns immediately, which means you can instantly
send messages to the port, long before the remote node has even heard
of our request to create a port on it. In fact, the remote node might
not even be running. Despite these troubling facts, everything should
work just fine: if the node isn't running (or the init function throws an
exception), then the monitor will trigger because the port doesn't exist.
If the spawn message gets delivered, but the monitoring message is not
because of network problems (extremely unlikely, but monitoring, after
all, is implemented by passing a message, and messages can get lost), then
this connection loss will eventually trigger the monitoring action. On the
remote node (which in return monitors the client) the port will also be
cleaned up on connection loss. When the remote node comes up again and our
monitoring message can be delivered, it will instantly fail because the
port has been cleaned up in the meantime.
If your head is spinning by now, that's fine - just keep in mind, after
creating a port using C<spawn>, monitor it on the local node, and monitor
"the other side" from the remote node, and all will be cleaned up just
fine.
=head2 Services
Above it was mentioned that C<spawn> automatically loads modules. This can
be exploited in various useful ways.
Assume for a moment you put the server into a file called
F<mymod/chatserver.pm> reachable from the current directory. Then you
could run a node there with:
aemp run
The other nodes could C<spawn> the server by using
C<mymod::chatserver::client_connect> as init function - without any other
configuration.
Likewise, when you have some service that starts automatically when loaded
(similar to AnyEvent::MP::Global), then you can configure this service
statically:
aemp profile mysrvnode services mymod::service::
aemp run profile mysrvnode
And the module will automatically be loaded in the node, as specifying a
module name (with C<::>-suffix) will simply load the module, which is then
free to do whatever it wants.
Of course, you can also do it in the much more standard way by writing
a module (e.g. C<BK::Backend::IRC>), installing it as part of a module
distribution and then configure nodes. For example, if I wanted to run the
Bummskraut IRC backend on a machine named "ruth", I could do this:
aemp profile ruth addservice BK::Backend::IRC::
And any F<aemp run> on that host will automatically have the Bummskraut
IRC backend running.
There are plenty of possibilities you can use - it's all up to you how you
structure your application.
=head1 PART 4: Coro::MP - selective receive
Not all problems lend themselves naturally to an event-based solution:
sometimes things are easier if you can decide in what order you want to
receive messages, regardless of the order in which they were sent.
( run in 1.612 second using v1.01-cache-2.11-cpan-81fc1098f69 )