Search code examples
erlangerlang-shell

Why werl executes the same code in one shell window and freezes in another?


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:

  1. c(frequency).

  2. frequency:start().

  3. CTRL-G -> S -> C -- to switch to new Erlang shell

  4. frequency:allocate().

  5. 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.

Solution

  • 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.