|
Author: Thomas Quinot, AdaCore
Abstract: Ada Gem #84 — This is the first in a series of Gems introducing the facilities defined by the optional annex for distributed systems (Annex E) in the Ada Reference Manual. In this introduction, we show how a simple client/server architecture can be implemented easily with the Distributed Systems Annex (DSA).
Many aspects of software engineering require, or can benefit from, distributed technology:
… among others.
In each of these instances, it is useful to enlist the contribution of multiple computers to achieve a certain goal in a coordinated fashion. In a distributed application design, parts of the processing are thus assigned to distinct hosts which communicate in order to provide a given service. In Ada parlance, the fraction of the complete application that is assigned to each host is called a partition.
A distributed design can be implemented using direct calls to communication services provided by the environment, allowing the exchange of data between partitions. However, this is extremely cumbersome and error-prone. Distribution models have therefore been defined, which are sets of high-level abstractions allowing the programmer to express the interactions between components of a distributed application — possibly located on different partitions – in convenient high-level terms.
Distribution models support various communication patterns. The simplest ones support simple message passing. More elaborate models also provide more structured patterns, such as remote subprogram calls (based on the natural abstraction boundaries represented by subprograms) and distributed (remote) objects, extending remote subprogram calls to the case of method calls in an object-oriented design.
The services afforded by distribution middleware (i.e., the implementation of a distribution model) can be made available to the programmer in different ways. Explicit distribution APIs can be used. Alternatively, distribution may be included in the facilities provided by a programming language. Ada 95 and Ada 2005 include such features as part of the optional Annex E of the Reference Manual.
In this first introductory example, we consider a simple application managing a public bulletin board, which we want to make available for posting from several partitions. The DSA allows a service to be offered in a very simple way: you just write a package declaration:
package Bulletin_Board is
pragma Remote_Call_Interface;
-- This makes the package a Remote Call Interface (RCI), so the subprograms
-- below are remotely callable.
-- This pragma enforces some restrictions on the unit to ensure that any
-- visible subprogram can actually be called remotely, and in particular
-- that the types of the parameters are suitable for transport over a
-- communication link from one partition to another.
subtype Length is Natural range 0 .. 100;
type News_Item (Author_Length, Message_Length : Length := 0) is record
Author : String (1 .. Author_Length);
Message : String (1 .. Message_Length);
end record;
type News_Items is array (Positive range <>) of News_Item;
procedure Post (Item : News_Item);
function Whats_Up return News_Items;
end Bulletin_Board;
A simple client can then be written that will just make calls to these subprograms. The fact that these calls may be executed remotely is completely transparent in the code.
with Ada.Text_IO; use Ada.Text_IO;
with Bulletin_Board; use Bulletin_Board;
procedure Post_Message is
Author, Message : String (1 .. 140);
Author_Length, Message_Length : Natural;
begin
Put ("Author name: ");
Get_Line (Author, Author_Length);
Put ("Message : ");
Get_Line (Message, Message_Length);
Post (News_Item'
(Author_Length => Author_Length,
Message_Length => Message_Length,
Author => Author (1 .. Author_Length),
Message => Message (1 .. Message_Length)));
-- This subprogram call may be remote, but we write it exactly in the
-- usual way.
end Post_Message;
Similarly, a procedure that displays all messages can be written as follows:
with Ada.Text_IO; use Ada.Text_IO;
with Bulletin_Board; use Bulletin_Board;
procedure Display_Messages is
begin
loop
Put_Line ("----- all messages -----");
declare
Contents : constant News_Items := Whats_Up;
begin
for J in Contents'Range loop
Put_Line (Contents (J).Author & " says:");
Put_Line (Contents (J).Message);
New_Line;
end loop;
delay 2.0;
end;
end loop;
end Display_Messages;
This procedure can run on the same partition as the one where
Bulletin_Board is located (the language requires that each
Remote_Call_Interface unit is assigned to exactly one partition).
However, since it only uses a visible subprogram declared in Bulletin_Board
(Whats_Up), it could also very well run in another partition.
The assignment of units to partitions need not be apparent in sources. The same set of sources can even be used for different partitioning configurations (or used without partitioning to build a monolithic version of the application, in which case there is no distribution overhead at all).
The process of partitioning a DSA application is implementation defined.
In GNAT, this is done using the gnatdist tool, and a po_gnatdist configuration
file. The syntax for this file is documented in the
PolyORB User’s Guide.
Here is an example configuration for the bulletin board application:
configuration Dist_App is pragma Starter (None); -- User starts each partition manually ServerP : Partition := (Bulletin_Board); -- RCI package Bulletin_Board is on partition ServerP ClientP : Partition := (); -- Partition ClientP has no RCI packages for ClientP'Termination use Local_Termination; -- No global termination procedure Display_Messages is in ServerP; -- Main subprogram of master partition procedure Post_Message; for ClientP'Main use Post_Message; -- Main subprogram of slave partition end Dist_App;
After running po_gnatdist on this configuration file, two executables are
produced: serverp and clientp. Serverp will loop,
displaying all posted messages, and clientp will allow sending a message to the server.
This example thus shows how a simple client/server design can be implemented in Ada without
any network programming.
In a future Gem we will discuss remote object designs, which allow flexible dynamic communication across partitions.
Posted
in Development Log, Devt log - Gem of the Week, PolyORB
If you have an idea for a Gem you would like to contribute please feel free to contact us at: gems@adacore.com
Siarhei Kirkorau said:
Useful article. I waited for a long time for publications on this theme. Thanks.
Chris Miller said:
Delighted to see some gems on the Ada DSA. I really like the DSA – not every application needs all the Corba / MOMA / SOAP / middleware etc. stuff. Sometime all you want is a small, simple Ada based distributed sytem and the DSA is a good start. I hope the Ada community continues to support the Annex E DSA.
Another Gem that explaines pragmas shared_passive & remote_types would be nice !
Peter Chapin said:
This is interesting material. I’m unclear about how the client program knows where the server is located in the case where they are on different machines. At some point one needs to talk about IP addresses or host names or similar things. True?
Thomas Quinot said:
Siarhei, thanks!
Chris, a second gem about distributed objects is already in the pipeline!
Peter, excellent question :-) Yes, absolutely, in the end the distribution library (or, in Reference Manual jargon, the PCS — Partition Communication Subsystem) needs to have network addresses to establish connections. The method used by each partition to obtain this information is not specified by the language. In the PolyORB/DSA implementation (which is the standard one in recent GNAT releases), this is achieved using a central name server. This name server can be either started automatically within a designated “master” partition, or as a standalone process. In turn, the address of the name server is passed to each partition as a configuration parameter (which can be read from a configuration file, from an environment variable, or from the command line). Alternate methods could be implemented, for example using mDNS and DNS-SD to locate partitions on a local network.
Frank said:
Hi Thomas,
thanks a lot for this “gem”. I played around with Ada/DSA/PolyORB a bit, and it is really nice.
I am currently searching for some kind of a drop-in replacement for C++/MPI (I want to migrate some parallel simulation code to Ada). So, what is the preferred way to do (cheap) message passing in Ada?
Furthermore, how to distribute parallelized Ada code onto some compute nodes efficiently – is the name server/master partition way the only one to go?
Thanks in advance.
Thomas Quinot said:
Frank, you can implement message passing on top of DSA distributed objects. The forthcoming 3rd episode in the DSA gems series (due next week) provides an example application that implements such a communication pattern. It uses pragma Asynchronous to identify subprograms that correspond to one-way interactions and do not require a reply. Calls to such subprograms are transported using a single network message.
The central name server is for now a requirement, but note that it is not a bottleneck for inter-partition communications: it is contacted only the first time each partitions calls a given RCI. Once the initial reference has been obtained, subprogram calls themselves go directly from the calling partition to the target one, without involving any interaction with the master partition.
Simon Wright said:
Type News_Item isn’t right — should be eg
type News_Item (Author_Length, Message_Length : Natural := 0) is record
Author : String (1 .. Author_Length);
Message : String (1 .. Message_Length);
end record;
(is Natural right? should there be a maximum length here?)
Thomas Quinot said:
Simon, indeed, News_Item can’t be unconstrained if we want to be able to make an array of it. I have adjusted the source code accordingly.
Brian.G said:
Post_Message should probably define:
Author, Message : String (1..Length’last);
Author_Length, Message_Length : Length;
to avoid overflow.
Nige said:
In your example “configuration” seems to be in place of an ada keyword. is this a adacore extension and where is this allowed in the language ?
Also po_gnatdist is not included in the standard gnatmake distribution, where can this be found ?
Also, how/where is it defined which machines are running the services and how do they know where to find each other?
Thomas Quinot said:
Nige, Configuration is not an Ada keyword, it is a keyword of the gnatdist configuration language, which has Ada-like syntax.
The partitioning tool po_gnatdist, as well as the PolyORB/DSA Partition Communication Subsystem (i.e. the distribution runtime library), are contained in the PolyORB add-on to GNAT.
The assignment of partitions to hosts is made by the user, either statically in the gnatdist configuration or at application launch time. The user can also start each partition himself. Partitions contact each other through the PolyORB name server. See the PolyORB User’s Guide for details.