[gambit-list] Termite

Marc Feeley feeley at iro.umontreal.ca
Fri Apr 20 11:23:21 EDT 2007

Hash: SHA1

At yesterday's MSLUG meeting Dominique showed us how easy  
distribution can be done using Erlang.  There was a question asking  
how Erlang and Termite compare.  To give a quick idea, here is an  
example of how distribution can be done with Gambit-C/Termite.

Assuming you have built the Gambit runtime to include Termite, you  
need to start a Termite instance on a machine (say "dynamo") and  
initialize it:

dynamo% gsi -e '(node-init (make-node "dynamo" 7777))' -

This instance of termite is a "node" which is identified by the IP  
address "dynamo" and the IP port 7777.  Note that a REPL is started,  
but we will not be using it.

Now let's start a Termite instance on machine "neo" to get a REPL:

neo% gsi -e '(node-init (make-node "neo" 8888))' -

This second node is identified by the IP address "neo" and the IP  
port 8888.  We could have started two nodes on the same machine, as  
long as the IP ports are different.  All the remaining REPL  
interaction is on neo.  First, let's create a reference to the first  
node on "dynamo":

 > (define dynamo (make-node "dynamo" 7777))

Synchronous remote procedure call is done with the "on" function.  It  
executes a thunk remotely and returns the result:

 > (on dynamo host-name)

Let's develop an asynchronous remote procedure call in stages.  First  
a simple example of local process creation and communication:

 > (define s (self))
 > (define p (spawn (lambda () (! s (expt 2 (?))))))
 > (! p 10)
 > (?)

The process p uses the "?" function to wait for and retrieve the next  
message in p's mailbox.  The function "!" sends a message back to the  
primordial process (whose PID was saved in global variable s).  This  
code can easily be extended to get a simple "power of 2" server:

 > (define p (spawn (lambda () (let loop () (! s (expt 2 (?)))  
 > (! p 16)
 > (! p 32)
 > (?)
 > (?)

Note that the results are retrieved asynchronously (i.e. the mailbox  
queues the messages received).  Moreover the results are always sent  
back to the primordial process.  To generalize, the request can be a  
pair containing, in the car, the PID of the process which the result  
should be sent to, and in the cdr, a function that is executed by the  
server to generate the result:

 > (define p (spawn (lambda ()
                      (let loop ()
                        (let ((x (?)))
                          (! (car x) ((cdr x)))
 > (! p (cons (self) host-name))
 > (?)

In this example the server is running locally.  By changing the  
"spawn" to a "remote-spawn" the server can be started on "dynamo":

 > (define p (remote-spawn dynamo
                           (lambda ()
                             (let loop ()
                               (let ((x (?)))
                                 (! (car x) ((cdr x)))
 > (! p (cons (self) host-name))
 > (?)

Any serializable Scheme object can be passed in a message.  This  
includes closures.  Here is an example:

 > (! p (cons (self)
              (let ((a (host-name)))
                (lambda ()
                  (let ((b (host-name)))
                    (lambda ()
                      (list a b (host-name))))))))
 > (define f (?))
 > (f)
("neo.iro.umontreal.ca" "dynamo.iro.umontreal.ca"  

Note that the cdr of the pair sent to p is a closure which remembers  
the binding for a, which is "neo.iro.umontreal.ca" because that's  
where the first (host-name) was evaluated.  When the server calls  
this closure, the second (host-name) is evaluated, giving  
"dynamo.iro.umontreal.ca".  The binding of b is remembered in the  
closure created by the second lambda.  This closure is sent back to  
the primordial process, which binds f to it.  When this closure is  
called the third (host-name) is evaluated, resulting in  

This example shows that the meaning of the global environment is node  
dependent.  The global environment is not automatically kept  
consistent on all nodes, it is the programmer's responsibility to  
ensure this where needed.  So for example:

 > (on dynamo (lambda ()
                 (lambda (e) e)
                 (lambda () (fact 10)))))
#<unbound-global-exception #2>
 > (on dynamo (lambda () (load "test.scm")))
 > (on dynamo (lambda ()
                 (lambda (e) e)
                 (lambda () (fact 10)))))
 > (fact 10)
*** ERROR IN (console)@24.2 -- Unbound variable: fact

Erlang's autoloading behavior is not built in to Termite.  However it  
could be implemented on top of Gambit's exception handling system.   
The idea would be to extract from a fully qualified name (such as  
test#fact) the filename of the file to load, like this:

(define (char-pos str c)
   (let loop ((i 0))
     (if (< i (string-length str))
         (if (char=? c (string-ref str i))
             (loop (+ i 1)))

(define base-eh (current-exception-handler))

  (lambda (e)
    (if (unbound-global-exception? e)
        (let* ((sym (unbound-global-exception-variable e))
               (str (symbol->string sym))
               (i (char-pos str #\#)))
          (if (not i)
              (base-eh e)
              (let ((mod (substring str 0 i)))
                (load (string-append mod))
                (let ((val (##global-var-ref (##make-global-var sym))))
                  (if (##unbound? val)
                      (base-eh e)
        (base-eh e))))

(namespace ("test#" fact))

(pp (fact 10))

The call (fact 10), due to the namespace declaration, is rewritten  
into (test#fact 10).  When the interpreter evaluates the reference to  
test#fact the exception that is raised is caught by the exception  
handler which extracts the module name "test" and loads that file to  
access the definition of "fact".  The file "test.scm" should contain:

(namespace ("test#" fact))

(define (fact n)
   (if (= n 0)
       (* n (fact (- n 1)))))


Version: GnuPG v1.4.3 (Darwin)


More information about the Gambit-list mailing list