To Alice
TUTORIAL
ERLANG AND JAVA: EXAMPLE OF IMPLEMENTATION OF DISTRIBUTED APPLICATION
INTRODUCTION
A Java developers interest to functional languages like Scala and Erlang is arising in recent time. And if Scala is tightly linked to Java and JVM and developers have no problem to try and learn Scala in a comfortable environment, then Erlang is much different from Java and requires more effort to make someone familiar with it. The tutorial's goal is to show to a developer a very simple example of concurrent application and how Erlang code can talk with Java application.
SHORT EXPLANATION OF DPP
It is very convenient to use a Dining Philosopher Problem (DPP) as a model task. An implementation of the DPP by using new language or technology allows to check and test is we really understand and apply everything in right way. The solution of the DPP is known a long time ago (see wikipedia article) so we can easy to build this model task. Let's remember DPP briefly. We have a Table, N Philosophers sitting around of the Table, N forks on the Table between each Philosopher and infinite amount spaghetti on the table top. A Philosopher has a three life-cycle states: thinking, hungry and eating of a spaghetti. From thinking state to hungry state (and from eating to thinking) Philosopher comes by himself (in random period of time) but changing state from hungry to eating requires a condition: the both forks on a sides of the Philosopher have to be free (in other words both neighbors of the given Philosopher must do not eat). Below you can find description of implementation of DPP in Erlang and watch a visual representation of the task running on a server.
PHILOSOPER
Each philosopher is represented with a process. A process is implemented as function loop/2 and is created by code:
create(0) -> []; create(M) -> Pid = spawn(fun() -> loop(thinking, M) end), io:format("Create philosopher ~p with pid:~p~n", [M, Pid]), Pid ! {doThinking}, [{M,thinking,Pid}|create(M-1)]
A function create/1 creates M philosopher's processes. The function returns a list of a tuples that represents a base information about philosopher process: index, state and Pid of the given process. The list will be used by Table process to control of the philosophers. Function loop/3 for given process is started with waiting for message receive. Philosopher can receive a two external messages: 'eat' and 'stop' and two internal messages 'doEating' and 'doThinking' (they will be explained below). Processing of the messages depend on current state of philosopher. Philosopher has three states: 'eating', 'thinking', 'hungry'. His life cycle starts with 'thinking' state. Let see how philosopher is processing incoming messages:
loop(State, N) -> receive %% (1) {eat} -> case (State) of hungry -> self() ! {doEating}, loop(eating, N); _ -> loop(State, N) end; %% (2) {doEating} -> case (State) of eating -> timer:sleep(1700 * random:uniform(7) + 300), try table ! {thinking, N} catch error:_ -> loop(thinking, N) end, self() ! {doThinking}, loop(thinking, N); _ -> loop(State, N) end; %% (3) {doThinking} -> case (State) of thinking -> timer:sleep(2300 * random:uniform(7) + 250), try table ! {hungry, N} catch error:_ -> loop(hungry, N) end, loop(hungry, N); _ -> loop(State, N) end; %% (4) {stop} -> io:format("Philosopher ~p is stopped ~n", [N]) end .
Philosopher's process is waiting for a messages. When 'eat' message has came (see comment %% (1) in code above) message processing is starting and it depends on philosopher state. If the state is 'hungry' the process sends 'doEating' message to itself and changes own state to 'eating'. Process ignores the 'eat' message in case of any other state. When the process receives 'doEating' message (see comment %% (2)) and state is 'eating' then it falls to sleep for a random amount of milliseconds. After awake the process sends 'thinking' message to the Table process, sends 'doThinking' message itself and changes own state to 'thinking'. When the process receives 'doThinking' message (see comment %% (3)) and state is 'thinking' then it falls to sleep for a random amount of milliseconds. After awake the process sends 'hungry' message to the Table process and changes own state to 'hungry'. Receiving 'stop' message breaks the infinite execution of loop/3 and leads to killing of the process.
TABLE
Table process is created by start/1 function with argument is as number of philosopher at the table. First the function creates PN philosophers and then the process registers itself as 'table'. List of a philosopher's configuration tuples is passed to loop/1 function that establishes a Table's process.
start(PN) -> PhList = philosopher:create(PN), Pid = spawn(fun() -> loop(PhList) end), register(table, Pid), io:format("Start diner ~p.~n", [Pid]) . stop() -> table ! {stop}.
The stop/0 function is used for killing of Table and all Philosopher processes. Now let introduce some helper function to easy understand further explanation.
getPid(I, List) -> [{_,_,Pid}] = lists:filter(fun({J,_,_}) -> J =:= I end, List), Pid .
This function takes two arguments: I as index of philosopher and List as list of philosopher configuration tuples. Remind that the tuple has structure as {Index, State, Pid}. So a first statement of the function is just filtering the List and return a tuple where Index equals I and applies it to pattern match. Next the function return extracted from the tuple Pid of I-th philosopher.
getStatus(I, List) -> [{_,Status,_}] = lists:filter(fun({J,_,_}) -> J =:= I end, List), Status .
A function getStatus/2 is very similar previous one. The only difference is that the function returns Status value.
setStatus(S, I, List) -> F = fun({J, _, _P}) when J =:= I -> {J, S, _P}; (_T) -> _T end, lists:map(F, List) .
A setStatus/3 function changes status of I-th philosopher to new value S and return new List with changed status.
isNeighboursEating(N, List) -> PN = length(List), {I1,I2} = case (N) of 1 -> {PN, 2}; PN -> {PN-1, 1}; M -> {M-1, M+1} end, (getStatus(I1, List) =:= eating) or (getStatus(I2, List) =:= eating) .
The function isNeighboursEating/2 checks: is any neighbors of the given N-th philosopher is eating? I1 and I2 are indexes of philosopher's neighbors. The function returns boolean value true if one or both neighbors are eating.
canPhilosopherEat(N, List) -> case ((not isNeighboursEating(N, List)) and (getStatus(N, List) =:= hungry)) of true -> getPid(N, List) ! {eat}, setStatus(eating, N, List); false -> List end .
The function determines: can given N-th philosopher starting to eat? If both neighbors are not eating and the philosopher itself is hungry then Table changes his status to 'eating' in control List and sends message {eat} to corresponded Philosopher process. The function returns changed List. If above conditions are false then the function returns original List. Now after we make ourself familiar with helper functions we can take a look at main function of the Table process loop/1:
loop(PhilosopherList) -> receive {hungry, N} -> PL0 = setStatus(hungry, N, PhilosopherList), PL = canPhilosopherEat(N, PL0), printStatus(N, "H", PL), loop(PL); {thinking, N} -> PN = length(PhilosopherList), PL0 = setStatus(thinking, N, PhilosopherList), NL = case (N) of 1 -> PN; _ -> N-1 end, PL1 = canPhilosopherEat(NL, PL0), NR = case (N) of PN -> 1; _ -> N+1 end, PL = canPhilosopherEat(NR, PL1), printStatus(N, "T", PL), loop(PL); {status, Pid} -> Pid ! {status, length(PhilosopherList), lists:map(fun({I,S,_}) -> {I,S} end, PhilosopherList)}, loop(PhilosopherList); {stop} -> lists:foreach(fun({_,_,Pid}) -> Pid ! {stop} end, PhilosopherList); _ -> io:format("unknown request~n", []), loop(PhilosopherList) end .
It is not surprising that the function is starting with waiting for a messages from philosopher's processes. Table can receive only two messages from a philosopher: {hungry, N} and {thinking, N} - where N is an index of a philosopher.
{hungry, N} -
After the message is received a status of N-th philosopher is changed to hungry and right now the function checks: can the given philosopher starts to eat? If yes then go ahead. We do not need to check next philosophers because they can not change them states anyway - both forks are busy.{thinking, N} -
After the message is received a status of N-th philosopher is changed to thinking. Now the philosopher releases both forks and his neighbors from left and right sides have a chance to start eating. So we have to check both neighbors: can they start to eat?{status, Pid} -
The message comes from external client to ask about a current status of philosopher's community. The client gets a message {status, PN, [{1, S1},{2, S2},..{PN, Sn}]}, where PN is philosopher's number; Si is status of I-th philosopher.{stop} -
The message activates process of stopping of philosopher processes.Now we can run this application. Steps to do this in Linux enviroment:
- Open Erlang shell:
$erl - Change directory:
1>cd("path-to-your-source-dir"). - Compile table and philosopher modules:
2>c(philosopher).
3>c(table). - Run application:
4>table:start(5).
JAVA ADAPTER TO ERLANG using JINTERFACE
Jinterface is a small library (in jar form) from OTP. The library allows to create connection between JVM and Erlang node. Let use this for our application.
An idea of Java-Erlang adapter is very simple. The ErlangJavaAdapter extends OtpNode class that simulates Erlang node in Java. Constructor of the adapter creates a mail box for this node and prepare request to remote Erlang node.
public class ErlangJavaAdapter extends OtpNode { private OtpMbox mBox; private OtpErlangTuple request; private String erlangNode; private String erlangHost; private String erlangProcess; /** */ public ErlangJavaAdapter(final String javaHost) throws IOException { super("javaNode@" + javaHost); mBox = createMbox(); request = new OtpErlangTuple( new OtpErlangObject [] { new OtpErlangAtom("status"), mBox.self() } ); } /** */ public String [] getStates() { String [] states = new String [0]; mBox.send(erlangProcess, erlangNode + "@" + erlangHost, request); OtpErlangTuple response; try { response = (OtpErlangTuple) mBox.receive(1000); if (response == null) { throw new OtpErlangException() { public String getMessage() { return "Timeout..."; } }; } int n = ((OtpErlangLong) (response.elementAt(1))).intValue(); OtpErlangList list = (OtpErlangList) (response.elementAt(2)); states = new String [n]; for (OtpErlangObject obj : list.elements()) { OtpErlangTuple tuple = (OtpErlangTuple) obj; int idx = ((OtpErlangLong) (tuple.elementAt(0))).intValue() - 1; states[idx] = ((OtpErlangAtom) (tuple.elementAt(1))).atomValue(); } } catch (OtpErlangException e) { } return states; } }
To use the adapter we need to invoke a getStates() method. First Java node sends to Erlang Node message {status}. And after that Java node goes to wait response from Erlang node. If it does not receive a response during 1 sec the adapter throws an exception otherwise Java node starts parsing of the response message {n, [S1,..Sn]}. The method returns String array of philosopher states. Now we can easily display the picture of table with philosophers by using rich opportunities of Java GUI.
I have to note that we can create Java-Erlang connection without Jinterface library just by using plain socket connection. I will write about this latter.
SHORT EXPLANATION OF STRUCTURE OF EMBEDDED WEB PAGE
Below you can see animated web page presented the DPP task running on Erlang node. The schema of the application is simple. The web page contains AJAX element that generates HTTP request in each second and sends it to a web service implemented as an ErlangJavaAdapter. The service connects to Erlang node and returns current state of DPP. Then web server generates HTTP response formatted as JSON object and returns it to web page's AJAX element. Small Javascript module completes the task - sets a corresponded color to all philosophers and forks.
ANIMATED REPRESENTATION OF DPP.
Stopped. |
|
|
DOWNLOAD
Download zip archive with source files: philosopher.erl, table.erl and ErlangJavaAdapter.java.
06.29.2010