Source

odeci / erlang / src / c_server.erl

Full commit
%%%-----------------------------------------------------------------
%%% child server
%%%-----------------------------------------------------------------
-module(c_server).
-behaviour(gen_server).

-export([
         get_timing/0,
         rr_on/0,
         rr_off/0,

         divide/2,
         subtract/2,
         add/2,
         sum/1,
         info/1,
         get/0,
         multiply/2,
         multiply_list/1,
         ping/0
        ]).

-export([start_link/0, stop/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2]).
-export([terminate/2, code_change/3]).

-record(state, {status, port, portcmd, from,
                start,      %% start time of current command
                count=0,    %% number of commands sent
                duration=0  %% now()
               }).

-define(SERVER, ?MODULE).

%%==================================================================
%% API
%%==================================================================
rr_off() ->
    gen_server:call(?SERVER, rr_off).

rr_on() ->
    gen_server:call(?SERVER, rr_on).

get_timing() ->
    gen_server:call(?SERVER, get_timing).

ping() ->
    gen_server:call(?SERVER, ping).

get() ->
    gen_server:call(?SERVER, get).

info(Term) ->
    gen_server:call(?SERVER, {info, Term}).

%-------------------------------------------------------------------
multiply_list(List) ->
    gen_server:call(?SERVER, {multiply_list, List}).

multiply(N1, N2) ->
    gen_server:call(?SERVER, {multiply, N1, N2}).

sum(List) ->
    gen_server:call(?SERVER, {sum, List}).

add(N1, N2) ->
    gen_server:call(?SERVER, {add, N1, N2}).

subtract(N1, N2) ->
    gen_server:call(?SERVER, {subtract, N1, N2}).

divide(N1, N2) ->
    gen_server:call(?SERVER, {divide, N1, N2}).

%-------------------------------------------------------------------
start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

%-------------------------------------------------------------------
stop() ->
    gen_server:call(?SERVER, stop).

%%==================================================================
%% gen_server callbacks
%%==================================================================
init(_) ->
    {ok, PortCommand} = application:get_env(port_command),
    Port = open_port({spawn, PortCommand}, [{packet, 4}, binary, exit_status]),
    port_command(Port, term_to_binary(ping)),
    {ok, #state{status = init, port = Port, portcmd = PortCommand }}.

%-------------------------------------------------------------------
handle_call(rr_on, From, #state{status=running, port=Port} = State) ->
    New = send_to_port(State, Port, term_to_binary({round_trip, true})),
    {noreply, New#state{from=From}};
handle_call(rr_off, From, #state{status=running, port=Port} = State) ->
    New = send_to_port(State, Port, term_to_binary({round_trip, false})),
    {noreply, New#state{from=From}};
handle_call(get_timing, _From, #state{count=Cnt, duration=Dur} = State) ->
    Res = {Cnt, Dur},
    {reply, Res, State};
handle_call(ping, From, #state{status=running, port=Port} = State) ->
    New = send_to_port(State, Port, term_to_binary(ping)),
    {noreply, New#state{from=From}};
handle_call({info, Term}, From, #state{status=running, port=Port} = State) ->
    Cmd = term_to_binary({info, Term}),
    New = send_to_port(State, Port, Cmd),
    {noreply, New#state{from=From}};
handle_call(get, From, #state{status=running, port=Port} = State) ->
    Cmd = term_to_binary({get, none}),
    New = send_to_port(State, Port, Cmd),
    {noreply, New#state{from=From}};
handle_call({multiply_list, List}, From,
            #state{status=running, port=Port} = State) ->
    Blist = bias_list(List),
    Cmd = term_to_binary({mult_list, Blist}),
    New = send_to_port(State, Port, Cmd),
    {noreply, New#state{from=From}};
handle_call({multiply, N1, N2}, From,
            #state{status=running, port=Port} = State) ->
    B1 = bias_number(N1),
    B2 = bias_number(N2),
    Cmd = term_to_binary({multiply, B1, B2}),
    New = send_to_port(State, Port, Cmd),
    {noreply, New#state{from=From}};
handle_call({sum, List}, From, #state{status=running, port=Port} = State) ->
    Blist = bias_list(List),
    Cmd = term_to_binary({sum_list, Blist}),
    New = send_to_port(State, Port, Cmd),
    {noreply, New#state{from=From}};
handle_call({add, N1, N2}, From,
            #state{status=running, port=Port} = State) ->
    B1 = bias_number(N1),
    B2 = bias_number(N2),
    Cmd = term_to_binary({add, B1, B2}),
    New = send_to_port(State, Port, Cmd),
    {noreply, New#state{from=From}};
handle_call({subtract, N1, N2}, From,
            #state{status=running, port=Port} = State) ->
    B1 = bias_number(N1),
    B2 = bias_number(N2),
    Cmd = term_to_binary({subtract, B1, B2}),
    New = send_to_port(State, Port, Cmd),
    {noreply, New#state{from=From}};
handle_call({divide, N1, N2}, From,
            #state{status=running, port=Port} = State) ->
    B1 = bias_number(N1),
    B2 = bias_number(N2),
    Cmd = term_to_binary({divide, B1, B2}),
    New = send_to_port(State, Port, Cmd),
    {noreply, New#state{from=From}};
handle_call(stop, _From, St) ->
    {stop, normal, ok, St};
handle_call(status, _From, St) ->
    {reply, St, St};
handle_call(_N, _From, St) ->
    error_logger:info_report({handle_call, unknown, _N}),
    {reply, {error, unknown_request}, St}.

%-------------------------------------------------------------------
handle_cast(stop, St) ->
    {stop, normal, St};
handle_cast(_I, St) ->
    error_logger:info_report({handle_cast, unknown, _I}),
    {noreply, St}.

%-------------------------------------------------------------------
terminate(_, #state{port=Port}) ->
    catch port_close(Port),
    ok.

%-------------------------------------------------------------------
handle_info({Port, {data, <<131,100,0,4,"pong">>}},
            #state{status=init, port = Port} = State) ->
    {noreply, State#state{status=running}};
handle_info({Port, {data, <<131,100,0,4,"pong">>}},
            #state{from=From, port = Port} = State) ->
    New = add_timing(State),
    gen_server:reply(From, pong),
    {noreply, New#state{from=undefined}};
handle_info({Port, {data, Data}}, #state{from=From, port=Port} = State) ->
    New = add_timing(State),
    Term = binary_to_term(Data),
    gen_server:reply(From, Term),
    {noreply, New#state{from=undefined}};
handle_info(_I, State) ->
    error_logger:info_report({handle_info, unknown, _I}),
    {noreply, State}.

%-------------------------------------------------------------------
code_change(_Old_vsn, State, _Extra) ->
    {ok, State}.

%-------------------------------------------------------------------
bias_number({Sign, Mant, Exp}) when Mant >= (2 bsl 29) andalso Mant < (2 bsl 30) ->
    %% this is necessary as ocaml Int32.to_int on 32-bit platform
    %% behaves as Int31, e.g. truncates highest bit
    {Sign, Mant * 10000000000, Exp - 10};
bias_number(N) ->
    N.

%-------------------------------------------------------------------
bias_list(L) ->
    [bias_number(X) || X <- L].

%-------------------------------------------------------------------
send_to_port(State, Port, Term) ->
    port_command(Port, Term),
    State#state{start=now()}.

%-------------------------------------------------------------------
add_timing(#state{start=T1, count=Cnt, duration=Dur} = State) ->
    T2 = now(),
    D = timer:now_diff(T2, T1),
    State#state{start=undefined, count=Cnt+1, duration=Dur+D}.

%-------------------------------------------------------------------