A Tcl Interface to Horus

The Tcl language is a convenient language for prototyping applications. Together with its TK graphical toolkit, it is possible to write real applications with fancy graphical interfaces. Combining Tcl/TK with Horus, which provides a plethora of communication and fault-tolerance mechanisms, results in an extremely powerful development environment. In this document, we assume working knowledge of both Tcl/TK and the Horus system with its Uniform Group Interface.

Marriaging Tcl/TK and Horus is not a trivial endeavor, however. Tcl/TK has its own scheduler (in fact, putting Tcl, TK, and X-windows together already was somewhat of an headache, judging from its implementation). Horus, which is multi-threaded, also has its own scheduler. However, we think we did a reasonable job. For this we run Tcl/TK inside a single Horus thread. To synchronize this thread with the rest of Horus, we used the MUTS intercept device.

We added two built-in commands to Tcl, "group" and "endp". The "group" command simply creates group addresses that can be used to set up groups of communicating Tcl processes. The "endp" command creates an endpoint object that can join groups. Joining a group results in a new object, called a group object, which is used to send and receive messages to the group. Do not confuse group addresses with group objects. A "group address" is a simple bit string shared among multiple processes. A "group object" is a local data structure that is used for interfacing to the other processes.

The syntax of the "group" command is

group ?protocol?
This command returns a new group address for the given protocol. A default protocol is used if none is specified. The group address is an ASCII string of 32 characters that encode a 16-byte MUTS group entity identifier for the given protocol. Basically, this command is the equivalent of the MUTS "mt_neweid()" command for creating group addresses. For example, this is part of a Tcl session that uses the group command:
tcl% group
003200000000046955cb8d6600000000
tcl% 
The syntax of the "endp" command is
endp name ?protocol stack?
This command creates a Horus endpoint object for the given protocol stack. The endpoint is called name afterwards, and is returned by the command (much the same as the TK command ``"button .b"'' creates a button object and returns its name). If no protocol stack is specified, a default stack is used. This command is the equivalent of the Horus "xxx_endpoint()" command for creating endpoints, and name is used to access the handle that is returned. For example:
tcl% endp ep UNPACK:TOTAL:MERGE:VIEWS
ep
tcl% 
The endpoint object itself is a new Tcl command. In this particular case the command is called ep, but most other Tcl names will work to. The syntax of the command is one of:
ep
ep help
ep join name address ?options?
Without arguments, the command returns the MUTS endpoint address of the object. With the argument help, it will print a short menu of endpoint commands. The last form is the most important one: it allows the endpoint to join a group, given by address. It will return a group object called name. In addition this command recognizes the following options:
-contact endpoint address
-upcall Tcl function
The first option specifies who the group contact is, in which case an automatic group merge takes place (see the Horus documentation). The second option specifies which Tcl function should be invoked when events are delivered to this group object. This upcall function is invoked with one argument, namely the group object's name. The function can access the event structure using the info command that will be described later. For example:
tcl% set ga [group]
00320000000004698b64cc2d00000000
tcl% proc upcall {g} { puts "upcall for $g" }
tcl% endp ep UNPACK:TOTAL:MERGE:VIEWS
ep
tcl% ep join grp $ga -upcall upcall
grp
tcl%
Similar to the endpoint object's name, the group object's name is a new Tcl function that can be used to operate on the object. The general syntax of the group object operations ("grp" in this case) is:
grp
grp help
grp ?options? {command} ?args? ?more options?
As with endpoints, the group operation without arguments will return the group address. With the argument "help", a menu of operations on the group will be returned. The last form allows a set of operations on the group object, each of which may be modified by a list of options. The options may be specified either before the command, or all the way at the end (or both). The list, and syntax, of the group command (grp in this case) is the following:
grp cast ?message?
grp send {list} ?message?
grp leave ?message?
grp flush {members} ?message?
grp flush_ok
grp view {members} ?message?
grp info which ?args?
grp next
This list follows the Horus Uniform Group Interface fairly closely. "cast" allows a string message to be sent to the whole group (note: there is also an interface that allows binary data to be sent, but this is not documented yet). It results in the upcall being invoked at every member of the group. "send" allows data to be sent to a subset of group. The list contains the ranks of the destination members. To leave a group, the "leave" command sends a message to the group, and then cleans up the group object. "flush" and "flush_ok" are used for flushing a group, as described in the Horus manuals. The list of members contains the endpoint addresses of the members to be removed from the view of the group. Similarly, with "view" a set of members can be added to the view of the group.

The last two commands have to do with the upcall mechanism. Since Tcl/TK is not truly multi-threaded, we introduced the notion of current event. The current event can be accessed with the info command. Currently, there are the following info commands:

grp info type			 grp info primary
grp info nmembers		 grp info byte-swapped
grp info my_rank		 grp info transitional
grp info origin			 grp info mcast-acks ?x? y
grp info view			 grp info pt2pt-acks ?x? y
grp info joiners		 grp info msg-time ?x? y
grp info problem		 grp info current-time ?x? y
For the meaning of these, please refer to the Horus Uniform Group Interface documentation. For example, grp info nmembers returns the number of members in the current view of the group. The type command returns the type of the event as defined in Horus, without the XXX_ prefix. The last four commands return an entry from the named matrix if x is specified, or a whole column if not.

To move on to the next event, and make that the current event, the next command is used. A more realistic version of the upcall function, then, is the following:

proc upcall {g} {
	global joiners

	$g next
	switch [$g info type] {
		CAST - SEND {
			if {[$g info origin] != [$g info my_rank]} { $g ack }
			eval [$g info msg]
		}
		VIEW {
			if {[$g info origin] != [$g info my_rank]} { $g ack }
			puts "VIEW: [$g info nmembers] members"
			set joiners($g) {}
		}
		JOIN_REQUEST {
			set joiners($g) [
				concat $joiners($g) [$g info joiners]
			]
			$g flush {}
		}
		PROBLEM { $g flush [$g info problem] }
		FLUSH { $g flush_ok }
		FLUSH_OK { $g view $joiners($g) }
	}
}

(NOTE: this does not count flushes the way it's supposed to.) This version of upcall will evaluate message events as Tcl commands, and print the event otherwise. The Horus default Tcl library has a function eveval which is similar, but will also create a TK widget that display information about the current view.

For example, a simple distributed whiteboard can be implemented as follows:

proc whiteboard {} {
	canvas .canvas -background Green
	pack .canvas
	set ga 0032000000000464963f743c00000000
	endp ep LOG:UNPACK:MERGE:TOTAL:VIEWS
	ep join grp $ga -upcall eveval
	bind .canvas <B1-Motion> {
		grp cast {
			.canvas create rectangle %x %y %x %y -width 4 -outline Red
		}
		button .quit -text Quit -command {
			grp leave
			destroy .canvas
			destroy .quit
		}
		pack .quit
	}
}