ORG, ROI, pem - Specification 03/16/93

Peter Mueller mueller@sc.ZIB-Berlin.DE
Wed, 17 Mar 93 10:06:45 +0100


Howdy! 
 
In this mail I will give you a first specification of remote object 
invocation. There were no (zero!) responses to my previous mail. I get 
a little confused, have I said something wrong? Don't you have enough 
time to think it over? Well, I actually assume, that some of you have 
not received my mail and I've sent out an acknowledgement request. 
 
This specification presents only my point of view. It is written in 
present time so it's possible to use it as documentation when things 
work. I will try to keep it up-to-date and to `broadcast' it in a 
periodical manner (if there are no other opinions). I hope, you will 
send me many suggestions, ideas, wishes, and negative or positive criticism 
(which always should be constructive, of course :-). 
 
As I pointed out in previous mails, I'm an advocate of a strict separation 
between design/specification and implementation. So, the specification only 
says what happens and not how it is actually done. Nevertheless, as most of 
you are more interested in implementation details, I will give an example on 
how ROI is involved in an application at the end of the specification. 
 
And now happy reading, 
 
Peter 
 
----- Cut here ----- 
 
R e m o t e   O b j e c t   I n v o c a t i o n 
 
Specification Release 0.00 (03/16/93) 
 
1 Overview 
========== 
 
   This specification will give an abstract view on how remote object 
invocation is done in MOOSE. Remote object invocation (ROI) means to invoke 
a method from an object, which is not sharing the invoker's address space. 
Thus, `remote' in this context means still both invoker and invokee be 
running on the same machine. (This restriction will disappear, when MOOSE 
becomes a distributed OS. Then `remote' means not the same address space or 
even on a remote machine.) 
 
   Usually ROI is used, when an object needs access to services provided 
by other objects. Therefore the terms `client' and `server' are used to 
distinguish between the invoking and invoked object. Clients use services 
from servers. This allows a strict separation between these two 
functionalities, although it is still possible to have objects, which are 
both, client and server, at the same time. 
 
2 Design goals 
============== 
 
   The following points give an overview of desired characteristics for ROI. 
Thus, ROI should be: 
 
     1. general 
     2. object location transparent 
     3. very efficient regarding communication costs 
     4. user-friendly 
 
2.1 Generality 
-------------- 
 
   ROI must be provided for a variety of objects, which can be wide spreaded 
among different applications as well as different contexts or privilege 
levels. 
 
2.2 Object Location Transparency 
-------------------------------- 
 
   Most objects within MOOSE are location independent; it is not definitely 
clear where the object exist at a given time. Additionally, objects are 
created dynamically in a non-deterministic fashion. 
 
   Mechanisms are needed which provide a mapping function from an object's 
description to its actual location. These mechanisms are provided by a 
special kind of server object, the NAME SERVER. Name servers know the 
absolute locations of their objects and make them transparently available 
to other objects. 
 
2.3 Efficiency 
-------------- 
 
   As one design goal of MOOSE is to provide a highly dynamic system, where 
components are added on the fly, ROI will be a highly used communication 
mechanism. Almost all traditional operating system services are provided by 
independent objects. Therefore ROI must be very efficient to minimize the 
remote invocation overhead. This can only be reached if very efficient IPC 
mechanisms exist. Thus the kernel must provide very fast IPC (which is to be 
specified in another release). 
 
2.4 User-friendliness 
--------------------- 
 
   Users and their applications should not be aware of ROI. If an application 
needs to invoke methods of a remote server it should simply instance an 
appropriate client object. All necessary ROI stuff should be done by creation 
of the client, which then provides a transparent view to the servers' 
methods. 
 
   Surely there must exist mechanisms, which allow to interfere in the ROI 
process as much as the user want. Eg, it must be possible to ask a specific 
name server for a server object's location. 
 
3. Object Identification 
======================== 
 
   To give access to a server's method the server must be known to all 
possible clients. Thus the client must have the functionality to identify 
the right server. Identifying can be done in several ways, eg.: 
 
     1. by name 
     2. by description 
     3. by value 
 
This specification will only handle the first one, nameing. 
 
3.1 Nameing 
----------- 
 
   A server can be unambiguously identified by its name. A `name' is a unique 
identifier within a given context, its `scope'. Note that several scopes may 
coexist at the same time, eg. each application may have its own name scope. 
This facilitates that multiple objects with the same name may coexist, but 
each one must reside in its own scope. 
 
   A server is now an object, which can be seen as a tuple, combining a name 
with provided services. To make these services available, the server 
publishes its name within the desired scope by contacting the appropriate 
name server. This is illustrated in Fig. 1: 
 
 
      +----------+  Make `MyServer' public   +--------------+ 
      | MyServer |-------------------------->| MyNameServer | 
      +----------+                           +--------------+ 
 
Fig. 1 
 
 
   Here the server `MyServer' makes its services available within the scope 
of the name server `MyNameServer'. Each time a client asks `MyNameServer' 
for `MyServer' it will return the server's location (Fig. 2). 
 
 
      +----------+       Ask for `MyServer'      +--------------+ 
      |          |------------------------------>|              | 
      | MyClient |                               | MyNameServer | 
      |          |   Return `MyServer' location  |              | 
      |          |<------------------------------|              | 
      +----------+                               +--------------+ 
 
Fig. 2 
 
 
   Now `MyClient' is able to invoke all provided methods from `MyServer' as 
if `MyServer' is a local object. All necessary message passing is done by 
ROI and hidden from the user of `MyClient'. 
 
3.2 Name scopes 
--------------- 
 
   A scope is created whenever a name server is started. Thus all known names 
to a name server form the name server's scope. An application, which creates 
a name server have the ability to build up its own scope, allowing it to 
redefine names as needed. Eg, it's possible to `overwrite' an existing file 
server to an application specific one, which always encrypt data on writing 
and decrypt it when reading. The application's file server can then be an 
inherited file server, which is specialized for encryption. Of course it can 
use methods from the `traditional' file server. 
 
   One question arises: What is the first scope OR where is the location of 
the first name server? 
 
   As almost all traditional operating system services are done by objects, 
there's the need for an default name server (DNS). DNS is installed at system 
startup, providing the first name scope, which holds information about, for 
example, the file server, printer server or disk servers. DNS is the only 
object which must be installed at a well known location (or: the kernel must 
provide a service to get its location). 
 
 
        +-------------+  client request or server registration 
        | Application |----------+ 
        +-------------+          v 
                 |   ^        +-----+ 
         ask for |   |        | DNS | 
          DNS by |   |        +-----+ 
         request |   |           | 
                 |   | return    | bounded 
                 |   | DNS       | location                User level 
      ...........|...|...........|................................... 
                 v   |           |                       Kernel level 
                +-------------+  | 
                |   Kernel    |--+ 
                | +---------+ | 
                | | Nucleus | | 
                +-+---------+-+ 
 
Fig. 3 
 
 
   A DNS location request solved by a kernel service is shown in Fig. 3. For 
simplicity only two privilege levels are shown, user and kernel level. An 
application, wishing to use services of a remote server object or to install 
a new server in the default name scope, must first find out the DNS location. 
It initiates a request to the kernel, which returns the current DNS location. 
Then the application can contact the DNS as usual. 
 
   Providing a kernel service have the advantage, that access to the DNS 
is location transparent. The system is free to place the DNS as it wishes. 
It has the disadvantage, that a kernel request must be additionally done to 
find out its location. But for flexibility the advantage outweights the 
disadvantage. 
 
   All necessary communication stuff for finding the DNS location should be 
encapsulated in clients or servers, respectively. If no other attributes are 
specified the DNS should be used automatically. Of course, an application, 
which creates its own name server can declare this server as its DNS. Then 
this name server is used, if no further attributes are given. 
 
3.3 Addressing 
-------------- 
 
   The name server is responsible to map a received name into an useful 
object location. This can be done either direct or indirect. A direct mapping 
function can calculate the location from the name, as shown in Fig. 4: 
 
 
      +------+  Mapping function   +-------------------+ 
      | Name |-------------------->| Object's Location | 
      +------+                     +-------------------+ 
 
Fig. 4 
 
 
   This is usually done by makeing the object's location part of the name. 
This has one great disadvantage. Consider the case, where several clients 
exist, which all want to use services of one server. Then all these clients 
will use the same name. For some reason the server must be migrated to 
another location. Then all names are invalid. 
 
   A second approach is to map a name to an `address' which then can be 
mapped to the object's location (Fig. 5). If the destination object migrates 
only the address must be changed. All names will still be valid. 
 
 
      +------+         +-------------------+ 
      | Name |         | Object's location | 
      +------+         +-------------------+ 
         |                ^ 
         |                | 
         |   +---------+  | 
         +-->| Address |--+ 
             +---------+ 
 
Fig. 5 
 
 
   With this approach a name can be very small. They only have to provide 
all necessary information to indicate an address. 
 
----- Cut here ----- 
 
To all of you, who want to see a more `implementation' oriented version 
I will now present examples, how an user should be able to use ROI 
in his/her application. All definitions are specified in a syntax similiar 
to those of C++. Note: This is still a conceptual model! At the end I will 
present problems which arise at this layer. 
 
Examples 
======== 
 
   Think of a server providing two services: 
 
    o doSomething() 
      Invoke a very funny procedure within the server. The service must 
      be called with no arguments. 
 
    o doSomethingElse(first, second) 
      Invoke another procedure within the server. This service must be 
      called with two arguments. 
 
The designer of the server have to do two things: He first have to create 
a server object and he secondly have to provide a client interface to use 
his server object. (This should be done automatically, either from a 
compiler or preprocessor.) 
   The server might look like this: 
 
      01: class MYSERVER : SERVER { 
      02:     // private and/or protected stuff (not visible) 
      03: public: 
      04:     MYSERVER(NAMETYPE name = "myserver") : SERVER(name) {} 
      05:     ~MYSERVER() {} 
      06:     doSomething(); 
      07:     doSomethingElse(FIRSTTYPE first, SECONDTYPE second); 
      08: }; 
      09: 
      10: MYSERVER::doSomething() { 
      11:     // do something 
      12: } // MYSERVER::doSomething 
      13: 
      14: MYSERVER::doSomethingElse(FIRSTTYPE first, SECONDTYPE second) { 
      15:     // do something with first and second. 
      16: } // MYSERVER::doSomethingElse 
      17: 
      18: main() { 
      19:     MYSERVER server; 
      20: } // main 
 
What happens? 
 
Lines 01-08: This is the definition of the server object's interface. There 
             should be an abstract SERVER object from which all servers are 
             inherited. The constructor should be responsible to publish the 
             server to an appropriate name server with a useful, hence, user 
             readable name ("myserver"). When no other arguments are given, 
             the default name server is used. (The case when a server with 
             the name already exist is not discussed any further. Yet.) 
 
             The destructor will be responsible to unpublish the server. This 
             means the name server will be informed that there is no more 
             server with this name. 
 
Lines 10-16: Usual method definitions. These methods should be invoked by 
             ROI. 
 
Line 19:     This actually establishes a new server. As there are no 
             arguments given, the server uses the default name (in this case 
             "myserver") and the default name server. Now a new server is 
             available in the scope of the default name server. 
 
The provided client interface should look like this: 
 
      01: class MYSERVER_CLIENT : CLIENT { 
      02:     // private and/or protected stuff 
      03: public: 
      04:     MYSERVER_CLIENT(NAMETYPE name = "myserver") : CLIENT(name) {} 
      05:     ~MYSERVER_CLIENT() {} 
      06:     doSomething(); 
      07:     doSomethingElse(FIRSTTYPE first, SECONDTYPE second); 
      08: }; 
      09: 
      10: main() { 
      11:     MYSERVER_CLIENT myclient; 
      12:     FIRSTTYPE first; 
      13:     SECONDTYPE second; 
      14: 
      15:     // Initialisation of first and second. 
      16:     myclient.doSomething(); 
      17:     myclient.doSomethingElse(first, second); 
      18: } // main 
 
What happens? 
 
Lines 01-08: Definition of the client's interface. The public part should 
             always look like the one from the corresponding server object. 
             The constructor will contact the name server trying to locate 
             a useful server. When no other arguments are specified the 
             default name server is contacted. 
 
Line 11:     In this line a new client is created. As there are no arguments 
             specified, the default server (in this case the server with the 
             name "myserver") is used. Its location was received by asking 
             the default name server. 
 
Lines 16,17: In both lines methods from the server are called. These calls 
             are performed by ROI, invoking the methods in the server 
             object. 
 
Note: There are no definitions of doSomething() and doSomethingElse(), 
      because both are executed remotely. 
 
Inheritance should be possible: 
 
       01: class NEWCLIENT : MYSERVER_CLIENT { 
       02:     // private and/or protected stuff 
       03: public: 
       04:     doSomething(); 
       05: }; 
       06: 
       07: NEWCLIENT::doSomething() { 
       08:     // do NEWCLIENT'S specialization 
       09:     MYSERVER_CLIENT::doSomething(); 
       10: } // NEWCLIENT::doSomething 
       11: 
       12: main() { 
       13:     NEWCLIENT myclient; 
       14: 
       15:     myclient.doSomething(); 
       16: } // main 
 
Line 09:     This line invoke doSomething() by ROI. All other stuff is 
             performed locally. 
Line 15:     Call of specialized doSomething() method locally. 
 
Note: doSomethingElse() cannot be called, because NEWCLIENT uses 
      MYSERVER_CLIENT as a private base class. 

   Inheritance should even be possible with servers. This can lead to a 
hierarchy of servers or to a call chain from descendants to ancestors with
ROI.
 
Problems 
======== 
 
What, when public methods of a server call private methods? 
----------------------------------------------------------- 
 
   Private methods are able to change the server's state. There must be 
mechanisms which avoid an influence from one client to another client's 
point of view to this state. Thus, each client must see it's own server 
state. 
 
What, when a server chooses a name which is already occupied by another 
server? 
----------------------------------------------------------------------- 
 
   This leads to a name conflict, which must be solved. The name server will 
deny the request and the server is not published. Thus, the provided services 
are not available. 
 
What, when a client cannot locate an appropriate server? 
-------------------------------------------------------- 
 
   There are two reasons for this problem: First, there's no server 
available. Then the client can simply create a new server instance. This 
instance will then publish itself to the name server, which was asked by the 
client. In this case the client knows the server's location. There's no need 
to ask the name server again. 
 
   Second, there's a server using the name, but which is not appropriate 
because it have the wrong type. There must be a mechanism which ensures that 
the returned server is useful for the asking client.