0
votes

I have a supervisor / worker tree organized as follows:

game_super -> {keyserver: worker, loginserver: super}
loginserver -> loginlistener: worker (socket pool)

The key server works okay. It just returns the string of random bytes it uses to construct login tokens. It is periodically replaced.

My login server use to work under R16. Then I upgraded to R18 because I wanted to use maps. IDK if that is the issue... I doubt it though. The other change I made is the reason I need maps was for the convenience of setting up children.

I have multiple redis servers, each with their own purpose. One is rate limiting. That's the only one operating right now. I call game_core:redis_init() in game_supervisor:init([]). It returns {ok,{#MapPids, #MapScriptKeys}. Where #MapScriptKeys values are tuples containing all Sha hashes of LUA scripts.

I'm getting exception exit: {shutdown,{failed_to_start_child,login,<0.178.0>}} Pids obviously change but that was the last result after calling game_supervisor:start_inshell().

I've inserted an io:format("~p\n",[State]), just before the {ok, State} in login_listener:init(State) and it prints out everything... once. Considering that's right before returning.... how does it fail?

The code is based on: http://learnyousomeerlang.com/buckets-of-sockets

Is this similar to: Erlang supervisor exception on starting worker , where the same name was reused for children?

Game Supervisor

-module(game_supervisor).
-behavior(supervisor).

-include("constants.hrl").

-export([start/0, start/1, start_inshell/0, init/1]).

start(Args) ->
    spawn(fun() ->
            supervisor:start_link({local, ?MODULE}, ?MODULE, Args)
        end).

start_inshell() ->
    {ok, Pid} = supervisor:start_link({local, ?MODULE}, ?MODULE, []),
    unlink(Pid).

start() -> start([]).

init([]) ->
    {ok, RedisState} = game_core:redis_init(),
    {ok, {
        {one_for_one, 2, 10}, [ %more than 2 restarts w/i 10 seconds all killed
        {key,
            {key_server, start_link, []},
            permanent,
            10000,
            worker,
            [key_server]},
        {login,
            {login_server, start_link, [RedisState]},
            permanent,
            10000,
            supervisor,
            [login_server]}%,
%       {game,
%           {game_server, start_link, [RedisState]},
%           permanent,
%           10000,
%           supervisor,
%           [game_server]}
    ]}}.

Login Server

-module(login_server).
-behavior(supervisor).

-include("constants.hrl").

-export([start_link/0, start_link/1, init/1, start_socket/0]).

start_link(Args) ->
    spawn(fun() ->
            supervisor:start_link({local, ?MODULE}, ?MODULE, Args)
    end).

start_link() -> start_link([]).

init({Pid,Scripts}) ->
    process_flag(trap_exit, true),
    {ok, Socket} = gen_tcp:listen(?PORT_LISTEN_LOGIN, [{active,once}, {reuseaddr, true}]),

    %Construct socket pool
    spawn_link(fun start_pool/0),

    %Put all redis information for login clients here
    RedisState = {maps:get(limiter,Pid),maps:get(limiter,Scripts)},

    %Child specification
    {ok, {
        {simple_one_for_one, 2, 10}, [  %more than 2 restarts w/i 10 seconds all killed
        {listener,
            {login_listener, start_link, [{Socket, RedisState}]},
            temporary,
            1000,
            worker,
            [login_listener]}
    ]}}.

start_pool() ->
    [start_socket() || _ <- lists:seq(1,32)],
    ok.

start_socket() -> supervisor:start_child(?MODULE, []).

Login Handler

-module(login_listener).
-behavior(gen_server).

-include("constants.hrl").

-export([start_link/1]).

%required callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

start_link(State) ->
    gen_server:start_link(?MODULE, State,[]).

init(State) ->
    process_flag(trap_exit, true),
    gen_server:cast(self(), accept),
    {ok, State}.

handle_cast(accept, State) ->                   %Param(s): Msg, State
    {Socket, RedisState} = State,

    {ok, AcceptSocket} = gen_tcp:accept(Socket),
    login_server:start_socket(),
    {noreply, {AcceptSocket, RedisState}}.

handle_info({tcp, Socket, Str}, State) ->           %Param(s): Info
    %%Login
    {_, {PidLimiter,{ShaLimiter}}} = State,
    {ok, {ClientIP, _ }} = inet:peername(Socket),

    {ok, Result} = game_core:rate_limit(PidLimiter, ShaLimiter, ClientIP),

    ok = inet:setopts(Socket, [{active, once}]),
    {noreply, State}.

%%Surpress warnings
handle_call(_, _, State) -> {noreply, State}.       %Param(s): Request, From
terminate(_, _) -> ok.                              %Param(s): Reason, State
code_change(_, State,_) -> {ok, State}.         %Param(s): OldVsn, Extra
1

1 Answers

2
votes

Why are your supervisor start functions calling spawn/1? For game_supervisor, change this:

start(Args) ->
    spawn(fun() ->
            supervisor:start_link({local, ?MODULE}, ?MODULE, Args)
        end).

to this:

start(Args) ->
    supervisor:start_link({local, ?MODULE}, ?MODULE, Args).

And for login_server, change this:

start_link(Args) ->
    spawn(fun() ->
            supervisor:start_link({local, ?MODULE}, ?MODULE, Args)
    end).

to this:

start_link(Args) ->
    supervisor:start_link({local, ?MODULE}, ?MODULE, Args).