Maestro Types

This document is a part of online Ensemble documentation, under Maestro Open Toolkit.

Index:

  • Messages
  • Maestro_Message
  • Endpoints
  • Maestro_EndpID
  • Threads
  • Maestro_Thread
  • Maestro_Semaphore
  • Maestro_Lock
  • Maestro_Barrier
  • Miscellaneous Types
  • Maestro_ErrorHandler
  • Maestro_Base
  • Maestro_String
  • Maestro_OrderedSet

  • Maestro_Message

  • Communication between group members is via messages implemented with the Maestro_Message class. The following public methods are provided:
    	void read(int&);			// read an integer from the message
    	void read(void *buf, int size);		// read a buffer from the message
    
    	void write(int);			// write an integer into the message
    	void write(void *buf, int size);	// write a buffer into the message
    	
    	unsigned size();			// get total size of the message
    	unsigned getPos();			// get current position of message pointer
    	void setPos(unsigned offset);		// set position of message pointer	
    	void seek(int offset, Maestro_MsgSeek whence); // seek to the specified offset. 
    
    	// Pack an array of messages into the given msg.
    	void pack(Maestro_Message *msgs, unsigned nmsgs); 
    	
    	// Unpack an array of messages from the given msg.
    	void unpack(Maestro_Message *&msgs, unsigned &nmsgs); 
    
    	void reset();				// reset the message
    	int isWritable();			// return non-zero iff the message is writable
    
    Read/write operations on Maestro messages follow the stack paradigm (first in/last out). This is convenient in the layering architecture of Maestro/Ensemble, where layers push headers on outgoing messages and pop matching headers from incoming messages. There are also stream-like message I/O operators defined for basic types, such as int, Maestro_EndpID, Maestro_EndpList, Maestro_String. For example, consider the following code at the sender side:
    	Maestro_Message msg;
    	Maestro_EndpID endp;
    	Maestro_EndpList elist;
    	Maestro_String s("hello world");
    	.......
    	msg << endp << elist << s;		// push endp, elist, s into msg
    	memb.cast(msg);				// multicast msg to the memb's group
    This could correspond to something like this at the destination:
    	receivedMsg >> s >> elist >> endp;	// pop ent, elist, s from the msg
  • See the discussion of Maestro_Base on how to define message I/O operators for subclasses of Maestro_Base.

  • The assignment operator = can be used to make an alias of a message. After the assignment, both the source message and the alias become read-only. Aliases point to the same message buffer as the original message, however each alias has its own read-offset pointer. In order to make a writable copy of a message, the overloaded <<= operator should be used; for example:
    	Maestro_Message m1, m2, m3;
    	int i, pos;
    	m1 << 5;			// push an integer into m1
    	pos = m1.getPos();		// remember the current offset at m1
    	m1 >> i;			// pop an integer from m1
    	m1.setPos(pos);			// rewind m1 (which becomes read-only)
    	m2 = m1;			// make m2 an alias of m1 (m2 is read-only)
    	m3 <<= m1;			// copy m1 into m3 (m3 is writable)
    

  • There is a run-time type checking for types supported by Maestro_Message, which can be disabled if desired (to disable, undefine MAESTRO_MESSAGE_TYPECHECKING in the Maestro configuration file, Maestro_Config.h).

  • Initially a message is created in the writable mode (except when the copy-from-buffer constructor is used). After one the methods read(), unpack(), seek(), setPos() is invoked, the message becomes read-only. The reset() method can be used to empty a message and make it writable again.

    NOTE: Byte ordering issues are not yet addressed in Maestro messages.


    Maestro_EndpID

    Endpoint ID's are implemented in Maestro with the Maestro_EndpID class. The class defines stream-like message I/O and standard output operators, for example:
    	Maestro_EndpID endp;
    	Maestro_Message msg1, msg2;
    	.....
    	msg1 >> endp;			// pop endp from the message
    	msg2 << endp;			// push endp onto the message
    	cout << endp;			// print out the value of endp


    Maestro_Thread

    Thread creation is supported in Maestro with the Maestro_Thread class. The only public method of Maestro_Thread is static create, defined as follows:
    	struct Maestro_ThreadOps { int stackSize; };
    
    	static void create(void (*proc)(void*), 
    			   void *arg,
    			   Maestro_ThreadOps *ops = 0,
    			   Maestro_ErrorHandler *error_handler = 0);
    A call to Maestro_Thread::create(proc, arg) invokes proc(arg) in a separate thread. The size of the thread's stack can be adjusted with the ops argument.


    Maestro_Semaphore

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


    Maestro_Lock

    Maestro_Lock defines two public methods, lock and unlock. Initially the lock is open. For example:
    	Maestro_Lock lck;
    	lck.lock();			// acquire the lock
    	.......
    	lck.unlock();			// release the lock


    Maestro_Barrier

    Maestro_Barrier is a specialized condition variable with the following property: all threads blocked on a closed barrier are released at once when the barrier is open.

    The public methods defined by Maestro_Barrier are open(), close(), pass(), isOpen(), and isClosed().

  • By default, the initial state of a barrier object is "open".

  • A call to pass() returns immediately if the barrier is open, and blocks if the barrier is closed.

  • When the open() method is invoked, the state of the barrier is set to "open", and all waiting threads are released.

  • When the close() method is invoked, the barrier's status becomes "closed".

    Example:

    	Maestro_Barrier hb;
    	hb.pass();			// doesn't block since the barrier is open
    	hb.close();			// closes the barrier
    	hb.pass();			// blocks until another thread calls open()


    Maestro_ErrorHandler

    Error handling is implemented in Maestro with the Maestro_ErrorHandler class. A Maestro_ErrorHandler object can be passed as an optional parameter to constructors of most of Maestro classes. When a fatal error occurs, a panic() method of the corresponding error-handler object (of either Maestro_ErrorHandler class or a user-defined subclass of Maestro_ErrorHandler) is invoked. Maestro_ErrorHandler defines two versions of the panic method:
    		void panic(char *context);
      		void panic(hot_err_t err, char *context);
    In the default implementation of panic() methods in the Maestro_ErrorHandler class, when panic() is invoked, an error message is printed, after which the process is terminated.


    Maestro_Base

    Most of Maestro classes are subclasses of Maestro_Base, which defines stream-like standard output and message I/O operators.

    In order to be readable/writable from/to a Maestro_Message object, a subclass of Maestro_Base must overload the following two operators:

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

  • If MAESTRO_MESSAGE_TYPECHECKING is defined (in Maestro configuration file, Maestro_Config.h), then message I/O operations will include run-time type checking based on "magic numbers". Maestro_Base defines a messageMagic() method, which returns the "magic number" to be pushed along with a Maestro_Base object on an outgoing message, and checked at the destination when extracting the object from the message. The messageMagic() method can be overloaded for finer typechecking in subclasses of Maestro_Base.


    Maestro_String

    Maestro_String is a wrapper class for C char strings. It defines public methods for string comparison, assignment, and stream-like standard output and message I/O. For example:
    	Maestro_String s1;			// create an empty string
    
    	Maestro_String s2("hello world");		
    	cout << s2 << endl;			// print "hello world"
    
    	s1 = s2;
    	assert(s1 == s2);
    
    	Maestro_String s3;			// s3 is empty
    	Maestro_Message m;
    	m << s1;
    	m >> s3;
    	assert(s1 == s3);


    Maestro_OrderedSet

    Maestro_OrderedSet is a macro class, which can be used just like a template (but doesn't incur compilation problems of templates). The difference from a template implementation is mostly syntactical (see examples below).

    Maestro_OrderedSet implements standard operations on ordered sets: union, intersection, difference, and random access. As a subclass of Maestro_Base, Maestro_OrderedSet supports stream-like standard output and message I/O.

  • It is guaranteed that the order of elements in a Maestro_OrderedSet object 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 Maestro_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 Maestro_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 Maestro, such as Maestro_EndpList and Maestro_MessageList. Maestro_EndpList defines operators for message I/O and standard output, for example:
    	Maestro_Message msg;
    	Maestro_EndpID e1, e2, e3;
    	Maestro_EndpList 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