Erlang-Lua:
How to write an Erlang C Node
Robby Raschke
Erlang Solutions Ltd.
@rtraschke
Erlang Solutions Ltd.
([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>
open_port/2
:ei_x_new(ei_x_buff* x)
)struct hostent *gethostbyname(char *hostname)
) and build the C Node nameei_connect_xinit(...)
)ei_connect(...)
){Port, {data, {noeol, S}}}
{Port, {data, {eol, S}}}
{Port, {exit_status, N}}
{'EXIT', Port, Reason}
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};
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};
Use two-part gen_server calls:
noreply
gen_server:reply/2
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}};
int ei_xreceive_msg(int fd, erlang_msg* msg, ei_x_buff* x)
)ERL_TICK
- The Erlang Node sends these regularly to see if the C Node is still aliveERL_ERROR
- Some kind of error happened while waiting for a messageERL_MSG
- A real message arrived, metadata is in the erlang_msg
structure, and the payload is in the ei_x_buff
ERL_EXIT
and ERL_UNLINK
- The Erlang Node is disconnecting, in our case this means shut down the C nodeERL_SEND
- The actual message sent from the gen_server to the C Node, e.g., {exec, Pid, Code}
and {stop, Pid, []}
int ei_send(int fd, erlang_pid* to, char* buf, int len)
fd
in the two calls (ei_xreceive_msg()
and ei_send()
) was returned to us by the ei_connect()
call earlier and is the descriptor of the connection to the Erlang Nodeei_decode_...()
functions to disassemble the expected termsif (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; }
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);
luaL_dostring()
{error, Reason}
reply{lua, ok}
reply is sent back{lua, Result}
replyei_x_encode_...()
functions([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>
([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>
([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>
([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!
kernel
net_ticktime
config settingei_xreceive_msg()
in the C node produces the I/O error
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;
([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>
printf()
for logging when started as a Port programhttps://github.com/rtraschke/erlang-lua
Thank You!
Robby Raschke
Erlang Solutions Ltd.
@rtraschke