billcyz
billcyz

Reputation: 1389

Erlang noproc error when trying to terminate child process

I tried to design one server to start temporary process when it receives request. However when I tried to terminate or kill the temporary process, I got noproc error. Can anyone tell me how to solve this problem?

I got the following files:

temp.erl (main supervisor)

-module(temp).
-behaviour(supervisor).
-export([start_link/0, init/1]).
-export([list_children/0]).

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

list_children() ->
    supervisor:which_children(?MODULE).

init([]) ->
    process_flag(trap_exit, true),
    io:format("****Main supervisor****~n"),
    {ok, {{one_for_one, 5, 10}, 
          [{temp_srv, {temp_srv, start_link, []}, 
            permanent, infinity, worker, [temp_srv]}, 
           {temp_sup, {temp_sup, start_link, []}, 
            permanent, infinity, supervisor, [temp_sup]}]}}.

temp_srv.erl (server)

-module(temp_srv).
-behaviour(gen_server).
-export([init/1, handle_cast/2, handle_info/2, handle_call/3, terminate/2, code_change/3]).
-export([start_link/0, echo_call/1]).

-record(state, {}).

-define(SERVER, ?MODULE).

echo_call(Data) ->
    gen_server:call(?SERVER, {echo, Data}).

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

init([]) ->
    process_flag(trap_exit, true),
    {ok, #state{}}.

handle_call({echo, Data}, _From, State) ->
    Result = temp_sup:assign_task({echo, Data}),
    {reply, Result, State};
handle_call(_Request, _From, State) ->
    {noreply, State}.

handle_info(_Info, State) ->
    {noreply, State}.

handle_cast(_Request, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

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

temp_sup.erl (supervisor to start temporary process)

-module(temp_sup).
-behaviour(supervisor).
-export([start_link/0, init/1]).
-export([start_temp_worker/0, list_children/0, assign_task/1, select_available_children/1, select_finished_children/1]).

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

list_children() ->
        supervisor:which_children(?MODULE).

init([]) ->
    io:format("****Temp supervisor*****~n"),
    spawn_link(fun start_pool/0),
    {ok, {{simple_one_for_one, 0, 1}, [{temp_worker, {temp_worker, start_link, []}, temporary, 1000, worker, [temp_worker]}]}}.

start_pool() ->
        [start_temp_worker() || _ <- lists:seq(1, 3)].

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

assign_task(Data) ->
        case list_children() of
                [] ->
                        {ok, Pid} = start_temp_worker(),
                        gen_server:call(Pid, Data);
                ChildL when is_list(ChildL) ->
            case select_finished_children(ChildL) of
                no_pid_to_kill -> ok;
                {ok, KPid} -> gen_server:call(KPid, stop)
            end,
            {ok, Pid} = select_available_children(ChildL),
            start_temp_worker(),
            io:format("Assign task to ~p~n", [Pid]),
            gen_server:call(Pid, Data)
        end.

select_available_children([ChildH | ChildT]) ->
    {undefined, Pid, worker, [temp_worker]} = ChildH,
    case gen_server:call(Pid, check_status) of
        active -> 
            io:format("Find available children ~p~n", [Pid]),
            {ok, Pid};
        running -> select_available_children(ChildT);
        done -> select_available_children(ChildT)
    end.

select_finished_children([ChildH | ChildT]) ->
    {undefined, KPid, worker, [temp_worker]} = ChildH,
    case gen_server:call(KPid, check_status) of
        done -> 
            io:format("Find process ~p to kill~n", [KPid]),
            {ok, KPid};
        _ -> select_finished_children(ChildT)
    end;
select_finished_children([]) -> no_pid_to_kill.

temp_worker.erl (temporary process)

-module(temp_worker).
-behaviour(gen_server).
-export([start_link/0, init/1]).
-export([handle_info/2, handle_call/3, handle_cast/2, code_change/3, terminate/2]).
-record(state, {status}).

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

init([]) ->
    process_flag(trap_exit, true),
    io:format("Start temporary worker process ~p~n", [self()]),
    {ok, #state{status = active}}.

handle_info(_Request, State) ->
    {noreply, State}.

handle_call(check_status, _From, #state{status = Status} = State) ->
    {reply, Status, State};
handle_call({echo, Msg}, _From, State) ->
    io:format("~p get echo request for message ~p~n", [self(), Msg]),
    {reply, Msg, State#state{status = done}};
handle_call(stop, _From, State) ->
    {stop, normal, stopped, State};
handle_call(_Request, _From, State) ->
    {noreply, State}.

handle_cast(_Request, State) ->
    {noreply, State}.

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

terminate(_Reason, _State) ->
    ok.

The design is supposed to start three temporary process once the temporary supervisor starts, once it receives request, it will terminate one temporary process which has finished work.

That's how I run these code:

9> temp:start_link().      
****Main supervisor****
****Temp supervisor*****
Start temporary worker process <0.98.0>
{ok,<0.94.0>}
Start temporary worker process <0.99.0>
Start temporary worker process <0.101.0>
10> temp_srv:echo_call(asd).
Find available children <0.98.0>
Start temporary worker process <0.102.0>
Assign task to <0.98.0>
<0.98.0> get echo request for message asd
asd
11> temp_srv:echo_call(asd).
Find process <0.98.0> to kill

=ERROR REPORT==== 23-May-2017::23:16:34 ===
** Generic server temp_srv terminating 
** Last message in was {echo,asd}
** When Server state == {state}
** Reason for termination == 
** {{noproc,{gen_server,call,[<0.98.0>,check_status]}},
    [{gen_server,call,2,[{file,"gen_server.erl"},{line,204}]},
     {temp_sup,select_available_children,1,[{file,"temp_sup.erl"},{line,41}]},
     {temp_sup,assign_task,1,[{file,"temp_sup.erl"},{line,33}]},
     {temp_srv,handle_call,3,[{file,"temp_srv.erl"},{line,21}]},
     {gen_server,try_handle_call,4,[{file,"gen_server.erl"},{line,615}]},
     {gen_server,handle_msg,5,[{file,"gen_server.erl"},{line,647}]},
     {proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,247}]}]}

=ERROR REPORT==== 23-May-2017::23:16:34 ===
** Generic server temp terminating 
** Last message in was {'EXIT',<0.92.0>,
                           {{{noproc,
                                 {gen_server,call,[<0.98.0>,check_status]}},
                             {gen_server,call,[temp_srv,{echo,asd}]}},
                            [{gen_server,call,2,
                                 [{file,"gen_server.erl"},{line,204}]},
                             {erl_eval,do_apply,6,
                                 [{file,"erl_eval.erl"},{line,674}]},
                             {shell,exprs,7,[{file,"shell.erl"},{line,686}]},
                             {shell,eval_exprs,7,
                                 [{file,"shell.erl"},{line,641}]},
                             {shell,eval_loop,3,
                                 [{file,"shell.erl"},{line,626}]}]}}
** When Server state == {state,
                            {local,temp},
                            one_for_one,
                            [{child,<0.96.0>,temp_sup,
                                 {temp_sup,start_link,[]},
                                 permanent,infinity,supervisor,
                                 [temp_sup]},
                             {child,<0.104.0>,temp_srv,
                                 {temp_srv,start_link,[]},
                                 permanent,infinity,worker,
                                 [temp_srv]}],
                            undefined,5,10,
                            [-576459529],
                            0,temp,[]}
** Reason for termination == 
** {{{noproc,{gen_server,call,[<0.98.0>,check_status]}},
     {gen_server,call,[temp_srv,{echo,asd}]}},
    [{gen_server,call,2,[{file,"gen_server.erl"},{line,204}]},
     {erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,674}]},
     {shell,exprs,7,[{file,"shell.erl"},{line,686}]},
     {shell,eval_exprs,7,[{file,"shell.erl"},{line,641}]},
     {shell,eval_loop,3,[{file,"shell.erl"},{line,626}]}]}
** exception exit: {{noproc,{gen_server,call,[<0.98.0>,check_status]}},
                    {gen_server,call,[temp_srv,{echo,asd}]}}
     in function  gen_server:call/2 (gen_server.erl, line 204)

Upvotes: 0

Views: 1673

Answers (1)

7stud
7stud

Reputation: 48649

From the error docs:

noproc -- Trying to link to a non-existing process.

The error message says the problematic line is:

case gen_server:call(Pid, check_status) of

That means Pid is is a non-existent process. In your output, you can see:

Assign task to <0.98.0>
<0.98.0> get echo request for message asd
asd

I think that means process <0.98.0> has finished executing and has terminated. You can verify that by outputting process_info(Pid), which will return undefined for a non-existent process. That process is at the head of your ChildL list. You can verify that with an io:format() statement:

    assign_task(Data) ->
        ...
        case select_finished_children(ChildL) of
            no_pid_to_kill -> ok;
            {ok, KPid} -> gen_server:call(KPid, stop)
        end,
        io:format("assign_task(): ChildL: ~w~n", [ChildL]),  %%<***HERE
        io:format("****Info: ~w~n", [process_info(Pid)]),    %%<***HERE
        {ok, Pid} = select_available_children(ChildL),

Then your code does this:

select_available_children([ChildH | ChildT]) ->
    {undefined, Pid, worker, [temp_worker]} = ChildH,
    case gen_server:call(Pid, check_status) of

ChildH contains the pid <0.98.0>, which has terminated, and that pid gets bound to Pid. Then gen_server:call(Pid) executes, which results in a noproc error.

Upvotes: 1

Related Questions