Gem #87: The Distributed Systems Annex, Part 3 — Mailboxes

by Thomas Quinot—AdaCore

Let's get started...

In the previous two DSA gems, all communication between partitions occurred as subprogram calls: the received message is handled immediately by the receiving partition (the subprogram body is executed), and the caller resumes execution only after the call returns.

In some applications, a different communication pattern is desired. One partition may want to send a message to another and then forget about it; the receiving partition may not be available to process the message at that time, and may want to keep it queued for later processing.

Sending a message in a "fire-and-forget" fashion can be implemented in the DSA using pragma Asynchronous. This pragma, which applies to subprograms and to remote access types, means that the called subprogram does not return any information (it must be a procedure, and may not have any OUT or IN OUT formal parameters), and that the caller is not interested in any exception that might be raised. When this pragma applies to a remote procedure, execution resumes in the calling task immediately after sending the call (without waiting for any confirmation from the receiver). When the pragma applies to an RACW, this extends to all relevant primitive operations (i.e. procedures with no OUT or IN OUT formals).

The message sending capability is thus simply described by a remote access type declaration:

<strong>package</strong> Mailboxes <strong>is</strong>
   <strong>pragma</strong> Remote_Types;
   <strong>subtype</strong> Message_Type <strong>is</strong> String;
   <em>--  In this simple example, exchanged messages are just strings, but this</em>
   <em>--  could be changed to any other type, or made a generic type.</em>
   <strong>type</strong> Mailbox <strong>is</strong> <strong>limited</strong> <strong>interface</strong>;
   <em>--  This is Ada 2005!</em>
   <em>--  Using an interface as the base type allows the capability to</em>
   <em>--  receive a message to be subsequently imparted on arbitrary objects</em>
   <em>--  (they just need to implement that interface).</em>
   <strong>procedure</strong> Send_Message (Recipient : <strong>access</strong> Mailbox; Message : Message_Type)
     <strong>is</strong> <strong>abstract</strong>;
   <strong>type</strong> Remote_Mailbox <strong>is</strong> <strong>access</strong> <strong>all</strong> Mailbox'Class;
   <em>--  Remote access to mailbox</em>
   <strong>pragma</strong> Asynchronous (Remote_Mailbox);
   <em>--  Calls to Send_Message will return to the caller without waiting for</em>
   <em>--  any reply from the callee.</em>
<strong>end</strong> Mailboxes;

A very simple implementation of a mailbox is the "active" mailbox, where a dedicated task handles each incoming message:

<strong>package</strong> Mailboxes.Active <strong>is</strong>
   <strong>task</strong> <strong>type</strong> Active_Mailbox <strong>is</strong> <strong>new</strong> Mailbox <strong>with</strong>
      <strong>entry</strong> Start (Id : Integer);
      <strong>entry</strong> Send_Message (Message : Message_Type);
   <strong>end</strong> Active_Mailbox;
   <strong>type</strong> Active_Mailbox_Acc <strong>is</strong> <strong>access</strong> <strong>all</strong> Active_Mailbox;
   <em>--  Local access type</em>
<strong>end</strong> Mailboxes.Active;
<strong>with</strong> Ada.Text_IO; <strong>use</strong> Ada.Text_IO;
<strong>package</strong> <strong>body</strong> Mailboxes.Active <strong>is</strong>
   <strong>task</strong> <strong>body</strong> Active_Mailbox <strong>is</strong>
      My_Id : Integer;
   <strong>begin</strong>
      <strong>accept</strong> Start (Id : Integer) <strong>do</strong>
         My_Id := Id;
      <strong>end</strong> Start;
      Put_Line ("Active_Mailbox #" & My_Id'Img & " starting");
      <strong>loop</strong>
         <strong>accept</strong> Send_Message (Message : Message_Type) <strong>do</strong>
            Put_Line ("... got message: " & Message);
         <strong>end</strong> Send_Message;
      <strong>end</strong> <strong>loop</strong>;
   <strong>end</strong> Active_Mailbox;
<strong>end</strong> Mailboxes.Active;

Note that this implementation could perfectly well be replaced with any other type implementing the Mailbox interface, for example a protected bounded buffer. Any partition can thus create a mailbox on which it will receive messages from others, just by creating an object of type Mailboxes.Active.Active_Mailbox.

Now, another partition that needs to send it a message will need to obtain an RACW designating that mailbox in order to do so, just like you'd need an address to send a postcard. An RCI package can be used as a central clearinghouse to exchange these initial references: the RCI acts as a directory of partitions that can receive messages.

<strong>with</strong> Mailboxes;
<strong>package</strong> Hub <strong>is</strong>
   <strong>pragma</strong> Remote_Call_Interface;
   <strong>procedure</strong> Register_Listener (Id : Integer; Ptr : Mailboxes.Remote_Mailbox);
   <em>--  A partition that has created a mailbox registers it here, associating</em>
   <em>--  it with a unique identifier Id.</em>
   <strong>function</strong> Get_Listener (Id : Integer) <strong>return</strong> Mailboxes.Remote_Mailbox;
   <em>--  A partition that wants to send a message to the mailbox identified by Id</em>
   <em>--  retrieves the corresponding RACW (previously registered using the above</em>
   <em>--  procedure) by calling this function.</em>
   <em>--  The implementation of this unit can be as simple as an array of RACWs:</em>
   <em>--    All_Listeners : array (1 .. Max_Mailboxes) of Mailboxes.Remote_Mailbox;</em>
<strong>end</strong> Hub;

It should be noted that the RCI is used only to initially disseminate references to partitions. The messages themselves are sent directly across partitions. There is no single point of failure or communication bottleneck.

Complete source code for this application (message sender, message receiver, and central hub) is available in subdirectory examples/dsa/mailboxes of the PolyORB source package, or can also be downloaded directly from this page.

mailboxes.tar.gz


About the Author

Thomas Quinot holds an engineering degree from Télécom Paris and a PhD from Université Paris VI. The main contribution of his research work is the definition of a flexible middleware architecture aiming at interoperability across distribution models. He joined AdaCore as a Senior Software Engineer in 2003, and is responsible for the distribution technologies. He also participates in the development, maintainance and support of the GNAT compiler.


Last Updated: 10/26/2017
Posted on: 6/2/2010