Previous Contents

Chapter 3   The JoCaml language and system

3.1   Jocaml Tools

Most jocaml tools are Objective-Caml tools with an initial ``j''. For example, ocamlc is called jocamlc, ocaml is called jocaml, ocamlrun is called jocamlrun, and so on. There is no compatibility between Objective-Caml compiled files and Jocaml compiled files, even with a same source file.

In this section, we first describe new options for old Objective-Caml tools, and then new tools designed for Join programmers.

3.1.1   jocamlc

New options:

3.1.2   jocamlopt

Mixing native code and bytecode

As an experimental feature, the Jocaml system enables you to mix native code and bytecode in a same executable. This feature is useful since join constructs are only available using bytecode, whereas good performances are achieved using native code (As an example, see mandel.opt in the distribution).

In a mixed runtime, native code program is started first. At the end of the native code, the bytecode program is executed. The bytecode may call native functions through dedicated ``call-back'' modules. These modules are Objective-Caml source files with a ``.mlx'' suffix, which, when compiled with jocamlopt, generate two object files, one in native code, with real functions, and one in bytecode, which functions are only wrappers to the native code functions.

New options:

3.1.3   jocrun, joc, jocl and joctop

The jocaml system includes three runtimes: jocamlrun, jocrun and jogrun. jocrun is created using the -make_vm option, and contains the thread-safe standard library, the Unix library, the bytecode thread library and the join library. Thus, it can be used to execute most standard Join programs. To compile programs using this runtime, you can use the command jocamlc -use_vm jocaml-bin-directory/jocrun other-args, or use the dedicated program joc: joc other-args.

If you need to link your program with other libraries which are not contained in the jocrun runtime, you can use the command jocamlc -join -l unix -l join other-args, or the dedicated program jocl: jocl other-args.

Both joc and jocl programs add a -linkall option to include all modules in the program created. This is safer, since join programs may receive locations from other programs which have more dependencies than the original program.

The jocaml system also includes two toplevels: jocaml and joctop. jocaml is the Objective-Caml toplevel, without join constructs, whereas joctop is the Objective-Caml toplevel compiled on the jocrun runtime, and thus extended with join constructs (join definitions and locations).

3.1.4   jocns, jocclient

jocns is a name server used by the Ns module. It can be started directly. It is not started automatically by a program calling Ns.register or Ns.lookup. The default port is 20001, but can be modified with the JNSPORT environment variable. The JNSNAME environment variable can be used to specify the name server machine if it is not the localhost. The user name is appended to the resource name that is registered or looked up. If no user name is specified (for instance in the environment variable USER), the default user name pub is used. To change the user name in a jocaml program, simply set the Ns.user string reference:
Ns.user := "toto"
jocclient is a generic client for JoCaml applications: It queries the default name server (jocns) for a generic channel name ``newclient'' of type Join.location * string * string list -> unit, and applies this channel to its location, the user login name, and its list of arguments. The Genserver module enables programmers to easily create servers for generic clients. The name server host and port can be set either by environment variables (JNSNAME and JNSPORT) or by options (-ns_host and -ns_port). The queried name can be modified by the -ns_name option and the user name used by -ns_user.

3.1.5   jogrun, jogc and jogclient

jogrun is a specialized runtime including the Join library and the Graphics library. jogc is the equivalent of joc for building programs using this runtime, and jogclient is a generic client built on this runtime (thus, this client must be used to receive locations needing the Graphics library). It can be used for most examples of the distribution (Tron, Bomberman, Bataille, Pong, Mandel,...).

3.2   Syntax and Examples

declaration ::= Ocaml-declaration
    let def automata-definition
    let loc join-locations
automata-definition ::= automaton [and automata-definition ]
automaton ::= join-pattern = process [or automaton ]
join-pattern ::= channel-decl [ | join-pattern ]
channel-decl ::= synchronous-name arguments
    asynchronous-name arguments
synchronous-name ::= Ocaml-lower-ident
asynchronous-name ::= Ocaml-lower-lident !
arguments ::= Ocaml-pattern
process ::=  
    final-process | process
    declaration in process
    if expression then final-process
    expression ; process
final-process ::= reply [expression] [to Ocaml-lower-lident]
    if expression then final-process else final-process
    match expression with
           Ocaml-pattern -> final-process
       [ | Ocaml-pattern -> final-process [...]]
    loc join-locations
    { process }
expression ::= Ocaml-expression
    spawn { process }
asynchronous-send ::= expression expression
join-locations ::= location-definition [and join-locations]
location-definition ::= location-name[def automata-definition]
           do final-process
channel-type ::= function-type
asynchronous-channel ::= << core-type >>

3.2.1   Join definitions

Join channels are defined with the let def construct. For example, the definition of a memory cell flows (this lines have been written in the joctop program):
# let def new_cell s = 
    let def get () | state! s = reply s |  state s
         or set s | state! _ = state s | reply ()
    state s | reply (get,set)
