CS 557: RMI Assignment

Due October 22, 11:59 PM.


Read this assignment carefully!

The goal of this assignment is to implement remote method invocation (RMI). This will allow a client to invoke a method on an object that actually resides remotely on a server. If you wish to use Java, or some other language, please contact the instructor to get specific details on what will be required. For this assignment, you may work in groups of two. Also, I encourage you to learn how to use a revision control system such as git, CVS, or SVN, so that you can easily work collaboratively.

The application writer will use the following code to invoke a remote method. You must support such usage.

std::string obj_ref(...);
MyInterface_stub stub(obj_ref);
stub.my_method(...);

The string obj_ref is a “stringified” object reference. We will assume that the client obtains this reference via some unspecified way that occurs outside of the RMI system. For the assignment, you can just use copy-and-paste from one window to another, as you are developing and testing. The client application constructs a stub, using the object reference to bind the stub to the remote object. Invocations on the stub are then passed transparently to the remote object, by the RMI system.

On the server side, the application writer will implement an application class that provides the methods to be invoked remotely. This application object must be bound to a skeleton, by creating a skeleton and passing in a pointer to the application implementation object. To ensure that the application object implements the correct interface, the application object must inherit from the interface class. The application object is bound to the skeleton as shown below. You must support such usage.

class MyApplicationImplementationClass : public MyInterface {
    ...
    // This declares the actual implementation of my_method().
    int my_method(...);
};
...
MyApplicationImplementationClass obj;
MyInterface_skel skel(&obj);

Note that the RMI-related classes used above are interface-specific. That is, they must be modified if the interface of the remote object changes. Thus, these classes cannot be written once by the developer of the RMI system, and reused by the application. Instead, these classes must be generated from the application interface, by a code generator, which you will develop.

Code Generator

The code generator generates code that is specific to the application-level, remote object interface. This code is part of your RMI system, though, so we cannot expect the application developer (who designs the application interface), to do develop code. So, instead, we generate this code automatically from the IDL input.

Interface Definition Language

Your code generator must take a file containing one or more interface declarations, and output the stub and skeleton code. The interface declaration file will be in the following format:

class_name
number_of_methods
return_type_of_first_method
method_name_of_first_method
number_of_parameters_in_first_method
type_of_first_parameter_in_first_method
...
type_of_last_parameter_in_first_method
...
return_type_of_last_method
method_name_of_last_method
number_of_parameters_in_last_method
type_of_first_parameter_in_last_method
...
type_of_last_parameter_in_last_method
...

You may assume no more than 100 methods per class, and no more than 10 parameters per method. Interface and method names will be no longer than 100 characters. You need to support integer and string types, and also arrays (using vectors) of these two primitive types. An array is indicated by array of int, or array of string, respectively.

To indicate that a method is one-way or asynchronous, the return type will be given as async. It is up to you whether you want the caller to return immediately, or block until the invocation has been safely transmitted to the server.

C++ Binding

For C++, the string type in the interface declaration should be bound to the standard string type, which is std::string. An input parameter of the string type should be passed in as a constant reference. When used as a return type, it should be returned by value. The function signature below shows the two possible uses of the string type.

std::string my_method(const std::string &);

Methods given with a return type of async in the IDL file should be bound to a C++ return type of void.

Below is an example interface declaration which would correspond to a interface named MyInterface that has three methods.

MyInterface
4
int
my_method1
3
int
string
int
string
my_method2
2
int
string
int
my_method3
2
array of int
int
async
my_method4
0

Your code generator must generate three classes for the application. Assuming that the interface declaration is as above, the code generator will generate the classes:

MyInterface
MyInterface_stub
MyInterface_skel

The MyInterface class provides just the interface definition. It should be of the form:

class MyInterface {
  public:
      virtual ~MyInterface() {}
      virtual int my_method1(int, const std::string &, int) = 0;
      virtual std::string my_method2(int, int) = 0;
      virtual std::string my_method3(const vector &, int) = 0;
      virtual void my_method4() = 0;
};

The exact signature of the methods will depend on the interface defined in the input file.

The stub class must include one public constructor. It should also inherit from the interface class, and should include the methods declared in the interface.

class MyInterface_stub : public MyInterface {
  public:
      MyInterface_stub(const std::string &);
      virtual ~MyInterface_stub();
      virtual int my_method1(int, const std::string &, int);
      virtual std::string my_method2(int, int);
      virtual std::string my_method3(const vector &, int);
      virtual void my_method4();
  ...
  // The stub will contain methods and fields specific to your
  // code.
};

The skeleton class must include one public constructor, and the method getObjectReference(), which must return a stringified object reference.

