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:

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

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

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

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


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.