val new_cell : 'a -> (unit -> 'a) * ('a -> unit) = chan
In this definition, we create a synchronous name new_cell, with one parameter. When this name is called, it creates three names, two synchronous (get and set) and one asynchronous (note the ! notation) (state). Notice that all names are lowercase standard Objective-Caml identifiers. Multiple join patterns in the same definition are introduced with the or keyword. Continuations for reply constructs are not specified, since there is only one synchronous name per join pattern in this example. With join patterns containing several synchronous names, continuations should have been specified with the reply value to name construct.

The type of new_cell is a polymorphic synchronous channel, returning a tuple of two synchronous channels. Asynchronous calls use the functional application construct (state s), but only inside a process construct. Indeed, state s; would raise a type error, since state is an asynchronous channel, used in a functional application inside an expression (the context before a semi colon (;) is always an expression, and not a process). In the same way, reply constructs and parallel compositions (|) can only appear in processes. However, the spawn { process } construct can be used to fork a process from within an expression.

# let (get_a,set_a) = new_cell "hello";;
val get_a : unit -> string = <fun>
val set_a : string -> unit = <fun>

# let _ = 
    print_string (get_a());
    print_char ' ';
    set_a "world";
    print_string (get_a());
    print_newline ()
hello world
- : unit = ()
Other more complicated examples can be found in the examples/ directory of the distribution.

3.2.2   Location definitions and migration

Here is the definition of a location mobile, with two included join definitions, one with one synchronous name go, and another close to the previous memory cell:

# let loc mobile def
       goto location = go location; set_position location; reply
and    set_position location | position! _ = position location | reply
    or get_position () | position! location = position location | reply location
 do {  position here } 
val mobile : Join.location = <abstr>
val goto : Join.location -> unit = chan
val position : <<Join.location>> = chan
val set_position : Join.location -> unit = chan
val get_position : unit -> Join.location = chan
First, we can notice that the type of locations is Join.location, ie location from the Join module. The Join module packages basic primitives for the join-calculus language. Here, we also use the Join.go function, which is used for location migration, and, which is the predefined toplevel location of the program.

Second, the join-calculus init process end construct is replaced by the struct location-items end construct. This construct enables you to define local values inside the location, which are not available from outside the location.

During location migration, join automata (definitions) and Objective-Caml values behave differently. Join automata are always unique. Consequently, when they migrate with their location to another runtime, local pointers to these automata from the old runtime are replaced by remote pointers to the new runtime. On the contrary, Objective-Caml values are always copied. For Objective-caml primitives and functions, this means that next call to one of them will yield to an attempt to find the function code or the primitive C code locally in the new runtime. This will success if the module where the function was defined is contained in the new runtime, or if the module code has been copied during the migration (indeed, the smallest unit of bytecode is the module, thus, all the module code migrate when the location code defined in this module migrate). This will abort and an Reloc.ModuleNotAvailable exception will be raised if the function code is not present. However, since the default runtime contains the standard library, the Unix module, the Thread module and the Join module, most needed functions are always available, and other locations dependencies should be defined in the same module as the locations.

3.2.3   Name server

The join library contains a Ns module used to make requests to the jocns name server. This module contains two functions:
val lookup : string -> 'a metatype -> 'a
val register : string -> 'b -> 'b metatype -> unit
val user : string ref
Ns.lookup is used to lookup the value associated with a particular name in the name server, whereas Ns.register is used to associate a value to a name in the name server. Ns.user is appended to every name for a lookup or a register, and is equal by default either to the environment variable USER, or to pub if this variable is not set. Since it is a reference, its value can be modified to access a particular resource registered by another user.

If no name server is running during these requests, Ns.lookup and Ns.register raise an exception. The name server address is hostname:20001 by default, and can be overridden with JNSNAME and JNSPORT environment variables.

Ns.lookup and Ns.register require an extra argument of type 'a metatype which is used to check the types of the requested value with the real type of the value in the name server. This extra argument is set with the vartype keyword, and a type constraint on the 'a metatype parameter type.

For example,
# Ns.register "mobile" mobile (vartype:Join.location metatype);;
will register the previously defined location mobile with the name mobile and the type Join.location.

3.2.4   Distributed Garbage collection

A distributed garbage collector is implemented to collect unreachable objects. It is based on the SSPC garbage collector. This garbage collector can only collect acyclic distributed garbage. Distributed garbage collection is triggered by major garbage collections.

To suppress distributed garbage collection, the environment variable JNODGC can be set before starting the runtime. To avoid distributed garbage collections only during critical periods, use the two functions Join.dgc_stop and Join.dgc_restart ().

3.2.5   Failures

The Join module contains two functions to handle failures:
val halt : unit -> 'a
val fail : location -> unit
Join.halt is used to halt a location. Currently, it does not work for the toplevel location. is used to detect location failures. Indeed, a call to only returns when the location in argument has failed. Currently, only locations halted with Join.halt are detected. Moreover, this detection is immediate for locations in the same runtime, and lazily propagated by the distributed garbage collector for different runtimes. As a consequence, only active runtimes may detect location failures.

3.2.6   Distributed objects

The Objective-Caml objects system has been extended with distributed objects: Objective-Caml objects are also locations. Thus, they can migrate using the Join.go function with all automata, locations and objects they contain. More precisely, any object or location that migrated to this object will be carried along, as well as any channel definition, location, or object created inside the migrating object. The function can be used to migrate a location into an object, or an object into another object.

Moreover, all methods calls are implemented as a new process started in the object (as location). Thus, running method threads migrate with the object.

3.2.7   Exceptions

Exceptions raised in automata processes are propagated back to all synchronous names which will not receive a reply owing to the raised exception:

#let def a() | b() | c() = reply to a | 
   raise Not_found; { reply to b | reply to c}
val c : unit -> unit = chan
val b : unit -> unit = chan
val a : unit -> unit = chan
In this example, the call to a will receive a reply, whereas the calls to b and c will raise the Not_found exception.

3.2.8   Join module

Most join-calculus primitives are available from the Join module:

type location
type space_id

val new_port : int -> unit
val server : unit -> unit

val here : location
val go : location -> unit
val goo : <  > -> unit

val halt : unit -> 't
val fail : location -> unit

val getRemoteService : space_id -> string -> 'n metatype -> 'n
val getLocalService : string -> 'o metatype -> 'o
val setLocalService : string -> 'p -> 'p metatype -> unit
val getSpaceService : Unix.inet_addr -> int -> string -> 'q metatype -> 'q

val dgc_stop : unit -> unit
val dgc_restart : unit -> unit

Previous Contents