class MyInterface_skel {
  public:
      MyInterface_skel(MyInterface *);
      std::string getObjectReference() const;
  ...
  // The skeleton will contain methods and fields specific to your
  // code.
};

The skeleton should not crash if invoked concurrently by more than one client.

Your code generator should output the files corresponding to the various classes that should be generated.

MyInterface.hpp
MyInterface.cpp
MyInterface_stub.hpp
MyInterface_stub.cpp
MyInterface_skel.hpp
MyInterface_skel.cpp

The code generator will generate code that is interface-specific. The code output by the code generator may call other code, however, which does not depend on the interface. Such code can be written just once, and placed in a runtime library, as explained below. You will find it easier to develop and debug if you keep the amount of generated code to the absolute minimum. Therefore, always think about whether or not any particular piece of code must be generated, or whether or not it can be moved to the runtime library.

Runtime Library

There will be a significant amount of code that is part of your RMI system, but that will be generic for all distributed objects. Such code can go in the runtime library. This library is shared by all applications. For this assignment, such code should go into the librmi.so library. The supplied assignment outline has provisions for putting code there.

The actual amount of code you put in here is flexible. In theory, you could put no code here, but in practice, you will find that the more code you move into your runtime library (and out of the generated code) the easier it will be to develop and debug your assignment.

Usage

The process for actually using your RMI toolkit is as follows. The user first puts the interface declaration in an ASCII file, following the format given above. He then runs the code generator, giving the name of the interface declaration file as a argument.

$ ./cg interface_declaration_file

The user will then compile the files together, and link them with your library.

$ g++ -o client client.cpp MyInterface.cpp MyInterface_stub.cpp librmi.so
$ g++ -o server server.cpp MyInterface.cpp MyInterface_skel.cpp librmi.so

There must be some way for the client to get the object reference. This could be done via a registry of some kind, but for the purposes of this assignment, we will mainly rely on copy-and-paste.

Submission and Grading

Submit your assignment by e-mailing a tar file to cs557-internal@cs.binghamton.edu. Your tar file should be named rmi.tar.gz, and expand into a directory named rmi, with your files inside of it. Your tar file should contain a makefile that will compile at least the code generator cg, librmi.so, dev_client, and dev_server. when we type make.

Your dev_client and dev_server should demonstrate the functionality of your code. It is your opportunity to show what your code can do.

To demonstrate your one-way or asynchronous invocations, you should also supply async_client.cpp and async_server.cpp. To obtain credit for this part, you should submit test results that show your one-way or asynchronous calls are faster than your normal blocking calls. We should be able to replicate your test results using your supplied async_client.cpp and async_server.cpp.

Though it is not required, you may also wish to include a report that explains what you did. Think of this report as a chance for you to explain issues or unusual aspects of your assignment that may impact your grade.

Optimization for the librmi.so library should be turned on, since performance of your code will be part of your grade. Your code should compile and run on the classroom cluster.

80% of your grade will be based on functionality and correctness. In, other words, does it work? Performance will make up 15% of your grade, and design, documentation, and style will make up the remaining 5%. For performance testing, we will test both speed and scalabity. We will try to push your code up to as many objects as possible (such as 1,000,000), and also test how many invocations per second you can perform.

It will be challenging to make your code perform well under all loads, so focus on getting it to just work without crashing under heavy load. Memory leaks must also be avoided.

We will grade your assignment as follows:

  1. Unpack your tar file with tar xfz rmi.tar.gz.
  2. Enter your rmi directory and execute make.
  3. Run the dev_server, which will print out the object reference.
  4. Run the dev_client, giving the object reference as the one and only argument.
  5. Verify that it runs, then read your documentation and code to see what functionality you are demonstrating.
  6. We will then execute your cg on the evaluation interface, and compile the evaluation client and server. (If you use the code outline, these will already automatically be compiled.)
  7. We will then test the performance of your submission.
  8. We will then examine your one-way or asynchronous code and test results, and verify them.

Note that your RMI system must work on the unmodified .idl files, and also the unmodified eval_client.cpp and eval_server.cpp. We will be using our own client and server code, so your system must work with the specification as given in this assignment.

A code outline for this assignment is available here. There are two test applications contained inside, each consisting of a client and a server. The development application is for your development and debugging purposes. When you submit your assignment, this should illustrate the functionality that you have implemented. The evaluation application is for performance testing. Instructions for how to run it are in README.txt. Also look at EMITTER.txt. If you cannot pass all the evaluation tests, please extend your development client to show what you can do.

We will be running code similarity checks on all submitted assignments as well as from previous years.