Need help with creating an Emoji server in Erlang

I want to make an emoji server in Erlang.

Bellow is the code of my emoji.erl file:

-module(emoji).

-export([start/1, new_shortcode/3, alias/3, delete/2, lookup/2,

analytics/5, get_analytics/2, remove_analytics/3,

stop/1]).

-record(emoji_state, {

shortcodes = dict:new(),

aliases = dict:new(),

analytics = dict:new()

}).

-type shortcode() :: string().

-type emoji() :: binary().

-type analytic_fun(State) :: fun((shortcode(), State) -> State).

-spec start(Initial :: [{shortcode(), emoji()}]) -> {ok, pid()} | {error, Reason :: term()}.

start(Initial) when is_list(Initial) ->

{ok, spawn(fun() -> emoji_loop(#emoji_state{shortcodes = dict:from_list(Initial)}) end)};

start(_) ->

{error, invalid_argument}.

-spec new_shortcode(Pid :: pid(), Short :: shortcode(), Emo :: emoji()) -> ok | {error, Reason :: term()}.

new_shortcode(Pid, Short, Emo) when is_pid(Pid), is_binary(Emo), is_list(Short) ->

Pid ! {new_shortcode, Short, Emo},

receive

{ok, Short} -> ok;

{error, Reason} -> {error, Reason}

after 5000 -> {error, timeout}

end.

-spec alias(Pid :: pid(), Short1 :: shortcode(), Short2 :: shortcode()) -> ok | {error, Reason :: term()}.

alias(Pid, Short1, Short2) when is_pid(Pid), is_list(Short1), is_list(Short2) ->

Pid ! {alias, Short1, Short2},

receive

{ok, Short2} -> ok;

{error, Reason} -> {error, Reason}

after 5000 -> {error, timeout}

end.

-spec delete(Pid :: pid(), Short :: shortcode()) -> ok.

delete(Pid, Short) when is_pid(Pid), is_list(Short) ->

Pid ! {delete, Short},

ok.

-spec lookup(Pid :: pid(), Short :: shortcode()) -> {ok, emoji()} | no_emoji.

lookup(Pid, Short) when is_pid(Pid), is_list(Short) ->

Pid ! {lookup, Short},

receive

{ok, Emo} -> {ok, Emo};

no_emoji -> no_emoji

after 5000 -> {error, timeout}

end.

-spec analytics(Pid :: pid(), Short :: shortcode(), Fun :: analytic_fun(State :: any()), Label :: string(), Init :: any()) -> ok | {error, Reason :: term()}.

analytics(Pid, Short, Fun, Label, Init) when is_pid(Pid), is_list(Short), is_function(Fun, 2) ->

Pid ! {analytics, Short, Fun, Label, Init},

receive

{ok, Label} -> ok;

{error, Reason} -> {error, Reason}

after 5000 -> {error, timeout}

end.

-spec get_analytics(Pid :: pid(), Short :: shortcode()) -> {ok, [{string(), any()}]} | {error, Reason :: term()}.

get_analytics(Pid, Short) when is_pid(Pid), is_list(Short) ->

Pid ! {get_analytics, Short},

receive

{ok, Stats} -> {ok, Stats};

{error, Reason} -> {error, Reason}

after 5000 -> {error, timeout}

end.

-spec remove_analytics(Pid :: pid(), Short :: shortcode(), Label :: string()) -> ok.

remove_analytics(Pid, Short, Label) when is_pid(Pid), is_list(Short), is_list(Label) ->

Pid ! {remove_analytics, Short, Label},

ok.

-spec stop(Pid :: pid()) -> ok | {error, Reason :: term()}.

stop(Pid) when is_pid(Pid) ->

Pid ! stop,

receive

ok -> ok;

{error, Reason} -> {error, Reason}

after 5000 -> {error, timeout}

end.

emoji_loop(State = #emoji_state{}) ->

receive

{new_shortcode, Short, Emo} ->

case dict:find(Short, State#emoji_state.shortcodes) of

{ok, _} -> emoji_loop(State);

error ->

NewShortcodes = dict:store(Short, Emo, State#emoji_state.shortcodes),

emoji_loop(State#emoji_state{shortcodes = NewShortcodes})

end;

{alias, Short1, Short2} ->

case dict:find(Short1, State#emoji_state.shortcodes) of

{ok, _} ->

case dict:find(Short2, State#emoji_state.aliases) of

{ok, _} -> emoji_loop(State);

error ->

NewAliases = dict:store(Short2, Short1, State#emoji_state.aliases),

emoji_loop(State#emoji_state{aliases = NewAliases})

end;

error -> emoji_loop(State)

end;

{delete, Short} ->

NewShortcodes = dict:erase(Short, State#emoji_state.shortcodes),

NewAliases = dict:filter(fun(_, S) -> S /= Short end, State#emoji_state.aliases),

emoji_loop(State#emoji_state{shortcodes = NewShortcodes, aliases = NewAliases});

{lookup, Short} ->

case dict:find(Short, State#emoji_state.shortcodes) of

{ok, Emo} -> emoji_loop(State);

error ->

case dict:find(Short, State#emoji_state.aliases) of

{ok, Alias} -> emoji_loop(State#emoji_state{shortcodes = State#emoji_state.shortcodes, aliases = State#emoji_state.aliases});

error -> emoji_loop(State)

end

end;

{analytics, Short, Fun, Label, Init} ->

case dict:find(Short, State#emoji_state.analytics) of

{ok, _} -> emoji_loop(State);

error ->

NewAnalytics = dict:store(Short, [{Label, Fun, Init}], State#emoji_state.analytics),

emoji_loop(State#emoji_state{analytics = NewAnalytics})

end;

{get_analytics, Short} ->

case dict:find(Short, State#emoji_state.analytics) of

{ok, Analytics} ->

Stats = [{Label, Fun(Short, Init)} || {Label, Fun, Init} <- Analytics],

emoji_loop(State);

error -> emoji_loop(State)

end;

{remove_analytics, Short, Label} ->

case dict:find(Short, State#emoji_state.analytics) of

{ok, Analytics} ->

NewAnalytics = dict:store(Short, [], State#emoji_state.analytics),

%%NewAnalytics = dict:update_counter(Short, [{Label, _, _}] --> [], State#emoji_state.analytics),

emoji_loop(State#emoji_state{analytics = NewAnalytics});

error -> emoji_loop(State)

end;

stop ->

exit(normal)

end.

My server runs fine, but whenever I am trying to test any of the following inputs in the terminal, i am getting timeout errors as attached bellow;


6> emoji:new_shortcode(Pid, "love", <<"❤️">>). {error,timeout}

7> emoji:alias(Pid, "happy", "joy"). {error,timeout}

8> emoji:new_shortcode(Pid, "love", <<"❤️">>). {error,timeout}

9> emoji:lookup(Pid, "happy"). {error,timeout}

10> Fun = fun(_, State) -> State + 1 end, .. emoji:analytics(Pid, "happy", Fun, "counter", 0). {error,timeout}

11> emoji:get_analytics(Pid, "happy"). {error,timeout}

12> emoji:remove_analytics(Pid, "happy", "counter"). ok

13> emoji:stop(Pid). {error,timeout}

Can anyone guide me on how to solve these timeout errors?

You are not replying to the caller, but you can not do this without providing the pid of the caller in the message you’re sending to your loop proc. In other words, you need to say Pid ! {self(), remove_analytics, Short, Label}. Now your loop can grab the caller pid and bang back at with a reply and then carry on with the loop.

While there’s value in putting this together using bare bones in that it’s important to understand how things work , but you’d be better served by a gen_server. Here is the simplest example possible and hopefully illustrates how this could be better put together :

-module(ping_pong).

-behaviour(gen_server).

%% API
-export([start_link/0, ping/1]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2]).

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

ping(Server) ->
    gen_server:call(Server, ping).

init(_Arg) ->
    {ok, {}}.

handle_call(ping, _From, State) ->
    {reply, pong, State}.

handle_cast(_msg, State) ->
    {noreply, State}.
3 Likes