Erlang-Lua:
How to write an Erlang C Node



Robby Raschke

Erlang Solutions Ltd.


@rtraschke

Problem: I want to integrate my Erlang program with that other existing technology over there.

Several ways of getting Erlang to interact with foreign systems:


Port programs


Natively Implemented Functions


C/Java Nodes


My example: Allow calling out to Lua from Erlang

https://github.com/rtraschke/erlang-lua

Stick to something manageable


What I want


([email protected])1> erlang_lua:start_link(foo).

{ok,<0.47.0>}

([email protected])2> erlang_lua:lua(foo, <<"return {x=1, y='foo'}">>).

{lua,[[{y,<<"foo">>},{x,1}]]}

([email protected])3> erlang_lua:lua(foo, <<" x = 42 + 'fourty two' ">>).

{error,"[string \" x = 42 + 'fourty two' \"]:1: attempt to perform
 arithmetic on a string value"}

([email protected])4> erlang_lua:stop(foo).

ok

([email protected])5>

Starting up (Erlang side)


Starting up (C side)


Monitoring (Erlang side)


Executing some Lua code



handle_call({exec, Code}, From, #state{mbox=Mbox} = State) ->
    Mbox ! {exec, self(), Code},
    Result = receive
        {error, _Reason} = Error ->
            Error;
        {lua, _Result} = Reply
            Reply
    end,
    {reply, Result, State};

Executing some Lua code (cont.)


Not quite, remember that we are in a gen_server, and port messages could arrive while we are waiting for our result. For example, an exit of the C Node, due to an error.




handle_call({exec, Code}, From, #state{mbox=Mbox} = State) ->
    Mbox ! {exec, self(), Code},
    Result = receive
        {error, _Reason} = Error ->
            Error;
        {lua, _Result} = Reply
            Reply
    end,
    {reply, Result, State};

Executing some Lua code (cont.)


Use two-part gen_server calls:

handle_call({exec, Code}, From,
        #state{mbox=Mbox, from=undefined} = State) ->
    Mbox ! {exec, self(), Code},
    {noreply, State#state{from=From}};

handle_info({error, _Reason} = Error, #state{from=From} = State)
        when From =/= undefined ->
    gen_server:reply(From, Error),
    {noreply, State#state{from=undefined}};
handle_info({lua, _Result} = Reply, #state{from=From} = State)
        when From =/= undefined ->
    gen_server:reply(From, Reply),
    {noreply, State#state{from=undefined}};

Message Loop (C side)


Message Loop (C side) (cont.)


Handling the Payload (C side)

if (ei_decode_version(x_in->buff, &x_in->index, &version) < 0) {
    print("WARNING: Ignoring malformed message (bad version: %d).", version); return -1;
}
if (ei_decode_tuple_header(x_in->buff, &x_in->index, &arity) < 0) {
    print("WARNING: Ignoring malformed message (not tuple)."); return -1;
}
if (arity != 3) {
    print("WARNING: Ignoring malformed message (not 3-arity tuple)."); return -1;
}
if (ei_decode_atom(x_in->buff, &x_in->index, lua_atom) < 0) {
    print("WARNING: Ignoring malformed message (first tuple element not atom)."); return -1;
}
if (ei_decode_pid(x_in->buff, &x_in->index, pid) < 0) {
    print("WARNING: Ignoring malformed message (second tuple element not pid)."); return -1;
}

Handling the Payload (C side) (cont.)


if (strcmp(lua_atom, "stop") == 0) {
    print("DEBUG: Lua Erlang Node stopping normally.");
    x_out->index = 0;
    return 0;
}
if (strcmp(lua_atom, "exec") == 0) {
    ei_get_type(x_in->buff, &x_in->index, &type, &len);
    code = (char *) calloc(len+1, sizeof(char));
    if (ei_decode_binary(x_in->buff, &x_in->index, code, NULL) < 0) {
        free(code);
        set_error_msg(x_out, "Third tuple element is not a binary.");
        return 1; /* send the error message back to our gen_server */
    }
}
x_out->index = 0;
ei_x_encode_version(x_out);
ei_x_encode_tuple_header(x_out, 2);
ei_x_encode_atom(x_out, "lua");
execute_code(EI_LUA_STATE.L, x_out, code);

Handling the Payload (C side) (cont.)


So, How Does This Look (Starting Up)


([email protected])1> erlang_lua:start_link(foo).                      
=INFO REPORT==== 26-Nov-2014::10:50:04 ===
2014-11-26 10:50:04.621 UTC INFO ELua 'foo' starting using command:
lua5.1/bin/lua_enode 'foo' '127.0.0.1' '[email protected]'
    'BXYEUYSCJRYFVTWCEXAO' '0'

=INFO REPORT==== 26-Nov-2014::10:50:04 ===
2014-11-26 10:50:04.627 UTC DEBUG ELua 'foo' startup message:
Lua Erlang Node '[email protected]' starting.

=INFO REPORT==== 26-Nov-2014::10:50:04 ===
2014-11-26 10:50:04.628 UTC DEBUG ELua 'foo' startup message:
Lua Erlang Node started.

=INFO REPORT==== 26-Nov-2014::10:50:04 ===
2014-11-26 10:50:04.628 UTC INFO ELua 'foo' is ready to accept Lua code.

{ok,<0.47.0>}
([email protected])2> 

So, How Does This Look (Run Something)


([email protected])2> erlang_lua:lua(foo, <<"return {x=1, y='foo'}">>).
=INFO REPORT==== 26-Nov-2014::10:50:11 ===
2014-11-26 10:50:11.730 UTC DEBUG ELua 'foo' executing:
return {x=1, y='foo'}

{lua,[[{y,<<"foo">>},{x,1}]]}
([email protected])3>

So, How Does This Look (Stop It)


([email protected])3> erlang_lua:stop(foo).
=INFO REPORT==== 26-Nov-2014::11:08:11 ===
2014-11-26 11:08:11.179 UTC DEBUG ELua 'foo' is being asked to stop.

=INFO REPORT==== 26-Nov-2014::11:08:11 ===
2014-11-26 11:08:11.179 UTC INFO ELua 'foo' terminating: normal

=INFO REPORT==== 26-Nov-2014::11:08:11 ===
2014-11-26 11:08:11.180 UTC DEBUG ELua 'foo':
Lua Erlang Node stopping normally.

=INFO REPORT==== 26-Nov-2014::11:08:11 ===
2014-11-26 11:08:11.180 UTC INFO ELua 'foo':
Lua Erlang Node stopped.

=INFO REPORT==== 26-Nov-2014::11:08:11 ===
2014-11-26 11:08:11.180 UTC INFO ELua 'foo' stopped normally.

ok
([email protected])4>

So, How Does This Look (Oops)

([email protected])2> erlang_lua:lua(foo,
        <<"os.execute('sleep 300') return {x=1, y='foo'}">>).

=INFO REPORT==== 26-Nov-2014::11:12:43 ===
2014-11-26 11:12:43.472 UTC DEBUG ELua 'foo' executing:
os.execute('sleep 300') return {x=1, y='foo'}

=ERROR REPORT==== 26-Nov-2014::11:13:32 ===
** Node '[email protected]' not responding **
** Removing (timedout) connection **

=INFO REPORT==== 26-Nov-2014::11:17:43 ===
2014-11-26 11:17:43.448 UTC DEBUG ELua 'foo':
Lua Erlang Node error in receive: 5 (Input/output error)

=INFO REPORT==== 26-Nov-2014::11:17:43 ===
2014-11-26 11:17:43.448 UTC INFO ELua 'foo':
Lua Erlang Node '[email protected]' reconnecting.

=INFO REPORT==== 26-Nov-2014::11:17:43 ===
2014-11-26 11:17:43.449 UTC INFO ELua 'foo':
Lua Erlang Node reconnected.

NOTE: The call is now stuck without a return!

Oops, What Happened There?

Oops, What Can Be Done?

Oops, What Can Be Done?

Call to erlang:is_alive() before sending the reply in the C main_message_loop():


case ERL_SEND:
    x_in->index = 0;
    running = handle_msg(&pid);
    if (running == -1) {
        running = 1; /* Ignore messages without a return pid! */
    } else {
        x_rpc_in->index = x_rpc_out->index = 0;
        ei_x_encode_empty_list(x_rpc_in); /*empty param list for erlang:is_alive()*/
        if (ei_rpc(&EI_LUA_STATE.ec, EI_LUA_STATE.fd, "erlang", "is_alive",
                x_rpc_in->buff, x_rpc_in->index, x_rpc_out) < 0) {
            reconnect();
        }
        if (x_out->index > 0 && ei_send(EI_LUA_STATE.fd, &pid,
                x_out->buff, x_out->index) < 0) {
            print("FATAL: Lua Erlang Node error in send to '%s'.", pid.node);
            exit(8);
        }
    }
    break;

Oops Resolved

([email protected])2> erlang_lua:lua(foo,
        <<"os.execute('sleep 300') return {x=1, y='foo'}">>).
=INFO REPORT==== 26-Nov-2014::10:52:45 ===
2014-11-26 10:52:45.791 UTC DEBUG ELua 'foo' executing:
os.execute('sleep 300') return {x=1, y='foo'}

=ERROR REPORT==== 26-Nov-2014::10:53:32 ===
** Node '[email protected]' not responding **
** Removing (timedout) connection **

=INFO REPORT==== 26-Nov-2014::10:57:45 ===
2014-11-26 10:57:45.769 UTC DEBUG ELua 'foo':
Lua Erlang Node error in 'is alive?' rpc to '[email protected]'.

=INFO REPORT==== 26-Nov-2014::10:57:45 ===
2014-11-26 10:57:45.769 UTC INFO ELua 'foo':
Lua Erlang Node '[email protected]' reconnecting.

=INFO REPORT==== 26-Nov-2014::10:57:45 ===
2014-11-26 10:57:45.770 UTC INFO ELua 'foo':
Lua Erlang Node reconnected.

{lua,[[{y,<<"foo">>},{x,1}]]}
([email protected])3>

Summing Up

C / Java / Python / ... Nodes


I would use a NIF


Available on Github

https://github.com/rtraschke/erlang-lua

Future Plans

Thank You!

Robby Raschke
Erlang Solutions Ltd.
@rtraschke