Last updated: May 26, 2013
by Sriram Srinivasan
Please send comments, encouragement, money, cakes, code fixes to kilim@malhar.net. Thank you.
A Java framework for fast, safe, cheap message passing.
The message passing (MP) paradigm is often seen as a superior alternative to the typical mix of idioms in concurrent (shared-memory, locks) and distributed programming (CORBA/RMI). MP eliminates worries endemic to the shared-memory mindset: lock ordering, failure-coupling, low-level data races and memory models. It simplifies synchronization between data and control planes (no lost signals or updates), and unifies APIs for local and remote process interaction.
Curiously however, there are no efficient _and_ safe frameworks for intra-process message-passing, except for Ada and Erlang. The Kilim framework is intended to fix this state of affairs. It provides:
Kilim is portable; one of our explicit goals was to not require changes to the Java language syntax or to the JVM.
Kilim scales comfortably to handle hundreds of thousands of actors and messages on modest hardware. It is fast as well -- task-switching is 1000x faster than Java threads and 60x faster than other lightweight tasking frameworks, and message-passing is 3x faster than Erlang (currently the gold standard for concurrency-oriented programming).
The term "Kilim" refers to a class of rugs in a number of middle-eastern and central european countries. Kilims are light and are flat-woven with thin threads (as opposed to deep-pile carpets).
The first few pages of the ECOOP'08 paper give an overview of the facilities and the programming model.
Take a look at the examples to get an idea. The only important classes to begin with are kilim.Task and kilim.Mailbox.
Let's start with conventional thread programming. This is how you create your own thread in Java.
class MyThread extends Thread { public void run() { } }
And spawn a thread thus:
new MyThread().start();
Now, if you replace the words "Thread" with "Task", "run" with
"execute" and add an exception called Pausable
to execute()
, you have a
Kilim Task. Simple.
import kilim.*; public class MyTask extends Task { public void execute() throws Pausable{ } }and to start it, you say
new MyTask().start()
The "Pausable" exception is used as an annotation, and is never actually thrown (it replaces @pausable from earlier versions). A method with such an annotation is deemed "pausable". Pausing refers to voluntarily yielding control to the scheduler so that another task may run. For example, Task.sleep() is a pausable method that pauses a task for a give time. Another example is Mailbox.get(), which pauses a task until a mailbox is non-empty.
A pausbale method is similar to blocking calls such as InputStream.read() and Thread.wait(), except that it makes its intention clear in its signature with the Pausable annotation. This annotation is used by the Kilim weaver to rewrite the bytecode of all pausable methods to incorporate the voluntary yielding logic. One important property of this scheme is that a pausable method can only be called by another pausable method.
Mailboxes are the medium of communication between tasks. They are typed buffers that can have at most one consumer task waiting on it at any point in time, but multiple producers are allowed. Mailboxes are not owned by anyone (unlike Erlang's process mailbox). They can be static fields, instance variables, local variables .. it is your design choice. They can even be sent as part of messages themselves, which can make for an extremely dynamic and mobile topology.
Creating a mailbox:
// This mailbox only allows Strings Mailbox <String> mb = new Mailbox<String>();
Sending and receiving messages:
mb.put("hello"); String s = mb.get();
These two methods pause the task when they are unable to perform the operation.
mailbox.getnb()
and putnb()
are variants that neither pause the task, nor block the thread. They return immediately indicating whether they were able to dequeue (respectively enqueue) an object from the mailbox.
mailbox.getb()
and putb are the *thread*-blocking versions. Use them if you want to wait for a message but cannot make the calling method
pausable (like main()
, for example). You shouldn't be using these
inside a task.
First, the lightweight thread part.
This package comes with a bytecode transformation tool called Weaver (package: kilim.tools.Weaver) that post-processes .class files looking for the "throws Pausable" annotation.
When a task needs to pause, it unwinds its stack, squirrels away all state that it'll need later on resumption. This unwinding and rewinding the stack is automatically performed by the code introduced by the Weaver. (Debug information is adjusted so that the transformed code can be debugged inside eclipse) This is identical to what a programmer would have written
The transformation is a variation of continuation passing style; the details are in the paper entitled "A Thread of Ones Own" presented at the Workshop on New Horizons in Compilers (2006). A simple introduction to CPS is in the accompanying IFAQ.txt.
Build the sources by running ant (or build.sh) at the topmost level.
To manually compile a kilim task (say kilim.examples.SimpleTask),
Now, compile (into "./classes")
javac -d ./classes ./examples/kilim/examples/SimpleTask.java
Weave the class (and overwrite it).
java kilim.tools.Weaver -d ./classes kilim.examples.SimpleTask
Run it like any regular java program.
java -cp ./classes kilim.examples.SimpleTask
Create a million tasks just for the heck of it.
Try java kilim.bench.LotsOfTasks -ntasks 300000
You can supply a different directory for the weaver's output, but remember to include that directory in the classpath before the original, otherwise you will see "class not woven" errors at run time.
NOTE: It is safer (and convenient) to supply the entire directory to weave, like this:
java kilim.tools.Weave -d ./classes ./classes ^^^^^^^^^^That eliminates the chances of mistakenly omitting to weave inner and anonymous classes.
Loading kilim under eclipse is like any other java project.
Select File -> New -> Java Project
"Create project from existing source"
.There is no plugin (yet) to run the weaver automatically; you will have to run the weaver separately in the command line. If you are changing the kilim examples, you can just run "build.sh" or "ant" from the command line once the project's been created.
Debugging and profiling should work as usual.
Check the examples and the bench directory for ideas on how to use multiple schedulers, nio, http, generators and so on.