Gem #85:The Distributed Systems Annex 2 — Distributed Objects

by Thomas Quinot —AdaCore

Let's get started…

In the previous DSA Gem, we showed how subprograms in a package can be made remotely callable using a pragma Remote_Call_Interface (RCI for short). Each RCI unit is present in only one partition of a distributed application, and any call to a subprogram in such a unit made from another partition is transparently handled by the distribution run-time library.

This is sufficient to implement simple client/server communication, where a single partition is identified as the provider of a service (defined by an RCI package) and accepts requests from other partitions. Different services can be provided by different partitions, and services can be clients of one another. However this scheme is inflexible in that a given service can only ever be provided by a single server. Furthermore, the association between services and partitions is static.

In some contexts, however, more flexible interactions between application components are desired: multiple partitions may want to provide the same service, for performance or fault-tolerance reasons; servers may need to call back their clients; finally, direct (peer-to-peer) interactions between partitions may need to be established in a dynamic fashion, without determining in advance (prior to execution) who will interact with whom.

Such a flexible organization can be implemented using distributed objects. In nondistributed object-oriented programming, an object is an entity with an identity (you can reference, or designate it), internal state, and a set of methods that are common to all objects that belong to the same class, and which represent the ways any object of the class can interact with others. In a distributed world, this paradigm is naturally extended by allowing object references to designate objects that are located on another partition.

In the DSA, distributed objects are created using a specific pragma: Remote_Types. When this pragma is applied to a package, certain type declarations have additional semantics specific to distribution. If you declare a tagged limited private type in such a package, and a corresponding access-to-class-wide type, then that access type is a Remote Access to Class-Wide type (or RACW), and is allowed to designate objects that are located on partitions other than the current one.

These remote object references can be passed around as parameters in remote subprogram calls. For example, they can be sent to an RCI package, or retrieved from it, by passing them as parameters in remote subprogram calls.

Methods of remote objects can be called by just writing a regular dispatching call on any primitive operation. All underlying communication is handled transparently by the distribution run-time library.

So let's now assume that we want to allow users of our bulletin board application to exchange direct messages with one another. Each user will instantiate an object of a concrete type derived from the User type:

package Chat_Users is
   pragma Remote_Types;
   --  This package declares a remote object type
   type User is abstract tagged limited private;
   --  Remote objects must be tagged, limited, and private
   type User_Ref is access all User'Class;
   --  This is a remote access-to-class-wide type
   function Name (Who : User) return String;
   procedure Say
     (From : User_Ref;
      To   : User;
      What : String);
   --  The controlling formal 'To' determines the object that calls are sent to.
   --  The recipient object may be remote. Formal parameter 'From' is a reference
   --  to the originating user, and can be used to call the user back at a later
   --  time.
private
   ...
end Chat_Users;

Each message posted to the bulletin board can now include a reference to the message author:

with Chat_Users;
package Bulletin_Board is
  ...
  type News_Item (Message_Length : Natural) is
     Author  : Chat_Users.User_Ref;
     Message : String (1 .. Message_Length);
  end News_Item;
  ...
end Bulletin_Board;

Now each client can create an instance of a concrete type derived from Chat_Users.User, and pass a 'Access to that object to the bulletin board as it posts messages.

with Chat_Users;
package Client is
   --  This is a regular package, no pragma needed
   type Myself_Type is new Chat_Users.User with null record;
   function Name (Self : Myself_Type) return String;
   procedure Say
     (From : Chat_Users.User_Ref;
      To   : Myself_Type;
      What : String);
end Client;
with Ada.Text_IO; use Ada.Text_IO;
package body Client is
   function Name (Self : Myself_Type) return String is
   begin
      return "Jean-Pierre";
   end Name;
   procedure Say
     (From : Chat_Users.User_Ref;
      To   : Myself_Type;
      What : String)
   is
      pragma Unreferenced (To);
      --  Parameter 'To' is unused within the body. Its purpose is just to cause
      --  dispatching to the appropriate object instance.
   begin
      Put_Line ("Got a message from " & From.Name);
      --  Dispatching call to Name to retrieve user name of the sender 'From'
      Put_Line (What);
      --  Display received message.
   end Say;
   Myself : aliased Myself_Type;
   ...
end Client;

Other clients can use the Author component retrieved from the bulletin board to directly contact other clients using the Say method:

   Say
     (From    => Myself'Access,
      To      => Some_Item.Author,
      Message => "I like it!");

Arbitrary partition-to-partition interactions can thus be established using distributed objects. More precisely, these are actually normal objects with the additional property that they can be designated from other partitions using special access types (RACWs). RCI units serve as switchboards to initially propagate references to remote objects across partition boundaries. Once these references are disseminated, partitions can interact directly without the mediation of RCIs.

In a future Gem, we will discuss the implementation of mailbox-based message passing using the Distributed Systems Annex.


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.