This manual does not explain the concepts behind the distributed processing model of Ada 95. The reader is supposed to have a clear understanding of the Ada 95 distributed annex.
An Ada 95 distributed application comprises a number of partitions which can be executed concurrently on the same machine or, and this is the interesting part, can be distributed on a network of machines. The way in which partitions communicate is described in Annex E of the Ada 95 reference manual.
A partition is a set of compilation units which are linked together to produce an executable binary. A distributed program comprises two or more communicating partitions.
The distributed systems annex does not describe how a distributed application should be configured. It is up to the user to define what are the partitions in his program and on which machines they should be executed.
The tool gnatdist and its configuration language have been purposely designed to allow you to partition your program and specify the machines where the individual partitions are to execute on.
gnatdist reads a configuration file (whose syntax is described below) and builds several executables, one for each partition. It also takes care of launching the different partitions (default) and to pass arguments specific to each partition.
gnatdist [switches] configuration-file [list-of-partitions]
The switches of gnatdist are, for the time being, exactly the same as for gnatmake. Read the gnatinfo.txt file from the GNAT distribution for info on these switches. By default gnatdist outputs a configuration report and the actions performed. The switch -n allows gnatdist to skip the first stage of recompilation of the non-distributed application.
All configuration files should end with the `.cfg' suffix. There may be several configuration files for the same distributed application, as you may want to use different distributed configurations according to your computing environment.
If a list of partitions is provided on the command line, only these partitions will be build. In the following configuration example, you can type : gnatdist configuration partition_2 partition_3.
Here is what goes on behind the scenes in gnatdist when building a distributed application:
The configuration language is "Ada-like". Because of its simplicity, it is described by means of an example. As the capabilities of GLADE will evolve, so will this configuration language.
Every keyword and construct defined in the configuration language have been used in the following sample configuration file.
--------------------------- -- File `my_config.cfg' -- --------------------------- -- Typically after having created the following configuration file -- you would type -- -- gnatdist my_config.cfg -- -- If you wish to build only certain partitions then list the partitions -- to build on the gnatdist command line as follows: -- -- gnatdist my_config.cfg partition_2 partition_3 configuration My_Config is -- The name of the file prefix must be the same as the name of -- the configuration unit, in this example `my_config'. The file -- suffix must be `cfg'. For a given distributed application -- you can have as many configuration files as you wish. Partition_1 : Partition := (); procedure Master_Procedure is in Partition_1; -- Partition 1 contains no RCI package. -- However, it will contain the main procedure of the distributed -- application, called `Master_Procedure' in this example. If the -- line `procedure Master_Procedure is in Partition_1;' was missing -- Partition 1 would be completely empty. This is forbidden, a -- partition has to contain at least one library unit. -- -- gnatdist produces an executable with the name of Master_Procedure -- which will starts the various partitions on their host machines -- in the background. The main partition is launched in the foreground. -- Note that by killing this main procedure the whole distributed -- application is halted. Partition_2, Partition_3 : Partition; for Partition_2'Host use "foo.bar.com"; -- Specify the host on which to run partition 2. function Best_Node (Partition_Name : String) return String; pragma Import (Shell, Best_Node, "best-node"); for Partition_3'Host use Best_Node; -- Use the value returned by an a program to figure out at execution time -- the name of the host on which partition 3 should execute. -- For instance, execute the shell script `best-node' which takes -- the partition name as parameter and returns a string giving the name of -- the machine on which partition_3 should be launched. Partition_4 : Partition := (RCI_B5); -- Partition 4 contains one RCI package RCI_B5 -- No host is specified for this partition. The startup script -- will ask for it interactively when it is executed. for Partition_1'Storage_Dir use "/usr/you/test/bin"; -- Specify the directory in which the executables in each partition -- will be stored. for Partition'Storage_Dir use "bin"; -- Specify the directory in which all the partition executables -- will be stored. Default is the current directory. procedure Another_Main; for Partition_3'Main use Another_Main; -- Specify the partition main subprogram to use in a given -- partition. for Partition_4'Command_Line use "-v"; -- Specify additional arguments to pass on the command line when a -- given partition is launched. pragma Starter (Method => Ada); -- Specify the kind of startup method you would like. There are 3 -- possibilities: Shell, Ada and None. Specifying `Shell' builds a shell -- script. All the partitions will be launched from a shell script. -- If `Ada' is chosen, then the main Ada procedure itself is used to launch -- the various partitions. If method `None' is chosen, then -- no launch method is used and you have to start each partition -- manually. -- -- If no starter is given, then an Ada starter will be used. -- -- In this example, Partition_2, Partitions_3 and Partition_4 will be -- started from Partition_1 (ie from the Ada procedure Master_Procedure). pragma Boot_Server (Protocol_Name => "tcp", Protocol_Data => "`hostname`:`unused-port`"); -- Specify the use of a particular boot server. It is especially -- useful when the default port 5555 used by GARLIC is already assigned. pragma Version (False); -- It is a bounded error to elaborate a partition of a distributed -- program that contains a compilation unit that depends on a -- different version of the declaration of RCI library unit than -- that included in the partition to which the RCI library unit was -- assigned. When the pragma Version is set to False, no -- consistency check is performed. Channel_1 : Channel := (Partition_1, Partition_4); Channel_2 : Channel := (Partition_2, Partition_3); -- Declare two channels. Other channels between partitions remain unknown. for Channel_1'Filter use "ZIP"; -- Use transparent compression/decompression for the arguments and results -- of any remote calls on channel "Channel_1", i.e. between "Partition_1" -- and "Partition_4". for Channel_2'Filter use "My_Own_Filter"; -- Use filter "My_Own_Filter" on "Channel_2". This filter must be imple- -- mented in a package "System.Garlic.Filters.My_Own_Filter". for Partition_3'Filter use "ZIP"; -- For all data exchanged with "Partition_3", use the filter "ZIP". (I.e. -- for both arriving remote calls as well as for calls made by this parti- -- tion.) Only for calls on "Channel_2" (i.e. for communication between -- "Partition_2" and "Partition_3"), the filter "My_Own_Filter" is used. pragma Registration_Filter ("Some_Filter"); -- "Some_Filter" will be used to exchange a filter's parameters between -- two partitions. "Some_Filter" itself must be an algorithm that doesn't -- need its own parameters to be filtered again! -- On all other channels (i.e., for remote calls between partitions where -- no channel was declared), filtering is not used. begin -- The configuration body is optional. You may have fully described your -- configuration in the declaration part. Partition_2 := (RCI_B2, RCI_B4, Normal); -- Partition 2 contains two RCI packages RCI_B2 and RCI_B4 -- and a normal package. A normal package is not categorized. Partition_3 := (RCI_B3); -- Partition 3 contains one RCI package RCI_B3 end My_Config;
To start a partition, the main partition executable executes a remote shell. Thus you have to make sure that you are authorized to execute a remote shell on the remote machine. In this case, a first step would be to add into your ${HOME}/.rhosts file a line like :
remote-machine your-username
If you are not authorized at all, you can bypass this problem. All you have to do is:
% PART [--nolaunch] [--slave] --boot_server tcp://MAIN_HOST:PORT_NUMThe --nolaunch parameter must be included for the main partition, it means that this partition is not in charge of launching others. The --slave parameter must be included for other partitions, meaning that in no case the name server is located on them.
All GLADE intermediate files (stubs, objects, etc) are stored under a common directory named "dsa". You may remove this whole directory and its content when you do not intend to rebuild your distributed applications.
GLADE contains a transparent extensible filtering mechanism allowing the user to define various data transformations to be performed on the arguments and return values of remote calls. One possible application would be to compress all data before sending it and to decompress it on the receiving partition.
With GLADE, it is no longer necessary that the application take care of such transformations. Instead, users can write their own data transformations and hook them into GLADE so that they are automatically and transparently applied depending on the configuration of the distributed application.
As a default, no filtering is performed by GLADE. As a default, the compression filter is available. Therefore, you can configure your distributed application in order to use this filter.
The configuration language not only knows about partitions, it also knows about the connections between them. Such a connection is called a "Channel" and represents a bi-directional link between two partitions. In order to define filtering, one must first declare the channels between the partitions of an application:
A_Channel : Channel := (Partition_1, Partition_2);
This gives the link between partitions "Partition_1" and "Partition_2" the name "A_Channel". It is not possible to declare more than one channel between the same two partitions.
Now that this channel is known, the data transformation that is to be applied on all data sent through it can be defined:
for A_Channel'Filter use "ZIP";
This specifies that all data sent over this channel should be transformed by the filter named "ZIP". (There must be a filter with this name, imple- mented in the package 'System.Garlic.Filters.Zip'.)
Some filtering algorithms require that some parameters must be sent to the receiver first to enable it to correctly de-filter the data. If this is the case, it may be necessary to filter these parameters again. For such purposes, it is possible to install a global filter for all partitions, which then will be used to filter the parameters of other filters. This filter is called the "registration filter". It can be set by a pragma:
pragma Registration_Filter ("Filter_Name");
(Note: this is a pragma of the configuration language, not of Ada 95!).
It may also be useful to specify that a partition use a certain filter for all remote calls, regardless of the channel (i.e., regardless of the partition that'll receive the remote call). This can be specified using the attribute 'Filter on a partition:
for Partition_1'Filter use "ZIP";
or even
for Partition'Filter use "ZIP";
(The latter set the default filter for all partitions of the application, the former only sets the default filter for the partition "Partition_1".) It is also possible to apply a default filter and to override this default for specific channels:
My_Channel : Channel := (Partition_1, Partition_2); for My_Channel'Filter use "ZIP"; for Partition_1'Filter use "Some_Other_Filter";
This makes 'Partition_1' use "Some_Other_Filter" for all remote calls except for any communication with 'Partition_2', where the filter "ZIP" is applied.
Gnatdist takes care of consistency checking of a filter definition. By default, no filtering is done. Filtering is only active if specified explicitly in the configuration file.
As has been briefly mentioned above, a filter with a name "NAME" must be implemented in a package called 'System.Garlic.Filters.Name'. You may write your own filters, which must implement their filtering of data in the primitive operations of a type derived from the type 'System.Garlic.Filters.Filter_Type'. Your filter package must then register an instance of your newly derived type with GLADE by calling 'System.Garlic.Filters.Register'. From that on, your filter is ready to be used.
For more information on how to write your own filter packages see the sample implementation of a ZIP filter in files 's-gafizi.ad[bs]' in the distribution. You might also want to look at the example in the 'Filtering' directory of the GLADE distribution.
GLADE has a facility for trace/replay based debugging. If trace mode is turned on, GLADE will record all messages received by a partition into a trace file. The trace file can then be used to replay the execution of the partition, in isolation.
To get a partition to generate a trace file, it has to be passed the command line argument "--trace". This is most easily done by using the "for Partition'Command_Line use..." construct (described above) in the configuration file to add "--trace" to the command lines of the partitions whose executions are to be replayed. When the application has been built, starting it using the starter, as usual, will then result in the trace files being generated.
By default, the file name of the trace file is the name of the partition's executable (i.e. the string returned by the standard procedure Ada.Command_Line.Command_Name) with a trailing ".trace". This can be changed with the "--trace_file othername" command line argument. Note that since the remote partitions are launched with rsh under Unix, the current directory during execution will be the user's home directory. This is no problem when using the default trace file name, because the executable's name will include the absolute path. When using the "--trace_file" option, on the other hand, if you don't want the trace file to be created/read in the home directory, the absolute path will have to be included in the desired name.
In order to replay a partition whose execution has been previously traced, the command line argument "--replay" is required. In addition, the special boot server location "replay://" has to be specified, i.e. by using the "--boot_server replay://" command line argument.
Example: To replay a traced execution of partition whose executable is named PART, you would start it with the command
% PART [--nolaunch] [--slave] --replay --boot_server replay://
possibly under the control of a debugger, such as gdb.
Since the exact contents of the messages received is recorded, differences in input from external sources (such as standard input) during replay will most likely give unexpected results. Also, replay of applications whose behavior is inherently non-deterministic will be problematic.
N.B. It is important that the same executable is used for replay as when the trace file was generated, otherwise strange behavior can be expected.
Static remote procedures, asynchronous remote procedures, remote access to class wide types and asynchronous transfer of control with remote procedures are implemented. Remote types packages are implemented.
Remote access to subprogram have not yet been implemented.
Pragma All_Calls_Remote has not yet been implemented.
Shared passive packages and generic RCI packages are also unimplemented.
Language-defined exceptions propagate well through different partitions.
For the time being, gnatdist is only able to build distributed applications for a pool of homogeneous or heterogeneous machines using TCP/IP as a common network protocol.
Please send any comment to glade-report@act-europe.fr.