The Horus Object Tools: MUTS Wrapper Classes

This document is a part of the online Horus Documentation, under Horus Object Tools.


HorusMUTS

HorusMUTS is at the top of HOT hierarchy. It automatically initializes MUTS before anything else is done. A HorusMUTS object has an error field, which is a pointer to an error handler (see discussion of HorusErrorHandler below).


HorusErrorHandler

HorusErrorHandler has two public methods called panic, which are usually invoked when a fatal error occurs. When panic is invoked, an error message is printed, after which the process is terminated. For example,

	HorusErrorHandler eh;
	error_t err;

	err = foo();				// foo may return an error value
	if (!e_isok(err))	
		eh.panic(err, "foo");           // prints error type + "foo"

	err = boo();
		if (!e_isok(err))
		eh.panic("boo has failed for some reason");


HorusMemory

HorusMemory is an ADT around MUTS memory channels. Its public methods are alloc, free, and realloc, which correspond to those of C standard library. Ideally, all memory allocation in Horus should be done through memory channels. That can be done in C++ as well by overloading new/delete operators (currently not yet implemented).


HorusBase

Most of HOT classes are subclasses of HorusBase.

  • One of HorusBase's member objects is memory, which is used as the default memory channel within the process.

  • All subclasses of HorusBase support stream-like message I/O (see the discussion of HorusMessage below).

    In order to be readable/writeable from/to a HorusMessage object, a subclass of HorusBase has to overload the following two operators:

    	virtual void operator << (HorusMessage& msg); 	// reading itself from msg
    	virtual void operator >> (HorusMessage& msg); 	// pushing itself onto msg
    That is, a subclass of HorusBase is required to "know" how to read/push itself from/onto a message if it is to support stream-like message I/O operations.

  • If HORUS_MESSAGE_TYPECHECKING is defined (in HOT configuration file, config.h), then message I/O operations will provide run-time type checking based on magic numbers. HorusBase defines the messageMagic method, which can be overloaded for finer typechecking.

    Example:

    	const int FOO_MAGIC_NUMBER = 668668;
    
    	class Foo: public HorusBase {
    	public:
    		void operator << (HorusMessage& msg) {
    			msg >> data;	// read `data' from msg
    		}
    	
    		void operator >> (HorusMessage& msg) {
    			msg << data;	// push `data' onto msg
    		}
    		
    		int messageMagic() const { return FOO_MAGIC_NUMBER; }
    
    	private:
    		int data;	
    	}
    
    	HorusMessage msg, msg2;
    	Foo foo1, foo2, foo3, foo4;
    	
    	msg << foo1 << foo2;			// push foo1 and foo2 onto msg
    	msg >> foo3 >> foo4;			// read msg into foo3 and foo4
    	
    	msg2 << 1995;				// read an integer onto msg2
    	msg2 >> foo1;				// this will panic:  foo1 has a wrong type!
    


    HorusMessage

  • HorusMessage is a wrapper class for MUTS messages. It defines methods that follow basic MUTS functions:
    	void read(int&);			// read an integer
    	void read(void *buf, int size);		// read a buffer
    	void push(int);				// push an integer
    	void push(void *buf, int size);		// push a buffer
    	int tail();				// position of msg pointer
    	void seek(int offset, int whence);
    Other methods include:
    	void reset();			       	// makes message empty and writable
    	void rewind(int offset);		// seeks backwards from the end	
    There are also stream-like I/O operators defined for basic types, such as int, HorusEntity, HorusEntityList, HorusString. For example, at the sender side you might execute:
    	HorusMessage msg;
    	HorusEntity ent;
    	HorusEntityList elist;
    	HorusString s("hello world");
    	.......
    	msg << ent << elist << s;		// pushes ent, elist, s into msg
    	memb.cast(msg);				// casts msg to the memb's group
    That could correspond to something like this at the destination:
    	receivedMsg >> s >> elist >> ent;	// read ent, elist, s
  • See the discussion of HorusBase on how to define message I/O operators for its subclasses.

  • There is run-time type checking for types supported by HorusMessage, which can be disabled if desired (to disable, undefine HORUS_MESSAGE_TYPECHECKING in the HOT configuration file, config.h).

  • When a message is first created, it is writeable/sendable. At that stage, data can be pushed into the message. After the message is sent, it is automatically released and cannot be read from or written to. However, it can be reset(), after which it will become empty and writable again.

  • If a writable message is read from (or one of the methods seek, rewind, or tail is invoked), it becomes read-only. In order to make a writable copy of a read-only message, the copyFrom method should be used.

  • An assignment operation like m1 = m2 results in both m1 and m2 having the same contents. Also, m1 acquires the same read/write status as m2 had before the assignment, whereas m2 becomes read-only. For example, if m2 is read-only, then after the assignment m1 = m2, the status of m1 will be read-only (whereas m1.copyFrom(m2) would leave m1 writeable).

    Here are some examples that illustrate message read/write properties:

    	HorusMessage m1, m2, m3;	// m1, m2, and m3 are empty & writable
    
    	m1 << HorusString("hello world"); // m1 is still writable
    	m2 = m1;           		// now m1 is read-only, m2 is writable
    	memb.cast(m2);			// m2 has been released when cast returns
    
    	int l = m1.tail(); 		// remember m1's size         
    	HorusString s;
    	m1 >> s; 			// m1 is still readable; has to be rewound  
    
    	m1.rewind(l);			// rewind m1, so it can be read again 
    	HorusString ss;
    	m1 >> ss;			// m1 is still readable; has to be rewound
    
    	m1.rewind(l);			// rewind m1, so it can be read again
    	m3.copyFrom(m1);		// m3 is writeable, same contents as m1;  
    					// m1 is read-only at this point
    
    	memb.cast(m3);			// m3 has been released at this point
    	m1 >> s;			// m1 is still readable
    NOTE: Byte ordering issues are not yet addressed in HOT messages.


    HorusSemaphore

    HorusSemaphore has two methods, inc() and dec(). The default initial value of a semaphore is 0, so executing the following code will block the thread:
    	HorusSemaphore sema;
    	sema.dec();


    HorusThread

    The constructor of HorusThread expects a function void(*)(void*, void*) as an argument. No thread is actually created at the time of creating a HorusThread object. To start up a new thread, the create method has to be called. It takes two arguments, that specify the environment of the thread and parameters for it. For example, executing the following code
    	struct Moo {
    		void boo(char *s) {
    			cout << "new thread with argument " << s;
    		}
    
    		static void foo(void *env, void *param) {
    			Moo *moo = (Moo*) env;
    			char *str = (char*) param;
    			moo->boo(str);
    		}
    	};
    
    	HorusThread ht(Moo::foo);
    	Moo m;
    	ht.create(&m, "hello");
    will invoke m::boo("hello") in a new thread.

    Other methods of HorusThread include yield, setPriority, and getPriority. Usually threads created by Horus have the same priority (currently 128), with some exceptions. A thread with lower priority won't be scheduled as long as there are runnable threads of higher priorities.


    HorusLock

    HorusLock has two methods, lock and unlock. Initially the lock is open. For example:
    	HorusLock hl;
    	hl.lock();			// acquire the lock
    	.......
    	hl.unlock();			// release the lock


    HorusEventCounter

    HorusEventCounter is used to synchronize (upcall) threads. There are two methods, sync (which blocks until event counter reaches the specified value), and inc (which increases the value of the event counter by one). The initial value of an event counter is 0.


    HorusEntity

    HorusEntity is a wrapper class around Horus's eid structure. The default constructor does not actually create a new eid (however, there are several copy constructors and assignment operators of various flavors). Other methods are operators == and != and stream-like message I/O operators.

    There are two subclasses of HorusEntity, HorusEndpointEntity and HorusGroupEntity, which do create new eid's. Note that the total number of eid's per process is currently (arbitrarily) limited to 256, so that HorusEndpointEntity and HoruGroupEntity objects should be created only when a new entity is required. For example:

    	HorusEntity e1, e2; 		// creates two empty entities
    	assert(e1 == e2);		// empty entities are equal
    
    	HorusEndpointEntity ee1, ee2;	// creates new endpoint entities
    	assert(e1 != ee1);		
    	assert(ee1 != ee2);		// each entity is unique
    
    	ee1 = ee2;
    	assert(ee1 == ee2);				
    
    	HorusGroupEntity ge;		// creates new group eid
    	assert(ee1 != ge);


    HorusBarrier

    HorusBarrier is a condition variable with a special property: all threads that were blocked on the closed barrier are released at once when the barrier is open.

    The methods of HorusBarrier are open, close, wait, isOpen, and isClosed. By default, the initial state is "open". When the open() method is invoked, the state of the barrier is set to "open", and all waiting threads are released. When close() is called, the barrier's status becomes "closed". A call to wait() returns immediately if the barrier is open, and blocks if the barrier is closed. For example:

    	HorusBarrier hb;
    	hb.wait();			// won't block since the barrier is open
    	hb.close();			// closes the barrier
    	hb.wait();			// blocks until some thread calls open()


    HorusString

    HorusString defines methods for comparing, assigning, and printing C char strings, and supports stream-like message I/O (which is why it belongs to HOT). For example:
    	HorusString s1;			// creates an empty string
    
    	HorusString s2("hello world");		
    	cout << s2 << endl;		// prints "hello world"
    
    	s1 = s2;
    	assert(s1 == s2);
    
    	HorusString s3;			// s3 is empty at this point
    	HorusMessage m;
    	m << s1;
    	m >> s3;
    	assert(s1 == s3);


    HorusNameServer

    HorusNameServer is the default name server used in HOT. It is based on MUTS install/lookup functions (that make use of the file system). The install/lookup methods of HorusNameServer expect arguments of the HorusBase type; however the current implementation allows install/lookup of HorusEntity's only.

    Two other methods are lock/unlock. If an entity is locked, a subsequent call to lock will block until unlock is invoked, or the lock expires (the locks will expire automatically after some time).

    NOTE: An entity can be looked-up/(re)installed even if it's locked.

    For example:

    	HorusEndpointEntity ent;
    	HorusNameServer ns;
    	HorusString entityName("my_entity");
    
    	ns.lock(entityName);	// lock the entity
    				// a second call to lock(entityName) would block 
    
    	ns.install(entityName, ent);
    
    	HorusEntity tmp;
    	assert(tmp != ent);
    
    	ns.lookup(entityName, tmp);
    	assert(tmp == ent);   	// this assertion may fail b/c of a race condition!
    
    	ns.unlock(entityName);


    OrderedSet

    OrderedSet is a macro class, which means it "feels" like a template but doesn't get you into the trouble of compiling templates. The difference from a template implementation (from a user's point of view) is mostly syntactical (see examples below). OrderedSet implements standard operations on ordered sets: union, intersection, difference, and random access. As a subclass of HorusBase, OrderedSet supports stream-like message I/O (which is why this class belongs to HOT).

  • It is guaranteed that the order of elements in an OrderedSet is preserved under union, intersection, and difference operations.
  • When a new element is added to the set, it becomes the last one.
  • A class used in an instantiation of OrderedSet must define the copy constructor and equality operator, "==". For example:
    	struct Foo {
    		Foo(Foo& f) {
    			key = f.key;
    			value = f.value;
    		}
    
    		int operator==(Foo& f) {
    			return (key == f.key);
    		}
    	
    		int key, value;
    	};
    
    	// Note the syntax here!  Parentheses () are used
    	// where the angle brackets <> would be used in a
    	// template instantiation:
    	typedef OrderedSet(Foo) FooList;
    
    	FooList L;			// creates an empty list
    	assert(L.size() == 0);
    
    	Foo f;
    	f.key = 1;
    	f.value = 2;
    	L += f;				// add f to L
    	assert(L.size() == 1);
    
    	Foo f2;
    	f2.key = 1;			// Note that f == f2
    	f2.value = 3;
    	FooList L2(f2);			// L2 contains one element, f2
    	assert(L2.size() == 1);
    	assert(L2.contains(f2)); 
    	assert(L2[f2] == 0);		// index of f2 in L2 is 0
    	assert(L2[0] == f2);		// f2 is the 0'th element in L2
    
    	FooList L3(L2);			// L2 and L3 have now the same contents
    
    	L3.clear();			// remove all elements from L3
    	assert(L3.size() == 0);
    
    	assert(f == f2);		// Note that L already contains f
    	L += f2;			// f2 is *not* added to L since f == f2
    	assert(L.size() == 1);
    
    	Foo f3;
    	f3.key = 5;
    	f3.value = 4;
    	L += f3;			// add f3 to L
    	assert(L.size() == 2);
    
    	L -= f;				// remove f from L
    	assert(L.size() == 1);
    
    	L -= f;				// can't remove same thing twice
    	assert(L.size() == 1);
    
    	L3 = L;				// now L and L3 have the same contents
    
    	L += L2;			// L = set union of L and L2
    	L -= L2;			// L = set difference of L and L2
    	L &= L2;			// L = set intersection of L and L2
    
    	FooList L4;
    	L4 <<= L;			// L4 "steals" the contents of L
    	assert(L.size() == 0);		// L has become empty!
    There are several ordered-set types defined in HOT, such as HorusEntityList and HorusMessageList:
    	class HorusEntityList: 				
    		public HorusBase, 
    		public OrderedSet(HorusEntity) { ... };
    
    	class HorusMessageList: 
    		public HorusBase,
    		public OrderedSet {};
    HorusEntityList has operators for message I/O and standard output. For example:
    	HorusMessage msg;
    	HorusEndpointEntity e1, e2, e3;
    	HorusEntityList L, L2;
    	L += e1;
    	L += e2;
    	L += e3;
    	cout << L;			// print the contents of L
    	msg << L;			// push L into the message
    	msg >> L2;			// read the list into L2

    send mail to alexey@cs.cornell.edu