Skip to content

Latest commit

 

History

History
299 lines (171 loc) · 7.11 KB

README.md

File metadata and controls

299 lines (171 loc) · 7.11 KB

jobim

An actors library for Clojure modelled after Erlang OTP plaform.

External dependencies

Jobim has two dependencies:

  • Directory service: ZooKeeper
  • Messaging system: RabbitMQ/ZeroMQ

RabbitMQ or ZeroMQ both can be used as the underlying messaging solution. In order to use Jobim, ZeroMQ java bindings must be installed, including the native jni library.

Basic usage

starting a node : jobim/bootstrap-node

A path to a configuration file must be provided. The path must include the name of the node, the selected messaging mechanism and configuration options for RabbitMQ, ZeroMQ and ZooKeeper.

This is a sample configuration file:

;; RabbitMQ used
{:node-name "remote-test"
 :messaging-type :rabbitmq
 :messaging-options {:host "192.168.1.35"}
 :zookeeper-options ["192.168.1.35:2181" {:timeout 3000}]}

 This is a configuration file for ZeroMQ:

{:node-name "remote-test"
 :messaging-type :zeromq
 :messaging-options {:protocol-and-port "tcp://192.168.1.35:5555"}
 :zookeeper-options ["localhost:2181" {:timeout 3000}]}

Checking available nodes : jobim/nodes jobim/resolve-node-name

The nodes function returns a map with all the node names and their identifiers. The function resolve-node-name can be used to retrieve the indetifier of a node provided its name.

=> (use 'jobim)

nil

=> (bootstrap-node "node-config.clj")

"6811651bd83e4d428359b419e7f76a75"

=> (nodes)

{"osx" "5299491ea4184c02ad8c0fbc100c49f9", "linux" "6811651bd83e4d428359b419e7f76a75"}

Agent creation : jobim/spawn

We can define an actor using any Clojure function and the spawn function.

; A Sample echo actor
(defn ping
  ([]
     (loop [continue true
            msg (receive)]
       (cond-match
        [#"exit" msg]       (recur false msg)
        [#"exception" msg]  (throw (Exception.
                                     (str "Ping actor with PID:"
                                          (self)
                                          "received exception")))
        [[?from ?data] msg] (do (send! from data)
                            (recur true (receive)))))))

Creation of the actor:

=> (def *pid* (spawn examples/ping))

#'clojure.core/*pid*

=> *pid*

"5299491ea4184c02ad8c0fbc100c49f9.1"

Transforming a thread into an actor : jobim/spawn-in-repl

The REPL or any other thread can become an actor calling spawn-in-repl from the code being executed in the thread:

=> (spawn-in-repl)

"5299491ea4184c02ad8c0fbc100c49f9.2"

=> (send! *pid* [(self) 13123])

:ok

=> (receive)

13123

Sending and receiving : jobim/send! jobim/receive

This couple of function can be used to send and receive messages to and from actors. The PID of the actor must be provided. By default, any serializable java object can be send and retrieved.

=> (send! pid [(self) (java.util.Date.)]) :ok

=> (receive)

#<Date Fri Sep 03 13:20:26 CEST 2010>

RPC calls : jobim/rpc-call jobim/rpc-blocking-call

Used to execute a function call in a remote node. They can be used to spawn remote actors and retrieve the PID (using the blocking variant).

=> (nodes)

{"osx" "5299491ea4184c02ad8c0fbc100c49f9", "linux" "6811651bd83e4d428359b419e7f76a75"}

=> (resolve-node-name "linux")

"6811651bd83e4d428359b419e7f76a75"

=> (rpc-blocking-call (resolve-node-name "linux") "clojure.core/+" [1 2 3 4 5])

15

Spawning a remote actor:

=> (def *pid* (rpc-blocking-call (resolve-node-name "linux") "jobim/spawn" ["jobim.examples.actors/ping"]))

#'clojure.core/*pid*

=> *pid*

"6811651bd83e4d428359b419e7f76a75.1"

=> (send! *pid* [(self) 345])

nil

=> (receive)

345

Registering PIDs : jobim/register-name jobim/registered-names jobim/resolve-name

An actor can be registered with a globally available name using the register-name function. All registered names can be retrieved with the registered-names function.

To transform an actor name into a PID the resolve-name function can be used.

=> (def *ping* (spawn examples/ping))

#'clojure.core/*ping*

=> *ping*

"5299491ea4184c02ad8c0fbc100c49f9.8"

=> (register-name "ping" *ping*)

:ok

=> (registered-names)

{"ping" "5299491ea4184c02ad8c0fbc100c49f9.8"}

=> (resolve-name "ping")

"5299491ea4184c02ad8c0fbc100c49f9.8"

=> (send! (resolve-name "ping") [(self) 1234])

:ok

=> (receive)

1234

Error notifications : jobim/link

Two actors can be linked using the link function. From that moment onwards if one of the actors dies because of an exception or the node where it is being executed becomes unavailable due to a network partition, the other actor will receive a special message containing a signal.

=> (self)

"5299491ea4184c02ad8c0fbc100c49f9.1"

=> (def *pid* (spawn examples/ping))

#'clojure.core/*pid*

=> (link *pid*)

{"5299491ea4184c02ad8c0fbc100c49f9.1" ["5299491ea4184c02ad8c0fbc100c49f9.9"], "5299491ea4184c02ad8c0fbc100c49f9.9" ["5299491ea4184c02ad8c0fbc100c49f9.1"]}

=> ; the ping actor will throw an exception if receives a message containing the

"exception" string

=> (send! *pid* "exception")

:ok

=> (receive)

{:signal :link-broken, :from "5299491ea4184c02ad8c0fbc100c49f9.9", :cause "class java.lang.Exception:Ping actor received exception"}

Threadless actors : jobim/spawn-evented jobim/react-loop jobim/react

To avoid the limitation on the number of java threads that can be created in a single java VM, an evented actor can be created not attached to a single thread.

Functions react-loop and react can be used as subtitues of loop and receive in the code of the evented actor.

The evented actor must be started with the spawn-evented function.

This is an example of the evented actor equivalent to the previously shown echo actor.

(defn ping-evented
  ([]
     (let [name "test evented"]
       (react-loop [c 0]
         (react [msg]
           (cond-match
             [#"exit" msg]       false
             [#"exception" msg]  (throw (Exception. "Ping actor received exception"))
             [[?from ?data] msg] (do
                                   (send! from (str "actor " name " says "data " " c))
                                   (recur (inc c))))))))))

Tests

To run the tests follow the next instructions:

  • edit the test-node.clj file inserting the right configuration.

  • edit test/jobim/test/jobim.clj and select the right communication mechanism:

    (defonce messaging-to-test :rabbitmq)

  • Start the relevant services: ZooKeeper and RabbitMQ/ZeroMQ

  • Start the test node that will execute the test-node.clj configuration:

$ java -cp jobim-0.0.4-SNAPSHOT-standalone.jar -Djava.library.path=/usr/local/lib jobim.main test-node.clj

** Jobim node started **

- node-name: remote-test

- messaging-type :rabbitmq

- messaging-args {:host "192.168.1.35"}

- zookeeper-args ["192.168.1.35:2181" {:timeout 3000}

clojure.core=>

  • Run the tests using

$ lein test

License

Copyright (C) 2010 Antonio Garrote

Distributed under the LGPL