Implement your first AEON program

Please follow the step-by-step instructions to program you first elastic application: elastic binary tree.

Step #1: environment setup

Please follow installment to setup AEON environment.

Step #2: implement your first elastic application

AEON programming syntax just extends the syntax of C++. So it's easy for programmers to master AEON if they are familiar with C++.

actorclass and actor

actorclass in AEON is similar to class in C++, while actor in AEON is similar to object in C++. Porgrammers need to define actorclasses and create instances (actor) during runtime. The definition of actorclass is exact the same as class in C++. Check the following example:

  			actorclass SampleActor {
  				int id;
  				string name;

  				void setID(int newId);
  				void setName(string newName);
  			}
  		

However, there are still many differences between actorclass/actor and class/object. The actor in AEON is more like a reference (which is similar to JAVA). And programmers need to use a special API to create actors from actorclass:

  			SampleActor sampleA = createActor("SampleActor");
  			SampleActor sampleB = createActor("SampleActor");

  			simpleA.setID(1);
  			simpleB.setName("B");
  		

In the codes above, you have created two actors in one piece of codes. Please pay attention here, the runtime may put them on different servers. But the programmers needn't care about this problem. They could invoke their member funtions directly. If those contexts are on remote servers, the runtime will handle them.

Ownership

In order to preserve strong consistency and deadlock-freedom, we have introduce ownership into AEON. All actors in the application will form a DAG structure during runtime. Fortunately, the ownership are implicit to programmers. Programmers only need to follow several simple rules to construct the ownership.

Here we start from a simple example: University, Department and Student. Within one university, there are many departments. And each department will have hundreds of students. At the same time, one student may belong to more than one department.

  			Actorclass Student {
  				...
  			} 

  			Actorclass Department {
  				vector students;
  				...
  			}

  			Actorclass University {
  				vector departments;
  			}
  		

In the contextclass definition, if actor C is the member variable of actor B, then B is the owner of C. In the piece of codes above, the Department actor is the owner of Student actors which belong to this Department. Here one Student may be the member variable of two Department actors.

One problem the programmers should avoid in ownership is cycle. As the example above, if University is the owner of Department which Department is the owner of Student. Then Student contextclass shouldn't have a member variable typed University or Department.

The above approach could prevent cycle in ownership. But it limits the generalty of AEON. For example, it's impossible to implement some iterative applications like binary tree. To solve this problem, we relax this constrants a little. And to allow one contextclass has member variables typed itself. We have binary tree example:

  			actorclass BinaryTreeNode {
  				int key;
  				BinaryTreeNode left_node;
  				BinaryTreeNode right_node;

          void insertKey( int k );
  			} 
  		

Functions invocation mode

As shown in the above sample codes, the definition of contextclass member functions are similar to C++. But it's different to invoke those functions in AEON. AEON supports three different types of function invocation mode: event, async and sync.

Event:

        // when to receive the request from client side
        BinaryTreeNode root = getContextObject("BinaryTreeNode", rootId);
        event root.insertKey( k );
      

Event functions play the role of interface to clients. The application would treat each event function invocation as a request from a client. In the sample code, the client wants to insert a new key into the binary tree. So it passes a request to sever. Then it will triger the invocation of function "insertKey" of root context as event mode. From the root context, the application to search the right place to put the new key.

For this binary tree, clients would launch requests at the same time. So there are concurrent events being processed in parallel. For example, it's possible one client wants to insert a new key while the other client wants to delete another key. They may interfare each other. Fortunately, programmers needn't worry about this problem. As we claimed, AEON runtime will handle such case and provide strong consistency for events execution.

Async and sync:

As we introduce above, events present requests from clients. However, one request may involve multiple contexts. For example, it should traverse the binary tree before it finds the right node to insert the new key. Unlike other actor systems, there are constraints on interaction between contexts. In AEON, one context could only access its child contexts via sync or aysnc mode. In the binary tree example, one node context could access its left_node and right_node contexts.

        // sync mode
        left_node.insertKey(k1);
        right_node.insertKey(k2);

        // async mode
        async left_node.insertKey(k1);
        async right_node.insertKey(k2);
      

The funtionarity are quite similar for sync and async mode. async is used to introduce more parallel execution for better performance. Above sample codes shows one context wants insert k1 to its left subtree and k2 to its right subtree. If programmers use sync mode, the two invocation will be executed one by one. But for async mode, the two invocation will execute in parallel. For this case, the results are the same.

However, async may introduce undeterminitic execution within one event. Programmers should be careful when to use it for better performance. And async mode doesn't support return value. It could only be applied on functions with void as return type.

Client and server communication

AEON is also an extension of Mace. So here it makes use the deliver and downcall_route APIs from Mace, which should be convenient for programmers' usage. Programmers need to define messages structure:

        message SearchKey {
          int key;
        };
      

Then the upcall and downcall:

        // client passes SearchKey message to server
        downcall_route(server_addr, SearchKey(k) );

        // when server receives the SearchKey message.
        upcall deliver( const MaceKey& dest, const MaceKey& src, const SearchKey& msg ){
          ...
        }
      

Complete Example: binary tree

        #include "stdlib.h"

        message SearchKey {
          int key;
        };

        message InsertKey {
          int key;
        };

        message Reply {
          bool ret;
        };
        
        contextclass TreeNode {
          int key;
          TreeNode left;
          TreeNode right;

          void setKey( const int& key );
          void searchKey(const int& key, const MaceKey& src);
          void insertKey(const int& key);
        };

        contextclass RootNode {
          TreeNode root;

          void searchKey(const int& key, const MaceKey& src);
          void insertKey(const int& key);
        };


        upcall deliver( const MaceKey& dest, const MaceKey& src, const SearchKey& msg ){
          RootNode root = getContextObject("RootNode", 0);
          event root.searchKey( msg.key, src );
        }

        upcall deliver( const MaceKey& dest, const MaceKey& src, const InsertKey& msg ){
          RootNode root = getContextObject("RootNode", 0);
          event root.insertKey( msg.key );
        }

        void RootNode::searchKey(const int& key, const MaceKey& src){
          if( root == NULL ){
            downcall_route(src, Reply(False) );
          }else {
            async root.searchKey(key, src);
          }
        }

        void RootNode::insertKey(const int& key) {
          if( root == NULL ){
            root = createNewContext("TreeNode");
            root.set(key);
          }else {
            async root.insertKey(key);
          }
        }

        void TreeNode::searchKey(const int& k, const MaceKey& src){
          if( k == key ){
            downcall_route(src, Reply(True) );
          } else if( k < key ){
            if( left == NULL ){
              downcall_route(src, Reply(False) );
            } else {
              async left.searchKey( k, src );
            }
          } else {
            if( right == NULL ){
              downcall_route(src, Reply(False) );
            } else {
              async right.searchKey( k, src );
            }
          }
        }

        void TreeNode::insertKey(const int& k){
          if( k == key ){
            return;
          } else if( k < key ){
            if( left == NULL ){
              left = createNewContext("TreeNode");
              left.set(k);
            } else {
              async left.insertKey( k );
            }
          } else {
            if( right == NULL ){
              right = createNewContext("TreeNode");
              right.set(k);
            } else {
              async right.insertKey( k );
            }
          }
        }

        void TreeNode::set(const int& k ){
          key = k;
        }

        void maceInit() {

        }