diff options
author | Steve Vinoski <[email protected]> | 2013-03-28 20:34:52 -0400 |
---|---|---|
committer | Steve Vinoski <[email protected]> | 2013-04-02 20:04:36 -0400 |
commit | 876b3644ecfac12accf36fbf0d0625e3ac4f6498 (patch) | |
tree | 1e76f81c5f55bbac5563f32a15f0b83a6e096704 /lib/stdlib/test | |
parent | 460cb28eda044c8dd8ce28ac6bc36bbc49373c8a (diff) | |
download | otp-876b3644ecfac12accf36fbf0d0625e3ac4f6498.tar.gz otp-876b3644ecfac12accf36fbf0d0625e3ac4f6498.tar.bz2 otp-876b3644ecfac12accf36fbf0d0625e3ac4f6498.zip |
add sys:get_state/1,2 and sys:replace_state/2,3
At Erlang Factory 2013 there was discussion during one of the talks about
the sys:get_status functions and how useful they were for debugging. Geoff
Cant mentioned it would be very useful if the sys module also provided
functions to use while debugging to get just the state of a process and
also to be able to replace the state of a process, and many others in the
audience appeared to agree.
The sys:get_state/1,2 functions return the state of a gen_server, gen_fsm,
or gen_event process. The return value varies depending on the process
type: process state for a gen_server, state name and state data for a
gen_fsm, and handler module, handler id, and handler state for each handler
registered in a gen_event process.
The sys:replace_state/2,3 functions allow the state of a gen_server,
gen_fsm, or gen_event process to be replaced with a new state. These
functions take a function argument that updates or replaces the process
state; using a function to change the state eliminates the race condition
of first reading the state via sys:get_state/1 or sys:get_state/2, using
its return value to create a new state, and then replacing the old state
with the new state, since during that time the process might have received
other calls or messages that could have changed its state.
* For a gen_server process, the state replacement function takes the
process state as an argument and returns a new state.
* For a gen_fsm process, the state replacement function gets a tuple of
{StateName, StateData} and returns a similar tuple that specifies a new
state name, new state data, or both.
* For a gen_event process, the state replacement function is called for
each registered event handler. It gets a tuple {Module, Id, HandlerState}
and returns a similar tuple that specifies the same Module and Id values
but may specify a different value for HandlerState.
If the state replacement function crashes or results in an error, the
original state of a gen_server or gen_fsm process is maintained; if such a
crash occurs for a gen_event process, the original state of the event
handler for which the state replacement function was called is maintained,
but the states of other event handlers of the same gen_event process may
still be updated if no errors or crashes occur while replacing their
states.
Add documentation for sys:get_state/1,2 and sys:replace_state/2,3. The
documentation explicitly notes that the functions are intended for use
during debugging.
Add new tests for these functions to gen_server_SUITE, gen_fsm_SUITE, and
gen_event_SUITE.
Diffstat (limited to 'lib/stdlib/test')
-rw-r--r-- | lib/stdlib/test/gen_event_SUITE.erl | 47 | ||||
-rw-r--r-- | lib/stdlib/test/gen_fsm_SUITE.erl | 38 | ||||
-rw-r--r-- | lib/stdlib/test/gen_server_SUITE.erl | 48 |
3 files changed, 128 insertions, 5 deletions
diff --git a/lib/stdlib/test/gen_event_SUITE.erl b/lib/stdlib/test/gen_event_SUITE.erl index 5c51e12e35..1594049ebf 100644 --- a/lib/stdlib/test/gen_event_SUITE.erl +++ b/lib/stdlib/test/gen_event_SUITE.erl @@ -26,13 +26,14 @@ delete_handler/1, swap_handler/1, swap_sup_handler/1, notify/1, sync_notify/1, call/1, info/1, hibernate/1, call_format_status/1, call_format_status_anon/1, - error_format_status/1]). + error_format_status/1, get_state/1, replace_state/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [start, {group, test_all}, hibernate, - call_format_status, call_format_status_anon, error_format_status]. + call_format_status, call_format_status_anon, error_format_status, + get_state, replace_state]. groups() -> [{test_all, [], @@ -956,3 +957,45 @@ error_format_status(Config) when is_list(Config) -> ?line ok = gen_event:stop(Pid), process_flag(trap_exit, OldFl), ok. + +get_state(suite) -> + []; +get_state(doc) -> + ["Test that sys:get_state/1,2 return the gen_event state"]; +get_state(Config) when is_list(Config) -> + ?line {ok, Pid} = gen_event:start({local, my_dummy_handler}), + State1 = self(), + ?line ok = gen_event:add_handler(my_dummy_handler, dummy1_h, [State1]), + ?line [{dummy1_h,false,State1}] = sys:get_state(Pid), + ?line [{dummy1_h,false,State1}] = sys:get_state(Pid, 5000), + State2 = {?MODULE, self()}, + ?line ok = gen_event:add_handler(my_dummy_handler, {dummy1_h,id}, [State2]), + ?line Result1 = sys:get_state(Pid), + ?line [{dummy1_h,false,State1},{dummy1_h,id,State2}] = lists:sort(Result1), + ?line Result2 = sys:get_state(Pid, 5000), + ?line [{dummy1_h,false,State1},{dummy1_h,id,State2}] = lists:sort(Result2), + ?line ok = gen_event:stop(Pid), + ok. + +replace_state(suite) -> + []; +replace_state(doc) -> + ["Test that replace_state/2,3 replace the gen_event state"]; +replace_state(Config) when is_list(Config) -> + ?line {ok, Pid} = gen_event:start({local, my_dummy_handler}), + State1 = self(), + ?line ok = gen_event:add_handler(my_dummy_handler, dummy1_h, [State1]), + ?line [{dummy1_h,false,State1}] = sys:get_state(Pid), + NState1 = "replaced", + Replace1 = fun({dummy1_h,false,_}=S) -> setelement(3,S,NState1) end, + ?line [{dummy1_h,false,NState1}] = sys:replace_state(Pid, Replace1), + ?line [{dummy1_h,false,NState1}] = sys:get_state(Pid), + NState2 = "replaced again", + Replace2 = fun({dummy1_h,false,_}=S) -> setelement(3,S,NState2) end, + ?line [{dummy1_h,false,NState2}] = sys:replace_state(Pid, Replace2, 5000), + ?line [{dummy1_h,false,NState2}] = sys:get_state(Pid), + %% verify no change in state if replace function crashes + Replace3 = fun(_) -> exit(fail) end, + ?line [{dummy1_h,false,NState2}] = sys:replace_state(Pid, Replace3), + ?line [{dummy1_h,false,NState2}] = sys:get_state(Pid), + ok. diff --git a/lib/stdlib/test/gen_fsm_SUITE.erl b/lib/stdlib/test/gen_fsm_SUITE.erl index a637a8543b..f6b0fbc20d 100644 --- a/lib/stdlib/test/gen_fsm_SUITE.erl +++ b/lib/stdlib/test/gen_fsm_SUITE.erl @@ -31,7 +31,7 @@ -export([shutdown/1]). --export([ sys1/1, call_format_status/1, error_format_status/1]). +-export([ sys1/1, call_format_status/1, error_format_status/1, get_state/1, replace_state/1]). -export([hibernate/1,hiber_idle/3,hiber_wakeup/3,hiber_idle/2,hiber_wakeup/2]). @@ -66,7 +66,7 @@ groups() -> start8, start9, start10, start11, start12]}, {abnormal, [], [abnormal1, abnormal2]}, {sys, [], - [sys1, call_format_status, error_format_status]}]. + [sys1, call_format_status, error_format_status, get_state, replace_state]}]. init_per_suite(Config) -> Config. @@ -413,6 +413,40 @@ error_format_status(Config) when is_list(Config) -> process_flag(trap_exit, OldFl), ok. +get_state(Config) when is_list(Config) -> + State = self(), + ?line {ok, Pid} = gen_fsm:start(?MODULE, {state_data, State}, []), + ?line {idle, State} = sys:get_state(Pid), + ?line {idle, State} = sys:get_state(Pid, 5000), + ?line stop_it(Pid), + + %% check that get_state can handle a name being an atom (pid is + %% already checked by the previous test) + ?line {ok, Pid2} = gen_fsm:start({local, gfsm}, gen_fsm_SUITE, {state_data, State}, []), + ?line {idle, State} = sys:get_state(gfsm), + ?line {idle, State} = sys:get_state(gfsm, 5000), + ?line stop_it(Pid2), + ok. + +replace_state(Config) when is_list(Config) -> + State = self(), + ?line {ok, Pid} = gen_fsm:start(?MODULE, {state_data, State}, []), + ?line {idle, State} = sys:get_state(Pid), + NState1 = "replaced", + Replace1 = fun({StateName, _}) -> {StateName, NState1} end, + ?line {idle, NState1} = sys:replace_state(Pid, Replace1), + ?line {idle, NState1} = sys:get_state(Pid), + NState2 = "replaced again", + Replace2 = fun({idle, _}) -> {state0, NState2} end, + ?line {state0, NState2} = sys:replace_state(Pid, Replace2, 5000), + ?line {state0, NState2} = sys:get_state(Pid), + %% verify no change in state if replace function crashes + Replace3 = fun(_) -> error(fail) end, + ?line {state0, NState2} = sys:replace_state(Pid, Replace3), + ?line {state0, NState2} = sys:get_state(Pid), + ?line stop_it(Pid), + ok. + %% Hibernation hibernate(suite) -> []; hibernate(Config) when is_list(Config) -> diff --git a/lib/stdlib/test/gen_server_SUITE.erl b/lib/stdlib/test/gen_server_SUITE.erl index dffeadb423..a5cba6e4c4 100644 --- a/lib/stdlib/test/gen_server_SUITE.erl +++ b/lib/stdlib/test/gen_server_SUITE.erl @@ -32,7 +32,7 @@ spec_init_local_registered_parent/1, spec_init_global_registered_parent/1, otp_5854/1, hibernate/1, otp_7669/1, call_format_status/1, - error_format_status/1, call_with_huge_message_queue/1 + error_format_status/1, get_state/1, replace_state/1, call_with_huge_message_queue/1 ]). % spawn export @@ -57,6 +57,7 @@ all() -> spec_init_local_registered_parent, spec_init_global_registered_parent, otp_5854, hibernate, otp_7669, call_format_status, error_format_status, + get_state, replace_state, call_with_huge_message_queue]. groups() -> @@ -1033,6 +1034,51 @@ error_format_status(Config) when is_list(Config) -> process_flag(trap_exit, OldFl), ok. +%% Verify that sys:get_state correctly returns gen_server state +%% +get_state(suite) -> + []; +get_state(doc) -> + ["Test that sys:get_state/1,2 return the gen_server state"]; +get_state(Config) when is_list(Config) -> + State = self(), + ?line {ok, _Pid} = gen_server:start_link({local, get_state}, + ?MODULE, {state,State}, []), + ?line State = sys:get_state(get_state), + ?line State = sys:get_state(get_state, 5000), + ?line {ok, Pid} = gen_server:start_link(?MODULE, {state,State}, []), + ?line State = sys:get_state(Pid), + ?line State = sys:get_state(Pid, 5000), + ok. + +%% Verify that sys:replace_state correctly replaces gen_server state +%% +replace_state(suite) -> + []; +replace_state(doc) -> + ["Test that sys:replace_state/1,2 replace the gen_server state"]; +replace_state(Config) when is_list(Config) -> + State = self(), + ?line {ok, _Pid} = gen_server:start_link({local, replace_state}, + ?MODULE, {state,State}, []), + ?line State = sys:get_state(replace_state), + NState1 = "replaced", + Replace1 = fun(_) -> NState1 end, + ?line NState1 = sys:replace_state(replace_state, Replace1), + ?line NState1 = sys:get_state(replace_state), + ?line {ok, Pid} = gen_server:start_link(?MODULE, {state,NState1}, []), + ?line NState1 = sys:get_state(Pid), + Suffix = " again", + NState2 = NState1 ++ Suffix, + Replace2 = fun(S) -> S ++ Suffix end, + ?line NState2 = sys:replace_state(Pid, Replace2, 5000), + ?line NState2 = sys:get_state(Pid, 5000), + %% verify no change in state if replace function crashes + Replace3 = fun(_) -> throw(fail) end, + ?line NState2 = sys:replace_state(Pid, Replace3), + ?line NState2 = sys:get_state(Pid, 5000), + ok. + %% Test that the time for a huge message queue is not %% significantly slower than with an empty message queue. call_with_huge_message_queue(Config) when is_list(Config) -> |