I am trying to solve excercise 5-2 from O'Reilly Erlang Programming book.
My setup is Win10
, werl Eshell V7.3
.
Here are steps to reproduce my problem:
c(frequency).
frequency:start().
CTRL-G
-> S
-> C
-- to switch to new Erlang shell
frequency:allocate().
frequency:deallocate(10).
If I omit point 3 then all goes fine, but when I do everything according to above procedure, then shell gets stuck in line 30
or 43
.
Can someone explain me what am I doing wrong and how to get the exactly same behavior for these two cases: with one, and with two shells?
Module's code (there are two warnings here, but it compiles anyway, I was trying to get deallocation allowed only when called from the same Pid from which allocation was being done):
-module(frequency).
-export([start/0, stop/0, allocate/0, deallocate/1]).
-export([init/0]).
%% These are the start functions used to crate and
%% initialize the server.
start() ->
register(frequency, spawn(frequency, init, [])).
init() ->
Frequencies = {get_frequencies(), []},
loop(Frequencies).
% Hard Coded
get_frequencies() -> [10, 11, 12, 13, 14, 15].
%% The client Functions
stop() -> call(stop).
allocate() -> call(allocate).
deallocate(Freq)-> io:format("Calling deallocate~n",[]), call({deallocate, Freq}).
%% We hide all message passing and the message
%% protocol in a functional interface.
call(Message) ->
Self = self(),
io:format("Self: ~w~n", [Self]),
frequency ! {request, Self, Message},
receive
{reply, Reply} -> Reply
end.
%% The Main Loop
loop(Frequencies) ->
receive
{request, Pid, allocate} ->
{NewFrequencies, Reply} = allocate(Frequencies, Pid),
reply(Pid, Reply),
loop(NewFrequencies);
{request, Pid2, {deallocate, Freq}} ->
io:format("Dealocate ~w from pid ~w~n", [Freq, Pid2]),
NewFrequencies = deallocate(Frequencies, Freq), %, Pid2),
reply(Pid2, ok),
loop(NewFrequencies);
{request, Pid, stop} ->
reply(Pid, ok)
end.
reply(Pid, Reply) ->
Pid ! {reply, Reply}.
%% The Internal Help Functions used to allocate and
%% deallocate frequencies.
allocate({[], Allocated}, _Pid) ->
{{[], Allocated}, {error, no_frequency}};
allocate({[Freq|Free], Allocated}, Pid) ->
{{Free, [{Freq, Pid}| Allocated]}, {ok, Freq}}.
deallocate({Free, Allocated}, Freq) -> %, Pid) ->
Response = lists:keysearch(Freq, 1, Allocated),
io:format("Response: ~w~n", [Response]),
case Response of
{value, {Freq, OPid}} ->
case OPid of
Pid ->
NewAllocated = lists:keydelete(Freq, 1, Allocated),
io:format("Removed freq~n",[]),
{[Freq|Free], NewAllocated};
_OtherPid ->
io:format("Not allowed to remove freq~n",[]),
{Free, Allocated}
end;
_ -> io:format("Not removed freq~n",[]), {Free, Allocated}
end.
I have not a complete explanation of the problem, but it is linked to the usage of io:format, there is an obscure (obscure for me :o) concept of group leader which is used to determine on which shell an io:format should display a message.
In general, it is not a good idea to display messages in a server loop, which is executed in the server process, it is better to do it in the interface functions executed in the calling process.
I have change your code a little, removing the useless print and corrected the test of pid and it works fine in multiple shells.
-module(freq).
-export([start/0, stop/0, allocate/0, deallocate/1]).
-export([init/0]).
%% These are the start functions used to crate and
%% initialize the server.
start() ->
register(frequency, spawn(freq, init, [])).
init() ->
Frequencies = {get_frequencies(), []},
loop(Frequencies).
% Hard Coded
get_frequencies() -> [10, 11, 12, 13, 14, 15].
%% The client Functions
stop() -> call(stop).
allocate() -> call(allocate).
deallocate(Freq)-> io:format("Calling deallocate~n",[]), call({deallocate, Freq}).
%% We hide all message passing and the message
%% protocol in a functional interface.
call(Message) ->
Self = self(),
frequency ! {request, Self, Message},
receive
{reply, Reply} -> Reply
end.
%% The Main Loop
loop(Frequencies) ->
receive
{request, Pid, allocate} ->
{NewFrequencies, Reply} = allocate(Frequencies, Pid),
reply(Pid, Reply),
loop(NewFrequencies);
{request, Pid, {deallocate, Freq}} ->
{NewFrequencies,Reply} = deallocate(Frequencies, Freq, Pid),
reply(Pid, Reply),
loop(NewFrequencies);
{request, Pid, stop} ->
reply(Pid, ok)
end.
reply(Pid, Reply) ->
Pid ! {reply, Reply}.
%% The Internal Help Functions used to allocate and
%% deallocate frequencies.
allocate({[], Allocated}, _Pid) ->
{{[], Allocated}, {error, no_frequency}};
allocate({[Freq|Free], Allocated}, Pid) ->
{{Free, [{Freq, Pid}| Allocated]}, {ok, Freq}}.
deallocate({Free, Allocated}, Freq, Pid) ->
Response = lists:keysearch(Freq, 1, Allocated),
case Response of
{value, {Freq, Pid}} ->
NewAllocated = lists:keydelete(Freq, 1, Allocated),
{{[Freq|Free], NewAllocated},removed};
{value, {Freq, _OtherPid}} ->
{{Free, Allocated},not_owner};
_ ->
{{Free, Allocated},not_found}
end.