=head1 Message Passing for the Non-Blocked Mind =head1 Introduction and Terminology This is a tutorial about how to get the swing of the new L module, which allows programs to transparently pass messages within the process and to other processes on the same or a different host. What kind of messages? Basically a message here means a list of Perl strings, numbers, hashes and arrays, anything that can be expressed as a L text (as JSON is used by default in the protocol). Here are two examples: write_log => 1251555874, "action was successful.\n" 123, ["a", "b", "c"], { foo => "bar" } When using L it is customary to use a descriptive string as first element of a message, that indictes the type of the message. This element is called a I in L, as some API functions (C) support matching it directly. Supposedly you want to send a ping message with your current time to somewhere, this is how such a message might look like (in Perl syntax): ping => 1251381636 Now that we know what a message is, to which entities are those messages being I? They are I to I. A I is a destination for messages but also a context to execute code: when a runtime error occurs while executing code belonging to a port, the exception will be raised on the port and can even travel to interested parties on other nodes, which makes supervision of distributed processes easy. How do these ports relate to things you know? Each I belongs to a I, and a I is just the UNIX process that runs your L application. Each I is distinguished from other I running on the same or another host in a network by its I. A I is simply a unique string chosen manually or assigned by L in some way (UNIX nodename, random string...). Here is a diagram about how I, I and UNIX processes relate to each other. The setup consists of two nodes (more are of course possible): Node C (in UNIX process 7066) with the ports C and C. And the node C (in UNIX process 8321) with the ports C and C. |- PID: 7066 -| |- PID: 8321 -| | | | | | Node ID: A | | Node ID: B | | | | | | Port ABC =|= <----\ /-----> =|= Port FOO | | | X | | | Port DEF =|= <----/ \-----> =|= Port BAR | | | | | |-------------| |-------------| The strings for the I here are just for illustrative purposes: Even though I in L are also identified by strings, they can't be choosen manually and are assigned by the system dynamically. These I are unique within a network and can also be used to identify senders or as message tags for instance. The next sections will explain the API of L by going through a few simple examples. Later some more complex idioms are introduced, which are hopefully useful to solve some real world problems. =head1 Passing Your First Message As a start lets have a look at the messaging API. The following example is just a demo to show the basic elements of message passing with L. The example should print: C, in a rather complicated way, by passing some message to a port. use AnyEvent; use AnyEvent::MP; my $end_cv = AnyEvent->condvar; my $port = port; rcv $port, test => sub { my ($data) = @_; $end_cv->send ($data); }; snd $port, test => 123; print "Ending with: " . $end_cv->recv . "\n"; It already uses most of the essential functions inside L: First there is the C function which will create a I and will return it's I, a simple string. This I can be used to send messages to the port and install handlers to receive messages on the port. Since it is a simple string it can be safely passed to other I in the network when you want to refer to that specific port (usually used for RPC, where you need to tell the other end which I to send the reply to - messages in L have a destination, but no source). The next function is C: rcv $port, test => sub { ... }; It installs a receiver callback on the I that specified as the first argument (it only works for "local" ports, i.e. ports created on the same node). The next argument, in this example C, specifies a I to match. This means that whenever a message with the first element being the string C is received, the callback is called with the remaining parts of that message. Messages can be sent with the C function, which is used like this in the example above: snd $port, test => 123; This will send the message C<'test', 123> to the I with the I stored in C<$port>. Since in this case the receiver has a I match on C it will call the callback with the first argument being the number C<123>. The callback is a typicall AnyEvent idiom: the callback just passes that number on to the I C<$end_cv> which will then pass the value to the print. Condition variables are out of the scope of this tutorial and not often used with ports, so please consult the L about them. Passing messages inside just one process is boring. Before we can move on and do interprocess message passing we first have to make sure some things have been set up correctly for our nodes to talk to each other. =head1 System Requirements and System Setup Before we can start with real IPC we have to make sure some things work on your system. First we have to setup a I: for two L I to be able to communicate with each other and authenticate each other it is necessary to setup the same I for both of them (or use TLS certificates). The easiest way is to set this up is to use the F utility: aemp gensecret This creates a F<$HOME/.perl-anyevent-mp> config file and generates a random shared secret. You can copy this file to any other system and then communicate over the network (via TCP) with it. You can also select your own shared secret (F) and for increased security requirements you can even create a TLS certificate (F), causing connections to not just be authenticated, but also to be encrypted. Connections will only be successful when the I that want to connect to each other have the same I (or successfully verify the TLS certificate of the other side). B is the same on all hosts/user accounts that you try to connect with each other!> Thats all for now, there is more fiddling around with the C utility later. =head1 Passing Messages Between Processes =head2 The Receiver Lets split the previous example up into two small programs. First the receiver application: #!/opt/perl/bin/perl use AnyEvent; use AnyEvent::MP; use AnyEvent::MP::Global; initialise_node "eg_simple_receiver"; my $port = port; AnyEvent::MP::Global::register $port, "eg_receivers"; rcv $port, test => sub { my ($data, $reply_port) = @_; print "Received data: " . $data . "\n"; }; AnyEvent->condvar->recv; =head3 AnyEvent::MP::Global Now, that wasn't too bad, was it? Ok, lets step through the new functions and modules that have been used. For starters there is now an additional module loaded: L. That module provides us with a I, which lets us share data among all I in a network. Why do we need it you might ask? The thing is, that the I are just random strings, assigned by L. We can't know those I in advance, so we don't know which I to send messages to if the message is to be passed between I (or UNIX processes). To find the right I of another I in the network we will need to communicate that somehow to the sender. And exactly that is what L provides. =head3 initialise_node And The Network Now, lets have a look at the next new thing, the C: initialise_node "eg_simple_receiver"; Before we are able to send messages to other nodes we have to initialise ourself. The first argument, the string C<"eg_simple_receiver">, is called the I of this node. A profile holds some information about the application that is going to be a node in an L network. Most importantly the profile allows you to set the I that your application will use. You can also set I in the profile, meaning that you can define TCP ports that the application will listen on for incoming connections from other nodes of the network. Next you can configure I in profile. A I is just a TCP endpoint which tells the application where to find other nodes of it's network. To explain this a bit more detailed we have to look at the topology of an L network. The topology is called a I, here an example with 4 nodes: N1--N2 | \/ | | /\ | N3--N4 Now imagine another I C. wants to connect itself to that network: N1--N2 | \/ | N5 | /\ | N3--N4 The new node needs to know the I of all of those 4 already connected nodes. And exactly this is what the I are for. Now lets assume that the new node C has as I the TCP endpoint of the node C. It then connects to C: N1--N2____ | \/ | N5 | /\ | N3--N4 C then tells C the I of the other nodes it is connected to, and C builds up the rest of the connections: /--------\ N1--N2____| | \/ | N5 | /\ | /| N3--N4--- | \________/ Finished. C is now happily connected to the rest of the network. =head3 Setting Up The Profiles Ok, so much to the profile. Now lets setup the C I for later. For the receiver we just give the receiver a I: aemp profile eg_simple_receiver setbinds localhost:12266 And while we are at it, just setup the I for the sender in the second part of this example too. We will call the sender I C. For the sender we will just setup a I to the receiver: aemp profile eg_simple_sender setseeds localhost:12266 aemp profile eg_simple_sender setbinds You might wonder why we setup I to be empty here. Well, there can be exceptions to the I in the I in L. If you don't configure a I for a node's profile it won't bind itself somewhere. These kinds of I will not be able to send messages to other I that also didn't I them self to some TCP address. For this example, as well as some cases in the real world, we can live with this limitation. =head3 Registering The Receiver Ok, where were we. We now discussed the basic purpose of L and initialise_node with it's relations to profiles. We also setup our profiles for later use and now have to continue talking about the receiver example. Lets look at the next undiscussed line(s) of code: my $port = port; AnyEvent::MP::Global::register $port, "eg_receivers"; The C function already has been discussed. It just creates a new I and gives us the I. Now to the C function of L: The first argument is a I that we want to add to a I, and it's second argument is the name of that I. You can choose that name of such a I freely, and it's purpose is to store a set of I. That set is made available throughout the whole L network, so that each node can see which ports belong to that group. The sender will later look for the ports in that I and send messages to them. Last step in the example is to setup a receiver callback for those messages like we have discussed in the first example. We again match for the I C. The difference is just that we don't end the application after receiving the first message. We just infinitely continue to look out for new messages. =head2 The Sender Ok, now lets take a look at the sender: #!/opt/perl/bin/perl use AnyEvent; use AnyEvent::MP; use AnyEvent::MP::Global; initialise_node "eg_simple_sender"; my $find_timer = AnyEvent->timer (after => 0, interval => 1, cb => sub { my $ports = AnyEvent::MP::Global::find "eg_receivers" or return; snd $_, test => time for @$ports; }); AnyEvent->condvar->recv; It's even less code. The C is known now from the receiver above. As discussed in the section where we setup the profiles we configure this application to use the I C. Next we setup a timer that repeatedly calls this chunk of code: my $ports = AnyEvent::MP::Global::find "eg_receivers" or return; snd $_, test => time for @$ports; The new function here is the C function of L. It searches in the I named C for ports. If none are found C is returned and we wait for the next time the timer fires. In case the receiver application has been connected and the newly added port by the receiver has propagated to the sender C returns an array reference that contains the I of the receiver I. We then just send to every I in the I a message consisting of the I C and the current time in form of a UNIX timestamp. And thats all. =head1 SEE ALSO L L L L =head1 AUTHOR Robin Redeker