Sunday, October 14, 2007

Erlang Hot Code Swapping

In this post, i'm going to repeat Joe Armstrong's technique of hot code swapping using Erlang. It's a extremely powerful technique which basically allows you to swap/interchange code without restarting the server(s).Joe Armstrong, in his book, demonstrated the technique of hot swapping and i would like to repeat that with a J2EE twist to it. To my knowledge, most J2EE container cannot perform this smoothly yet; however, do let me know if my information is not correct.

What i have is a simple Stateless Session EJB (Enterprise Java Bean). The J2EE container model uses a callback invocation + client-server architecture so that the container can run both client and lifecycle functions.

Filename: "Converter.java" --- Actual EJB remote class
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
import java.math.*;

public interface Converter extends EJBObject {
public BigDecimal dollarToYen(BigDecimal dollars) throws RemoteException;
public BigDecimal yenToEuro(BigDecimal yen) throws RemoteException;
}

Filename: "ConverterHome.java" --- Actual EJB Home class
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;

public interface ConverterHome extends EJBHome {
Converter create() throws RemoteException, CreateException;
}

Filename: "ConverterBean.java" --- Actual EJB class

import java.rmi.RemoteException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import java.math.*;

public class ConverterBean implements SessionBean {

BigDecimal yenRate = new BigDecimal("121.6000");
BigDecimal euroRate = new BigDecimal("0.0077");

public BigDecimal dollarToYen(BigDecimal dollars) {
BigDecimal result = dollars.multiply(yenRate);
return result.setScale(2,BigDecimal.ROUND_UP);
}

public BigDecimal yenToEuro(BigDecimal yen) {
BigDecimal result = yen.multiply(euroRate);
return result.setScale(2,BigDecimal.ROUND_UP);
}

public ConverterBean() {}
public void ejbCreate() {}
public void ejbRemove() {}
public void ejbActivate() {}
public void ejbPassivate() {}
public void setSessionContext(SessionContext sc) {}
}
So, how do you accomplished the above to the Erlang equivalent? The toughest part is building a framework that will invoke the lifecycle methods like those ejbXXX as well as conformation to the specifications laid out by the J2EE Community and i will skip that part, for now and hence, i will concentrate on the client-server part as well as hot code swapping. Below is my attempt at the above mentioned J2EE implementation.

Filename: "callback.erl"
-module(callback).
-export([init/0, ejbActivate/0, ejbPassivate/0, ejbRemove/0, dollarToYen/1, yenToEuro/1, handle/2]).
-import(container, [rpc/2]).

%% client routines
dollarToYen(Dollars) -> rpc(ejbserver, {convertToYen, Dollars}).
yenToEuro(Yen) -> rpc(ejbserver, {convertToEuro, Yen}).
ejbActivate() -> rpc(ejbserver, {ejbactivate}).
ejbPassivate() -> rpc(ejbserver, {ejbpassivate}).
ejbRemove() -> rpc(ejbserver, {ejbremove}).

%% callback routines
init() -> dict:new().

handle({convertToYen, Dollars}, Dict) -> { Dollars * 126, Dict};
handle({ejbactivate}, Dict) -> { ejbActivate , Dict};
handle({ejbpassivate}, Dict) -> { ejbPassivate , Dict};
handle({ejbremove}, Dict) -> { ejbRemove , Dict};
handle({convertToEuro, Yen}, Dict) -> {Yen * 0.0077, Dict}.

Filename: "container.erl"
-module(container).
-export([start/2, rpc/2, swap_code/2]).

start(Name, Mod) ->
register(Name, spawn(fun() -> loop(Name, Mod, Mod:init()) end)).

swap_code(Name,Mod) -> rpc(Name, {swap_code, Mod}).

%
% Standard code for abstracting the "RPC-call" layer
%
rpc(Name, Request) ->
Name ! {self(), Request},
receive
{Name, Response} -> Response
end.

%
% Standard code for looping and waiting for messages from clients
%
loop(Name, Mod, OldState) ->
receive
{From, {swap_code, NewCallbackMod}} ->
From ! {Name, ack},
loop(Name, NewCallbackMod, OldState);
{From, Request} ->
{Response, NewState} = Mod:handle(Request,OldState),
From ! {Name, Response},
loop(Name, Mod, NewState)
end.

So how do you run it ? Refer to the figure below
I shall briefly explain what i did,
1/ compile the "server"
2/ compile the "callback" a.k.a. EJB
3/ start up the "server"
4/ invoke the callback / EJB to run the interface functions




So, the next thing is how do i do hot code swapping without bringing down the server? What you need to do first is to decide what your replacement code is going to be and swap it with the code that was just running. E.g. let's assume that i have decided to replace the callback.erl with another code callback2.erl and i want to replace the former with the latter.

Filename: "callback2.erl"
-module(callback2).
-export([init/0, ejbActivate/0, ejbPassivate/0, ejbRemove/0, dollarToYenSpecial/1, yenToEuroSpecial/1, handle/2]).
-import(container, [rpc/2]).

%% client routines
dollarToYenSpecial(Dollars) -> rpc(ejbserver, {convertToYen, Dollars}).
yenToEuroSpecial(Yen) -> rpc(ejbserver, {convertToEuro, Yen}).
ejbActivate() -> rpc(ejbserver, {ejbactivate}).
ejbPassivate() -> rpc(ejbserver, {ejbpassivate}).
ejbRemove() -> rpc(ejbserver, {ejbremove}).

%% callback routines
init() -> dict:new().

handle({convertToYen, Dollars}, Dict) -> { Dollars * 126 * 126, Dict};
handle({ejbactivate}, Dict) -> { ejbActivate , Dict};
handle({ejbpassivate}, Dict) -> { ejbPassivate , Dict};
handle({ejbremove}, Dict) -> { ejbRemove , Dict};
handle({convertToEuro, Yen}, Dict) -> {Yen * 0.0077 * 0.0077, Dict}.
The changes are highlighted in bold for easy viewing. After, you need to compile it and swap it with the running code. Note that the Mod:init() does not run again. Refer to the figure below for a sample hot code swapping.









I hope this demonstrates how powerful Erlang can be and possibly a replacement language for Java and the like?

1 comment:

Anonymous said...

Good for people to know.