Motile/Island is a reference implementation of the COAST architectural style. Motile is a language for mobile computation, and Island is the infrastructure supporting the deployment and execution of mobile computations. However, Motile and Island are closely integrated and although they are not the same thing, they are interrelated up to the point that we call them the Motile/Island Platform.
In Motile/Island, a peer is a single, homogeneous address-space called island, which is occupied by one or more islets. An island is a self-certifying entity with a public key associated, and all communications among islands are encrypted. Islets are implemented as actors, that is, computational agents capable of conducting computations, receiving and sending messages asynchronously.
Capability URLs (CURLs) convey the ability to communicate between computations. A CURL u is an unambiguous reference to a computation x that enables other computations holding u to send messages to x. CURLs are unguessable, unforgeable, and tamper-proof; they contain cryptographic material referencing the issuer computation, and are signed by the issuer's execution host.
In short, if you need to send a message to a computation, you must have a CURL issued by that computation.
A duplet is a convenient structure to communicate with other computations and deal with CURLs. A duplet has a resolver and a receiver. The former is precisely a CURL that can be obtained from the duplet by using the (duplet/resolver)
function. The latter is a mechanism to receive messages via the duplet's CURL. It is not explicitely referenced by internally used by the (duplet/block)
and (duplet/wait)
functions to receive messages that were sent via the duplet's CURL.
A promise is a particular case of a duplet. Promises work exactly as duplets but they can only be used once.
Gates are attached to CURLs and define the conditions that must be fulfilled for a computation to be executed. For example, a whitelist gate lists all the islands that are authorized to send messages via a specific CURL, whereas a blacklist gate defines which islands are not authorized for that. Gates can also limit the number of times, frequency, valid dates or time ranges that a CURL can be exercised. In addition, gates can constrain computational resources in the execution site such as CPU cycles and memory available for a visiting computation.
Motile is a language for creating mobile computations. We use Motile to write the computations we send to be remotely executed, either within the same island or on a different one. Motile is a single-assignment dialect of Scheme expressly created for defining COAST computations and the messages and CURLs they exchange.
For more information about Motile see What is Motile?.
A binding environment is a structure that maps symbols to functions, and it is used in Motile/Island to (1) define the functions available for a visiting computation and (2) to specify implementations for such functions. Binding environments provide functional capabilities to computations in the sense that they define what a computation can do.
Suppose Alice is a service provider offering image processing services. Bob needs to use Alice's services then it sends this computation:
(let ([bob-image (image/download "http://bob-server.com/some_picture.jpg")])
(image/rotate bob-image))
Here, (image/download)
and (image/rotate)
are functions provided by Alice. If Bob wants to use them, they need to be on the binding environment that Alice assigns to Bob's request. Without a binding environment, they are free variables with no particular meaning but with a binding environment, they get bound to a function implementation.
Now, suppose the Alice has a Premium level that uses more faster algorithms. For clients paying for the Premium level, Alice defines a new binding environment in which the same (image/download)
and (image/rotate)
symbols are bound to other function implementations. That is, even though clients use those symbols, the actual implementation on Alice will differ.
Because Motile/Island is implemented using Racket, we make extensive use of that language. For readers familiar with it, that will not be a problem. If understanding the examples becomes too difficult, you may want to take a look at the Racket Guide, Learn Racket in Y minutes or Scheme naming conventions (Racket is a dialect of Scheme).
All the examples presented in this page include a breakdown of the code as well as the entire source code. All the examples can be also found in our Github repository.
This example shows how to print a "Hello World!" message using the Island infrastructure. It instantiates an island called Alice, starts it up, and Alice prints the message. Notice that, in this example, we are not using CURLs or any Motile code, it is only a single island printing a message.
First thing that we need for working with COAST is to create a Racket file and to add the proper libraries. Create a file and name it hello.rkt and add the following code:
#lang racket/base
;; Basic require to work with Island
(require
Island/include/base)
That tells Racket that we will use the racket/base language and that we require Island/include/base, the basic component of the Motile/Island framework.
There is some boilerplate code that you will require to setup the certificates. For these examples, we will use the default islands (alice, bob, carol...) because they come with default certificates, but you can check out here how to create your own certificates. In the same hello.rkt file, add the following code:
;; Boilerplate to setup Alice's certificates. You will need them to create Alice island.
(define CERTIFICATE/SECRET "./certificates/secret/")
(define ALICE/SECRET/PATH (string-append CERTIFICATE/SECRET "alice_secret"))
;; Create Alice's CURVE cryptographic keys
(define ALICE/CURVE/SECRET (path-to-curve ALICE/SECRET/PATH))
That will let Motile/Island know where your certificates are (make sure that the path is correct). It will use alice_secret file to create the CURVE cryptographic keys for Alice.
Now we're ready to start adding our code in Alice.
We need an initial computation to bootstrap Alice, that is, to tell Alice what to do first:
;; The bootstrap computation for Alice island to say "Hello World!"
(define (alice/boot)
(display "Hello World!"))
We are creating a computation that will only print "Hello World!" and will do nothing else.
;; Instantiate Alice island
(define alice (island/new 'alice ALICE/CURVE/SECRET alice/boot))
Now, hit "Run" on DrRacket to execute your code, and then type on the DrRacket's console:
(island/start alice)
Don't forget to press enter after each command in the DrRacket's console.
If everything went right, you will see "Hello World!" in the console.
Now, shutdown Alice by typing:
(island/destroy alice)
That was it!
In this example, we have seen how to create a computation using the Motile/Island infrastructure. We setup Racket, loaded the certificates, and created an initial computation.
See the entire hello.rkt file in Github or here:
#lang racket/base
;; Basic require to work with Motile/Island
(require
Island/include/base)
;; Boilerplate to setup Alice's certificates. You will need them to create Alice island.
(define CERTIFICATE/SECRET "./certificates/secret/")
(define ALICE/SECRET/PATH (string-append CERTIFICATE/SECRET "alice_secret"))
;; Create Alice's CURVE cryptographic keys
(define ALICE/CURVE/SECRET (path-to-curve ALICE/SECRET/PATH))
;; The bootstrap computation for Alice island to say "Hello World!"
(define (alice/boot)
(display "Hello World!"))
;; Instantiate Alice island
(define alice (island/new 'alice ALICE/CURVE/SECRET alice/boot))
In this example we will create two islands that will print the same message but they will not communicate with each other. It is quite similar as the previous one but this time Alice, and the new island, Bob, will introduce themselves.
We will not explain again this boilerplate code since we did it in the previous example, but don't forget to include it:
#lang racket/base
(require
Island/include/base
)
(define CERTIFICATE/SECRET "./certificates/secret/")
(define ALICE/SECRET/PATH (string-append CERTIFICATE/SECRET "alice_secret"))
(define BOB/SECRET/PATH (string-append CERTIFICATE/SECRET "bob_secret"))
(define ALICE/CURVE/SECRET (path-to-curve ALICE/SECRET/PATH))
(define BOB/CURVE/SECRET (path-to-curve BOB/SECRET/PATH))
Now, we define a function that will be used for both Alice and Bob:
(define (islet/hello)
(display (format "Hi, I am ~a" (island/nickname (this/island)))))
The following code instantiates both islands:
(define alice (island/new 'alice ALICE/CURVE/SECRET islet/hello))
(define bob (island/new 'bob BOB/CURVE/SECRET islet/hello))
Finally, from the console we type:
(island/start alice)
(island/start bob)
Alice will say "Hi, I am Alice", and Bob will say "Hi, I am Bob". Notice the use of (this/island)
and (island/name)
. The former returns the island executing the computation. The latter returns the nickname of a given island.
See this example in Github or here:
#lang racket/base
(require
Island/include/base
)
(define CERTIFICATE/SECRET "./certificates/secret/")
(define ALICE/SECRET/PATH (string-append CERTIFICATE/SECRET "alice_secret"))
(define BOB/SECRET/PATH (string-append CERTIFICATE/SECRET "bob_secret"))
(define ALICE/CURVE/SECRET (path-to-curve ALICE/SECRET/PATH))
(define BOB/CURVE/SECRET (path-to-curve BOB/SECRET/PATH))
(define (islet/hello)
(display (format "Hi, I am ~a" (island/nickname (this/island)))))
(define alice (island/new 'alice ALICE/CURVE/SECRET islet/hello))
(define bob (island/new 'bob BOB/CURVE/SECRET islet/hello))
Islets use CURLs to communicate with each other. Briefly, a CURL unambiguously identifies a service offered by an islet, and also provides addressability for such service. That is, when an islet exercises a CURL, the underlying platform will take care of delivering the message. If islet x wants to send a message to islet y, then x must hold a CURL issued by y.
This example shows how two islets sharing memory space can communicate with each other. It is a simplified example because the CURL that enables the communication between islets is stored by the CURL issuer in a global variable. Notice that in Real-world, islets need to communicate with others without sharing memory space. Also, using a global variable to pass a CURL is far from being a good practice, nevertheless it works for our example.
Alice creates two islets and executes them: a Display Server and a message sender. The Display Server listens for messages coming via a CURL and prints them. The Message Sender uses the Display Server's CURL to send a message.
As usual, we must initialize Motile/Island and Racket:
#lang racket/base
;; Notice that we require some additional Motile/Island collections.
(require
Island/include/base
Island/baseline
Island/transport/gate)
(define CERTIFICATE/SECRET "./certificates/secret/")
(define ALICE/SECRET/PATH (string-append CERTIFICATE/SECRET "alice_secret"))
(define ALICE/CURVE/SECRET (path-to-curve ALICE/SECRET/PATH))
We define a box (i.e. a container to put a single value) where we will store the CURL that an islet will use to communicate with the other:
;; Create a box with a dummy value inside.
(define curl-box (box #f))
Now, we create the function with which Alice is created: (alice/boot). Internally, it defines four functions that are later on invoked:
(server/listen)
: Creates a new CURL and listens for messages coming via that CURL. It defines what the Display Server will do.(spawn/server)
: Creates and executes the Display Server.(sender/send)
: Sends a message via the Display Server's CURL(spawn/sender)
: Creates and executes the message sender.;; Define the bootstrap computation for Alice
(define (alice/boot)
;; This is the function for the Display Service at Alice. It creates a duplet containing the CURL used to communicate.
(define (server/listen)
;; Create a duplet (containing a CURL). A couple of points about this:
;; 1) '(echo alice) is the service path.
;; 2) GATE/ALWAYS means that there are no constraints to use this CURL.
;; 3) #f means that no metadata is attached to the CURL.
;; 4) 'INTRA means that the CURL can only be used within Alice (it is intra-Island).
(let ([display/duplet (islet/curl/new '(echo alice) GATE/ALWAYS #f 'INTRA)])
;; Put the CURL into the box (duplet/resolver extracts a CURL from a duplet)
(set-box! curl-box (duplet/resolver display/duplet))
;; Listen for messages coming through the CURL. It blocks on the duplet's CURL until a message arrives, then it blocks back.
(let loop ([m (duplet/block display/duplet)])
;; Extract the actual message (i.e. the murmur's payload)
(let ([message (murmur/payload m)])
;; Show the message.
(display message))
(loop (duplet/block display/duplet)))))
;; This is a function that uses the sender to send its message through the boxed CURL.
(define (sender/send)
(send (unbox curl-box) (format "Hi, I am ~a, and I also run in Alice!" (islet/nickname (this/islet)))))
;; This function creates and executes the sender.
(define (spawn/sender)
;; (islet/new) creates a new islet in the current island:
;; 1) (this/island) returns the current island.
;; 2) 'message.sender is the nickname of the new islet.
;; 3) TRUST/MODERATE defines the level of trust granted to the new islet.
;; 4) BASELINE/SPAWN is the Execution Site global binding environment.
;; 5) environ/null is an islet-specific binding environment.
(let ([x (islet/new (this/island) 'message.sender TRUST/MODERATE BASELINE/SPAWN environ/null)])
;; Launch the new islet with the sender/send function.
(islet/jumpstart x sender/send)))
;; This function creates an executes the Display Server.
(define (spawn/server)
;; Similar to the previous function but uses 'echo.server as the nickname.
(let ([x (islet/new (this/island) 'echo.server TRUST/MODERATE BASELINE/SPAWN environ/null)])
(islet/jumpstart x server/listen)))
;; Call the functions defined above.
(spawn/server)
(sleep 1.0)
(spawn/sender))
You may notice that there is a new concept here: duplet. In short, a duplet is an abstraction that wraps a CURL and the mechanism to receive messages through it. For now, it is enough to know that a duplet contains one CURL that can be obtained using the (duplet/resolver your_duplet)
function.
Lastly, we define Alice and we are ready to go:
(define alice (island/new 'alice ALICE/CURVE/SECRET alice/boot))
Typing (island/start alice)
in DrRacket's console should print:
Hi, I am message.sender@alice, and I also run in Alice!
See the entire source code for this example (or see it in Github):
#lang racket/base
(require
Island/include/base
Island/baseline
Island/transport/gate)
(define CERTIFICATE/SECRET "./certificates/secret/")
(define ALICE/SECRET/PATH (string-append CERTIFICATE/SECRET "alice_secret"))
(define ALICE/CURVE/SECRET (path-to-curve ALICE/SECRET/PATH))
;; Create a box with a dummy value inside.
(define curl-box (box #f))
;; Define the bootstrap computation for Alice
(define (alice/boot)
;; This is the function for the Display Service at Alice. It creates a duplet containing the CURL used to communicate.
(define (server/listen)
;; Create a duplet (containing a CURL). A couple of points about this:
;; 1) '(echo alice) is the service path.
;; 2) GATE/ALWAYS means that there are no constraints to use this CURL.
;; 3) #f means that no metadata is attached to the CURL.
;; 4) 'INTRA means that the CURL can only be used within Alice (it is intra-Island).
(let ([display/duplet (islet/curl/new '(echo alice) GATE/ALWAYS #f 'INTRA)])
;; Put the CURL into the box (duplet/resolver extracts a CURL from a duplet)
(set-box! curl-box (duplet/resolver display/duplet))
;; Listen for messages coming through the CURL. It blocks on the duplet's CURL until a message arrives, then it blocks back.
(let loop ([m (duplet/block display/duplet)])
;; Extract the actual message (i.e. the murmur's payload)
(let ([message (murmur/payload m)])
;; Show the message.
(display message))
(loop (duplet/block display/duplet)))))
;; This is a function that uses the sender to send its message through the boxed CURL.
(define (sender/send)
(send (unbox curl-box) (format "Hi, I am ~a, and I also run in Alice!" (islet/nickname (this/islet)))))
;; This function creates and executes the sender.
(define (spawn/sender)
;; (islet/new) creates a new islet in the current island:
;; 1) (this/island) returns the current island.
;; 2) 'message.sender is the nickname of the new islet.
;; 3) TRUST/MODERATE defines the level of trust granted to the new islet.
;; 4) BASELINE/SPAWN is the Execution Site global binding environment.
;; 5) environ/null is an islet-specific binding environment.
(let ([x (islet/new (this/island) 'message.sender TRUST/MODERATE BASELINE/SPAWN environ/null)])
;; Launch the new islet with the sender/send function.
(islet/jumpstart x sender/send)))
;; This function creates an executes the Display Server.
(define (spawn/server)
;; Similar to the previous function but uses 'echo.server as the nickname.
(let ([x (islet/new (this/island) 'echo.server TRUST/MODERATE BASELINE/SPAWN environ/null)])
(islet/jumpstart x server/listen)))
;; Call the functions defined above.
(spawn/server)
(sleep 1.0)
(spawn/sender))
(define alice (island/new 'alice ALICE/CURVE/SECRET alice/boot))
As the reader must have imagined, there was a reason in the previous example to use a box to store a CURL globally, and that reason was to bypass a security mechanism in COAST called communication by introduction. In COAST, we like to think that politeness is paramount, therefore a person must be introduced to someone if that person wants to communicate with him or her. In the COAST terminology, if an islet y wants to communicate with an islet x, then y must be introduced to x.
Now, suppose that Janet wants to send a message to Joe, then Joe (or someone) should provide a CURL issued by him to Janet. However, if Joe was not previously introduced to Janet, how can he give her his CURL? For the sake of clarity, we decided to solve this chicken-and-egg problem using a box. Joe does not provide its CURL to Janet directly but puts it on a box where Janet can pack it up. By the way, keep in mind that we solved this problem that way because of clarity and simplicity but that is a pretty bad practice in functional programming.
It is out of the scope of COAST how an islet receives the CURL of a service with which it needs to communicate; in the same way that, for example, REST does not define how a URL is distributed. In this example, an islet in Alice wants to send a message to an islet in Bob. Alice somehow received a textual representation of the CURL issued by Bob (perhaps by e-mail or written in a piece of paper). Let's see how our Display Service would work in this context.
As usual, we first setup the certificates:
#lang racket/base
(require
Island/include/base
Island/baseline
Island/transport/gate)
(define CERTIFICATE/PUBLIC "./certificates/public/")
(define CERTIFICATE/SECRET "./certificates/secret/")
(define ALICE/SECRET/PATH (string-append CERTIFICATE/SECRET "alice_secret"))
(define BOB/SECRET/PATH (string-append CERTIFICATE/SECRET "bob_secret"))
(define ALICE/CURVE/SECRET (path-to-curve ALICE/SECRET/PATH))
(define BOB/CURVE/SECRET (path-to-curve BOB/SECRET/PATH))
Notice that we define a constant for the public certificates as well (CERTIFICATE/PUBLIC
), we will use it next because we require using a Keystore to store the public certificates of the other islands.
In addition to the usual stuff we do for certificates, we define a constant for Alice's public key, create a keystore, and download all of the predefined public certificates:
;; Alice's public key.
(define ALICE/KP/BASE64 #"wdvbN1svfhEAewhM76oSVPKj-4kzfbDhaiTFW61VdUc")
;; Create a Keystore to store the public certificates of other islands.
(define KEYSTORE (keystore/new))
;; Download all of the predefined public certificates.
(keystore/load KEYSTORE CERTIFICATE/PUBLIC)
Now, we embed in the code a textual representation of Alice's CURL:
;; The textual representation of the CURL for Alice's display service.
(define/curl/inline ALICE/CURL/DISPLAY
#<<!!
SIGNATURE = #"Nxm6zGGiZDiao5vc8aYfdEeOIME104GEeOt4_K3ys2xDP673elLrwWa56SKAWp7gR2RI25QKZW0NvB2i23NJCg"
CURL
id = 2eac24e1-f4fb-440d-a771-b3b60266a982
origin = #"wdvbN1svfhEAewhM76oSVPKj-4kzfbDhaiTFW61VdUc"
path = (echo)
access/id = access:send:echo
created = "2014-05-17T16:17:17Z"
metadata = #f
!!
)
What we have just done is hard-coding a CURL in our code, analogously to when we hard-code a URL to connect to a service.
At this point, we need to define functions for Alice and Bob:
;; Bob's bootstrap function.
;; alice/curl is the CURL for Alice's service.
(define (bob/boot alice/curl)
; Wait until Bob sees Alice.
(island/enter/wait ALICE/KP/BASE64)
; Send message.
(send alice/curl "Hi, this is Bob!"))
;; Bootstrap function for Alice who offers a display service.
(define (alice/boot)
; Define a (service/display) function.
(define (service/display)
; Create a duplet to receive messages via Alice's CURL.
; Notice the use of (islet/curl/known/new). We use this function because it is a known CURL, we are not creating a brand new CURL.
; '(echo) is the service path (it must be the same as the one in the textual representation)
; 'access:send:echo is the access id (it must be the same as the one in the textual representation)
(let ([server/duplet (islet/curl/known/new '(echo) 'access:send:echo GATE/ALWAYS environ/null)])
(let loop ([m (duplet/block server/duplet)])
(let ([payload (murmur/payload m)])
(display payload))
(loop (duplet/block server/duplet)))))
(service/display))
Also, we must convert the CURL's textual representation to a CURL entity:
;; Create an in-memory CURL from the textual representation.
(define alice/curl/display (curl/zpl/safe-to-curl ALICE/CURL/DISPLAY KEYSTORE))
We instantiate both Alice and Bob:
;; Instantiate Alice and Bob.
(define alice (island/new 'alice ALICE/CURVE/SECRET alice/boot))
(define bob (island/new 'bob BOB/CURVE/SECRET (lambda () (bob/boot alice/curl/display))))
Finally, we set the keystore to Alice and Bob:
;; Set Alice' and Bob' keystore. Since both islands are in the same address space, they can share the keystore.
(island/keystore/set alice KEYSTORE)
(island/keystore/set bob KEYSTORE)
Everything is ready to run our example, so in DrRacket's console, type:
(island/start alice)
(island/start bob)
You will then see in DrRacket's console:
Hi, this is Bob!
If you see some red messages in the console, make sure that you set logging to a proper level:
(island/log/level/set 'warning
)
See the entire source code in Github or here:
#lang racket/base
(require
Island/include/base
Island/baseline
Island/transport/gate)
(define CERTIFICATE/PUBLIC "./certificates/public/")
(define CERTIFICATE/SECRET "./certificates/secret/")
(define ALICE/SECRET/PATH (string-append CERTIFICATE/SECRET "alice_secret"))
(define BOB/SECRET/PATH (string-append CERTIFICATE/SECRET "bob_secret"))
(define ALICE/CURVE/SECRET (path-to-curve ALICE/SECRET/PATH))
(define BOB/CURVE/SECRET (path-to-curve BOB/SECRET/PATH))
;; Alice's public key.
(define ALICE/KP/BASE64 #"wdvbN1svfhEAewhM76oSVPKj-4kzfbDhaiTFW61VdUc")
;; Create a Keystore to store the public certificates of other islands.
(define KEYSTORE (keystore/new))
;; Download all of the predefined public certificates.
(keystore/load KEYSTORE CERTIFICATE/PUBLIC)
;; The textual representation of the CURL for Alice's display service.
(define/curl/inline ALICE/CURL/DISPLAY
#<<!!
SIGNATURE = #"Nxm6zGGiZDiao5vc8aYfdEeOIME104GEeOt4_K3ys2xDP673elLrwWa56SKAWp7gR2RI25QKZW0NvB2i23NJCg"
CURL
id = 2eac24e1-f4fb-440d-a771-b3b60266a982
origin = #"wdvbN1svfhEAewhM76oSVPKj-4kzfbDhaiTFW61VdUc"
path = (echo)
access/id = access:send:echo
created = "2014-05-17T16:17:17Z"
metadata = #f
!!
)
;; Bob's bootstrap function.
;; alice/curl is the CURL for Alice's service.
(define (bob/boot alice/curl)
; Wait until Bob sees Alice.
(island/enter/wait ALICE/KP/BASE64)
; Send message.
(send alice/curl "Hi, this is Bob!"))
;; Bootstrap function for Alice who offers a display service.
(define (alice/boot)
; Define a (service/display) function.
(define (service/display)
; Create a duplet to receive messages via Alice's CURL.
; Notice the use of (islet/curl/known/new). We use this function because it is a known CURL, we are not creating a brand new CURL.
; '(echo) is the service path (it must be the same as the one in the textual representation)
; 'access:send:echo is the access id (it must be the same as the one in the textual representation)
(let ([server/duplet (islet/curl/known/new '(echo) 'access:send:echo GATE/ALWAYS environ/null)])
(let loop ([m (duplet/block server/duplet)])
(let ([payload (murmur/payload m)])
(display payload))
(loop (duplet/block server/duplet)))))
(service/display))
;; Create an in-memory CURL from the textual representation.
(define alice/curl/display (curl/zpl/safe-to-curl ALICE/CURL/DISPLAY KEYSTORE))
;; Instantiate Alice and Bob.
(define alice (island/new 'alice ALICE/CURVE/SECRET alice/boot))
(define bob (island/new 'bob BOB/CURVE/SECRET (lambda () (bob/boot alice/curl/display))))
;; Set Alice' and Bob' keystore. Since both islands are in the same address space, they can share the keystore.
(island/keystore/set alice KEYSTORE)
(island/keystore/set bob KEYSTORE)
(island/log/level/set 'warning)
We will expand the last example to introduce a particular case of duplets: promises. A promise is a duplet that can only be used once.
In order to show how promises work, we turn the previous Display Server into an Echo Server. For that, when the server receives a message, instead of printing it into the console, it will reply it back to the sender. In order to do so, a client must send a CURL that the Echo Server will use to send the response back.
The source code is very similar to the previous example but we need to introduce some modifications on the Alice's and Bob's functions. Here is the new code with comments:
;; Bob's bootstrap function.
;; alice/curl is the CURL for Alice's service.
(define (bob/boot alice/curl)
; Wait until Bob sees Alice.
(island/enter/wait ALICE/KP/BASE64)
; Create a CURL that the Echo Server (Alice) will use to send the response.
(let ([p (promise/new)])
; Send a pair with the message and the CURL to receive it back.
(send alice/curl (cons (promise/resolver p) "Hi, this is Bob!"))
; Wait for the response.
(let* ([m (promise/block p)]
[message (murmur/payload m)])
; Show the response.
(display message))))
;; Bootstrap function for Alice who offers a display service.
(define (alice/boot)
; Define a (service/display) function.
(define (service/echo)
; Create a duplet to receive messages via Alice's CURL.
; Notice the use of (islet/curl/known/new). We use this function because it is a known CURL, we are not creating a brand new CURL.
; '(echo) is the service path (it must be the same as the one in the textual representation)
; 'access:send:echo is the access id (it must be the same as the one in the textual representation)
(let ([server/duplet (islet/curl/known/new '(echo) 'access:send:echo GATE/ALWAYS environ/null)])
; Wait for messages.
(let loop ([m (duplet/block server/duplet)])
; Get the murmur's payload.
(let ([payload (murmur/payload m)])
; Verify that what the server received is well-formed.
(when (and
; It is a pair.
(pair? payload)
; First element is a CURL.
(curl? (car payload))
; Second element is a string.
(string? (cdr payload)))
; Send it back.
(send (car payload) (cdr payload))))
(loop (duplet/block server/duplet)))))
(service/echo))
Notice that Bob creates a promise (promise/new
), sends the promise's CURL, and then waits (promise/block
) for a response from Alice. Likewise, Alice now expects a pair instead of a message. The pair contains the CURL that must be used to send the response as its first element, and the message to send back as the second element.
Here is the entire source code for this example (or see it in Github:
#lang racket/base
(require
Island/include/base
Island/baseline
Island/transport/gate)
(define CERTIFICATE/PUBLIC "./certificates/public/")
(define CERTIFICATE/SECRET "./certificates/secret/")
(define ALICE/SECRET/PATH (string-append CERTIFICATE/SECRET "alice_secret"))
(define BOB/SECRET/PATH (string-append CERTIFICATE/SECRET "bob_secret"))
;; Alice's public key.
(define ALICE/KP/BASE64 #"wdvbN1svfhEAewhM76oSVPKj-4kzfbDhaiTFW61VdUc")
;; Create a Keystore to store the public certificates of other islands.
(define KEYSTORE (keystore/new))
;; Download all of the predefined public certificates.
(keystore/load KEYSTORE CERTIFICATE/PUBLIC)
(define ALICE/CURVE/SECRET (path-to-curve ALICE/SECRET/PATH))
(define BOB/CURVE/SECRET (path-to-curve BOB/SECRET/PATH))
;; The textual representation of the CURL for Alice's echo service.
(define/curl/inline ALICE/CURL/ECHO
#<<!!
SIGNATURE = #"Nxm6zGGiZDiao5vc8aYfdEeOIME104GEeOt4_K3ys2xDP673elLrwWa56SKAWp7gR2RI25QKZW0NvB2i23NJCg"
CURL
id = 2eac24e1-f4fb-440d-a771-b3b60266a982
origin = #"wdvbN1svfhEAewhM76oSVPKj-4kzfbDhaiTFW61VdUc"
path = (echo)
access/id = access:send:echo
created = "2014-05-17T16:17:17Z"
metadata = #f
!!
)
;; Bob's bootstrap function.
;; alice/curl is the CURL for Alice's service.
(define (bob/boot alice/curl)
; Wait until Bob sees Alice.
(island/enter/wait ALICE/KP/BASE64)
; Create a CURL that the Echo Server (Alice) will use to send the response.
(let ([p (promise/new)])
; Send a pair with the message and the CURL to receive it back.
(send alice/curl (cons (promise/resolver p) "Hi, this is Bob!"))
; Wait for the response.
(let* ([m (promise/block p)]
[message (murmur/payload m)])
; Show the response.
(display message))))
;; Bootstrap function for Alice who offers an Echo Service.
(define (alice/boot)
; Define a (service/echo) function.
(define (service/echo)
; Create a duplet to receive messages via Alice's CURL.
; Notice the use of (islet/curl/known/new). We use this function because it is a known CURL, we are not creating a brand new CURL.
; '(echo) is the service path (it must be the same as the one in the textual representation)
; 'access:send:echo is the access id (it must be the same as the one in the textual representation)
(let ([server/duplet (islet/curl/known/new '(echo) 'access:send:echo GATE/ALWAYS environ/null)])
; Wait for messages.
(let loop ([m (duplet/block server/duplet)])
; Get the murmur's payload.
(let ([payload (murmur/payload m)])
; Verify that what the server received is well-formed.
(when (and
; It is a pair.
(pair? payload)
; Left element is a CURL.
(curl? (car payload))
; Right element is a string.
(string? (cdr payload)))
; Send it back.
(send (car payload) (cdr payload))))
(loop (duplet/block server/duplet)))))
(service/echo))
;; Create an in-memory CURL from the textual representation.
(define alice/curl/echo (curl/zpl/safe-to-curl ALICE/CURL/ECHO KEYSTORE))
;; Instantiate Alice and Bob.
(define alice (island/new 'alice ALICE/CURVE/SECRET alice/boot))
(define bob (island/new 'bob BOB/CURVE/SECRET (lambda () (bob/boot alice/curl/echo))))
;; Set Alice's and Bob's keystore. Since both islands are in the same address space, they can share the keystore.
(island/keystore/set alice KEYSTORE)
(island/keystore/set bob KEYSTORE)
(island/log/level/set 'warning)
Gates let you constrain the conditions in which a CURL can be used. For example, an island can define who is authorized to exercise it, when, how many times, and also to impose limits at the Execution Site level such as the maximum allowed memory or CPU cycles to use by the visiting computation.
We will expand farther the previous example to include a third island, Carol, that attempts to use Alice's Echo Service but it will not be able to do it.
The base source code remains the same as in the previous example but we need to also account for Carol, the new island. For that, we add the necessary constants:
(define CAROL/SECRET/PATH (string-append CERTIFICATE/SECRET "carol_secret"))
(define CAROL/KP/BASE64 #"rqM_XCwrsziuhIEsG1d0yMA05mivoewXhUmzKUzhb0s")
(define CAROL/CURVE/SECRET (path-to-curve CAROL/SECRET/PATH))
We also slightly change Bob's function to make it a generic client function, that will be use by both Bob and Carol:
;; Echo Service Client bootstrap function.
;; alice/curl is the CURL for Alice's service.
(define (client/boot alice/curl)
; Wait until the client sees Alice.
(island/enter/wait ALICE/KP/BASE64)
; Create a CURL that the Echo Server (Alice) will use to send the response.
(let ([p (promise/new)])
; Send a pair with the message and the CURL to receive it back.
(send alice/curl (cons (promise/resolver p) (format "Hi, this is ~a!" (island/nickname (this/island)))))
; Wait for the response.
(let* ([m (promise/block p)]
[message (murmur/payload m)])
; Show the response.
(display message))))
In addition, we change how the CURL gets created in Alice's function from:
(let ([server/duplet (islet/curl/known/new '(echo) 'access:send:echo GATE/ALWAYS environ/null)])
to:
; The (gate/whitelist/island) function creates a gate that only lets pass messages coming from a specific set of islands.
(let ([server/duplet (islet/curl/known/new '(echo) 'access:send:echo (gate/whitelist/island BOB/KP/BASE64) environ/null)])
because we want to make the CURL available only for Bob, and to reject Carol's messages. For that, we use the (gate/whitelist/island)
function, and pass Bob's public key.
Finally, we add the instantiation of Carol and set its keystore:
(define carol (island/new 'carol CAROL/CURVE/SECRET (lambda () (client/boot alice/curl/echo))))
(island/keystore/set carol KEYSTORE)
Now, everything is ready to be executed. After you do:
(island/start alice)
(island/start bob)
(island/start carol)
You will only see Hi, this is bob!
coming from Bob, because messages from Carol are discarded at the CURL's gate.
Recall that the new stuff for Carol must be added to the previous example. See the entire source code in Github or here:
#lang racket/base
(require
Island/include/base
Island/baseline
Island/transport/gate
Island/transport/gates/whitelist)
(define CERTIFICATE/PUBLIC "./certificates/public/")
(define CERTIFICATE/SECRET "./certificates/secret/")
(define ALICE/SECRET/PATH (string-append CERTIFICATE/SECRET "alice_secret"))
(define BOB/SECRET/PATH (string-append CERTIFICATE/SECRET "bob_secret"))
(define CAROL/SECRET/PATH (string-append CERTIFICATE/SECRET "carol_secret"))
;; Alice's, Bob's and Carol's public keys.
(define ALICE/KP/BASE64 #"wdvbN1svfhEAewhM76oSVPKj-4kzfbDhaiTFW61VdUc")
(define BOB/KP/BASE64 #"49u_B0VEdFFS3WCPMMX5T5MFQ3SaSHjM8fM63I4L338")
(define CAROL/KP/BASE64 #"rqM_XCwrsziuhIEsG1d0yMA05mivoewXhUmzKUzhb0s")
;; Create a Keystore to store the public certificates of other islands.
(define KEYSTORE (keystore/new))
;; Download all of the predefined public certificates.
(keystore/load KEYSTORE CERTIFICATE/PUBLIC)
(define ALICE/CURVE/SECRET (path-to-curve ALICE/SECRET/PATH))
(define BOB/CURVE/SECRET (path-to-curve BOB/SECRET/PATH))
(define CAROL/CURVE/SECRET (path-to-curve CAROL/SECRET/PATH))
;; The textual representation of the CURL for Alice's echo service.
(define/curl/inline ALICE/CURL/ECHO
#<<!!
SIGNATURE = #"Nxm6zGGiZDiao5vc8aYfdEeOIME104GEeOt4_K3ys2xDP673elLrwWa56SKAWp7gR2RI25QKZW0NvB2i23NJCg"
CURL
id = 2eac24e1-f4fb-440d-a771-b3b60266a982
origin = #"wdvbN1svfhEAewhM76oSVPKj-4kzfbDhaiTFW61VdUc"
path = (echo)
access/id = access:send:echo
created = "2014-05-17T16:17:17Z"
metadata = #f
!!
)
;; Echo Service Client bootstrap function.
;; alice/curl is the CURL for Alice's service.
(define (client/boot alice/curl)
; Wait until the client sees Alice.
(island/enter/wait ALICE/KP/BASE64)
; Create a CURL that the Echo Server (Alice) will use to send the response.
(let ([p (promise/new)])
; Send a pair with the message and the CURL to receive it back.
(send alice/curl (cons (promise/resolver p) (format "Hi, this is ~a!" (island/nickname (this/island)))))
; Wait for the response.
(let* ([m (promise/block p)]
[message (murmur/payload m)])
; Show the response.
(display message))))
;; Bootstrap function for Alice who offers an Echo Service.
(define (alice/boot)
; Define a (service/echo) function.
(define (service/echo)
; Create a duplet to receive messages via Alice's CURL.
; Notice the use of (islet/curl/known/new). We use this function because it is a known CURL, we are not creating a brand new CURL.
; '(echo) is the service path (it must be the same as the one in the textual representation)
; 'access:send:echo is the access id (it must be the same as the one in the textual representation)
; The (gate/whitelist/island) function creates a gate that only lets pass messages coming from a specific set of islands.
(let ([server/duplet (islet/curl/known/new '(echo) 'access:send:echo (gate/whitelist/island BOB/KP/BASE64) environ/null)])
; Wait for messages.
(let loop ([m (duplet/block server/duplet)])
; Get the murmur's payload.
(let ([payload (murmur/payload m)])
; Verify that what the server received is well-formed.
(when (and
; It is a pair.
(pair? payload)
; Left element is a CURL.
(curl? (car payload))
; Right element is a string.
(string? (cdr payload)))
; Send it back.
(send (car payload) (cdr payload))))
(loop (duplet/block server/duplet)))))
(service/echo))
;; Create an in-memory CURL from the textual representation.
(define alice/curl/echo (curl/zpl/safe-to-curl ALICE/CURL/ECHO KEYSTORE))
;; Instantiate Alice and Bob.
(define alice (island/new 'alice ALICE/CURVE/SECRET alice/boot))
(define bob (island/new 'bob BOB/CURVE/SECRET (lambda () (client/boot alice/curl/echo))))
(define carol (island/new 'carol CAROL/CURVE/SECRET (lambda () (client/boot alice/curl/echo))))
;; Set Alice' and Bob' keystore. Since both islands are in the same address space, they can share the keystore.
(island/keystore/set alice KEYSTORE)
(island/keystore/set bob KEYSTORE)
(island/keystore/set carol KEYSTORE)
(island/log/level/set 'warning)
Before continuing with the Display Server and the Echo Server examples, we present a much simpler example using Motile. Here, an island sends a simple calculation to be solved by another island. Bob writes its computation using Motile and sends it to Alice, that will solve it and send the result back to Bob.
As always, we setup our islands:
#lang racket/base
(require
Island/include/base
Island/baseline
Island/transport/gate
Island/remote)
(define CERTIFICATE/PUBLIC "./certificates/public/")
(define CERTIFICATE/SECRET "./certificates/secret/")
(define ALICE/SECRET/PATH (string-append CERTIFICATE/SECRET "alice_secret"))
(define BOB/SECRET/PATH (string-append CERTIFICATE/SECRET "bob_secret"))
(define ALICE/CURVE/SECRET (path-to-curve ALICE/SECRET/PATH))
(define BOB/CURVE/SECRET (path-to-curve BOB/SECRET/PATH))
;; Alice's public key.
(define ALICE/KP/BASE64 #"wdvbN1svfhEAewhM76oSVPKj-4kzfbDhaiTFW61VdUc")
;; Create a Keystore to store the public certificates of other islands.
(define KEYSTORE (keystore/new))
;; Download all of the predefined public certificates.
(keystore/load KEYSTORE CERTIFICATE/PUBLIC)
;; The textual representation of the CURL for Alice's service.
(define/curl/inline ALICE/CURL/SPAWN
#<<!!
SIGNATURE = #"GNzBZNi6r6WTBdASzv_R0GJjAiwaBYtHkZhiMlyKTD8E-S-mL-A7SMFR7_9IKNl8_JJcfzOIBQh4YDnP3JoWBw"
CURL
id = 0dd4f4f5-72ce-40fe-996f-f80700c322f0
origin = #"wdvbN1svfhEAewhM76oSVPKj-4kzfbDhaiTFW61VdUc"
path = (service spawn)
access/id = access:send.service.spawn
created = "2014-05-30T14:47:58Z"
metadata = #f
!!
)
We now define the Motile computation, using the (island/compile)
function:
(define BOB/COMPUTATION
(island/compile
'(lambda () (+ 2 3))
))
As you can see, the computation is pretty simple, it sums 2 and 3. Let's break this snippet down. We define a constant called BOB/COMPUTATION
with the output of the (island/compile)
function. This function receives Motile code and compiles it to Racket code that can be shipped for remote execution. The single quote ('
) means that everything after that will be a symbol. That is, '(lambda () (+ 2 3))
represents one symbol containing the (lambda () (+ 2 3))
expression. The (island/compile)
function will compile that expression into Racket code.
We create a function for Bob:
;; Bob's bootstrap function.
;; alice/curl is the CURL for Alice's service.
(define (bob/boot alice/curl)
; Wait until Bob sees Alice.
(island/enter/wait ALICE/KP/BASE64)
; Send Bob's computation and wait for the response.
(let* ([p (promise/new)]
[response/curl (promise/resolver p)])
(send alice/curl (cons response/curl BOB/COMPUTATION))
(let* ([m (promise/block p)]
[result (murmur/payload m)])
; Show result.
(display result))))
The function presented above creates a pair with a promise's CURL and BOB/COMPUTATION
, and sends it to Alice via alice/curl
. After that, it waits for a response.
We define a function for Alice:
;; Bootstrap function for Alice.
(define (alice/boot)
; Define a (service/execute) function.
(define (service/execute)
; Create a duplet to receive messages via Alice's CURL.
; Notice the use of (islet/curl/known/new). We use this function because it is a known CURL, we are not creating a brand new CURL.
; '(service spawn) is the service path (it must be the same as the one in the textual representation)
; 'access:send.service.spawn is the access id (it must be the same as the one in the textual representation)
(let ([server/duplet (islet/curl/known/new '(service spawn) 'access:send.service.spawn GATE/ALWAYS environ/null)])
(let loop ([m (duplet/block server/duplet)])
; Create a worker (i.e. an islet) to solve the incoming computation.
; 'client-worker is the worker's nickname.
; BASELINE is the binding environment for the new islet.
(let ([worker (islet/new (this/island) 'client-worker TRUST/LOW BASELINE environ/null)]
[payload (murmur/payload m)])
; Check that all parameters are correct.
(when (and (pair? payload) (curl? (car payload)) (procedure? (cdr payload)))
(let ([client/curl (car payload)]
[thunk (cdr payload)])
; Execute the computation and send the result back in no more than 10 seconds.
(remote worker thunk client/curl 10.0))))
(loop (duplet/block server/duplet)))))
(service/execute))
With this function, Alice receives thunks (procedures with no parameters) and spawns workers to execute them. Notice that Alice expects to receive a pair with the thunk and a CURL to send the result back, for which it uses the (remote)
function.
We finally setup the islands and we are good to go:
;; Create an in-memory CURL from the textual representation.
(define alice/curl/execute (curl/zpl/safe-to-curl ALICE/CURL/SPAWN KEYSTORE))
;; Instantiate Alice and Bob.
(define alice (island/new 'alice ALICE/CURVE/SECRET alice/boot))
(define bob (island/new 'bob BOB/CURVE/SECRET (lambda () (bob/boot alice/curl/execute))))
;; Set Alice' and Bob' keystore. Since both islands are in the same address space, they can share the keystore.
(island/keystore/set alice KEYSTORE)
(island/keystore/set bob KEYSTORE)
(island/log/level/set 'warning)
See the entire source code in Github or here:
#lang racket/base
(require
Island/include/base
Island/baseline
Island/transport/gate
Island/remote)
(define CERTIFICATE/PUBLIC "./certificates/public/")
(define CERTIFICATE/SECRET "./certificates/secret/")
(define ALICE/SECRET/PATH (string-append CERTIFICATE/SECRET "alice_secret"))
(define BOB/SECRET/PATH (string-append CERTIFICATE/SECRET "bob_secret"))
(define ALICE/CURVE/SECRET (path-to-curve ALICE/SECRET/PATH))
(define BOB/CURVE/SECRET (path-to-curve BOB/SECRET/PATH))
;; Alice's public key.
(define ALICE/KP/BASE64 #"wdvbN1svfhEAewhM76oSVPKj-4kzfbDhaiTFW61VdUc")
;; Create a Keystore to store the public certificates of other islands.
(define KEYSTORE (keystore/new))
;; Download all of the predefined public certificates.
(keystore/load KEYSTORE CERTIFICATE/PUBLIC)
;; The textual representation of the CURL for Alice's service.
(define/curl/inline ALICE/CURL/SPAWN
#<<!!
SIGNATURE = #"GNzBZNi6r6WTBdASzv_R0GJjAiwaBYtHkZhiMlyKTD8E-S-mL-A7SMFR7_9IKNl8_JJcfzOIBQh4YDnP3JoWBw"
CURL
id = 0dd4f4f5-72ce-40fe-996f-f80700c322f0
origin = #"wdvbN1svfhEAewhM76oSVPKj-4kzfbDhaiTFW61VdUc"
path = (service spawn)
access/id = access:send.service.spawn
created = "2014-05-30T14:47:58Z"
metadata = #f
!!
)
(define BOB/COMPUTATION
(island/compile
'(lambda () (+ 2 3))
))
;; Bob's bootstrap function.
;; alice/curl is the CURL for Alice's service.
(define (bob/boot alice/curl)
; Wait until Bob sees Alice.
(island/enter/wait ALICE/KP/BASE64)
; Send Bob's computation and wait for the response.
(let* ([p (promise/new)]
[response/curl (promise/resolver p)])
(send alice/curl (cons response/curl BOB/COMPUTATION))
(let* ([m (promise/block p)]
[result (murmur/payload m)])
; Show result.
(display result))))
;; Bootstrap function for Alice.
(define (alice/boot)
; Define a (service/execute) function.
(define (service/execute)
; Create a duplet to receive messages via Alice's CURL.
; Notice the use of (islet/curl/known/new). We use this function because it is a known CURL, we are not creating a brand new CURL.
; '(service spawn) is the service path (it must be the same as the one in the textual representation)
; 'access:send.service.spawn is the access id (it must be the same as the one in the textual representation)
(let ([server/duplet (islet/curl/known/new '(service spawn) 'access:send.service.spawn GATE/ALWAYS environ/null)])
(let loop ([m (duplet/block server/duplet)])
; Create a worker (i.e. an islet) to solve the incoming computation.
; 'client-worker is the worker's nickname.
; BASELINE is the binding environment for the new islet.
(let ([worker (islet/new (this/island) 'client-worker TRUST/LOW BASELINE environ/null)]
[payload (murmur/payload m)])
; Check that all parameters are correct.
(when (and (pair? payload) (curl? (car payload)) (procedure? (cdr payload)))
(let ([client/curl (car payload)]
[thunk (cdr payload)])
; Execute the computation and send the result back in no more than 10 seconds.
(remote worker thunk client/curl 10.0))))
(loop (duplet/block server/duplet)))))
(service/execute))
;; Create an in-memory CURL from the textual representation.
(define alice/curl/execute (curl/zpl/safe-to-curl ALICE/CURL/SPAWN KEYSTORE))
;; Instantiate Alice and Bob.
(define alice (island/new 'alice ALICE/CURVE/SECRET alice/boot))
(define bob (island/new 'bob BOB/CURVE/SECRET (lambda () (bob/boot alice/curl/execute))))
;; Set Alice' and Bob' keystore. Since both islands are in the same address space, they can share the keystore.
(island/keystore/set alice KEYSTORE)
(island/keystore/set bob KEYSTORE)
(island/log/level/set 'warning)
We modify the previous example so that, instead of having a client that sends a calculation and receives a result (server-driven), it is the client who decides what its computation running at the server side will do (client-driven). Specifically, in this example, the client will send a computation, and the server will do no more than only executing it. When executed on the server, the computation will send a message back to the client. This is different from the previous Echo Server examples because on those, the client sent only data, and the server knew what to do. In this example, the client decides what to do by implementing a computation that does that.
#lang racket/base
(require
Island/include/base
Island/baseline
Island/transport/gate
Island/remote)
(define CERTIFICATE/PUBLIC "./certificates/public/")
(define CERTIFICATE/SECRET "./certificates/secret/")
(define ALICE/SECRET/PATH (string-append CERTIFICATE/SECRET "alice_secret"))
(define BOB/SECRET/PATH (string-append CERTIFICATE/SECRET "bob_secret"))
(define ALICE/CURVE/SECRET (path-to-curve ALICE/SECRET/PATH))
(define BOB/CURVE/SECRET (path-to-curve BOB/SECRET/PATH))
;; Alice's public key.
(define ALICE/KP/BASE64 #"wdvbN1svfhEAewhM76oSVPKj-4kzfbDhaiTFW61VdUc")
;; Create a Keystore to store the public certificates of other islands.
(define KEYSTORE (keystore/new))
;; Download all of the predefined public certificates.
(keystore/load KEYSTORE CERTIFICATE/PUBLIC)
;; The textual representation of the CURL for Alice's service.
(define/curl/inline ALICE/CURL/SPAWN
#<<!!
SIGNATURE = #"GNzBZNi6r6WTBdASzv_R0GJjAiwaBYtHkZhiMlyKTD8E-S-mL-A7SMFR7_9IKNl8_JJcfzOIBQh4YDnP3JoWBw"
CURL
id = 0dd4f4f5-72ce-40fe-996f-f80700c322f0
origin = #"wdvbN1svfhEAewhM76oSVPKj-4kzfbDhaiTFW61VdUc"
path = (service spawn)
access/id = access:send.service.spawn
created = "2014-05-30T14:47:58Z"
metadata = #f
!!
)
We define the computation that the client (Bob) will send:
(define BOB/COMPUTATION
(island/compile
'(lambda (curl/response)
(lambda ()
(send curl/response "Hello World!")))
))
This computation is different from BOB/COMPUTATION
in the previous example because:
Let's answer a few questions about BOB/COMPUTATION
:
Why are there two lambdas? This question is crucial to understand this example and it has to do with how functional programming works. Recall that we must send a thunk to be executed, that is, a procedure with no parameters. However, we do need one parameter in this computation, that is the CURL that will be used to send the result back. How do we deal with this situation? We define one function with the required parameters (i.e. the outer lambda) that returns another function with no parameters (i.e. the inner lambda). The outer lambda's parameters are in the lexical scope of the inner lambda, that is, those parameters are not free variables, they are bound variables. When the outer lambda gets evaluated, it will return the inner lambda, and nothing else will happen. In other words, evaluating the outer lambda does not cause the inner lambda to be evaluated; the inner lambda will be eventually evaluated. If you got this, you can continue to the next point, otherwise, take a look at this example:
Suppose you have a function f(x) that returns g(x). The f(x) function does not return the result of g(x), it returns g(x) itself. When someone evaluates f(x), the g(x) function will not be evaluated. For that to happen, someone will need to evaluate f(x) and then evaluate its result (i.e. g(x)). That is exactly what happens here with the two lambdas.
(send)
function? That is a Motile function that lets computations send messages to other computations. It receives a CURL and what is to be sent. A computation will be able to invoke (send)
if this function is in the computation's binding environment.
We now define Bob's function:
;; Bob's bootstrap function.
;; alice/curl is the CURL for Alice's service.
(define (bob/boot alice/curl)
; Wait until Bob sees Alice.
(island/enter/wait ALICE/KP/BASE64)
; Send Bob's computation and wait for the response.
(let* ([p (promise/new)]
[response/curl (promise/resolver p)]
[thunk (motile/call BOB/COMPUTATION environ/null response/curl)])
(send alice/curl thunk)
(let* ([m (promise/block p)]
[result (murmur/payload m)])
; Show result.
(display result))))
Here is where we solve the second part of passing an argument to the Motile computation. Recall that we are to send a procedure with no parameters (i.e. the inner lambda defined above), and it is returned when the outer lambda gets evaluated. If you look at the snippet above, you will see that the thunk
variable is defined as the result of the evaluation of (motile/call BOB/COMPUTATION environ/null response/curl)
. What we do here is precisely invoking the outer lambda, however we must do it by calling (motile/call)
because we are dealing with Motile code instead of Racket code. In short, executing BOB/COMPUTATION
will return the Motile inner lambda with the required arguments bound ready to be sent. Notice that we are passing the arguments we need when (motile/call)
is invoked.
The rest of the example does not differ much from the previous one. We define a function for Alice, which will spawn a new islet to execute the incoming thunk. Notice that Alice does not explicitely receive a CURL as in the previous example. Alice receives a computation that gets executed:
;; Bootstrap function for Alice.
(define (alice/boot)
; Define a (service/execute) function.
(define (service/execute)
; Create a duplet to receive messages via Alice's CURL.
; Notice the use of (islet/curl/known/new). We use this function because it is a known CURL, we are not creating a brand new CURL.
; '(service spawn) is the service path (it must be the same as the one in the textual representation)
; 'access:send:service.spawn is the access id (it must be the same as the one in the textual representation)
(let ([server/duplet (islet/curl/known/new '(service spawn) 'access:send.service.spawn GATE/ALWAYS environ/null)])
(let loop ([m (duplet/block server/duplet)])
; Create a worker (i.e. an islet) to solve the incoming computation.
; 'client-worker is the worker's nickname.
; BASELINE is the binding environment for the new islet.
(let ([worker (islet/new (this/island) 'client-worker TRUST/LOW BASELINE/SPAWN environ/null)]
[thunk (murmur/payload m)])
; Check that all parameters are correct.
(when (procedure? thunk)
; Execute the computation and send the result back in no more than 10 seconds.
(spawn worker thunk 10.0)))
(loop (duplet/block server/duplet)))))
(service/execute))
Finally, we instantiate islands and all the rest:
;; Create an in-memory CURL from the textual representation.
(define alice/curl/execute (curl/zpl/safe-to-curl ALICE/CURL/SPAWN KEYSTORE))
;; Instantiate Alice and Bob.
(define alice (island/new 'alice ALICE/CURVE/SECRET alice/boot))
(define bob (island/new 'bob BOB/CURVE/SECRET (lambda () (bob/boot alice/curl/execute))))
;; Set Alice' and Bob' keystore. Since both islands are in the same address space, they can share the keystore.
(island/keystore/set alice KEYSTORE)
(island/keystore/set bob KEYSTORE)
(island/log/level/set 'warning)
You can see the entire example at Github or here:
#lang racket/base
(require
Island/include/base
Island/baseline
Island/transport/gate
Island/remote)
(define CERTIFICATE/PUBLIC "./certificates/public/")
(define CERTIFICATE/SECRET "./certificates/secret/")
(define ALICE/SECRET/PATH (string-append CERTIFICATE/SECRET "alice_secret"))
(define BOB/SECRET/PATH (string-append CERTIFICATE/SECRET "bob_secret"))
(define ALICE/CURVE/SECRET (path-to-curve ALICE/SECRET/PATH))
(define BOB/CURVE/SECRET (path-to-curve BOB/SECRET/PATH))
;; Alice's public key.
(define ALICE/KP/BASE64 #"wdvbN1svfhEAewhM76oSVPKj-4kzfbDhaiTFW61VdUc")
;; Create a Keystore to store the public certificates of other islands.
(define KEYSTORE (keystore/new))
;; Download all of the predefined public certificates.
(keystore/load KEYSTORE CERTIFICATE/PUBLIC)
;; The textual representation of the CURL for Alice's service.
(define/curl/inline ALICE/CURL/SPAWN
#<<!!
SIGNATURE = #"GNzBZNi6r6WTBdASzv_R0GJjAiwaBYtHkZhiMlyKTD8E-S-mL-A7SMFR7_9IKNl8_JJcfzOIBQh4YDnP3JoWBw"
CURL
id = 0dd4f4f5-72ce-40fe-996f-f80700c322f0
origin = #"wdvbN1svfhEAewhM76oSVPKj-4kzfbDhaiTFW61VdUc"
path = (service spawn)
access/id = access:send.service.spawn
created = "2014-05-30T14:47:58Z"
metadata = #f
!!
)
(define BOB/COMPUTATION
(island/compile
'(lambda (curl/response)
(lambda ()
(send curl/response "Hello World!")))
))
;; Bob's bootstrap function.
;; alice/curl is the CURL for Alice's service.
(define (bob/boot alice/curl)
; Wait until Bob sees Alice.
(island/enter/wait ALICE/KP/BASE64)
; Send Bob's computation and wait for the response.
(let* ([p (promise/new)]
[response/curl (promise/resolver p)]
[thunk (motile/call BOB/COMPUTATION environ/null response/curl)])
(send alice/curl thunk)
(let* ([m (promise/block p)]
[result (murmur/payload m)])
; Show result.
(display result))))
;; Bootstrap function for Alice.
(define (alice/boot)
; Define a (service/execute) function.
(define (service/execute)
; Create a duplet to receive messages via Alice's CURL.
; Notice the use of (islet/curl/known/new). We use this function because it is a known CURL, we are not creating a brand new CURL.
; '(service spawn) is the service path (it must be the same as the one in the textual representation)
; 'access:send:service.spawn is the access id (it must be the same as the one in the textual representation)
(let ([server/duplet (islet/curl/known/new '(service spawn) 'access:send.service.spawn GATE/ALWAYS environ/null)])
(let loop ([m (duplet/block server/duplet)])
; Create a worker (i.e. an islet) to solve the incoming computation.
; 'client-worker is the worker's nickname.
; BASELINE is the binding environment for the new islet.
(let ([worker (islet/new (this/island) 'client-worker TRUST/LOW BASELINE/SPAWN environ/null)]
[thunk (murmur/payload m)])
; Check that all parameters are correct.
(when (procedure? thunk)
; Execute the computation and send the result back in no more than 10 seconds.
(spawn worker thunk 10.0)))
(loop (duplet/block server/duplet)))))
(service/execute))
;; Create an in-memory CURL from the textual representation.
(define alice/curl/execute (curl/zpl/safe-to-curl ALICE/CURL/SPAWN KEYSTORE))
;; Instantiate Alice and Bob.
(define alice (island/new 'alice ALICE/CURVE/SECRET alice/boot))
(define bob (island/new 'bob BOB/CURVE/SECRET (lambda () (bob/boot alice/curl/execute))))
;; Set Alice' and Bob' keystore. Since both islands are in the same address space, they can share the keystore.
(island/keystore/set alice KEYSTORE)
(island/keystore/set bob KEYSTORE)
(island/log/level/set 'warning)