Simulator for message passing protocols, supporting (really) unfair process scheduling and dropped messages
Erlang
53
131 commits
updated Jun 4, 2011
Wouldn't it be great if you could:
Ja, those things exist today. But there are things to be careful of, including but not limited to:
This message passing simulator attempts to address almost all of the above list.
{t0,t1,A,B},
then between simulated time t0 and t1, any message from a process
in set A that sent to a process in set B will be dropped.
$ cd /path/to/top/of/msgdropsim
$ make
$ erl -pz ./ebin
[... Erlang VM starts up]
> eqc:quickcheck(slf_msgsim_qc:prop_simulate(echomany_sim, [])).
Also: See the top of each .erl file in the src directory for
instructions or pointers to instructions.
General usage:
eqc:quickcheck(slf_msgsim_qc:prop_simulate(SimModuleName, OptionsList)).
SimModuleName is a protocol simulation implementation
module such as echo_sim or distrib_counter_2phase_vclocksetwatch.OptionsList is a list of zero or more of the following options: [{min_clients, N}, {max_clients, M}, %% defaults: N=1, M=9
{min_servers, N}, {max_servers, M}, %% defaults: N=1, M=9
disable_partitions, %% disable network partitions
disable_delays %% disable message delays
{min_keys, N}, %% typically not implemented
{max_keys, N}, %% typically not implemented
crash_report, %% enable verbose report upon crash
{stop_step, N}] %% stop execution at step N
For example:
eqc:quickcheck(slf_msgsim_qc:prop_simulate(distrib_counter_2phase_vclocksetwatch_sim, [])).
By default, QuickCheck will run 100 random test cases. For a protocol simulation, that usually isn't enough cases to find bugs in even a simple protocol.
eqc:quickcheck/1 is a property.slf_msgsim_qc:prop_simulate(SimModuleName, OptionsList)
is a property.
eqc:quickcheck(slf_msgsim_qc:prop_simulate(SimModuleName, OptionsList))., works as you'd expect.eqc:quickcheck(eqc:numtests(5000, Property)).eqc:quickcheck(eqc_gen:resize(R, Property)). where R is an integer
larger than 40. The effect seems to be exponential: test cases with
R=100 are much, much longer than tests that use R=50.resize() and numtests() wrappers can be used together, e.g.,
eqc:quickcheck(eqc:numtests(5000, eqc_gen:resize(60, Property)))For example, to run 10,000 test cases of the
distrib_counter_2phase_vclocksetwatch_sim simulator:
eqc:quickcheck(eqc:numtests(10*1000,slf_msgsim_qc:prop_simulate(distrib_counter_2phase_vclocksetwatch_sim, [{max_clients,9}, {max_servers,9}]))).
The simulator source contains code for simulating two protocols:
echo_bad1_sim.erl and echo_sim.erl,
respectively.distrib_counter_bad1_sim.erl through
distrib_counter_bad5_sim.erl.All of the buggy simulator code in a file foo.erl has a
corresponding text file called foo.txt which contains:
%% characters, that help
explain what the output means.The foo.txt file has annotated
simulator output and discussion of what's wrong with each
implementation, e.g. echo_bad1_sim.txt and
distrib_counter_bad1_sim.txt.
For the distributed counter simulations, it can be instructive to use "diff" to compare each implementation, in sequence, to see what changed.
diff -u distrib_counter_bad1_sim.erl distrib_counter_bad2_sim.erldiff -u distrib_counter_bad2_sim.erl distrib_counter_bad3_sim.erldiff -u distrib_counter_bad3_sim.erl distrib_counter_bad4_sim.erldiff -u distrib_counter_bad4_sim.erl distrib_counter_bad5_sim.erlTODO Finish this section
The simulator attempts to maintain Erlang message passing semantics.
Those semantics are not formally documented but can loosely be
described as "send and pray", i.e. no guarantee that any message will
be delivered. In the case where process X sends messages A and
B to process Y, if Y receives both messages B and A,
then message A will be delivered before B. (I hope I got that
right ... if not, the Async Message Passing Police will come and
arrest me.)
Write a callback module
gen_initial_ops/4 The simulator scheduler sends messages from
created by this generator to each of the simulated processes.
QuickCheck will randomly choose some number of client & server
processes for each test case.gen_client_initial_states/2
Generate the local process state data for each client process.gen_server_initial_states/2
Generate the local process state data for each server process.verify_property/11
After a simulated test case has run, verify that whatever protocol
properties should be true are indeed true. Any failure will cause
QuickCheck to try to find a smaller-but-still-failing
counterexample.Compile
Run via eqc:quickcheck(slf_msgsim_qc:prop_simulate(YourSimModuleName, PropertyList)).
eqc:numtests() and/or/both eqc_gen:resize(N, YourProperty)
where N is a large number on the range of 50-100.The current work on McErlang integration is ... well, barely recognizable as "integration". But it's trying to get there, slowly.
Short answer: look at the commit log entries starting on May 28, 2011. There are cut-and-paste'able examples and a fair amount of commentary there.
One major complication is the simulator's support for Erlang's
"selective receive" feature. Take this bit of code from
distrib_counter_2phase_sim.erl:
client_ph1_waiting({ph1_ask_ok, ClOp, _Server, _Cookie, _Count} = Msg,
C = #c{clop = ClOp, num_responses = Resps, ph1_oks = Oks}) ->
cl_p1_next_step(false, C#c{num_responses = Resps + 1,
ph1_oks = [Msg|Oks]});
client_ph1_waiting({ph1_ask_sorry, ClOp, _Server, _LuckyClient} = Msg,
C = #c{clop = ClOp,
num_responses = Resps, ph1_sorrys = Sorrys}) ->
cl_p1_next_step(false, C#c{num_responses = Resps + 1,
ph1_sorrys = [Msg|Sorrys]});
client_ph1_waiting(timeout, C) ->
cl_p1_next_step(true, C).
If the simulated process receives a {unexpected, ...} message
while in the client_ph1_waiting state, that message will be
ignored. Why? "Selective receive" will only pull a message out of a
mailbox when there is a sufficiently general pattern to match it. In
the code for client_ph1_waiting() above, there are exactly three
possible messages that can be processed while in that state:
{ph1_ask_ok, ClOp, _, _, _} where ClOp is exactly equal to the
second argument's C#c.clop value. (Remember, the underscore _
means "don't care".){ph1_ask_sorry, ClOp, _, _} where ClOp is exactly equal to the
second argument's C#c.clop value.timeoutIt may be possible to perform an automatic (or semi-automatic) transformation of this code into something like:
client_ph1_waiting(C) ->
C = #c{clop = ClOp, num_responses = Resps, ph1_oks = Oks, ph1_sorrys = Sorrys},
receive
{ph1_ask_ok, ClOp, _Server, _Cookie, _Count} = Msg ->
cl_p1_next_step(false, C#c{num_responses = Resps + 1,
ph1_oks = [Msg|Oks]});
{ph1_ask_sorry, ClOp, _Server, _LuckyClient} = Msg ->
cl_p1_next_step(false, C#c{num_responses = Resps + 1,
ph1_sorrys = [Msg|Sorrys]})
after ?SomeTimeOut ->
cl_p1_next_step(true, C)
end.
syntax_tools tools.Contact Scott Lystig Fritchie via GitHub email or via slfritchie
(at} snookles{dot)com.
130 commits
1 commits
Erlang
100.0%
Simulator for message passing protocols, supporting (really) unfair process scheduling and dropped messages
Erlang
53
131 commits
updated Jun 4, 2011
Wouldn't it be great if you could:
Ja, those things exist today. But there are things to be careful of, including but not limited to:
This message passing simulator attempts to address almost all of the above list.
{t0,t1,A,B},
then between simulated time t0 and t1, any message from a process
in set A that sent to a process in set B will be dropped.
$ cd /path/to/top/of/msgdropsim
$ make
$ erl -pz ./ebin
[... Erlang VM starts up]
> eqc:quickcheck(slf_msgsim_qc:prop_simulate(echomany_sim, [])).
Also: See the top of each .erl file in the src directory for
instructions or pointers to instructions.
General usage:
eqc:quickcheck(slf_msgsim_qc:prop_simulate(SimModuleName, OptionsList)).
SimModuleName is a protocol simulation implementation
module such as echo_sim or distrib_counter_2phase_vclocksetwatch.OptionsList is a list of zero or more of the following options: [{min_clients, N}, {max_clients, M}, %% defaults: N=1, M=9
{min_servers, N}, {max_servers, M}, %% defaults: N=1, M=9
disable_partitions, %% disable network partitions
disable_delays %% disable message delays
{min_keys, N}, %% typically not implemented
{max_keys, N}, %% typically not implemented
crash_report, %% enable verbose report upon crash
{stop_step, N}] %% stop execution at step N
For example:
eqc:quickcheck(slf_msgsim_qc:prop_simulate(distrib_counter_2phase_vclocksetwatch_sim, [])).
By default, QuickCheck will run 100 random test cases. For a protocol simulation, that usually isn't enough cases to find bugs in even a simple protocol.
eqc:quickcheck/1 is a property.slf_msgsim_qc:prop_simulate(SimModuleName, OptionsList)
is a property.
eqc:quickcheck(slf_msgsim_qc:prop_simulate(SimModuleName, OptionsList))., works as you'd expect.eqc:quickcheck(eqc:numtests(5000, Property)).eqc:quickcheck(eqc_gen:resize(R, Property)). where R is an integer
larger than 40. The effect seems to be exponential: test cases with
R=100 are much, much longer than tests that use R=50.resize() and numtests() wrappers can be used together, e.g.,
eqc:quickcheck(eqc:numtests(5000, eqc_gen:resize(60, Property)))For example, to run 10,000 test cases of the
distrib_counter_2phase_vclocksetwatch_sim simulator:
eqc:quickcheck(eqc:numtests(10*1000,slf_msgsim_qc:prop_simulate(distrib_counter_2phase_vclocksetwatch_sim, [{max_clients,9}, {max_servers,9}]))).
The simulator source contains code for simulating two protocols:
echo_bad1_sim.erl and echo_sim.erl,
respectively.distrib_counter_bad1_sim.erl through
distrib_counter_bad5_sim.erl.All of the buggy simulator code in a file foo.erl has a
corresponding text file called foo.txt which contains:
%% characters, that help
explain what the output means.The foo.txt file has annotated
simulator output and discussion of what's wrong with each
implementation, e.g. echo_bad1_sim.txt and
distrib_counter_bad1_sim.txt.
For the distributed counter simulations, it can be instructive to use "diff" to compare each implementation, in sequence, to see what changed.
diff -u distrib_counter_bad1_sim.erl distrib_counter_bad2_sim.erldiff -u distrib_counter_bad2_sim.erl distrib_counter_bad3_sim.erldiff -u distrib_counter_bad3_sim.erl distrib_counter_bad4_sim.erldiff -u distrib_counter_bad4_sim.erl distrib_counter_bad5_sim.erlTODO Finish this section
The simulator attempts to maintain Erlang message passing semantics.
Those semantics are not formally documented but can loosely be
described as "send and pray", i.e. no guarantee that any message will
be delivered. In the case where process X sends messages A and
B to process Y, if Y receives both messages B and A,
then message A will be delivered before B. (I hope I got that
right ... if not, the Async Message Passing Police will come and
arrest me.)
Write a callback module
gen_initial_ops/4 The simulator scheduler sends messages from
created by this generator to each of the simulated processes.
QuickCheck will randomly choose some number of client & server
processes for each test case.gen_client_initial_states/2
Generate the local process state data for each client process.gen_server_initial_states/2
Generate the local process state data for each server process.verify_property/11
After a simulated test case has run, verify that whatever protocol
properties should be true are indeed true. Any failure will cause
QuickCheck to try to find a smaller-but-still-failing
counterexample.Compile
Run via eqc:quickcheck(slf_msgsim_qc:prop_simulate(YourSimModuleName, PropertyList)).
eqc:numtests() and/or/both eqc_gen:resize(N, YourProperty)
where N is a large number on the range of 50-100.The current work on McErlang integration is ... well, barely recognizable as "integration". But it's trying to get there, slowly.
Short answer: look at the commit log entries starting on May 28, 2011. There are cut-and-paste'able examples and a fair amount of commentary there.
One major complication is the simulator's support for Erlang's
"selective receive" feature. Take this bit of code from
distrib_counter_2phase_sim.erl:
client_ph1_waiting({ph1_ask_ok, ClOp, _Server, _Cookie, _Count} = Msg,
C = #c{clop = ClOp, num_responses = Resps, ph1_oks = Oks}) ->
cl_p1_next_step(false, C#c{num_responses = Resps + 1,
ph1_oks = [Msg|Oks]});
client_ph1_waiting({ph1_ask_sorry, ClOp, _Server, _LuckyClient} = Msg,
C = #c{clop = ClOp,
num_responses = Resps, ph1_sorrys = Sorrys}) ->
cl_p1_next_step(false, C#c{num_responses = Resps + 1,
ph1_sorrys = [Msg|Sorrys]});
client_ph1_waiting(timeout, C) ->
cl_p1_next_step(true, C).
If the simulated process receives a {unexpected, ...} message
while in the client_ph1_waiting state, that message will be
ignored. Why? "Selective receive" will only pull a message out of a
mailbox when there is a sufficiently general pattern to match it. In
the code for client_ph1_waiting() above, there are exactly three
possible messages that can be processed while in that state:
{ph1_ask_ok, ClOp, _, _, _} where ClOp is exactly equal to the
second argument's C#c.clop value. (Remember, the underscore _
means "don't care".){ph1_ask_sorry, ClOp, _, _} where ClOp is exactly equal to the
second argument's C#c.clop value.timeoutIt may be possible to perform an automatic (or semi-automatic) transformation of this code into something like:
client_ph1_waiting(C) ->
C = #c{clop = ClOp, num_responses = Resps, ph1_oks = Oks, ph1_sorrys = Sorrys},
receive
{ph1_ask_ok, ClOp, _Server, _Cookie, _Count} = Msg ->
cl_p1_next_step(false, C#c{num_responses = Resps + 1,
ph1_oks = [Msg|Oks]});
{ph1_ask_sorry, ClOp, _Server, _LuckyClient} = Msg ->
cl_p1_next_step(false, C#c{num_responses = Resps + 1,
ph1_sorrys = [Msg|Sorrys]})
after ?SomeTimeOut ->
cl_p1_next_step(true, C)
end.
syntax_tools tools.Contact Scott Lystig Fritchie via GitHub email or via slfritchie
(at} snookles{dot)com.
130 commits
1 commits
Erlang
100.0%