[erlang-questions] gen_server and init

Jesper Louis Andersen jesper.louis.andersen@REDACTED
Mon Jul 27 20:29:17 CEST 2015


On Mon, Jul 27, 2015 at 4:40 PM, Loïc Hoguin <essen@REDACTED> wrote:

> This and sending yourself a message is a bad idea. It will usually work,
> until it doesn't, and you will have a very hard time figuring out why.


I'm quite curious. What is the scenario where this fails? I have never
observed this in practice on very busy systems, and I think going through
proc_lib for this is a detour I'd rather not we'd have to take.

Here are two modules, z0 and z.erl. They are very boring gen servers:

-module(z0).
-behaviour(gen_server).

-ifdef(PULSE).
-include_lib("pulse_otp/include/pulse_otp.hrl").
-endif.

-export([start_link/0]).

%% Operational API
-export([read/0, read_p/1]).

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

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

read() ->
    gen_server:call(?MODULE, read).

read_p(P) ->
    gen_server:call(P, read).

%% Callbacks
init([]) ->
    {ok, initializing, 0}.

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

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

handle_info(timeout, _State) ->
    {noreply, ready}.

terminate(_How, _State) ->
    ok.

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

---------------------

-module(z).
-behaviour(gen_server).

-ifdef(PULSE).
-include_lib("pulse_otp/include/pulse_otp.hrl").
-endif.

-export([start_link/0]).

%% Operational API
-export([read/0, read_p/1]).

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

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

read() ->
    gen_server:call(?MODULE, read).

read_p(P) ->
    gen_server:call(P, read).

%% Callbacks
init([]) ->
    self() ! timeout,
    {ok, initializing}.

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

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

handle_info(timeout, _State) ->
    {noreply, ready}.

terminate(_How, _State) ->
    ok.

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

---------------------

Ok, now with these down, we can use EQC and PULSE to generate the
counterexample if any should be in there:

-module(z_eqc).
-include_lib("eqc/include/eqc.hrl").
-include_lib("eqc/include/eqc_statem.hrl").

-include_lib("pulse/include/pulse.hrl").
-include_lib("pulse_otp/include/pulse_otp.hrl").

-compile(export_all).

-record(state, { init, ref }).

initial_state() ->
    #state { init = false }.

server_start() ->
    {ok, Pid} = z:start_link(),
    unlink(Pid),
    Pid.

%% SERVER START_LINK
server_start_pre(S) -> not initialized(S).
server_start_args(_S) -> [].

server_start_next(S, Ref, []) ->
    S#state { ref = Ref, init = true }.

server_start_post(_S, [], Ref) -> is_pid(Ref).

%% READ BY PID
read_p(P) ->
    z:read_p(P).

read_p_pre(S) -> initialized(S).
read_p_args(#state { ref = Ref }) -> [Ref].

read_p_return(_S, [_Ref]) -> ready.

%% READ
read() ->
    try z:read() of
      M -> M
    catch
      _Class:_Err ->
        {error, undefined}
    end.

read_args(_S) -> [].
read_return(#state { init = false}, []) -> {error, undefined};
read_return(#state { init = true}, []) -> ready.

%% Run a test under PULSE to randomize the process schedule as well.
prop_model_pulse() ->
   ?SETUP(fun() ->
                   setup(),
                   fun() -> ok end
           end,
  ?LET(Shrinking, parameter(shrinking, false),
  ?FORALL(Cmds, parallel_commands(?MODULE),
    ?ALWAYS(if not Shrinking -> 1; Shrinking -> 20 end,
      ?PULSE(HSR={_, _, R},
        begin
          ok = cleanup(),
          run_parallel_commands(?MODULE, Cmds)
        end,
        aggregate(command_names(Cmds),
        pretty_commands(?MODULE, Cmds, HSR, R == ok))))))).

setup() ->
    error_logger:tty(false),
    ok.

cleanup() ->
    case whereis(z) of
        undefined -> ok;
        Pid ->
            exit(Pid, kill),
            timer:sleep(3)
    end,
    ok.

initialized(#state { init = I }) -> I.

pulse_instrument() ->
  [ pulse_instrument(File) || File <- filelib:wildcard("*.erl") ],
  ok.

pulse_instrument(File) ->
    io:format("Compiling: ~p~n", [File]),
    {ok, Mod} = compile:file(File, [{d, 'PULSE', true}, {d, 'WITH_PULSE',
true},
                                    {d, 'EQC_TESTING', true},
                                    {parse_transform, pulse_instrument}]),
  code:purge(Mod),
  code:load_file(Mod),
  Mod.

-----------------------------

However, when I run this, I get no errors, even though I'm trying to behave
non-nicely:

I won't call the Pid until I know about it.
I will blindly call the name, z, and if I get an error on this, I'll verify
that the possible linearization is that the process was not started yet. If
I get a result, I force it to be ready:

60> eqc:module({testing_time, 30}, z_eqc).
prop_model_pulse:
....................................................................................................(x10)....................................................................................................(x100)............................(x10).......
Time limit reached: 30.0 seconds.

OK, passed 3970 tests

49.857% {z_eqc,read,0}
44.492% {z_eqc,read_p,1}
5.651% {z_eqc,server_start,0}
[]

...

So: What is the scenario where this approach fails?



-- 
J.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20150727/0bbf7617/attachment.htm>


More information about the erlang-questions mailing list