diff options
138 files changed, 7945 insertions, 1391 deletions
@@ -36,6 +36,8 @@ Here are the [instructions for submitting patches] [2]. In short: +* Go to the JIRA issue tracker at [bugs.erlang.org] [7] to see reported issues which you can contribute to. Search for issues with the status *Contribution Needed*. + * We prefer to receive proposed updates via email on the [`erlang-patches`] [3] mailing list or through a pull request. @@ -58,8 +60,6 @@ In short: may suggest improvements that are needed before the change can be accepted and merged. -* Once or twice a week, a status email called ["What's cooking in Erlang/OTP"] [4] - will be sent to the [`erlang-patches`] [3] mailing list. Bug Reports -------------------------- @@ -91,8 +91,9 @@ Copyright and License [1]: http://www.erlang.org - [2]: http://wiki.github.com/erlang/otp/submitting-patches + [2]: http://wiki.github.com/erlang/otp/contribution-guidelines [3]: http://www.erlang.org/static/doc/mailinglist.html [4]: http://erlang.github.com/otp/ [5]: HOWTO/INSTALL.md [6]: https://github.com/erlang/otp/wiki/Bug-reports + [7]: http://bugs.erlang.org diff --git a/bootstrap/bin/start.boot b/bootstrap/bin/start.boot Binary files differindex df79888afc..cc0edd5427 100644 --- a/bootstrap/bin/start.boot +++ b/bootstrap/bin/start.boot diff --git a/bootstrap/bin/start_clean.boot b/bootstrap/bin/start_clean.boot Binary files differindex df79888afc..cc0edd5427 100644 --- a/bootstrap/bin/start_clean.boot +++ b/bootstrap/bin/start_clean.boot diff --git a/bootstrap/lib/compiler/ebin/beam_asm.beam b/bootstrap/lib/compiler/ebin/beam_asm.beam Binary files differindex 3249ce70a5..c8bc82022c 100644 --- a/bootstrap/lib/compiler/ebin/beam_asm.beam +++ b/bootstrap/lib/compiler/ebin/beam_asm.beam diff --git a/bootstrap/lib/compiler/ebin/cerl.beam b/bootstrap/lib/compiler/ebin/cerl.beam Binary files differindex 88857b8905..1fea28a4dc 100644 --- a/bootstrap/lib/compiler/ebin/cerl.beam +++ b/bootstrap/lib/compiler/ebin/cerl.beam diff --git a/bootstrap/lib/compiler/ebin/cerl_trees.beam b/bootstrap/lib/compiler/ebin/cerl_trees.beam Binary files differindex 37120929ba..a2c27861c4 100644 --- a/bootstrap/lib/compiler/ebin/cerl_trees.beam +++ b/bootstrap/lib/compiler/ebin/cerl_trees.beam diff --git a/bootstrap/lib/compiler/ebin/compile.beam b/bootstrap/lib/compiler/ebin/compile.beam Binary files differindex 0f121b4db9..b309544c39 100644 --- a/bootstrap/lib/compiler/ebin/compile.beam +++ b/bootstrap/lib/compiler/ebin/compile.beam diff --git a/bootstrap/lib/compiler/ebin/core_parse.beam b/bootstrap/lib/compiler/ebin/core_parse.beam Binary files differindex 9860250dbb..baf2c9d70f 100644 --- a/bootstrap/lib/compiler/ebin/core_parse.beam +++ b/bootstrap/lib/compiler/ebin/core_parse.beam diff --git a/bootstrap/lib/compiler/ebin/core_pp.beam b/bootstrap/lib/compiler/ebin/core_pp.beam Binary files differindex ddf504dfd2..8f97709248 100644 --- a/bootstrap/lib/compiler/ebin/core_pp.beam +++ b/bootstrap/lib/compiler/ebin/core_pp.beam diff --git a/bootstrap/lib/compiler/ebin/v3_core.beam b/bootstrap/lib/compiler/ebin/v3_core.beam Binary files differindex 4d4087948d..9b2450089a 100644 --- a/bootstrap/lib/compiler/ebin/v3_core.beam +++ b/bootstrap/lib/compiler/ebin/v3_core.beam diff --git a/bootstrap/lib/stdlib/ebin/erl_lint.beam b/bootstrap/lib/stdlib/ebin/erl_lint.beam Binary files differindex c4c3323013..22d3607866 100644 --- a/bootstrap/lib/stdlib/ebin/erl_lint.beam +++ b/bootstrap/lib/stdlib/ebin/erl_lint.beam diff --git a/bootstrap/lib/stdlib/ebin/gen.beam b/bootstrap/lib/stdlib/ebin/gen.beam Binary files differindex 8547d4e9f0..08735311fb 100644 --- a/bootstrap/lib/stdlib/ebin/gen.beam +++ b/bootstrap/lib/stdlib/ebin/gen.beam diff --git a/bootstrap/lib/stdlib/ebin/gen_event.beam b/bootstrap/lib/stdlib/ebin/gen_event.beam Binary files differindex 57bb7b50dc..2486647f46 100644 --- a/bootstrap/lib/stdlib/ebin/gen_event.beam +++ b/bootstrap/lib/stdlib/ebin/gen_event.beam diff --git a/bootstrap/lib/stdlib/ebin/gen_fsm.beam b/bootstrap/lib/stdlib/ebin/gen_fsm.beam Binary files differindex f78a7368d5..dbf38090af 100644 --- a/bootstrap/lib/stdlib/ebin/gen_fsm.beam +++ b/bootstrap/lib/stdlib/ebin/gen_fsm.beam diff --git a/bootstrap/lib/stdlib/ebin/gen_server.beam b/bootstrap/lib/stdlib/ebin/gen_server.beam Binary files differindex d0cb00c3e6..e7026a2a9a 100644 --- a/bootstrap/lib/stdlib/ebin/gen_server.beam +++ b/bootstrap/lib/stdlib/ebin/gen_server.beam diff --git a/bootstrap/lib/stdlib/ebin/gen_statem.beam b/bootstrap/lib/stdlib/ebin/gen_statem.beam Binary files differnew file mode 100644 index 0000000000..08cb19ca6f --- /dev/null +++ b/bootstrap/lib/stdlib/ebin/gen_statem.beam diff --git a/bootstrap/lib/stdlib/ebin/otp_internal.beam b/bootstrap/lib/stdlib/ebin/otp_internal.beam Binary files differindex c9881fdd18..e1ebd34afd 100644 --- a/bootstrap/lib/stdlib/ebin/otp_internal.beam +++ b/bootstrap/lib/stdlib/ebin/otp_internal.beam diff --git a/bootstrap/lib/stdlib/ebin/proc_lib.beam b/bootstrap/lib/stdlib/ebin/proc_lib.beam Binary files differindex ccc5b492e8..d3249b120e 100644 --- a/bootstrap/lib/stdlib/ebin/proc_lib.beam +++ b/bootstrap/lib/stdlib/ebin/proc_lib.beam diff --git a/bootstrap/lib/stdlib/ebin/stdlib.app b/bootstrap/lib/stdlib/ebin/stdlib.app index d33f06335e..4c8f196620 100644 --- a/bootstrap/lib/stdlib/ebin/stdlib.app +++ b/bootstrap/lib/stdlib/ebin/stdlib.app @@ -65,6 +65,7 @@ gen_event, gen_fsm, gen_server, + gen_statem, io, io_lib, io_lib_format, diff --git a/bootstrap/lib/stdlib/include/assert.hrl b/bootstrap/lib/stdlib/include/assert.hrl index 151794d114..9e5d4eb598 100644 --- a/bootstrap/lib/stdlib/include/assert.hrl +++ b/bootstrap/lib/stdlib/include/assert.hrl @@ -59,19 +59,22 @@ -define(assert(BoolExpr),ok). -else. %% The assert macro is written the way it is so as not to cause warnings -%% for clauses that cannot match, even if the expression is a constant. +%% for clauses that cannot match, even if the expression is a constant or +%% is known to be boolean-only. -define(assert(BoolExpr), begin ((fun () -> + __T = is_process_alive(self()), % cheap source of truth case (BoolExpr) of - true -> ok; + __T -> ok; __V -> erlang:error({assert, [{module, ?MODULE}, {line, ?LINE}, {expression, (??BoolExpr)}, {expected, true}, - case __V of false -> {value, __V}; - _ -> {not_boolean,__V} + case not __T of + __V -> {value, false}; + _ -> {not_boolean, __V} end]}) end end)()) @@ -85,15 +88,17 @@ -define(assertNot(BoolExpr), begin ((fun () -> + __F = not is_process_alive(self()), case (BoolExpr) of - false -> ok; + __F -> ok; __V -> erlang:error({assert, [{module, ?MODULE}, {line, ?LINE}, {expression, (??BoolExpr)}, {expected, false}, - case __V of true -> {value, __V}; - _ -> {not_boolean,__V} + case not __F of + __V -> {value, true}; + _ -> {not_boolean, __V} end]}) end end)()) @@ -149,7 +154,8 @@ -else. -define(assertEqual(Expect, Expr), begin - ((fun (__X) -> + ((fun () -> + __X = (Expect), case (Expr) of __X -> ok; __V -> erlang:error({assertEqual, @@ -159,7 +165,7 @@ {expected, __X}, {value, __V}]}) end - end)(Expect)) + end)()) end). -endif. @@ -169,7 +175,8 @@ -else. -define(assertNotEqual(Unexpected, Expr), begin - ((fun (__X) -> + ((fun () -> + __X = (Unexpected), case (Expr) of __X -> erlang:error({assertNotEqual, [{module, ?MODULE}, @@ -178,7 +185,7 @@ {value, __X}]}); _ -> ok end - end)(Unexpected)) + end)()) end). -endif. diff --git a/erts/doc/src/erl_dist_protocol.xml b/erts/doc/src/erl_dist_protocol.xml index b435d5c9b4..f9fa981d9a 100644 --- a/erts/doc/src/erl_dist_protocol.xml +++ b/erts/doc/src/erl_dist_protocol.xml @@ -364,14 +364,14 @@ If Result > 0, the packet only consists of [119, Result]. NodeInfo is, as expressed in Erlang: </p> <code> - io:format("active name ~ts at port ~p, fd = ~p ~n", + io:format("active name ~ts at port ~p, fd = ~p~n", [NodeName, Port, Fd]). </code> <p> or </p> <code> - io:format("old/unused name ~ts at port ~p, fd = ~p~n", + io:format("old/unused name ~ts at port ~p, fd = ~p ~n", [NodeName, Port, Fd]). </code> diff --git a/erts/emulator/beam/beam_bif_load.c b/erts/emulator/beam/beam_bif_load.c index b10250dc49..87508dcf5f 100644 --- a/erts/emulator/beam/beam_bif_load.c +++ b/erts/emulator/beam/beam_bif_load.c @@ -832,7 +832,7 @@ check_process_code(Process* rp, Module* modp, Uint flags, int *redsp) /* * Message queue can contains funs, but (at least currently) no - * constants. If we got references to this module from the message + * literals. If we got references to this module from the message * queue, a GC cannot remove these... */ @@ -853,7 +853,7 @@ check_process_code(Process* rp, Module* modp, Uint flags, int *redsp) for (; hfrag; hfrag = hfrag->next) { if (check_mod_funs(rp, &hfrag->off_heap, mod_start, mod_size)) return am_true; - /* Should not contain any constants... */ + /* Should not contain any literals... */ ASSERT(!any_heap_refs(&hfrag->mem[0], &hfrag->mem[hfrag->used_size], literals, @@ -908,7 +908,7 @@ check_process_code(Process* rp, Module* modp, Uint flags, int *redsp) #ifdef DEBUG /* * Message buffer fragments should not have any references - * to constants, and off heap lists should already have + * to literals, and off heap lists should already have * been moved into process off heap structure. */ for (msgp = rp->msg_frag; msgp; msgp = msgp->next) { @@ -945,7 +945,7 @@ check_process_code(Process* rp, Module* modp, Uint flags, int *redsp) need_gc &= ~done_gc; /* - * Try to get rid of constants by by garbage collecting. + * Try to get rid of literals by by garbage collecting. * Clear both fvalue and ftrace. */ diff --git a/erts/emulator/beam/erl_alloc.types b/erts/emulator/beam/erl_alloc.types index ad62a87326..ba216c7eb4 100644 --- a/erts/emulator/beam/erl_alloc.types +++ b/erts/emulator/beam/erl_alloc.types @@ -264,7 +264,6 @@ type PRTSD STANDARD SYSTEM port_specific_data type CPUDATA LONG_LIVED SYSTEM cpu_data type TMP_CPU_IDS SHORT_LIVED SYSTEM tmp_cpu_ids type EXT_TERM_DATA SHORT_LIVED PROCESSES external_term_data -type ZLIB STANDARD SYSTEM zlib type CPU_GRPS_MAP LONG_LIVED SYSTEM cpu_groups_map type AUX_WORK_TMO LONG_LIVED SYSTEM aux_work_timeouts type MISC_AUX_WORK_Q LONG_LIVED SYSTEM misc_aux_work_q @@ -297,8 +296,10 @@ type THR_Q_LL LONG_LIVED SYSTEM long_lived_thr_queue +if smp type ASYNC SHORT_LIVED SYSTEM async +type ZLIB STANDARD SYSTEM zlib +else -# sl_alloc is not thread safe in non smp build; therefore, we use driver_alloc +# sl/std_alloc is not thread safe in non smp build; therefore, we use driver_alloc +type ZLIB DRIVER SYSTEM zlib type ASYNC DRIVER SYSTEM async +endif diff --git a/erts/emulator/beam/erl_alloc_util.c b/erts/emulator/beam/erl_alloc_util.c index be7c16cc05..2995f2f822 100644 --- a/erts/emulator/beam/erl_alloc_util.c +++ b/erts/emulator/beam/erl_alloc_util.c @@ -860,8 +860,9 @@ erts_alcu_literal_32_mseg_alloc(Allctr_t *allctr, Uint *size_p, Uint flags) { void* res; Uint sz = ERTS_SUPERALIGNED_CEILING(*size_p); - ERTS_LC_ASSERT(allctr->alloc_no == ERTS_ALC_A_LITERAL - && !allctr->t && allctr->thread_safe); + ERTS_LC_ASSERT(allctr->alloc_no == ERTS_ALC_A_LITERAL && + allctr->t == 0); + ERTS_SMP_LC_ASSERT(allctr->thread_safe); res = erts_alcu_mseg_alloc(allctr, &sz, flags); if (res) { @@ -877,8 +878,9 @@ erts_alcu_literal_32_mseg_realloc(Allctr_t *allctr, void *seg, { void* res; Uint new_sz = ERTS_SUPERALIGNED_CEILING(*new_size_p); - ERTS_LC_ASSERT(allctr->alloc_no == ERTS_ALC_A_LITERAL - && !allctr->t && allctr->thread_safe); + ERTS_LC_ASSERT(allctr->alloc_no == ERTS_ALC_A_LITERAL && + allctr->t == 0); + ERTS_SMP_LC_ASSERT(allctr->thread_safe); if (seg && old_size) clear_literal_range(seg, old_size); @@ -894,8 +896,9 @@ void erts_alcu_literal_32_mseg_dealloc(Allctr_t *allctr, void *seg, Uint size, Uint flags) { - ERTS_LC_ASSERT(allctr->alloc_no == ERTS_ALC_A_LITERAL - && !allctr->t && allctr->thread_safe); + ERTS_LC_ASSERT(allctr->alloc_no == ERTS_ALC_A_LITERAL && + allctr->t == 0); + ERTS_SMP_LC_ASSERT(allctr->thread_safe); erts_alcu_mseg_dealloc(allctr, seg, size, flags); @@ -1007,8 +1010,9 @@ erts_alcu_literal_32_sys_alloc(Allctr_t *allctr, Uint* size_p, int superalign) { void* res; Uint size = ERTS_SUPERALIGNED_CEILING(*size_p); - ERTS_LC_ASSERT(allctr->alloc_no == ERTS_ALC_A_LITERAL - && !allctr->t && allctr->thread_safe); + ERTS_LC_ASSERT(allctr->alloc_no == ERTS_ALC_A_LITERAL && + allctr->t == 0); + ERTS_SMP_LC_ASSERT(allctr->thread_safe); res = erts_alcu_sys_alloc(allctr, &size, 1); if (res) { @@ -1024,8 +1028,9 @@ erts_alcu_literal_32_sys_realloc(Allctr_t *allctr, void *ptr, Uint* size_p, Uint void* res; Uint size = ERTS_SUPERALIGNED_CEILING(*size_p); - ERTS_LC_ASSERT(allctr->alloc_no == ERTS_ALC_A_LITERAL - && !allctr->t && allctr->thread_safe); + ERTS_LC_ASSERT(allctr->alloc_no == ERTS_ALC_A_LITERAL && + allctr->t == 0); + ERTS_SMP_LC_ASSERT(allctr->thread_safe); if (ptr && old_size) clear_literal_range(ptr, old_size); @@ -1040,8 +1045,9 @@ erts_alcu_literal_32_sys_realloc(Allctr_t *allctr, void *ptr, Uint* size_p, Uint void erts_alcu_literal_32_sys_dealloc(Allctr_t *allctr, void *ptr, Uint size, int superalign) { - ERTS_LC_ASSERT(allctr->alloc_no == ERTS_ALC_A_LITERAL - && !allctr->t && allctr->thread_safe); + ERTS_LC_ASSERT(allctr->alloc_no == ERTS_ALC_A_LITERAL && + allctr->t == 0); + ERTS_SMP_LC_ASSERT(allctr->thread_safe); erts_alcu_sys_dealloc(allctr, ptr, size, 1); diff --git a/erts/emulator/beam/erl_gc.c b/erts/emulator/beam/erl_gc.c index f33ade27f3..4698458521 100644 --- a/erts/emulator/beam/erl_gc.c +++ b/erts/emulator/beam/erl_gc.c @@ -2053,8 +2053,26 @@ copy_one_frag(Eterm** hpp, ErlOffHeap* off_heap, *hp++ = val; break; case TAG_PRIMARY_LIST: +#ifdef SHCOPY_SEND + if (erts_is_literal(val,list_val(val))) { + *hp++ = val; + } else { + *hp++ = offset_ptr(val, offs); + } +#else + *hp++ = offset_ptr(val, offs); +#endif + break; case TAG_PRIMARY_BOXED: - *hp++ = offset_ptr(val, offs); +#ifdef SHCOPY_SEND + if (erts_is_literal(val,boxed_val(val))) { + *hp++ = val; + } else { + *hp++ = offset_ptr(val, offs); + } +#else + *hp++ = offset_ptr(val, offs); +#endif break; case TAG_PRIMARY_HEADER: *hp++ = val; diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c index 1a66044627..d485affa3b 100644 --- a/erts/emulator/beam/erl_process.c +++ b/erts/emulator/beam/erl_process.c @@ -10932,9 +10932,7 @@ erl_create_process(Process* parent, /* Parent of process (default group leader). INITIALIZE_SHCOPY(info); #endif -#ifdef ERTS_SMP erts_smp_proc_lock(parent, ERTS_PROC_LOCKS_ALL_MINOR); -#endif /* * Check for errors. @@ -11235,6 +11233,8 @@ erl_create_process(Process* parent, /* Parent of process (default group leader). * Schedule process for execution. */ + erts_smp_proc_unlock(parent, locks & ERTS_PROC_LOCKS_ALL_MINOR); + schedule_process(p, state, 0); VERBOSE(DEBUG_PROCESSES, ("Created a new process: %T\n",p->common.id)); @@ -11248,6 +11248,7 @@ erl_create_process(Process* parent, /* Parent of process (default group leader). DTRACE2(process_spawn, process_name, mfa); } #endif + return res; error: diff --git a/erts/preloaded/ebin/init.beam b/erts/preloaded/ebin/init.beam Binary files differindex b13b33170d..ee32066f53 100644 --- a/erts/preloaded/ebin/init.beam +++ b/erts/preloaded/ebin/init.beam diff --git a/erts/preloaded/src/init.erl b/erts/preloaded/src/init.erl index 77684751c8..618b53f6bb 100644 --- a/erts/preloaded/src/init.erl +++ b/erts/preloaded/src/init.erl @@ -41,6 +41,7 @@ %% -s : Start own processes. %% %% Experimental flags: +%% -profile_boot : Use an 'eprof light' to profile boot sequence %% -init_debug : Activate debug printouts in init %% -loader_debug : Activate debug printouts in erl_prim_loader %% -code_path_choice : strict | relaxed @@ -184,6 +185,11 @@ boot(BootArgs) -> erl_tracer:on_load(), {Start0,Flags,Args} = parse_boot_args(BootArgs), + %% We don't get to profile parsing of BootArgs + case get_flag(profile_boot, Flags, false) of + false -> ok; + true -> debug_profile_start() + end, Start = map(fun prepare_run_args/1, Start0), boot(Start, Flags, Args). @@ -765,7 +771,14 @@ do_boot(Init,Flags,Start) -> %% print the node name into the Purify log. (catch erlang:system_info({purify, "Node: " ++ atom_to_list(node())})), - start_em(Start). + start_em(Start), + case get_flag(profile_boot,Flags,false) of + false -> ok; + true -> + debug_profile_format_mfas(debug_profile_mfas()), + debug_profile_stop() + end, + ok. get_root(Flags) -> case get_argument(root, Flags) of @@ -1339,3 +1352,64 @@ run_on_load_handlers([M|Ms], Debug) -> end end; run_on_load_handlers([], _) -> ok. + + +%% debug profile (light variant of eprof) +debug_profile_start() -> + _ = erlang:trace_pattern({'_','_','_'},true,[call_time]), + _ = erlang:trace_pattern(on_load,true,[call_time]), + _ = erlang:trace(all,true,[call]), + ok. + +debug_profile_stop() -> + _ = erlang:trace_pattern({'_','_','_'},false,[call_time]), + _ = erlang:trace_pattern(on_load,false,[call_time]), + _ = erlang:trace(all,false,[call]), + ok. + +debug_profile_mfas() -> + _ = erlang:trace_pattern({'_','_','_'},pause,[call_time]), + _ = erlang:trace_pattern(on_load,pause,[call_time]), + MFAs = collect_loaded_mfas() ++ erlang:system_info(snifs), + collect_mfas(MFAs,[]). + +%% debug_profile_format_mfas should be called at the end of the boot phase +%% so all pertinent modules should be loaded at that point. +debug_profile_format_mfas(MFAs0) -> + MFAs = lists:sort(MFAs0), + lists:foreach(fun({{Us,C},{M,F,A}}) -> + Str = io_lib:format("~w:~w/~w", [M,F,A]), + io:format(standard_error,"~55s - ~6w : ~w us~n", [Str,C,Us]) + end, MFAs), + ok. + +collect_loaded_mfas() -> + Ms = [M || M <- [element(1, Mi) || Mi <- code:all_loaded()]], + collect_loaded_mfas(Ms,[]). + +collect_loaded_mfas([],MFAs) -> MFAs; +collect_loaded_mfas([M|Ms],MFAs0) -> + MFAs = [{M,F,A} || {F,A} <- M:module_info(functions)], + collect_loaded_mfas(Ms,MFAs ++ MFAs0). + + +collect_mfas([], Info) -> Info; +collect_mfas([MFA|MFAs],Info) -> + case erlang:trace_info(MFA,call_time) of + {call_time, []} -> + collect_mfas(MFAs,Info); + {call_time, false} -> + collect_mfas(MFAs,Info); + {call_time, Data} -> + case collect_mfa(MFA,Data,0,0) of + {{0,_},_} -> + %% ignore mfas with zero time + collect_mfas(MFAs,Info); + MfaData -> + collect_mfas(MFAs,[MfaData|Info]) + end + end. + +collect_mfa(Mfa,[],Count,Time) -> {{Time,Count},Mfa}; +collect_mfa(Mfa,[{_Pid,C,S,Us}|Data],Count,Time) -> + collect_mfa(Mfa,Data,Count + C,Time + S * 1000000 + Us). diff --git a/erts/test/nt_SUITE.erl b/erts/test/nt_SUITE.erl index f798a40a6c..624e5484ba 100644 --- a/erts/test/nt_SUITE.erl +++ b/erts/test/nt_SUITE.erl @@ -37,13 +37,13 @@ suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap, {minutes, 3}}]. -all() -> - case os:type() of - {win32, nt} -> - [nt, service_basic, service_env, user_env, synced, - service_prio, logout, debug, restart, restart_always, - stopaction]; - _ -> [nt] +all() -> + case {os:type(), os:version()} of + {{win32, nt}, Vsn} when Vsn =< {6,1,999999} -> + [nt, service_basic, service_env, user_env, synced, + service_prio, logout, debug, restart, restart_always, + stopaction]; + _ -> [nt] end. init_per_testcase(_Func, Config) -> @@ -367,11 +367,13 @@ stopaction(Config) when is_list(Config) -> %%% other platforms than NT. nt(Config) when is_list(Config) -> - case os:type() of - {win32,nt} -> - nt_run(); - _ -> - {skipped, "This test case is intended for Win NT only."} + case {os:type(), os:version()} of + {{win32, nt}, Vsn} when Vsn =< {6,1,999999} -> + nt_run(); + {{win32, nt}, _} -> + {skipped, "This test case requires admin privileges on Win 8 and later."}; + _ -> + {skipped, "This test case is intended for Win NT only."} end. diff --git a/lib/common_test/test_server/ts_run.erl b/lib/common_test/test_server/ts_run.erl index a856fc6f8d..66db1ff9a7 100644 --- a/lib/common_test/test_server/ts_run.erl +++ b/lib/common_test/test_server/ts_run.erl @@ -261,7 +261,7 @@ run_batch(Vars, _Spec, State) -> Command = State#state.command ++ " -noinput -s erlang halt", ts_lib:progress(Vars, 1, "Command: ~ts~n", [Command]), io:format(user, "Command: ~ts~n",[Command]), - Port = open_port({spawn, Command}, [stream, in, eof]), + Port = open_port({spawn, Command}, [stream, in, eof, exit_status]), Timeout = 30000 * case os:getenv("TS_RUN_VALGRIND") of false -> 1; _ -> 100 @@ -284,7 +284,16 @@ tricky_print_data(Port, Timeout) -> ok after 1 -> % force context switch ok - end + end, + receive + {Port, {exit_status, 0}} -> + ok; + {Port, {exit_status, N}} -> + io:format(user, "Test run exited with status ~p~n", [N]) + after 1 -> + %% This shouldn't happen, but better safe then hanging + ok + end after Timeout -> case erl_epmd:names() of {ok,Names} -> diff --git a/lib/compiler/src/beam_asm.erl b/lib/compiler/src/beam_asm.erl index 9a81537006..f6ca7a0afb 100644 --- a/lib/compiler/src/beam_asm.erl +++ b/lib/compiler/src/beam_asm.erl @@ -230,9 +230,7 @@ build_attributes(Opts, SourceFile, Attr, MD5) -> [_|_] -> [{source,SourceFile}] end, Misc = case member(slim, Opts) of - false -> - {{Y,Mo,D},{H,Mi,S}} = erlang:universaltime(), - [{time,{Y,Mo,D,H,Mi,S}}|Misc0]; + false -> Misc0; true -> [] end, Compile = [{options,Opts},{version,?COMPILER_VSN}|Misc], diff --git a/lib/compiler/src/cerl.erl b/lib/compiler/src/cerl.erl index d033050d3c..37ec4e97c9 100644 --- a/lib/compiler/src/cerl.erl +++ b/lib/compiler/src/cerl.erl @@ -126,6 +126,7 @@ %% keep map exports here for now c_map_pattern/1, is_c_map/1, + is_c_map_pattern/1, map_es/1, map_arg/1, update_c_map/3, @@ -1636,6 +1637,11 @@ is_c_map_empty(#c_map{ es=[] }) -> true; is_c_map_empty(#c_literal{val=M}) when is_map(M),map_size(M) =:= 0 -> true; is_c_map_empty(_) -> false. +-spec is_c_map_pattern(c_map()) -> boolean(). + +is_c_map_pattern(#c_map{is_pat=IsPat}) -> + IsPat. + -spec ann_c_map([term()], [c_map_pair()]) -> c_map() | c_literal(). ann_c_map(As, Es) -> diff --git a/lib/compiler/src/cerl_trees.erl b/lib/compiler/src/cerl_trees.erl index 6d38748964..b3decbec1f 100644 --- a/lib/compiler/src/cerl_trees.erl +++ b/lib/compiler/src/cerl_trees.erl @@ -61,6 +61,7 @@ map_arg/1, map_es/1, ann_c_map/3, update_c_map/3, + is_c_map_pattern/1, ann_c_map_pattern/2, map_pair_key/1,map_pair_val/1,map_pair_op/1, ann_c_map_pair/4, update_c_map_pair/4 @@ -752,10 +753,17 @@ label(T, N, Env) -> {As, N2} = label_ann(T, N1), {ann_c_tuple_skel(As, Ts), N2}; map -> - {M, N1} = label(map_arg(T), N, Env), - {Ts, N2} = label_list(map_es(T), N1, Env), - {As, N3} = label_ann(T, N2), - {ann_c_map(As, M, Ts), N3}; + case is_c_map_pattern(T) of + false -> + {M, N1} = label(map_arg(T), N, Env), + {Ts, N2} = label_list(map_es(T), N1, Env), + {As, N3} = label_ann(T, N2), + {ann_c_map(As, M, Ts), N3}; + true -> + {Ts, N1} = label_list(map_es(T), N, Env), + {As, N2} = label_ann(T, N1), + {ann_c_map_pattern(As, Ts), N2} + end; map_pair -> {Op, N1} = label(map_pair_op(T), N, Env), {Key, N2} = label(map_pair_key(T), N1, Env), diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index 65566df025..149086152a 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -1317,7 +1317,7 @@ generate_key(String) when is_list(String) -> encrypt({des3_cbc=Type,Key,IVec,BlockSize}, Bin0) -> Bin1 = case byte_size(Bin0) rem BlockSize of 0 -> Bin0; - N -> list_to_binary([Bin0,crypto:rand_bytes(BlockSize-N)]) + N -> list_to_binary([Bin0,crypto:strong_rand_bytes(BlockSize-N)]) end, Bin = crypto:block_encrypt(Type, Key, IVec, Bin1), TypeString = atom_to_list(Type), @@ -1798,4 +1798,5 @@ pre_load() -> v3_core, v3_kernel, v3_life], - code:ensure_modules_loaded(L). + _ = code:ensure_modules_loaded(L), + ok. diff --git a/lib/compiler/src/core_parse.yrl b/lib/compiler/src/core_parse.yrl index 315324e906..8028aa99bb 100644 --- a/lib/compiler/src/core_parse.yrl +++ b/lib/compiler/src/core_parse.yrl @@ -47,12 +47,14 @@ receive_expr timeout try_expr sequence catch_expr variable clause clause_pattern -map_expr map_pairs map_pair map_pair_assoc map_pair_exact +map_expr anno_map_expr map_pairs anno_map_pair map_pair map_pair_assoc map_pair_exact map_pattern map_pair_patterns map_pair_pattern -annotation anno_fun anno_expression anno_expressions +annotation anno_atom anno_fun anno_expression anno_expressions anno_variable anno_variables anno_pattern anno_patterns anno_function_name +anno_literal +anno_segment anno_segment_pattern anno_clause anno_clauses. Terminals @@ -90,7 +92,7 @@ module_definition -> module_definition -> '(' 'module' atom module_export module_attribute module_defs 'end' '-|' annotation ')' : - #c_module{anno='$9',name=tok_val('$3'),exports='$4', + #c_module{anno='$9',name=#c_literal{val=tok_val('$3')},exports='$4', attrs='$5',defs='$6'}. module_export -> '[' ']' : []. @@ -99,7 +101,7 @@ module_export -> '[' exported_names ']' : '$2'. exported_names -> exported_name ',' exported_names : ['$1' | '$3']. exported_names -> exported_name : ['$1']. -exported_name -> function_name : '$1'. +exported_name -> anno_function_name : '$1'. module_attribute -> 'attributes' '[' ']' : []. module_attribute -> 'attributes' '[' attribute_list ']' : '$3'. @@ -107,8 +109,16 @@ module_attribute -> 'attributes' '[' attribute_list ']' : '$3'. attribute_list -> attribute ',' attribute_list : ['$1' | '$3']. attribute_list -> attribute : ['$1']. -attribute -> atom '=' literal : - {#c_literal{val=tok_val('$1')},'$3'}. +attribute -> anno_atom '=' anno_literal : + {'$1','$3'}. + +anno_atom -> atom : + cerl:c_atom(tok_val('$1')). +anno_atom -> '(' atom '-|' annotation ')' : + cerl:ann_c_atom('$4', tok_val('$2')). + +anno_literal -> literal : '$1'. +anno_literal -> '(' literal '-|' annotation ')' : cerl:set_ann('$2', '$4'). module_defs -> function_definitions : '$1'. @@ -186,7 +196,9 @@ tuple_pattern -> '{' anno_patterns '}' : c_tuple('$2'). map_pattern -> '~' '{' '}' '~' : c_map_pattern([]). map_pattern -> '~' '{' map_pair_patterns '}' '~' : - c_map_pattern(lists:sort('$3')). + c_map_pattern('$3'). +map_pattern -> '~' '{' map_pair_patterns '|' anno_map_expr '}' '~' : + ann_c_map_pattern('$5', '$3'). map_pair_patterns -> map_pair_pattern : ['$1']. map_pair_patterns -> map_pair_pattern ',' map_pair_patterns : ['$1' | '$3']. @@ -194,6 +206,9 @@ map_pair_patterns -> map_pair_pattern ',' map_pair_patterns : ['$1' | '$3']. map_pair_pattern -> anno_expression ':=' anno_pattern : #c_map_pair{op=#c_literal{val=exact}, key='$1',val='$3'}. +map_pair_pattern -> '(' anno_expression ':=' anno_pattern '-|' annotation ')' : + #c_map_pair{anno='$6',op=#c_literal{val=exact}, + key='$2',val='$4'}. cons_pattern -> '[' anno_pattern tail_pattern : c_cons('$2', '$3'). @@ -206,8 +221,12 @@ tail_pattern -> ',' anno_pattern tail_pattern : binary_pattern -> '#' '{' '}' '#' : #c_binary{segments=[]}. binary_pattern -> '#' '{' segment_patterns '}' '#' : #c_binary{segments='$3'}. -segment_patterns -> segment_pattern ',' segment_patterns : ['$1' | '$3']. -segment_patterns -> segment_pattern : ['$1']. +segment_patterns -> anno_segment_pattern ',' segment_patterns : ['$1' | '$3']. +segment_patterns -> anno_segment_pattern : ['$1']. + +anno_segment_pattern -> segment_pattern : '$1'. +anno_segment_pattern -> '(' segment_pattern '-|' annotation ')' : + cerl:set_ann('$2', '$4'). segment_pattern -> '#' '<' anno_pattern '>' '(' anno_expressions ')': case '$6' of @@ -289,11 +308,17 @@ tuple -> '{' anno_expressions '}' : c_tuple('$2'). map_expr -> '~' '{' '}' '~' : c_map([]). map_expr -> '~' '{' map_pairs '}' '~' : c_map('$3'). -map_expr -> '~' '{' map_pairs '|' variable '}' '~' : ann_c_map([], '$5', '$3'). -map_expr -> '~' '{' map_pairs '|' map_expr '}' '~' : ann_c_map([], '$5', '$3'). +map_expr -> '~' '{' map_pairs '|' anno_variable '}' '~' : ann_c_map([], '$5', '$3'). +map_expr -> '~' '{' map_pairs '|' anno_map_expr '}' '~' : ann_c_map([], '$5', '$3'). -map_pairs -> map_pair : ['$1']. -map_pairs -> map_pair ',' map_pairs : ['$1' | '$3']. +anno_map_expr -> map_expr : '$1'. +anno_map_expr -> '(' map_expr '-|' annotation ')' : cerl:set_ann('$2', '$4'). + +map_pairs -> anno_map_pair : ['$1']. +map_pairs -> anno_map_pair ',' map_pairs : ['$1' | '$3']. + +anno_map_pair -> map_pair : '$1'. +anno_map_pair -> '(' map_pair '-|' annotation ')' : cerl:set_ann('$2', '$4'). map_pair -> map_pair_assoc : '$1'. map_pair -> map_pair_exact : '$1'. @@ -312,8 +337,11 @@ tail -> ',' anno_expression tail : c_cons('$2', '$3'). binary -> '#' '{' '}' '#' : #c_literal{val = <<>>}. binary -> '#' '{' segments '}' '#' : make_binary('$3'). -segments -> segment ',' segments : ['$1' | '$3']. -segments -> segment : ['$1']. +segments -> anno_segment ',' segments : ['$1' | '$3']. +segments -> anno_segment : ['$1']. + +anno_segment -> segment : '$1'. +anno_segment -> '(' segment '-|' annotation ')' : cerl:set_ann('$2', '$4'). segment -> '#' '<' anno_expression '>' '(' anno_expressions ')': case '$6' of @@ -413,7 +441,8 @@ Erlang code. -include("core_parse.hrl"). --import(cerl, [ann_c_map/3,c_cons/2,c_map/1,c_map_pattern/1,c_tuple/1]). +-import(cerl, [ann_c_map/3,ann_c_map_pattern/2,c_cons/2,c_map/1, + c_map_pattern/1,c_tuple/1]). tok_val(T) -> element(3, T). tok_line(T) -> element(2, T). diff --git a/lib/compiler/src/core_pp.erl b/lib/compiler/src/core_pp.erl index 78a081e9ca..88275998be 100644 --- a/lib/compiler/src/core_pp.erl +++ b/lib/compiler/src/core_pp.erl @@ -21,7 +21,7 @@ -module(core_pp). --export([format/1]). +-export([format/1,format_all/1]). -include("core_parse.hrl"). @@ -38,20 +38,30 @@ item_indent = 2 :: integer(), body_indent = 4 :: integer(), tab_width = 8 :: non_neg_integer(), - line = 0 :: integer()}). + line = 0 :: integer(), + clean = true :: boolean()}). -spec format(cerl:cerl()) -> iolist(). format(Node) -> format(Node, #ctxt{}). -maybe_anno(Node, Fun, Ctxt) -> +-spec format_all(cerl:cerl()) -> iolist(). + +format_all(Node) -> + format(Node, #ctxt{clean=false}). + +maybe_anno(Node, Fun, #ctxt{clean=false}=Ctxt) -> As = cerl:get_ann(Node), - case get_line(As) of + maybe_anno(Node, Fun, Ctxt, As); +maybe_anno(Node, Fun, #ctxt{clean=true}=Ctxt) -> + As0 = cerl:get_ann(Node), + case get_line(As0) of none -> - maybe_anno(Node, Fun, Ctxt, As); + maybe_anno(Node, Fun, Ctxt, As0); Line -> - if Line > Ctxt#ctxt.line -> + As = strip_line(As0), + if Line > Ctxt#ctxt.line -> [io_lib:format("%% Line ~w",[Line]), nl_indent(Ctxt), maybe_anno(Node, Fun, Ctxt#ctxt{line = Line}, As) @@ -61,22 +71,22 @@ maybe_anno(Node, Fun, Ctxt) -> end end. -maybe_anno(Node, Fun, Ctxt, As) -> - case strip_line(As) of - [] -> - Fun(Node, Ctxt); - List -> - Ctxt1 = add_indent(Ctxt, 2), - Ctxt2 = add_indent(Ctxt1, 3), - ["( ", - Fun(Node, Ctxt1), - nl_indent(Ctxt1), - "-| ",format_anno(List, Ctxt2)," )" - ] - end. +maybe_anno(Node, Fun, Ctxt, []) -> + Fun(Node, Ctxt); +maybe_anno(Node, Fun, Ctxt, List) -> + Ctxt1 = add_indent(Ctxt, 2), + Ctxt2 = add_indent(Ctxt1, 3), + ["( ", + Fun(Node, Ctxt1), + nl_indent(Ctxt1), + "-| ",format_anno(List, Ctxt2)," )" + ]. format_anno([_|_]=List, Ctxt) -> [$[,format_anno_list(List, Ctxt),$]]; +format_anno({file,Name}, _Ctxt) -> + %% Optimization: Reduces file size considerably. + io_lib:format("{'file',~p}", [Name]); format_anno(Tuple, Ctxt) when is_tuple(Tuple) -> [${,format_anno_list(tuple_to_list(Tuple), Ctxt),$}]; format_anno(Val, Ctxt) when is_atom(Val) -> @@ -172,7 +182,8 @@ format_1(#c_tuple{es=Es}, Ctxt) -> format_hseq(Es, ",", add_indent(Ctxt, 1), fun format/2), $} ]; -format_1(#c_map{arg=#c_literal{val=M},es=Es}, Ctxt) when is_map(M),map_size(M)=:=0 -> +format_1(#c_map{arg=#c_literal{anno=[],val=M},es=Es}, Ctxt) + when is_map(M), map_size(M) =:= 0 -> ["~{", format_hseq(Es, ",", add_indent(Ctxt, 1), fun format/2), "}~" @@ -195,9 +206,16 @@ format_1(#c_values{es=Es}, Ctxt) -> format_1(#c_alias{var=V,pat=P}, Ctxt) -> Txt = [format(V, Ctxt)|" = "], [Txt|format(P, add_indent(Ctxt, width(Txt, Ctxt)))]; -format_1(#c_let{vars=Vs0,arg=A,body=B}, Ctxt) -> - Vs = [cerl:set_ann(V, []) || V <- Vs0], - case is_simple_term(A) of +format_1(#c_let{anno=Anno0,vars=Vs0,arg=A0,body=B}, #ctxt{clean=Clean}=Ctxt) -> + {Vs,A,Anno} = case Clean of + false -> + {Vs0,A0,Anno0}; + true -> + {[cerl:set_ann(V, []) || V <- Vs0], + cerl:set_ann(A0, []), + []} + end, + case is_simple_term(A) andalso Anno =:= [] of false -> Ctxt1 = add_indent(Ctxt, Ctxt#ctxt.body_indent), ["let ", @@ -214,7 +232,7 @@ format_1(#c_let{vars=Vs0,arg=A,body=B}, Ctxt) -> ["let ", format_values(Vs, add_indent(Ctxt, 4)), " = ", - format(cerl:set_ann(A, []), Ctxt1), + format(A, Ctxt1), nl_indent(Ctxt), "in " | format(B, add_indent(Ctxt, 4)) @@ -362,7 +380,10 @@ format_values(Vs, Ctxt) -> format_hseq(Vs, ",", add_indent(Ctxt, 1), fun format/2), $>]. -format_bitstr(#c_bitstr{val=V,size=S,unit=U,type=T,flags=Fs}, Ctxt0) -> +format_bitstr(Node, Ctxt) -> + maybe_anno(Node, fun do_format_bitstr/2, Ctxt). + +do_format_bitstr(#c_bitstr{val=V,size=S,unit=U,type=T,flags=Fs}, Ctxt0) -> Vs = [S, U, T, Fs], Ctxt1 = add_indent(Ctxt0, 2), Val = format(V, Ctxt1), @@ -387,7 +408,7 @@ format_clause_1(#c_clause{pats=Ps,guard=G,body=B}, Ctxt) -> width(Ptxt, Ctxt) + 6))]; false -> [nl_indent(Ctxt2), "when ", - format_guard(G, add_indent(Ctxt2, 2))] + format_guard(G, add_indent(set_class(Ctxt2, expr), 2))] end++ " ->", nl_indent(Ctxt2) diff --git a/lib/compiler/src/rec_env.erl b/lib/compiler/src/rec_env.erl index 2b89dc628c..936c5f6106 100644 --- a/lib/compiler/src/rec_env.erl +++ b/lib/compiler/src/rec_env.erl @@ -603,7 +603,8 @@ generate(_N, Range) -> %% same BEAM code. case rand:export_seed() of undefined -> - rand:seed(exsplus, {1,42,2053}); + _ = rand:seed(exsplus, {1,42,2053}), + ok; _ -> ok end, diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl index de775097e3..3299149457 100644 --- a/lib/compiler/src/v3_core.erl +++ b/lib/compiler/src/v3_core.erl @@ -784,7 +784,7 @@ badmap_term(_Map, #core{in_guard=true}) -> %% since it is not user-visible. #c_literal{val=badmap}; badmap_term(Map, #core{in_guard=false}) -> - #c_tuple{es=[#c_literal{val=badmap},Map]}. + c_tuple([#c_literal{val=badmap},Map]). map_build_pairs(Map, Es0, Ann, St0) -> {Es,Pre,St1} = map_build_pairs_1(Es0, St0), diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl index e1db99b357..f55ea9a3a6 100644 --- a/lib/compiler/test/compile_SUITE.erl +++ b/lib/compiler/test/compile_SUITE.erl @@ -31,7 +31,8 @@ binary/1, makedep/1, cond_and_ifdef/1, listings/1, listings_big/1, other_output/1, encrypted_abstr/1, strict_record/1, - missing_testheap/1, cover/1, env/1, core/1, asm/1, + missing_testheap/1, cover/1, env/1, core/1, + core_roundtrip/1, asm/1, sys_pre_attributes/1, dialyzer/1, warnings/1, pre_load_check/1 ]). @@ -50,7 +51,7 @@ all() -> binary, makedep, cond_and_ifdef, listings, listings_big, other_output, encrypted_abstr, strict_record, - missing_testheap, cover, env, core, asm, + missing_testheap, cover, env, core, core_roundtrip, asm, sys_pre_attributes, dialyzer, warnings, pre_load_check]. groups() -> @@ -545,7 +546,6 @@ verify_abstract(Target) -> has_crypto() -> try crypto:start(), - <<_,_,_,_,_>> = crypto:rand_bytes(5), crypto:stop(), true catch @@ -790,6 +790,120 @@ compile_forms(Forms, Opts) -> Other -> throw({error,Other}) end. +%% Pretty-print core and read it back. Should be identical. + +core_roundtrip(Config) -> + PrivDir = proplists:get_value(priv_dir, Config), + Outdir = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME)), + ok = file:make_dir(Outdir), + + Wc = filename:join(filename:dirname(code:which(?MODULE)), "*.beam"), + TestBeams = filelib:wildcard(Wc), + test_lib:p_run(fun(F) -> do_core_roundtrip(F, Outdir) end, TestBeams). + +do_core_roundtrip(Beam, Outdir) -> + try + {ok,{Mod,[{abstract_code,{raw_abstract_v1,Abstr}}]}} = + beam_lib:chunks(Beam, [abstract_code]), + do_core_roundtrip_1(Mod, Abstr, Outdir) + catch + throw:{error,Error} -> + io:format("*** compilation failure '~p' for file ~s\n", + [Error,Beam]), + error; + Class:Error -> + io:format("~p: ~p ~p\n~p\n", + [Beam,Class,Error,erlang:get_stacktrace()]), + error + end. + +do_core_roundtrip_1(Mod, Abstr, Outdir) -> + {ok,Mod,Core0} = compile:forms(Abstr, [to_core0]), + do_core_roundtrip_2(Mod, Core0, Outdir), + + %% Primarily, test that annotations are accepted for all + %% constructs. Secondarily, smoke test cerl_trees:label/1. + {Core,_} = cerl_trees:label(Core0), + do_core_roundtrip_2(Mod, Core, Outdir). + +do_core_roundtrip_2(M, Core0, Outdir) -> + CoreFile = filename:join(Outdir, atom_to_list(M)++".core"), + CorePP = core_pp:format_all(Core0), + ok = file:write_file(CoreFile, CorePP), + + %% Parse the .core file and return the result as Core Erlang Terms. + Core2 = case compile:file(CoreFile, [report_errors,from_core, + no_copt,to_core,binary]) of + {ok,M,Core1} -> Core1; + Other -> throw({error,Other}) + end, + Core = undo_var_translation(Core2), + ok = file:delete(CoreFile), + + case cmp_core(Core0, Core, M) of + true -> ok; + false -> error + end, + + ok. + +undo_var_translation(Tree) -> + F = fun(Node) -> + case cerl:is_c_var(Node) of + true -> + Name0 = cerl:var_name(Node), + try atom_to_list(Name0) of + "_X"++Name -> + cerl:update_c_var(Node, list_to_atom(Name)); + "_"++Name -> + cerl:update_c_var(Node, list_to_atom(Name)); + _ -> + Node + catch + error:badarg -> + Node + + end; + false -> + Node + end + end, + cerl_trees:map(F, Tree). + +cmp_core(E, E, _Mod) -> + true; +cmp_core(M1, M2, Mod) -> + cmp_core_fs(cerl:module_defs(M1), cerl:module_defs(M2), Mod). + +cmp_core_fs([F1|T1], [F2|T2], Mod) -> + cmp_core_f(F1, F2, Mod) andalso cmp_core_fs(T1, T2, Mod); +cmp_core_fs([], [], _Mod) -> + true; +cmp_core_fs(_, _, _Mod) -> + false. + +cmp_core_f(E, E, _Mod) -> + true; +cmp_core_f({Name,F1}, {Name,F2}, Mod) -> + case diff(F1, F2) of + F1 -> + true; + Diff -> + io:format("~p ~p:\n~p\n", [Mod,Name,Diff]), + false + end. + +diff(E, E) -> + E; +diff([H1|T1], [H2|T2]) -> + [diff(H1, H2)|diff(T1, T2)]; +diff(T1, T2) when tuple_size(T1) =:= tuple_size(T2) -> + L = diff(tuple_to_list(T1), tuple_to_list(T2)), + list_to_tuple(L); +diff(E1, E2) -> + {'DIFF',E1,E2}. + + %% Compile to Beam assembly language (.S) and then try to %% run .S through the compiler again. diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c index 1be22a0b8a..067e220863 100644 --- a/lib/crypto/c_src/crypto.c +++ b/lib/crypto/c_src/crypto.c @@ -214,7 +214,6 @@ static ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_ static ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM rand_bytes_1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM strong_rand_bytes_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM rand_bytes_3(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM strong_rand_mpint_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM mod_exp_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -285,7 +284,6 @@ static ErlNifFunc nif_funcs[] = { {"aes_ctr_stream_decrypt", 2, aes_ctr_stream_encrypt}, {"rand_bytes", 1, rand_bytes_1}, {"strong_rand_bytes_nif", 1, strong_rand_bytes_nif}, - {"rand_bytes", 3, rand_bytes_3}, {"strong_rand_mpint_nif", 3, strong_rand_mpint_nif}, {"rand_uniform_nif", 2, rand_uniform_nif}, {"mod_exp_nif", 4, mod_exp_nif}, @@ -1927,27 +1925,7 @@ static ERL_NIF_TERM strong_rand_bytes_nif(ErlNifEnv* env, int argc, const ERL_NI return ret; } -static ERL_NIF_TERM rand_bytes_3(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Bytes, TopMask, BottomMask) */ - unsigned bytes; - unsigned char* data; - unsigned top_mask, bot_mask; - ERL_NIF_TERM ret; - if (!enif_get_uint(env, argv[0], &bytes) - || !enif_get_uint(env, argv[1], &top_mask) - || !enif_get_uint(env, argv[2], &bot_mask)) { - return enif_make_badarg(env); - } - data = enif_make_new_binary(env, bytes, &ret); - RAND_pseudo_bytes(data, bytes); - ERL_VALGRIND_MAKE_MEM_DEFINED(data, bytes); - if (bytes > 0) { - data[bytes-1] |= top_mask; - data[0] |= bot_mask; - } - return ret; -} static ERL_NIF_TERM strong_rand_mpint_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {/* (Bytes, TopMask, BottomMask) */ unsigned bits; diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml index e0b989436f..5a5627747c 100644 --- a/lib/crypto/doc/src/crypto.xml +++ b/lib/crypto/doc/src/crypto.xml @@ -308,6 +308,8 @@ <desc> <p>Generates public keys of type <c>Type</c>. See also <seealso marker="public_key:public_key#generate_key-1">public_key:generate_key/1</seealso> + May throw exception <c>low_entropy</c> in case the random generator + failed due to lack of secure "randomness". </p> </desc> </func> @@ -596,22 +598,6 @@ </func> <func> - <name>rand_bytes(N) -> binary()</name> - <fsummary>Generate a binary of random bytes</fsummary> - <type> - <v>N = integer()</v> - </type> - <desc> - <p>Generates N bytes randomly uniform 0..255, and returns the - result in a binary. Uses the <c>crypto</c> library pseudo-random - number generator.</p> - <p>This function is not recommended for cryptographic purposes. - Please use <seealso marker="#strong_rand_bytes/1"> - strong_rand_bytes/1</seealso> instead.</p> - </desc> - </func> - - <func> <name>rand_seed(Seed) -> ok</name> <fsummary>Set the seed for random bytes generation</fsummary> <type> diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl index a154476560..025d57e9c5 100644 --- a/lib/crypto/src/crypto.erl +++ b/lib/crypto/src/crypto.erl @@ -28,7 +28,7 @@ -export([generate_key/2, generate_key/3, compute_key/4]). -export([hmac/3, hmac/4, hmac_init/2, hmac_update/2, hmac_final/1, hmac_final_n/2]). -export([exor/2, strong_rand_bytes/1, mod_pow/3]). --export([rand_bytes/1, rand_bytes/3, rand_uniform/2]). +-export([rand_uniform/2]). -export([block_encrypt/3, block_decrypt/3, block_encrypt/4, block_decrypt/4]). -export([next_iv/2, next_iv/3]). -export([stream_init/2, stream_init/3, stream_encrypt/2, stream_decrypt/2]). @@ -39,6 +39,9 @@ -export([rand_seed/1]). %% DEPRECATED +-export([rand_bytes/1]). +-deprecated({rand_bytes, 1, next_major_release}). + %% Replaced by hash_* -export([md4/1, md4_init/0, md4_update/2, md4_final/1]). -export([md5/1, md5_init/0, md5_update/2, md5_final/1]). @@ -407,8 +410,6 @@ strong_rand_bytes(Bytes) -> end. strong_rand_bytes_nif(_Bytes) -> ?nif_stub. -rand_bytes(_Bytes, _Topmask, _Bottommask) -> ?nif_stub. - rand_uniform(From,To) when is_binary(From), is_binary(To) -> case rand_uniform_nif(From,To) of @@ -546,7 +547,7 @@ generate_key(dh, DHParameters, PrivateKey) -> generate_key(srp, {host, [Verifier, Generator, Prime, Version]}, PrivArg) when is_binary(Verifier), is_binary(Generator), is_binary(Prime), is_atom(Version) -> Private = case PrivArg of - undefined -> random_bytes(32); + undefined -> strong_rand_bytes(32); _ -> ensure_int_as_bin(PrivArg) end, host_srp_gen_key(Private, Verifier, Generator, Prime, Version); @@ -554,7 +555,7 @@ generate_key(srp, {host, [Verifier, Generator, Prime, Version]}, PrivArg) generate_key(srp, {user, [Generator, Prime, Version]}, PrivateArg) when is_binary(Generator), is_binary(Prime), is_atom(Version) -> Private = case PrivateArg of - undefined -> random_bytes(32); + undefined -> strong_rand_bytes(32); _ -> PrivateArg end, user_srp_gen_key(Private, Generator, Prime); @@ -606,16 +607,6 @@ compute_key(ecdh, Others, My, Curve) -> nif_curve_params(Curve), ensure_int_as_bin(My)). - -random_bytes(N) -> - try strong_rand_bytes(N) of - RandBytes -> - RandBytes - catch - error:low_entropy -> - rand_bytes(N) - end. - %%-------------------------------------------------------------------- %%% On load %%-------------------------------------------------------------------- diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl index 0d18cd8017..6732f27824 100644 --- a/lib/crypto/test/crypto_SUITE.erl +++ b/lib/crypto/test/crypto_SUITE.erl @@ -269,7 +269,6 @@ rand_uniform() -> [{doc, "rand_uniform and random_bytes testing"}]. rand_uniform(Config) when is_list(Config) -> rand_uniform_aux_test(10), - 10 = byte_size(crypto:rand_bytes(10)), 10 = byte_size(crypto:strong_rand_bytes(10)). %%-------------------------------------------------------------------- @@ -649,8 +648,8 @@ ipow(A, B, M, Prod) -> do_exor(B) -> Z1 = zero_bin(B), Z1 = crypto:exor(B, B), - B1 = crypto:rand_bytes(100), - B2 = crypto:rand_bytes(100), + B1 = crypto:strong_rand_bytes(100), + B2 = crypto:strong_rand_bytes(100), Z2 = zero_bin(B1), Z2 = crypto:exor(B1, B1), Z2 = crypto:exor(B2, B2), diff --git a/lib/crypto/test/old_crypto_SUITE.erl b/lib/crypto/test/old_crypto_SUITE.erl index f57e9ff341..0d97290d10 100644 --- a/lib/crypto/test/old_crypto_SUITE.erl +++ b/lib/crypto/test/old_crypto_SUITE.erl @@ -2068,8 +2068,8 @@ exor_test(Config) when is_list(Config) -> B = <<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>>, Z1 = zero_bin(B), Z1 = crypto:exor(B, B), - B1 = crypto:rand_bytes(100), - B2 = crypto:rand_bytes(100), + B1 = crypto:strong_rand_bytes(100), + B2 = crypto:strong_rand_bytes(100), Z2 = zero_bin(B1), Z2 = crypto:exor(B1, B1), Z2 = crypto:exor(B2, B2), diff --git a/lib/eunit/src/eunit_lib.erl b/lib/eunit/src/eunit_lib.erl index 4dbe023257..9dbb4835f8 100644 --- a/lib/eunit/src/eunit_lib.erl +++ b/lib/eunit/src/eunit_lib.erl @@ -192,7 +192,6 @@ error_msg(Title, Fmt, Args) -> io_lib:fwrite("*** ~ts ***\n~ts\n\n", [Title, Msg]). -ifdef(TEST). --dialyzer({no_match, format_exception_test_/0}). format_exception_test_() -> [?_assertMatch( "\nymmud:rorre"++_, @@ -274,7 +273,6 @@ dlist_next([], Xs) -> -ifdef(TEST). --dialyzer({no_match, dlist_test_/0}). dlist_test_() -> {"deep list traversal", [{"non-list term -> singleton list", @@ -340,7 +338,6 @@ is_nonempty_string([]) -> false; is_nonempty_string(Cs) -> is_string(Cs). -ifdef(TEST). --dialyzer({no_match, is_string_test_/0}). is_string_test_() -> {"is_string", [{"no non-lists", ?_assert(not is_string($A))}, @@ -402,7 +399,7 @@ uniq([X | Xs]) -> [X | uniq(Xs)]; uniq([]) -> []. -ifdef(TEST). --dialyzer({[no_match, no_fail_call, no_improper_lists], uniq_test_/0}). +-dialyzer({[no_fail_call, no_improper_lists], uniq_test_/0}). uniq_test_() -> {"uniq", [?_assertError(function_clause, uniq(ok)), @@ -581,7 +578,6 @@ trie_match([], _T) -> -ifdef(TEST). --dialyzer({no_match, trie_test_/0}). trie_test_() -> [{"basic representation", [?_assert(trie_new() =:= gb_trees:empty()), diff --git a/lib/inets/doc/src/mod_esi.xml b/lib/inets/doc/src/mod_esi.xml index ae567ea185..deef010e54 100644 --- a/lib/inets/doc/src/mod_esi.xml +++ b/lib/inets/doc/src/mod_esi.xml @@ -23,10 +23,6 @@ </legalnotice> <title>mod_esi</title> - <prepared>Joakim Grebenö</prepared> - <docno></docno> - <date>1997-10-14</date> - <rev>2.2</rev> <file>mod_esi.sgml</file> </header> <module>mod_esi</module> @@ -39,6 +35,56 @@ <marker id="deliver"></marker> </description> + <section> + <title>DATA TYPES</title> + <p>The following data types are used in the functions for mod_esi:</p> + + <taglist> + <tag><c>env() = </c></tag> + <item> <p><c>{EnvKey()::atom(), Value::term()}</c></p> + </item> + + <p>Currently supported key value pairs</p> + <taglist> + + <tag><c>{server_software, string()}</c></tag> + <item><p>Indicates the inets version.</p></item> + + <tag><c>{server_name, string()}</c></tag> + <item><p>The local hostname. </p></item> + + <tag><c>{gateway_interface, string()}</c></tag> + <item><p>Legacy string used in CGI, just ignore.</p> </item> + + <tag><c>{server_protocol, string()}</c></tag> + <item><p> HTTP version, currently "HTTP/1.1"</p></item> + + <tag>{server_port, integer()}</tag> + <item><p>Servers port number.</p></item> + + <tag><c>{request_method, "GET | "PUT" | "DELETE | "POST" | "PATCH"}</c></tag> + + <tag><c>{remote_adress, inet:ip_address()} </c></tag> + <item><p>The clients ip address.</p></item> + + <tag><c>{peer_cert, undefined | no_peercert | DER:binary()</c></tag> + <item> + <p>For TLS connections where client certificates are used this will + be an ASN.1 DER-encoded X509-certificate as an Erlang binary. + If client certificates are not used the value will be <c>no_peercert</c>, + and if TLS is not used (HTTP or connection is lost due to network failure) + the value will be <c>undefined</c>. + </p></item> + + <tag><c>{script_name, string()}</c></tag> + <item><p>Request URI</p></item> + + <tag><c>{http_LowerCaseHTTPHeaderName, string()}</c></tag> + <item><p>example: {http_content_type, "text/html"}</p></item> + </taglist> + + </taglist> + <funcs> <func> <name>deliver(SessionID, Data) -> ok | {error, Reason}</name> @@ -63,11 +109,11 @@ overhead. Do not assume anything about the data type of <c>SessionID</c>. <c>SessionID</c> must be the value given as input to the ESI callback function that you implemented.</p> - </note> + </note> </desc> </func> </funcs> - + </section> <section> <title>ESI Callback Functions</title> </section> @@ -78,9 +124,7 @@ to the server process by calling <c>mod_esi:deliver/2</c>.</fsummary> <type> <v>SessionID = term()</v> - <v>Env = [EnvironmentDirectives] ++ ParsedHeader</v> - <v>EnvironmentDirectives = {Key,Value}</v> - <v>Key = query_string | content_length | server_software | gateway_interface | server_protocol | server_port | request_method | remote_addr | script_name</v> + <v>Env = env()</v> <v>Input = string()</v> </type> <desc> @@ -111,9 +155,7 @@ <fsummary>Creates a dynamic web page and returns it as a list. This function is deprecated and is only kept for backwards compatibility.</fsummary> <type> - <v>Env = [EnvironmentDirectives] ++ ParsedHeader</v> - <v>EnvironmentDirectives = {Key,Value}</v> - <v>Key = query_string | content_length | server_software | gateway_interface | server_protocol | server_port | request_method | remote_addr | script_name.</v> + <v>Env = env()</v> <v>Input = string()</v> <v>Response = string()</v> </type> diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index b3d2221930..bd6b76a73e 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -33,7 +33,22 @@ <file>notes.xml</file> </header> - <section><title>Inets 6.2.1</title> + <section><title>Inets 6.2.2</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Add environment information item peer_cert to mod_esi</p> + <p> + Own Id: OTP-13510</p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 6.2.1</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/inets/src/http_server/httpd_example.erl b/lib/inets/src/http_server/httpd_example.erl index 366e37742b..424d269859 100644 --- a/lib/inets/src/http_server/httpd_example.erl +++ b/lib/inets/src/http_server/httpd_example.erl @@ -20,7 +20,7 @@ %% -module(httpd_example). -export([print/1]). --export([get/2, post/2, yahoo/2, test1/2, get_bin/2]). +-export([get/2, post/2, yahoo/2, test1/2, get_bin/2, peer/2]). -export([newformat/3]). %% These are used by the inets test-suite @@ -94,10 +94,26 @@ default(Env,Input) -> io_lib:format("~p",[httpd:parse_query(Input)]),"\n", footer()]. +peer(Env, Input) -> + Header = + case proplists:get_value(peer_cert, Env) of + undefined -> + header("text/html", "Peer-Cert-Exist:false"); + _ -> + header("text/html", "Peer-Cert-Exist:true") + end, + [Header, + top("Test peer_cert environment option"), + "<B>Peer cert:</B> ", + io_lib:format("~p",[proplists:get_value(peer_cert, Env)]),"\n", + footer()]. + header() -> header("text/html"). header(MimeType) -> "Content-type: " ++ MimeType ++ "\r\n\r\n". +header(MimeType, Other) -> + "Content-type: " ++ MimeType ++ "\r\n" ++ Other ++ "\r\n\r\n". top(Title) -> "<HTML> diff --git a/lib/inets/src/http_server/httpd_script_env.erl b/lib/inets/src/http_server/httpd_script_env.erl index 25f9bea7b3..e15613273e 100644 --- a/lib/inets/src/http_server/httpd_script_env.erl +++ b/lib/inets/src/http_server/httpd_script_env.erl @@ -61,6 +61,19 @@ which_port(#mod{config_db = ConfigDb}) -> which_peername(#mod{init_data = #init_data{peername = {_, RemoteAddr}}}) -> RemoteAddr. +which_peercert(#mod{socket_type = {Type, _}, socket = Socket}) when Type == essl; + Type == ssl -> + case ssl:peercert(Socket) of + {ok, Cert} -> + Cert; + {error, no_peercert} -> + no_peercert; + _ -> + undefined + end; +which_peercert(_) -> %% Not an ssl connection + undefined. + which_resolve(#mod{init_data = #init_data{resolve = Resolve}}) -> Resolve. @@ -78,6 +91,7 @@ create_basic_elements(esi, ModData) -> {server_port, which_port(ModData)}, {request_method, which_method(ModData)}, {remote_addr, which_peername(ModData)}, + {peer_cert, which_peercert(ModData)}, {script_name, which_request_uri(ModData)}]; create_basic_elements(cgi, ModData) -> diff --git a/lib/inets/src/inets_app/inets.appup.src b/lib/inets/src/inets_app/inets.appup.src index 4d0d656acb..a603357ebd 100644 --- a/lib/inets/src/inets_app/inets.appup.src +++ b/lib/inets/src/inets_app/inets.appup.src @@ -18,12 +18,16 @@ %% %CopyrightEnd% {"%VSN%", [ - {<<"6.2">>, [{load_module, httpc, soft_purge, soft_purge, []}]}, + {<<"6.2.1">>, [{load_module, httpd_script_env, soft_purge, soft_purge, []}]}, + {<<"6.2">>, [{load_module, httpd_script_env, soft_purge, soft_purge, []}, + {load_module, httpc, soft_purge, soft_purge, []}]}, {<<"6\\..*">>,[{restart_application, inets}]}, {<<"5\\..*">>,[{restart_application, inets}]} ], [ - {<<"6.2">>, [{load_module, httpc, soft_purge, soft_purge, []}]}, + {<<"6.2.1">>, [{load_module, httpd_script_env, soft_purge, soft_purge, []}]}, + {<<"6.2">>, [{load_module, httpd_script_env, soft_purge, soft_purge, []}, + {load_module, httpc, soft_purge, soft_purge, []}]}, {<<"6\\..*">>,[{restart_application, inets}]}, {<<"5\\..*">>,[{restart_application, inets}]} ] diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl index 1d8a603981..93520c1cb4 100644 --- a/lib/inets/test/httpd_SUITE.erl +++ b/lib/inets/test/httpd_SUITE.erl @@ -755,7 +755,11 @@ esi(Config) when is_list(Config) -> %% Check "ErlScriptNoCache" directive (default: false) ok = http_status("GET /cgi-bin/erl/httpd_example:get ", Config, [{statuscode, 200}, - {no_header, "cache-control"}]). + {no_header, "cache-control"}]), + ok = http_status("GET /cgi-bin/erl/httpd_example:peer ", + Config, [{statuscode, 200}, + {header, "peer-cert-exist", peer(Config)}]). + %%------------------------------------------------------------------------- mod_esi_chunk_timeout(Config) when is_list(Config) -> ok = httpd_1_1:mod_esi_chunk_timeout(?config(type, Config), @@ -2065,3 +2069,11 @@ response_default_headers() -> {"X-Frame-Options", "SAMEORIGIN"}, %% Override built-in default {"Date", "Override-date"}]. + +peer(Config) -> + case proplists:get_value(type, Config) of + ssl -> + "true"; + _ -> + "false" + end.
\ No newline at end of file diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk index 121f55c389..fc33b546d9 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -19,6 +19,6 @@ # %CopyrightEnd% APPLICATION = inets -INETS_VSN = 6.2.1 +INETS_VSN = 6.2.2 PRE_VSN = APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)" diff --git a/lib/mnesia/test/mnesia_config_test.erl b/lib/mnesia/test/mnesia_config_test.erl index a31c9b1f4f..204b8fa394 100644 --- a/lib/mnesia/test/mnesia_config_test.erl +++ b/lib/mnesia/test/mnesia_config_test.erl @@ -693,9 +693,9 @@ event_module(Config) when is_list(Config) -> end, ?match({[ok, ok], []}, rpc:multicall(Nodes, mnesia, start, [Def])), - receive after 1000 -> ok end, + receive after 2000 -> ok end, mnesia_event ! {get_log, self()}, - DebugLog1 = receive + DebugLog1 = receive {log, L1} -> L1 after 10000 -> [timeout] end, @@ -706,9 +706,9 @@ event_module(Config) when is_list(Config) -> ?match({[ok], []}, rpc:multicall([N2], mnesia, start, [])), - receive after 1000 -> ok end, + receive after 2000 -> ok end, mnesia_event ! {get_log, self()}, - DebugLog = receive + DebugLog = receive {log, L} -> L after 10000 -> [timeout] end, diff --git a/lib/mnesia/test/mnesia_consistency_test.erl b/lib/mnesia/test/mnesia_consistency_test.erl index 6c3e68ba38..9cc84de87b 100644 --- a/lib/mnesia/test/mnesia_consistency_test.erl +++ b/lib/mnesia/test/mnesia_consistency_test.erl @@ -565,6 +565,7 @@ consistency_after_fallback_3_disc_only(Config) when is_list(Config) -> consistency_after_fallback(disc_only_copies, 3, Config). consistency_after_fallback(ReplicaType, NodeConfig, Config) -> + put(mnesia_test_verbose, true), %%?verbose("Starting consistency_after_fallback2 at ~p~n", [self()]), Delay = 5, Nodes = ?acquire_nodes(NodeConfig, [{tc_timeout, timer:minutes(10)} | Config]), @@ -594,10 +595,11 @@ consistency_after_fallback(ReplicaType, NodeConfig, Config) -> ?match(ok, mnesia_tpcb:verify_tabs()), %% Stop and then start mnesia and check table consistency - %%?verbose("Restarting Mnesia~n", []), + ?verbose("Kill Mnesia~n", []), mnesia_test_lib:kill_mnesia(Nodes), + ?verbose("Start Mnesia~n", []), mnesia_test_lib:start_mnesia(Nodes,[account,branch,teller,history]), - + ?verbose("Verify tabs~n", []), ?match(ok, mnesia_tpcb:verify_tabs()), if ReplicaType == ram_copies -> diff --git a/lib/observer/test/ttb_SUITE.erl b/lib/observer/test/ttb_SUITE.erl index 499cdb3fc8..c06ec21f36 100644 --- a/lib/observer/test/ttb_SUITE.erl +++ b/lib/observer/test/ttb_SUITE.erl @@ -819,6 +819,7 @@ myhandler(_Fd,Trace,_,Relay) -> simple_call_handler() -> {fun(A, {trace_ts, _, call, _, _} ,_,_) -> io:format(A, "ok.~n", []); + (A, {drop, N}, _, _) -> io:format(A, "{drop, ~p}.", [N]); (_, end_of_trace, _, _) -> ok end, []}. marking_call_handler() -> @@ -954,17 +955,24 @@ begin_trace_local(ServerNode, ClientNode, Dest) -> ?line ttb:tpl(client, get, []). check_size(N, Dest, Output, ServerNode, ClientNode) -> - ?line begin_trace(ServerNode, ClientNode, Dest), - ?line case Dest of + begin_trace(ServerNode, ClientNode, Dest), + case Dest of {local, _} -> - ?line ttb_helper:msgs_ip(N); + ttb_helper:msgs_ip(N); _ -> - ?line ttb_helper:msgs(N) + ttb_helper:msgs(N) end, - ?line {_, D} = ttb:stop([fetch, return_fetch_dir]), - ?line ttb:format(D, [{out, Output}, {handler, simple_call_handler()}]), - ?line {ok, Ret} = file:consult(Output), - ?line true = (N + 1 == length(Ret)). + {_, D} = ttb:stop([fetch, return_fetch_dir]), + ttb:format(D, [{out, Output}, {handler, simple_call_handler()}]), + {ok, Ret} = file:consult(Output), + check_output(N+1, Ret). + +check_output(Expected, Ret) + when length(Ret) =:= Expected -> ok; +check_output(Expected, Ret) -> + io:format("~p~n",[Ret]), + io:format("Expected ~p got ~p ~n",[Expected, length(Ret)]), + Expected = length(Ret). fetch_when_no_option_given(suite) -> []; @@ -1166,8 +1174,8 @@ changing_cwd_on_control_node(Config) when is_list(Config) -> ?line {_, D} = ttb:stop([fetch, return_fetch_dir]), ?line ttb:format(D, [{out, ?OUTPUT}, {handler, simple_call_handler()}]), ?line {ok, Ret} = file:consult(?OUTPUT), - ?line true = (2*(NumMsgs + 1) == length(Ret)), - ?line ok = file:set_cwd(OldDir). + check_output(2*(NumMsgs + 1),Ret), + ok = file:set_cwd(OldDir). changing_cwd_on_control_node(cleanup,_Config) -> ?line stop_client_and_server(). @@ -1176,18 +1184,19 @@ changing_cwd_on_control_node_with_local_trace(suite) -> changing_cwd_on_control_node_with_local_trace(doc) -> ["Changing cwd on control node during local tracing is safe"]; changing_cwd_on_control_node_with_local_trace(Config) when is_list(Config) -> - ?line {ok, OldDir} = file:get_cwd(), - ?line {ServerNode, ClientNode} = start_client_and_server(), - ?line begin_trace(ServerNode, ClientNode, {local, ?FNAME}), - ?line NumMsgs = 3, - ?line ttb_helper:msgs_ip(NumMsgs), - ?line ok = file:set_cwd(".."), - ?line ttb_helper:msgs_ip(NumMsgs), - ?line {_, D} = ttb:stop([fetch, return_fetch_dir]), - ?line ttb:format(D, [{out, ?OUTPUT}, {handler, simple_call_handler()}]), - ?line {ok, Ret} = file:consult(?OUTPUT), - ?line true = (2*(NumMsgs + 1) == length(Ret)), - ?line ok = file:set_cwd(OldDir). + {ok, OldDir} = file:get_cwd(), + {ServerNode, ClientNode} = start_client_and_server(), + begin_trace(ServerNode, ClientNode, {local, ?FNAME}), + NumMsgs = 3, + ttb_helper:msgs_ip(NumMsgs), + ok = file:set_cwd(".."), + ttb_helper:msgs_ip(NumMsgs), + {_, D} = ttb:stop([fetch, return_fetch_dir]), + ttb:format(D, [{out, ?OUTPUT}, {handler, simple_call_handler()}]), + {ok, Ret} = file:consult(?OUTPUT), + Expected = 2*(NumMsgs + 1), + check_output(Expected, Ret), + ok = file:set_cwd(OldDir). changing_cwd_on_control_node_with_local_trace(cleanup,_Config) -> ?line stop_client_and_server(). @@ -1205,7 +1214,7 @@ changing_cwd_on_remote_node(Config) when is_list(Config) -> ?line {_, D} = ttb:stop([fetch, return_fetch_dir]), ?line ttb:format(D, [{out, ?OUTPUT}, {handler, simple_call_handler()}]), ?line {ok, Ret} = file:consult(?OUTPUT), - ?line true = (2*(NumMsgs + 1) == length(Ret)). + check_output(2*(NumMsgs + 1),Ret). changing_cwd_on_remote_node(cleanup,_Config) -> ?line stop_client_and_server(). @@ -1497,7 +1506,7 @@ logic(N, M, TracingType) -> ct:log("formatted ~p",[{D,?OUTPUT}]), ?line {ok, Ret} = file:consult(?OUTPUT), ct:log("consulted: ~p",[Ret]), - ?line M = length(Ret). + check_output(M,Ret). begin_trace_with_resume(ServerNode, ClientNode, Dest) -> ?line {ok, _} = ttb:tracer([ServerNode,ClientNode], [{file, Dest}, resume]), diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml index 6923066da7..04daee460f 100644 --- a/lib/public_key/doc/src/public_key.xml +++ b/lib/public_key/doc/src/public_key.xml @@ -118,7 +118,7 @@ <p><c> not_encrypted | cipher_info()}</c></p></item> <tag><c>cipher_info() = </c></tag> - <item><p><c>{"RC2-CBC" | "DES-CBC" | "DES-EDE3-CBC", crypto:rand_bytes(8)</c></p> + <item><p><c>{"RC2-CBC" | "DES-CBC" | "DES-EDE3-CBC", crypto:strong_rand_bytes(8)</c></p> <p><c>| {#'PBEParameter{}, digest_type()} | #'PBES2-params'{}}</c></p> </item> diff --git a/lib/public_key/doc/src/using_public_key.xml b/lib/public_key/doc/src/using_public_key.xml index 86808adbea..e3a1eed4be 100644 --- a/lib/public_key/doc/src/using_public_key.xml +++ b/lib/public_key/doc/src/using_public_key.xml @@ -124,7 +124,7 @@ ...>>, not_encrypted}, {'Certificate',<<48,130,3,200,48,130,3,49,160,3,2,1,2,2,1, - 1,48,13,6,9,42,134,72,134,247,...>>>, + 1,48,13,6,9,42,134,72,134,247,...>>, not_encrypted}]</code> <p>Certificates can be decoded as usual:</p> diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl index be1a4472e9..51050c4480 100644 --- a/lib/public_key/test/public_key_SUITE.erl +++ b/lib/public_key/test/public_key_SUITE.erl @@ -199,7 +199,7 @@ encrypted_pem(Config) when is_list(Config) -> RSAKey = public_key:der_decode('RSAPrivateKey', DerRSAKey), - Salt0 = crypto:rand_bytes(8), + Salt0 = crypto:strong_rand_bytes(8), Entry0 = public_key:pem_entry_encode('RSAPrivateKey', RSAKey, {{"DES-EDE3-CBC", Salt0}, "1234abcd"}), RSAKey = public_key:pem_entry_decode(Entry0,"1234abcd"), @@ -208,7 +208,7 @@ encrypted_pem(Config) when is_list(Config) -> [{'RSAPrivateKey', _, {"DES-EDE3-CBC", Salt0}}] = erl_make_certs:pem_to_der(Des3KeyFile), - Salt1 = crypto:rand_bytes(8), + Salt1 = crypto:strong_rand_bytes(8), Entry1 = public_key:pem_entry_encode('RSAPrivateKey', RSAKey, {{"DES-CBC", Salt1}, "4567efgh"}), DesKeyFile = filename:join(Datadir, "des_client_key.pem"), diff --git a/lib/runtime_tools/test/dbg_SUITE.erl b/lib/runtime_tools/test/dbg_SUITE.erl index 17c04c0ed4..9c9d6ca352 100644 --- a/lib/runtime_tools/test/dbg_SUITE.erl +++ b/lib/runtime_tools/test/dbg_SUITE.erl @@ -20,31 +20,22 @@ -module(dbg_SUITE). %% Test functions --export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, - init_per_group/2,end_per_group/2, - big/1, tiny/1, simple/1, message/1, distributed/1, port/1, - ip_port/1, file_port/1, file_port2/1, file_port_schedfix/1, - ip_port_busy/1, wrap_port/1, wrap_port_time/1, - with_seq_trace/1, dead_suspend/1, local_trace/1, - saved_patterns/1, tracer_exit_on_stop/1, +-export([all/0, suite/0, + big/1, tiny/1, simple/1, message/1, distributed/1, port/1, + ip_port/1, file_port/1, file_port2/1, file_port_schedfix/1, + ip_port_busy/1, wrap_port/1, wrap_port_time/1, + with_seq_trace/1, dead_suspend/1, local_trace/1, + saved_patterns/1, tracer_exit_on_stop/1, erl_tracer/1, distributed_erl_tracer/1]). --export([init_per_testcase/2, end_per_testcase/2]). -export([tracee1/1, tracee2/1]). -export([dummy/0, exported/1]). -export([enabled/3, trace/6, load_nif/1]). -include_lib("common_test/include/ct.hrl"). --define(default_timeout, ?t:minutes(1)). - -init_per_testcase(_Case, Config) -> - ?line Dog=test_server:timetrap(?default_timeout), - [{watchdog, Dog}|Config]. -end_per_testcase(_Case, Config) -> - Dog=?config(watchdog, Config), - test_server:timetrap_cancel(Dog), - ok. -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> + [{ct_hooks,[ts_install_cth]}, + {timetrap, {minutes, 1}}]. all() -> [big, tiny, simple, message, distributed, port, ip_port, @@ -53,283 +44,248 @@ all() -> local_trace, saved_patterns, tracer_exit_on_stop, erl_tracer, distributed_erl_tracer]. -groups() -> - []. - -init_per_suite(Config) -> - Config. - -end_per_suite(_Config) -> - ok. - -init_per_group(_GroupName, Config) -> - Config. -end_per_group(_GroupName, Config) -> - Config. - - -big(suite) -> []; -big(doc) -> ["Rudimentary interface test"]; +%% Rudimentary interface test big(Config) when is_list(Config) -> - ?line {ok,OldCurDir} = file:get_cwd(), - Datadir=?config(data_dir, Config), - Privdir=?config(priv_dir, Config), - ?line ok=file:set_cwd(Privdir), + {ok,OldCurDir} = file:get_cwd(), + Datadir=proplists:get_value(data_dir, Config), + Privdir=proplists:get_value(priv_dir, Config), + ok=file:set_cwd(Privdir), try - %% make sure dbg is stopped (and returns correctly) - ?line ok = dbg:stop(), - - %% compile test module and make sure it is loaded. - ?line {ok,Mod} = compile:file(Datadir++"/dbg_test",[trace]), - ?line code:purge(dbg_test), - ?line {module, Mod}=code:load_file(dbg_test), - - %% run/debug a named test function. - ?line Pid = spawn_link(dbg_test, loop, [Config]), - ?line true = register(dbg_test_loop, Pid), - ?line {ok,_} = dbg:tracer(), - ?line {ok,[{matched, _node, 1}]} = dbg:p(dbg_test_loop, [m,p,c]), - ?line ok = dbg:c(dbg_test, test, [Config]), - ?line ok = dbg:i(), - ?line dbg_test_loop ! {dbg_test, stop}, - unregister(dbg_test_loop), - ?line ok = dbg:stop(), - - %% run/debug a Pid. - ?line Pid2=spawn_link(dbg_test,loop,[Config]), - ?line {ok,_} = dbg:tracer(), - ?line {ok,[{matched, _node, 1}]} = dbg:p(Pid2,[s,r,p]), - ?line ok = dbg:c(dbg_test, test, [Config]), - ?line ok = dbg:i(), - ?line Pid2 ! {dbg_test, stop}, - - ?line ok=file:set_cwd(OldCurDir) + %% make sure dbg is stopped (and returns correctly) + ok = dbg:stop(), + + %% compile test module and make sure it is loaded. + {ok,Mod} = compile:file(Datadir++"/dbg_test",[trace]), + code:purge(dbg_test), + {module, Mod}=code:load_file(dbg_test), + + %% run/debug a named test function. + Pid = spawn_link(dbg_test, loop, [Config]), + true = register(dbg_test_loop, Pid), + {ok,_} = dbg:tracer(), + {ok,[{matched, _node, 1}]} = dbg:p(dbg_test_loop, [m,p,c]), + ok = dbg:c(dbg_test, test, [Config]), + ok = dbg:i(), + dbg_test_loop ! {dbg_test, stop}, + unregister(dbg_test_loop), + ok = dbg:stop(), + + %% run/debug a Pid. + Pid2=spawn_link(dbg_test,loop,[Config]), + {ok,_} = dbg:tracer(), + {ok,[{matched, _node, 1}]} = dbg:p(Pid2,[s,r,p]), + ok = dbg:c(dbg_test, test, [Config]), + ok = dbg:i(), + Pid2 ! {dbg_test, stop}, + + ok=file:set_cwd(OldCurDir) after - ?line dbg:stop() + dbg:stop() end, ok. -tiny(suite) -> []; -tiny(doc) -> ["Rudimentary interface test"]; +%% Rudimentary interface test tiny(Config) when is_list(Config) -> - ?line {ok,OldCurDir} = file:get_cwd(), - Datadir=?config(data_dir, Config), - Privdir=?config(priv_dir, Config), - ?line ok=file:set_cwd(Privdir), + {ok,OldCurDir} = file:get_cwd(), + Datadir=proplists:get_value(data_dir, Config), + Privdir=proplists:get_value(priv_dir, Config), + ok=file:set_cwd(Privdir), try - %% compile test module and make sure it is loaded. - ?line {ok, Mod} = compile:file(Datadir++"/dbg_test",[trace]), - ?line code:purge(dbg_test), - ?line {module, Mod}=code:load_file(dbg_test), - - ?line Pid=spawn_link(dbg_test,loop,[Config]), - if - is_pid(Pid) -> - ?line dbg:tracer(), - ?line {ok,[{matched, _node, 1}]} = dbg:p(Pid,[s,r,m,p,c]), - ?line ok = dbg:c(dbg_test,test,[Config]), - ?line ok = dbg:i(), - ?line Pid ! {dbg_test, stop}; - true -> - ?line ok=file:set_cwd(OldCurDir), - ?t:fail("Could not spawn external test process.~n"), - failure - end + %% compile test module and make sure it is loaded. + {ok, Mod} = compile:file(Datadir++"/dbg_test",[trace]), + code:purge(dbg_test), + {module, Mod}=code:load_file(dbg_test), + + Pid=spawn_link(dbg_test,loop,[Config]), + if + is_pid(Pid) -> + dbg:tracer(), + {ok,[{matched, _node, 1}]} = dbg:p(Pid,[s,r,m,p,c]), + ok = dbg:c(dbg_test,test,[Config]), + ok = dbg:i(), + Pid ! {dbg_test, stop}; + true -> + ok=file:set_cwd(OldCurDir), + ct:fail("Could not spawn external test process.~n"), + failure + end after - ?line ok = dbg:stop(), - ?line ok = file:set_cwd(OldCurDir) + ok = dbg:stop(), + ok = file:set_cwd(OldCurDir) end, ok. -simple(suite) -> - []; -simple(doc) -> - ["Simple interface test with own handler"]; +%% Simple interface test with own handler simple(Config) when is_list(Config) -> try - ?line start(), - ?line dbg:p(self(),call), - ?line dbg:tp(dbg,ltp,[]), - ?line dbg:ltp(), - ?line stop(), - ?line S = self(), - ?line [{trace,S,call,{dbg,ltp,[]}}] = flush() + start(), + dbg:p(self(),call), + dbg:tp(dbg,ltp,[]), + dbg:ltp(), + stop(), + S = self(), + [{trace,S,call,{dbg,ltp,[]}}] = flush() after - ?line dbg:stop() + dbg:stop() end, ok. -message(suite) -> - []; -message(doc) -> - ["Simple interface test with pam code that appends a message"]; +%% Simple interface test with pam code that appends a message message(Config) when is_list(Config) -> - ?line {ok, _} = start(), + {ok, _} = start(), try - ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call), - ?line {ok, X} = dbg:tp(dbg,ltp,[{'_',[],[{message, {self}}]}]), - ?line {value, {saved, Saved}} = lists:keysearch(saved, 1, X), - ?line {ok, Y} = dbg:tp(dbg,ln,Saved), - ?line {value, {saved, Saved}} = lists:keysearch(saved, 1, Y), - ?line ok = dbg:ltp(), - ?line ok = dbg:ln() + {ok, [{matched, _node, 1}]} = dbg:p(self(),call), + {ok, X} = dbg:tp(dbg,ltp,[{'_',[],[{message, {self}}]}]), + {value, {saved, Saved}} = lists:keysearch(saved, 1, X), + {ok, Y} = dbg:tp(dbg,ln,Saved), + {value, {saved, Saved}} = lists:keysearch(saved, 1, Y), + ok = dbg:ltp(), + ok = dbg:ln() after - ?line stop() + stop() end, - ?line S = self(), - ?line [{trace,S,call,{dbg,ltp,[]},S}, - {trace,S,call,{dbg,ln,[]},S}] = flush(), + S = self(), + [{trace,S,call,{dbg,ltp,[]},S}, + {trace,S,call,{dbg,ln,[]},S}] = flush(), ok. -distributed(suite) -> - []; -distributed(doc) -> - ["Simple test of distributed tracing"]; +%% Simple test of distributed tracing distributed(Config) when is_list(Config) -> - ?line {ok, _} = start(), - ?line Node = start_slave(), + {ok, _} = start(), + Node = start_slave(), try - ?line RexPid = rpc:call(Node, erlang, whereis, [rex]), - ?line RexPidList = pid_to_list(RexPid), - ?line {ok, Node} = dbg:n(Node), - ?line {ok, X} = dbg:p(all,call), - ?line {value, {matched, Node, _}} = lists:keysearch(Node, 2, X), - ?line {ok, Y} = dbg:p(RexPidList, s), - ?line {value, {matched, Node, 1}} = lists:keysearch(Node, 2, Y), - ?line {ok, Z} = dbg:tp(dbg,ltp,[]), - ?line {value, {matched, Node, 1}} = lists:keysearch(Node, 2, Z), - ?line dbg:cn(Node), - ?line dbg:tp(dbg,ln,[]), - ?line ok = rpc:call(Node, dbg, ltp, []), - ?line ok = rpc:call(Node, dbg, ln, []), - ?line ok = dbg:ln(), - ?line S = self(), - ?line {TraceSend, TraceCall} = - lists:partition(fun ({trace,RP,send,_,_}) when RP =:= RexPid -> true; - (_) -> false end, - flush()), - ?line [_|_] = TraceSend, - ?line [{trace,Pid,call,{dbg,ltp,[]}}, - {trace,S,call,{dbg,ln,[]}}] = TraceCall, - ?line Node = node(Pid), - %% - ?line stop() + RexPid = rpc:call(Node, erlang, whereis, [rex]), + RexPidList = pid_to_list(RexPid), + {ok, Node} = dbg:n(Node), + {ok, X} = dbg:p(all,call), + {value, {matched, Node, _}} = lists:keysearch(Node, 2, X), + {ok, Y} = dbg:p(RexPidList, s), + {value, {matched, Node, 1}} = lists:keysearch(Node, 2, Y), + {ok, Z} = dbg:tp(dbg,ltp,[]), + {value, {matched, Node, 1}} = lists:keysearch(Node, 2, Z), + dbg:cn(Node), + dbg:tp(dbg,ln,[]), + ok = rpc:call(Node, dbg, ltp, []), + ok = rpc:call(Node, dbg, ln, []), + ok = dbg:ln(), + S = self(), + {TraceSend, TraceCall} = + lists:partition(fun ({trace,RP,send,_,_}) when RP =:= RexPid -> true; + (_) -> false end, + flush()), + [_|_] = TraceSend, + [{trace,Pid,call,{dbg,ltp,[]}}, + {trace,S,call,{dbg,ln,[]}}] = TraceCall, + Node = node(Pid), + %% + stop() after - ?line stop_slave(Node), - ?line stop() + stop_slave(Node), + stop() end, ok. -local_trace(suite) -> - []; -local_trace(doc) -> - ["Tests tracing of local function calls."]; +%% Tests tracing of local function calls. local_trace(Config) when is_list(Config) -> - ?line {ok, _} = start(), + {ok, _} = start(), try - ?line S = self(), - ?line %% Split "<X.Y.Z>" into {X, Y, Z} - ?line "<"++L1 = L = pid_to_list(S), - ?line NoDot = fun ($.) -> false; (_) -> true end, - ?line {LX,"."++L2} = lists:splitwith(NoDot, L1), - ?line {LY,"."++L3} = lists:splitwith(NoDot, L2), - ?line ">"++L4 = lists:reverse(L3), - ?line LZ = lists:reverse(L4), - ?line X = 0 = list_to_integer(LX), - ?line Y = list_to_integer(LY), - ?line Z = list_to_integer(LZ), - ?line XYZ = {X, Y, Z}, - ?line io:format("Self = ~w = ~w~n", [S,XYZ]), - ?line {ok, [{matched, _node, 1}]} = dbg:p(S,call), - ?line {ok, [{matched, _node, 1}]} = dbg:p(XYZ,call), - if Z =:= 0 -> - ?line {ok, [{matched, _node, 1}]} = dbg:p(Y,call); - true -> ok - end, - ?line {ok, [{matched, _node, 1}]} = dbg:p(L,call), - ?line {ok, _} = dbg:tpl(?MODULE,not_exported,[]), - ?line 4 = not_exported(2), - ?line [{trace,S,call,{?MODULE,not_exported,[2]}}] = flush(), - ?line {ok, _} = dbg:tp(?MODULE,exported,[]), - ?line 4 = ?MODULE:exported(2), - ?line [{trace,S,call,{?MODULE,exported,[2]}}, - {trace,S,call,{?MODULE,not_exported,[2]}}] = flush(), - ?line {ok, _} = dbg:ctpl(?MODULE), - ?line 4 = ?MODULE:exported(2), - ?line [{trace,S,call,{?MODULE,exported,[2]}}] = flush(), - ?line {ok, _} = dbg:tpl(?MODULE,not_exported,[]), - ?line {ok, _} = dbg:ctp(?MODULE), - ?line 4 = ?MODULE:exported(2), - ?line [] = flush(), - ?line {ok, _} = dbg:tpl(?MODULE,not_exported,x), - ?line catch ?MODULE:exported(x), - ?line [{trace,S,call,{dbg_SUITE,not_exported,[x]}}, - {trace,S,exception_from, - {dbg_SUITE,not_exported,1}, - {error,badarith}}] = flush() + S = self(), + %% Split "<X.Y.Z>" into {X, Y, Z} + "<"++L1 = L = pid_to_list(S), + NoDot = fun ($.) -> false; (_) -> true end, + {LX,"."++L2} = lists:splitwith(NoDot, L1), + {LY,"."++L3} = lists:splitwith(NoDot, L2), + ">"++L4 = lists:reverse(L3), + LZ = lists:reverse(L4), + X = 0 = list_to_integer(LX), + Y = list_to_integer(LY), + Z = list_to_integer(LZ), + XYZ = {X, Y, Z}, + io:format("Self = ~w = ~w~n", [S,XYZ]), + {ok, [{matched, _node, 1}]} = dbg:p(S,call), + {ok, [{matched, _node, 1}]} = dbg:p(XYZ,call), + if Z =:= 0 -> + {ok, [{matched, _node, 1}]} = dbg:p(Y,call); + true -> ok + end, + {ok, [{matched, _node, 1}]} = dbg:p(L,call), + {ok, _} = dbg:tpl(?MODULE,not_exported,[]), + 4 = not_exported(2), + [{trace,S,call,{?MODULE,not_exported,[2]}}] = flush(), + {ok, _} = dbg:tp(?MODULE,exported,[]), + 4 = ?MODULE:exported(2), + [{trace,S,call,{?MODULE,exported,[2]}}, + {trace,S,call,{?MODULE,not_exported,[2]}}] = flush(), + {ok, _} = dbg:ctpl(?MODULE), + 4 = ?MODULE:exported(2), + [{trace,S,call,{?MODULE,exported,[2]}}] = flush(), + {ok, _} = dbg:tpl(?MODULE,not_exported,[]), + {ok, _} = dbg:ctp(?MODULE), + 4 = ?MODULE:exported(2), + [] = flush(), + {ok, _} = dbg:tpl(?MODULE,not_exported,x), + catch ?MODULE:exported(x), + [{trace,S,call,{dbg_SUITE,not_exported,[x]}}, + {trace,S,exception_from, + {dbg_SUITE,not_exported,1}, + {error,badarith}}] = flush() after - ?line stop() + stop() end, ok. -port(suite) -> - []; -port(doc) -> - ["Test that tracing on port works"]; +%% Test that tracing on port works port(Config) when is_list(Config) -> try S = self(), - start(), + start(), TestFile = filename:join(proplists:get_value(priv_dir, Config),"port_test"), Fun = dbg:trace_port(file, TestFile), %% Do a run to get rid of all extra port operations port_close(Fun()), - dbg:p(new,ports), + dbg:p(new,ports), Port = Fun(), port_close(Port), - stop(), + stop(), TraceFileDrv = list_to_atom(lists:flatten(["trace_file_drv n ",TestFile])), - [{trace,Port,open,S,TraceFileDrv}, + [{trace,Port,open,S,TraceFileDrv}, {trace,Port,getting_linked,S}, {trace,Port,closed,normal}, {trace,Port,unlink,S}] = flush() after - dbg:stop() + dbg:stop() end, ok. -saved_patterns(suite) -> - []; -saved_patterns(doc) -> - ["Tests saving of match_spec's."]; +%% Tests saving of match_spec's. saved_patterns(Config) when is_list(Config) -> - ?line dbg:stop(), - ?line {ok,[{saved,1}]} = - dbg:tp(dbg,ctp,1,[{'_',[],[{message, blahonga}]}]), - ?line {ok,[{saved,2}]} = - dbg:tp(dbg,ctp,1,[{['_'],[],[{message, blahonga}]}]), - ?line Privdir=?config(priv_dir, Config), - ?line file:make_dir(Privdir), - ?line File = filename:join([Privdir, "blahonga.ms"]), - ?line dbg:wtp(File), - ?line dbg:stop(), - ?line dbg:ctp('_','_','_'), - ?line {ok, _} = start(), + dbg:stop(), + {ok,[{saved,1}]} = + dbg:tp(dbg,ctp,1,[{'_',[],[{message, blahonga}]}]), + {ok,[{saved,2}]} = + dbg:tp(dbg,ctp,1,[{['_'],[],[{message, blahonga}]}]), + Privdir=proplists:get_value(priv_dir, Config), + file:make_dir(Privdir), + File = filename:join([Privdir, "blahonga.ms"]), + dbg:wtp(File), + dbg:stop(), + dbg:ctp('_','_','_'), + {ok, _} = start(), try - ?line dbg:rtp(File), - ?line {ok,[{matched,_node,1},{saved,1}]} = dbg:tp(dbg,ltp,0,1), - ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call), - ?line dbg:ltp(), - ?line S = self(), - ?line [{trace,S,call,{dbg,ltp,[]},blahonga}] = flush() + dbg:rtp(File), + {ok,[{matched,_node,1},{saved,1}]} = dbg:tp(dbg,ltp,0,1), + {ok, [{matched, _node, 1}]} = dbg:p(self(),call), + dbg:ltp(), + S = self(), + [{trace,S,call,{dbg,ltp,[]},blahonga}] = flush() after - ?line stop() + stop() end, ok. @@ -341,148 +297,132 @@ not_exported(N) -> exported(N) -> not_exported(N). -ip_port(suite) -> - []; -ip_port(doc) -> - ["Test tracing to IP port"]; +%% Test tracing to IP port ip_port(Config) when is_list(Config) -> - ?line stop(), - ?line Port = dbg:trace_port(ip, 0), - ?line {ok, _} = dbg:tracer(port, Port), + stop(), + Port = dbg:trace_port(ip, 0), + {ok, _} = dbg:tracer(port, Port), try - ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call), - ?line {ok, X} = dbg:tp(dbg, ltp,[{'_',[],[{message, {self}}]}]), - ?line {value, {saved, _Saved}} = lists:keysearch(saved, 1, X), - ?line {ok, Y} = dbg:tp(dbg, ln, [{'_',[],[{message, hej}]}]), - ?line {value, {saved, _}} = lists:keysearch(saved, 1, Y), - ?line ok = dbg:ltp(), - ?line ok = dbg:ln(), - ?line {ok, IpPort} = dbg:trace_port_control(get_listen_port), - ?line io:format("IpPort = ~p~n", [IpPort]), - ?line dbg:trace_client(ip, IpPort, {fun myhandler/2, self()}), - ?line S = self(), - ?line [{trace,S,call,{dbg,ltp,[]},S}, - {trace,S,call,{dbg,ln,[]},hej}] = flush() + {ok, [{matched, _node, 1}]} = dbg:p(self(),call), + {ok, X} = dbg:tp(dbg, ltp,[{'_',[],[{message, {self}}]}]), + {value, {saved, _Saved}} = lists:keysearch(saved, 1, X), + {ok, Y} = dbg:tp(dbg, ln, [{'_',[],[{message, hej}]}]), + {value, {saved, _}} = lists:keysearch(saved, 1, Y), + ok = dbg:ltp(), + ok = dbg:ln(), + {ok, IpPort} = dbg:trace_port_control(get_listen_port), + io:format("IpPort = ~p~n", [IpPort]), + dbg:trace_client(ip, IpPort, {fun myhandler/2, self()}), + S = self(), + [{trace,S,call,{dbg,ltp,[]},S}, + {trace,S,call,{dbg,ln,[]},hej}] = flush() after - ?line stop() + stop() end, ok. -ip_port_busy(suite) -> - []; -ip_port_busy(doc) -> - ["Test that the dbg server does not hang if the tracer don't start ", - "(OTP-3592)"]; +%% Test that the dbg server does not hang if the tracer don't start (OTP-3592) ip_port_busy(Config) when is_list(Config) -> - ?line stop(), - ?line Tracer = dbg:trace_port(ip, 4745), - ?line Port = Tracer(), - ?line {error, Reason} = dbg:tracer(port, Tracer), + stop(), + Tracer = dbg:trace_port(ip, 4745), + Port = Tracer(), + {error, Reason} = dbg:tracer(port, Tracer), try - ?line io:format("Error reason = ~p~n", [Reason]), - ?line true = port_close(Port) + io:format("Error reason = ~p~n", [Reason]), + true = port_close(Port) after - ?line dbg:stop() + dbg:stop() end, - ?line ok. + ok. -file_port(suite) -> - []; -file_port(doc) -> - ["Test tracing to file port (simple)"]; +%% Test tracing to file port (simple) file_port(Config) when is_list(Config) -> - ?line stop(), - ?line {A,B,C} = erlang:now(), - ?line FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ "-" ++ - integer_to_list(B) ++ "-" ++ integer_to_list(C), - ?line FName = filename:join([?config(data_dir, Config), FTMP]), - ?line Port = dbg:trace_port(file, FName), - ?line {ok, _} = dbg:tracer(port, Port), + stop(), + {A,B,C} = erlang:now(), + FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ "-" ++ + integer_to_list(B) ++ "-" ++ integer_to_list(C), + FName = filename:join([proplists:get_value(data_dir, Config), FTMP]), + Port = dbg:trace_port(file, FName), + {ok, _} = dbg:tracer(port, Port), try - ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call), - ?line {ok, X} = dbg:tp(dbg, ltp,[{'_',[],[{message, {self}}]}]), - ?line {value, {saved, _Saved}} = lists:keysearch(saved, 1, X), - ?line {ok, Y} = dbg:tp(dbg, ln, [{'_',[],[{message, hej}]}]), - ?line {value, {saved, _}} = lists:keysearch(saved, 1, Y), - ?line ok = dbg:ltp(), - ?line ok = dbg:ln(), - ?line stop(), - ?line dbg:trace_client(file, FName, {fun myhandler/2, self()}), - ?line S = self(), - ?line [{trace,S,call,{dbg,ltp,[]},S}, - {trace,S,call,{dbg,ln,[]},hej}, - end_of_trace] = flush() + {ok, [{matched, _node, 1}]} = dbg:p(self(),call), + {ok, X} = dbg:tp(dbg, ltp,[{'_',[],[{message, {self}}]}]), + {value, {saved, _Saved}} = lists:keysearch(saved, 1, X), + {ok, Y} = dbg:tp(dbg, ln, [{'_',[],[{message, hej}]}]), + {value, {saved, _}} = lists:keysearch(saved, 1, Y), + ok = dbg:ltp(), + ok = dbg:ln(), + stop(), + dbg:trace_client(file, FName, {fun myhandler/2, self()}), + S = self(), + [{trace,S,call,{dbg,ltp,[]},S}, + {trace,S,call,{dbg,ln,[]},hej}, + end_of_trace] = flush() after - ?line stop(), - ?line file:delete(FName) + stop(), + file:delete(FName) end, ok. -file_port2(suite) -> - []; -file_port2(doc) -> - ["Test tracing to file port with 'follow_file'"]; +%% Test tracing to file port with 'follow_file' file_port2(Config) when is_list(Config) -> stop(), {A,B,C} = erlang:now(), FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ - "-" ++ integer_to_list(B) ++ "-" ++ integer_to_list(C), - FName = filename:join([?config(data_dir, Config), FTMP]), + "-" ++ integer_to_list(B) ++ "-" ++ integer_to_list(C), + FName = filename:join([proplists:get_value(data_dir, Config), FTMP]), %% Ok, lets try with flush and follow_file, not a chance on VxWorks %% with NFS caching... Port2 = dbg:trace_port(file, FName), {ok, _} = dbg:tracer(port, Port2), try - {ok, [{matched, _node, 1}]} = dbg:p(self(),call), - {ok, _} = dbg:tp(dbg, ltp,[{'_',[],[{message, {self}}]}]), - {ok, _} = dbg:tp(dbg, ln, [{'_',[],[{message, hej}]}]), - ok = dbg:ltp(), - ok = dbg:flush_trace_port(), - dbg:trace_client(follow_file, FName, - {fun myhandler/2, self()}), - S = self(), - [{trace,S,call,{dbg,ltp,[]},S}] = flush(), - ok = dbg:ln(), - ok = dbg:flush_trace_port(), - receive after 1000 -> ok end, %% Polls every second... - [{trace,S,call,{dbg,ln,[]},hej}] = flush(), - stop(), - [] = flush() + {ok, [{matched, _node, 1}]} = dbg:p(self(),call), + {ok, _} = dbg:tp(dbg, ltp,[{'_',[],[{message, {self}}]}]), + {ok, _} = dbg:tp(dbg, ln, [{'_',[],[{message, hej}]}]), + ok = dbg:ltp(), + ok = dbg:flush_trace_port(), + dbg:trace_client(follow_file, FName, + {fun myhandler/2, self()}), + S = self(), + [{trace,S,call,{dbg,ltp,[]},S}] = flush(), + ok = dbg:ln(), + ok = dbg:flush_trace_port(), + receive after 1000 -> ok end, %% Polls every second... + [{trace,S,call,{dbg,ln,[]},hej}] = flush(), + stop(), + [] = flush() after - stop(), - file:delete(FName) + stop(), + file:delete(FName) end, ok. -file_port_schedfix(suite) -> - []; -file_port_schedfix(doc) -> - ["Test that the scheduling timestamp fix for trace flag 'running' works."]; +%% Test that the scheduling timestamp fix for trace flag 'running' works. file_port_schedfix(Config) when is_list(Config) -> - ?line case (catch erlang:system_info(smp_support)) of - true -> - {skip, "No schedule fix on SMP"}; - _ -> - try - file_port_schedfix1(Config) - after - dbg:stop() - end - end. + case (catch erlang:system_info(smp_support)) of + true -> + {skip, "No schedule fix on SMP"}; + _ -> + try + file_port_schedfix1(Config) + after + dbg:stop() + end + end. file_port_schedfix1(Config) when is_list(Config) -> - ?line stop(), - ?line {A,B,C} = erlang:now(), - ?line FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ - "-" ++ integer_to_list(B) ++ "-" ++ integer_to_list(C), - ?line FName = filename:join([?config(data_dir, Config), FTMP]), + stop(), + {A,B,C} = erlang:now(), + FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ + "-" ++ integer_to_list(B) ++ "-" ++ integer_to_list(C), + FName = filename:join([proplists:get_value(data_dir, Config), FTMP]), %% - ?line Port = dbg:trace_port(file, {FName, wrap, ".wraplog", 8*1024, 4}), - ?line {ok, _} = dbg:tracer(port, Port), - ?line {ok,[{matched,_node,0}]} = dbg:p(new,[running,procs,send,timestamp]), + Port = dbg:trace_port(file, {FName, wrap, ".wraplog", 8*1024, 4}), + {ok, _} = dbg:tracer(port, Port), + {ok,[{matched,_node,0}]} = dbg:p(new,[running,procs,send,timestamp]), %% %% Generate the trace data %% @@ -503,146 +443,143 @@ file_port_schedfix1(Config) when is_list(Config) -> %% execution time. Wallclock. A normal result is about 10 times more %% without schedule in - schedule out compensation (OTP-3938). %% - ?line ok = token_volleyball(3, 4, 8), + ok = token_volleyball(3, 4, 8), %% - ?line {ok,[{matched,_,_}]} = dbg:p(all, [clear]), - ?line stop(), + {ok,[{matched,_,_}]} = dbg:p(all, [clear]), + stop(), % Some debug code to run on all platforms, for finding the fault on genny % Dont touch please /PaN - ?line io:format("Trace dump by PaN BEGIN~n"), - ?line dbg:trace_client(file,{FName, wrap, ".wraplog"},{fun(end_of_trace,Pid)-> Pid ! done; (Mesg,Pid) -> io:format("~w~n",[Mesg]),Pid end,self()}), + io:format("Trace dump by PaN BEGIN~n"), + dbg:trace_client(file,{FName, wrap, ".wraplog"},{fun(end_of_trace,Pid)-> Pid ! done; (Mesg,Pid) -> io:format("~w~n",[Mesg]),Pid end,self()}), receive done -> ok end, - ?line io:format("Trace dump by PaN END~n"), - %% + io:format("Trace dump by PaN END~n"), + %% %% Get the trace result %% - ?line Tag = make_ref(), - ?line dbg:trace_client(file, {FName, wrap, ".wraplog"}, - {fun schedstat_handler/2, {self(), Tag, []}}), - ?line Result = - receive - {Tag, D} -> - lists:map( - fun({Pid, {A1, B1, C1}}) -> - {Pid, C1/1000000 + B1 + A1*1000000} - end, - D) - end, - ?line ok = io:format("Result=~p", [Result]), -% erlang:display({?MODULE, ?LINE, Result}), + Tag = make_ref(), + dbg:trace_client(file, {FName, wrap, ".wraplog"}, + {fun schedstat_handler/2, {self(), Tag, []}}), + Result = + receive + {Tag, D} -> + lists:map( + fun({Pid, {A1, B1, C1}}) -> + {Pid, C1/1000000 + B1 + A1*1000000} + end, + D) + end, + ok = io:format("Result=~p", [Result]), + % erlang:display({?MODULE, ?LINE, Result}), %% %% Analyze the result %% - ?line {Min, Max} = - lists:foldl( - fun({_Pid, M}, {Mi, Ma}) -> - {if M < Mi -> M; true -> Mi end, - if M > Ma -> M; true -> Ma end} - end, - {void, 0}, - Result), + {Min, Max} = + lists:foldl( + fun({_Pid, M}, {Mi, Ma}) -> + {if M < Mi -> M; true -> Mi end, + if M > Ma -> M; true -> Ma end} + end, + {void, 0}, + Result), % More PaN debug - ?line io:format("Min = ~f, Max = ~f~n",[Min,Max]), + io:format("Min = ~f, Max = ~f~n",[Min,Max]), %% %% Cleanup %% - ?line ToBeDeleted = filelib:wildcard(FName++"*"++".wraplog"), - ?line lists:map(fun file:delete/1, ToBeDeleted), -% io:format("ToBeDeleted=~p", [ToBeDeleted]), + ToBeDeleted = filelib:wildcard(FName++"*"++".wraplog"), + lists:map(fun file:delete/1, ToBeDeleted), + % io:format("ToBeDeleted=~p", [ToBeDeleted]), %% %% Present the result %% P = (Max / Min - 1) * 100, BottomLine = lists:flatten(io_lib:format("~.2f %", [P])), if P > 100 -> - Reason = {BottomLine, '>', "100%"}, - erlang:display({file_port_schedfix, fail, Reason}), - test_server:fail(Reason); + Reason = {BottomLine, '>', "100%"}, + erlang:display({file_port_schedfix, fail, Reason}), + ct:fail(Reason); true -> - {comment, BottomLine} + {comment, BottomLine} end. -wrap_port(suite) -> - []; -wrap_port(doc) -> - ["Test tracing to wrapping file port"]; +%% Test tracing to wrapping file port wrap_port(Config) when is_list(Config) -> - ?line Self = self(), - ?line stop(), - ?line {A,B,C} = erlang:now(), - ?line FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ "-" ++ - integer_to_list(B) ++ "-" ++ integer_to_list(C) ++ "-", - ?line FName = filename:join([?config(data_dir, Config), FTMP]), - ?line FNameWildcard = FName++"*"++".trace", + Self = self(), + stop(), + {A,B,C} = erlang:now(), + FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ "-" ++ + integer_to_list(B) ++ "-" ++ integer_to_list(C) ++ "-", + FName = filename:join([proplists:get_value(data_dir, Config), FTMP]), + FNameWildcard = FName++"*"++".trace", %% WrapSize=0 and WrapCnt=11 will force the trace to wrap after %% every trace message, and to contain only the last 10 entries %% after trace stop since the last file will be empty waiting %% for its first trace message. - ?line WrapSize = 0, - ?line WrapCnt = 11, - ?line WrapFilesSpec = {FName, wrap, ".trace", WrapSize, WrapCnt}, - ?line wrap_port_init(WrapFilesSpec), + WrapSize = 0, + WrapCnt = 11, + WrapFilesSpec = {FName, wrap, ".trace", WrapSize, WrapCnt}, + wrap_port_init(WrapFilesSpec), %% The number of iterations, N, is tested to place wrap the log, %% giving a gap in the filename sequence at index 3. %% This should be a difficult case for %% the trace_client file sorting functionality. N = 7, - ?line lists:foreach( - fun(Cnt) -> - ?MODULE:tracee1(Cnt), - ?MODULE:tracee2(Cnt) - end, - lists:seq(1, N)), - ?line stop(), + lists:foreach( + fun(Cnt) -> + ?MODULE:tracee1(Cnt), + ?MODULE:tracee2(Cnt) + end, + lists:seq(1, N)), + stop(), try - ?line Files1 = filelib:wildcard(FNameWildcard), - ?line io:format("~p~n", [Files1]), - ?line Tc1 = dbg:trace_client(file, WrapFilesSpec, - {fun myhandler/2, {wait_for_go,Self}}), - ?line Tref1 = erlang:monitor(process, Tc1), - Tc1 ! {go,Self}, - ?line [{'DOWN',Tref1,_,_,normal}, - end_of_trace - |Result] = lists:reverse(flush()), - ?line M = N - (WrapCnt-1) div 2, - ?line M = wrap_port_result(Result, Self, N), - %% - %% Start a new wrap log with the same name to verify that - %% all files are cleared at wrap log start. Only produce - %% two trace messages to also place the gap at index 3, - %% so the trace log will be misinterpreted. - %% - ?line wrap_port_init(WrapFilesSpec), - ?line Files2 = filelib:wildcard(FNameWildcard), - ?line io:format("~p~n", [Files2]), - ?line -1 = ?MODULE:tracee1(-1), - ?line -1 = ?MODULE:tracee2(-1), - ?line stop(), - ?line Files = filelib:wildcard(FNameWildcard), - ?line io:format("~p~n", [Files]), - ?line Tc2 = dbg:trace_client(file, WrapFilesSpec, - {fun myhandler/2, {wait_for_go,Self}}), - ?line Tref2 = erlang:monitor(process, Tc2), - Tc2 ! {go,Self}, - ?line [{trace,Self,call,{?MODULE,tracee1,[-1]},Self}, - {trace,Self,call,{?MODULE,tracee2,[-1]},hej}, - end_of_trace, - {'DOWN',Tref2,_,_,normal}] = flush(), - %% - ?line lists:map(fun(F) -> file:delete(F) end, Files) + Files1 = filelib:wildcard(FNameWildcard), + io:format("~p~n", [Files1]), + Tc1 = dbg:trace_client(file, WrapFilesSpec, + {fun myhandler/2, {wait_for_go,Self}}), + Tref1 = erlang:monitor(process, Tc1), + Tc1 ! {go,Self}, + [{'DOWN',Tref1,_,_,normal}, + end_of_trace + |Result] = lists:reverse(flush()), + M = N - (WrapCnt-1) div 2, + M = wrap_port_result(Result, Self, N), + %% + %% Start a new wrap log with the same name to verify that + %% all files are cleared at wrap log start. Only produce + %% two trace messages to also place the gap at index 3, + %% so the trace log will be misinterpreted. + %% + wrap_port_init(WrapFilesSpec), + Files2 = filelib:wildcard(FNameWildcard), + io:format("~p~n", [Files2]), + -1 = ?MODULE:tracee1(-1), + -1 = ?MODULE:tracee2(-1), + stop(), + Files = filelib:wildcard(FNameWildcard), + io:format("~p~n", [Files]), + Tc2 = dbg:trace_client(file, WrapFilesSpec, + {fun myhandler/2, {wait_for_go,Self}}), + Tref2 = erlang:monitor(process, Tc2), + Tc2 ! {go,Self}, + [{trace,Self,call,{?MODULE,tracee1,[-1]},Self}, + {trace,Self,call,{?MODULE,tracee2,[-1]},hej}, + end_of_trace, + {'DOWN',Tref2,_,_,normal}] = flush(), + %% + lists:map(fun(F) -> file:delete(F) end, Files) after - ?line stop() + stop() end, ok. wrap_port_init(WrapFilesSpec) -> - ?line Port = dbg:trace_port(file, WrapFilesSpec), - ?line {ok, _} = dbg:tracer(port, Port), - ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call), - ?line {ok, X} = dbg:tp(?MODULE, tracee1,[{'_',[],[{message, {self}}]}]), - ?line {value, {saved, _Saved}} = lists:keysearch(saved, 1, X), - ?line {ok, Y} = dbg:tp(?MODULE, tracee2, [{'_',[],[{message, hej}]}]), - ?line {value, {saved, _}} = lists:keysearch(saved, 1, Y), + Port = dbg:trace_port(file, WrapFilesSpec), + {ok, _} = dbg:tracer(port, Port), + {ok, [{matched, _node, 1}]} = dbg:p(self(),call), + {ok, X} = dbg:tp(?MODULE, tracee1,[{'_',[],[{message, {self}}]}]), + {value, {saved, _Saved}} = lists:keysearch(saved, 1, X), + {ok, Y} = dbg:tp(?MODULE, tracee2, [{'_',[],[{message, hej}]}]), + {value, {saved, _}} = lists:keysearch(saved, 1, Y), ok. tracee1(X) -> @@ -654,106 +591,96 @@ tracee2(X) -> wrap_port_result([], _S, M) -> M; wrap_port_result([{trace, S, call, {?MODULE, tracee2, [M]}, hej}, - {trace, S, call, {?MODULE, tracee1, [M]}, S} | Tail], - S, - M) -> + {trace, S, call, {?MODULE, tracee1, [M]}, S} | Tail], + S, + M) -> wrap_port_result(Tail, S, M-1). -wrap_port_time(suite) -> - []; -wrap_port_time(doc) -> - ["Test tracing to time limited wrapping file port"]; +%% Test tracing to time limited wrapping file port wrap_port_time(Config) when is_list(Config) -> - ?line stop(), - ?line {A,B,C} = erlang:now(), - ?line FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ "-" ++ - integer_to_list(B) ++ "-" ++ integer_to_list(C) ++ "-", - ?line FName = filename:join([?config(data_dir, Config), FTMP]), + stop(), + {A,B,C} = erlang:now(), + FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ "-" ++ + integer_to_list(B) ++ "-" ++ integer_to_list(C) ++ "-", + FName = filename:join([proplists:get_value(data_dir, Config), FTMP]), %% WrapTime=2 and WrapCnt=4 will force the trace to wrap after %% every 2 seconds, and to contain between 3*2 and 4*2 seconds %% of trace entries. - ?line WrapFilesSpec = {FName, wrap, ".trace", {time, 2000}, 4}, - ?line Port = dbg:trace_port(file, WrapFilesSpec), - ?line {ok, _} = dbg:tracer(port, Port), + WrapFilesSpec = {FName, wrap, ".trace", {time, 2000}, 4}, + Port = dbg:trace_port(file, WrapFilesSpec), + {ok, _} = dbg:tracer(port, Port), try - ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call), - ?line {ok, X} = dbg:tp(?MODULE, tracee1,[{'_',[],[{message, {self}}]}]), - ?line {value, {saved, _Saved1}} = lists:keysearch(saved, 1, X), - ?line {ok, Y} = dbg:tp(?MODULE, tracee2, [{'_',[],[{message, hej}]}]), - ?line {value, {saved, _Saved2}} = lists:keysearch(saved, 1, Y), - %% The delays in the iterations places two trace messages in each - %% trace file, but the last which is empty waiting for its first - %% trace message. The number of iterations is chosen so that - %% one trace file has been wasted, and therefore the first pair - %% of trace messages. - ?line lists:foreach( - fun(Cnt) -> - receive after 1000 -> ok end, - ?MODULE:tracee1(Cnt), - ?MODULE:tracee2(Cnt), - receive after 1100 -> ok end - end, - lists:seq(1, 4)), - ?line stop(), - ?line Files = filelib:wildcard(FName ++ "*" ++ ".trace"), - ?line io:format("~p~n", [Files]), - ?line dbg:trace_client(file, WrapFilesSpec, {fun myhandler/2, self()}), - ?line S = self(), - ?line [{trace, S, call, {?MODULE, tracee1, [2]}, S}, - {trace, S, call, {?MODULE, tracee2, [2]}, hej}, - {trace, S, call, {?MODULE, tracee1, [3]}, S}, - {trace, S, call, {?MODULE, tracee2, [3]}, hej}, - {trace, S, call, {?MODULE, tracee1, [4]}, S}, - {trace, S, call, {?MODULE, tracee2, [4]}, hej}, - end_of_trace] = flush(), - ?line lists:map(fun(F) -> file:delete(F) end, Files) + {ok, [{matched, _node, 1}]} = dbg:p(self(),call), + {ok, X} = dbg:tp(?MODULE, tracee1,[{'_',[],[{message, {self}}]}]), + {value, {saved, _Saved1}} = lists:keysearch(saved, 1, X), + {ok, Y} = dbg:tp(?MODULE, tracee2, [{'_',[],[{message, hej}]}]), + {value, {saved, _Saved2}} = lists:keysearch(saved, 1, Y), + %% The delays in the iterations places two trace messages in each + %% trace file, but the last which is empty waiting for its first + %% trace message. The number of iterations is chosen so that + %% one trace file has been wasted, and therefore the first pair + %% of trace messages. + lists:foreach( + fun(Cnt) -> + receive after 1000 -> ok end, + ?MODULE:tracee1(Cnt), + ?MODULE:tracee2(Cnt), + receive after 1100 -> ok end + end, + lists:seq(1, 4)), + stop(), + Files = filelib:wildcard(FName ++ "*" ++ ".trace"), + io:format("~p~n", [Files]), + dbg:trace_client(file, WrapFilesSpec, {fun myhandler/2, self()}), + S = self(), + [{trace, S, call, {?MODULE, tracee1, [2]}, S}, + {trace, S, call, {?MODULE, tracee2, [2]}, hej}, + {trace, S, call, {?MODULE, tracee1, [3]}, S}, + {trace, S, call, {?MODULE, tracee2, [3]}, hej}, + {trace, S, call, {?MODULE, tracee1, [4]}, S}, + {trace, S, call, {?MODULE, tracee2, [4]}, hej}, + end_of_trace] = flush(), + lists:map(fun(F) -> file:delete(F) end, Files) after - ?line stop() + stop() end, ok. -with_seq_trace(suite) -> - []; -with_seq_trace(doc) -> - ["Test ordinary tracing combined with seq_trace"]; +%% Test ordinary tracing combined with seq_trace with_seq_trace(Config) when is_list(Config) -> try - ?line {ok, Server} = start(), - ?line {ok, Tracer} = dbg:get_tracer(), - ?line {ok, X} = dbg:tp(dbg, get_tracer, [{[],[], - [{set_seq_token, send, true}]}]), - ?line {value, {saved, _}} = lists:keysearch(saved, 1, X), - ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call), - ?line seq_trace:set_system_tracer(Tracer), - ?line dbg:get_tracer(), - receive - after 1 -> - ok - end, - ?line S = self(), - ?line ThisNode = node(), - ?line [{trace,S,call,{dbg,get_tracer,[]}}, - {seq_trace,0,{send,_,S,Server,{S,{get_tracer,ThisNode}}}}, - {seq_trace,0,{send,_,Server,S,{dbg,{ok,Tracer}}}}] = - flush() + {ok, Server} = start(), + {ok, Tracer} = dbg:get_tracer(), + {ok, X} = dbg:tp(dbg, get_tracer, [{[],[], + [{set_seq_token, send, true}]}]), + {value, {saved, _}} = lists:keysearch(saved, 1, X), + {ok, [{matched, _node, 1}]} = dbg:p(self(),call), + seq_trace:set_system_tracer(Tracer), + dbg:get_tracer(), + receive + after 1 -> + ok + end, + S = self(), + ThisNode = node(), + [{trace,S,call,{dbg,get_tracer,[]}}, + {seq_trace,0,{send,_,S,Server,{S,{get_tracer,ThisNode}}}}, + {seq_trace,0,{send,_,Server,S,{dbg,{ok,Tracer}}}}] = + flush() after - ?line stop() + stop() end, ok. -dead_suspend(suite) -> - []; -dead_suspend(doc) -> - ["Test that trace messages concerning a now dead process does " - "not crash dbg."]; +%% Test that trace messages concerning a now dead process does not crash dbg. dead_suspend(Config) when is_list(Config) -> - ?line start(), + start(), try - survived = run_dead_suspend() + survived = run_dead_suspend() after - ?line stop() + stop() end. run_dead_suspend() -> @@ -766,10 +693,10 @@ run_dead_suspend() -> spawn(?MODULE, dummy, []), receive after 1000 -> ok end, case whereis(dbg) of - undefined -> - died; - _ -> - survived + undefined -> + died; + _ -> + survived end. dummy() -> @@ -781,10 +708,10 @@ tracer_exit_on_stop(_) -> %% Tracer blocks waiting for fun to complete so that the trace message and %% the exit signal message from the dbg process are in its message queue. Fun = fun() -> - ?MODULE:dummy(), - Ref = erlang:trace_delivered(self()), - receive {trace_delivered, _, Ref} -> stop() end - end, + ?MODULE:dummy(), + Ref = erlang:trace_delivered(self()), + receive {trace_delivered, _, Ref} -> stop() end + end, {ok, _} = dbg:tracer(process, {fun spawn_once_handler/2, {self(), Fun}}), {ok, Tracer} = dbg:get_tracer(), MRef = monitor(process, Tracer), @@ -803,9 +730,9 @@ spawn_once_handler(Event, {Pid, done} = State) -> spawn_once_handler(Event, {Pid, Fun}) -> {_, Ref} = spawn_monitor(Fun), receive - {'DOWN', Ref, _, _, _} -> - Pid ! Event, - {Pid, done} + {'DOWN', Ref, _, _, _} -> + Pid ! Event, + {Pid, done} end. %% Test that erl_tracer modules work correctly @@ -898,38 +825,38 @@ wait_node(_,0) -> no; wait_node(Node, N) -> case net_adm:ping(Node) of - pong -> - ok; - pang -> - receive - after 1000 -> - ok - end, - wait_node(Node, N - 1) + pong -> + ok; + pang -> + receive + after 1000 -> + ok + end, + wait_node(Node, N - 1) end. myhandler(Message, {wait_for_go,Pid}) -> receive - {go,Pid} -> - myhandler(Message, Pid) + {go,Pid} -> + myhandler(Message, Pid) end; myhandler(Message, Relay) -> Relay ! Message, case Message of - end_of_trace -> - ok; - _ -> - Relay + end_of_trace -> + ok; + _ -> + Relay end. flush() -> flush([]). flush(Acc) -> receive - X -> - flush(Acc ++ [X]) + X -> + flush(Acc ++ [X]) after 1000 -> - Acc + Acc end. start() -> @@ -943,88 +870,88 @@ stop() -> schedstat_handler(TraceMsg, {Parent, Tag, Data} = State) -> case TraceMsg of - {trace_ts, Pid, in, _, Ts} -> - NewData = - case lists:keysearch(Pid, 1, Data) of - {value, {Pid, Acc}} -> - [{Pid, Acc, Ts} | lists:keydelete(Pid, 1, Data)]; - false -> - [{Pid, {0, 0, 0}, Ts} | Data]; - Other -> - exit(Parent, {?MODULE, ?LINE, Other}), - erlang:display({?MODULE, ?LINE, Other}), - Data - end, - {Parent, Tag, NewData}; - {trace_ts, Pid, out, _, {A3, B3, C3}} -> - NewData = - case lists:keysearch(Pid, 1, Data) of - {value, {Pid, {A1, B1, C1}, {A2, B2, C2}}} -> - [{Pid, {A3-A2+A1, B3-B2+B1, C3-C2+C1}} | - lists:keydelete(Pid, 1, Data)]; - Other -> - exit(Parent, {?MODULE, ?LINE, Other}), - erlang:display({?MODULE, ?LINE, Other}), - Data - end, - {Parent, Tag, NewData}; - {trace_ts, Pid, exit, normal, {A3, B3, C3}} -> - NewData = - case lists:keysearch(Pid, 1, Data) of - {value, {Pid, {A1, B1, C1}, {A2, B2, C2}}} -> - [{Pid, {A3-A2+A1, B3-B2+B1, C3-C2+C1}} | - lists:keydelete(Pid, 1, Data)]; - {value, {Pid, _Acc}} -> - Data; - false -> - [{Pid, {0, 0, 0}} | Data]; - Other -> - exit(Parent, {?MODULE, ?LINE, Other}), - erlang:display({?MODULE, ?LINE, Other}), - Data - end, - {Parent, Tag, NewData}; - {trace_ts, _Pid, send, _Msg, _OtherPid, _Ts} -> - State; - end_of_trace -> - Parent ! {Tag, Data}, - State + {trace_ts, Pid, in, _, Ts} -> + NewData = + case lists:keysearch(Pid, 1, Data) of + {value, {Pid, Acc}} -> + [{Pid, Acc, Ts} | lists:keydelete(Pid, 1, Data)]; + false -> + [{Pid, {0, 0, 0}, Ts} | Data]; + Other -> + exit(Parent, {?MODULE, ?LINE, Other}), + erlang:display({?MODULE, ?LINE, Other}), + Data + end, + {Parent, Tag, NewData}; + {trace_ts, Pid, out, _, {A3, B3, C3}} -> + NewData = + case lists:keysearch(Pid, 1, Data) of + {value, {Pid, {A1, B1, C1}, {A2, B2, C2}}} -> + [{Pid, {A3-A2+A1, B3-B2+B1, C3-C2+C1}} | + lists:keydelete(Pid, 1, Data)]; + Other -> + exit(Parent, {?MODULE, ?LINE, Other}), + erlang:display({?MODULE, ?LINE, Other}), + Data + end, + {Parent, Tag, NewData}; + {trace_ts, Pid, exit, normal, {A3, B3, C3}} -> + NewData = + case lists:keysearch(Pid, 1, Data) of + {value, {Pid, {A1, B1, C1}, {A2, B2, C2}}} -> + [{Pid, {A3-A2+A1, B3-B2+B1, C3-C2+C1}} | + lists:keydelete(Pid, 1, Data)]; + {value, {Pid, _Acc}} -> + Data; + false -> + [{Pid, {0, 0, 0}} | Data]; + Other -> + exit(Parent, {?MODULE, ?LINE, Other}), + erlang:display({?MODULE, ?LINE, Other}), + Data + end, + {Parent, Tag, NewData}; + {trace_ts, _Pid, send, _Msg, _OtherPid, _Ts} -> + State; + end_of_trace -> + Parent ! {Tag, Data}, + State end. pass_token(Token, Next, Loops) -> receive - {Token, 1} = Msg -> - sendloop(Loops), - Next ! Msg; - {Token, _Cnt} = Msg-> - sendloop(Loops), - Next ! Msg, - pass_token(Token, Next, Loops) + {Token, 1} = Msg -> + sendloop(Loops), + Next ! Msg; + {Token, _Cnt} = Msg-> + sendloop(Loops), + Next ! Msg, + pass_token(Token, Next, Loops) end. pass_token(Token, Final, Cnt, Loops) -> receive - {Token, start, Next} -> - sendloop(Loops), - Msg = {Token, Cnt}, - Next ! Msg, - pass_token(Token, Final, Next, Cnt, Loops) + {Token, start, Next} -> + sendloop(Loops), + Msg = {Token, Cnt}, + Next ! Msg, + pass_token(Token, Final, Next, Cnt, Loops) end. pass_token(Token, Final, Next, Cnt, Loops) -> receive - {Token, 1} -> - sendloop(Loops), - Msg = {Token, done}, - Final ! Msg; - {Token, Cnt} -> - sendloop(Loops), - NextCnt = Cnt-1, - Msg = {Token, NextCnt}, - Next ! Msg, - pass_token(Token, Final, Next, NextCnt, Loops) + {Token, 1} -> + sendloop(Loops), + Msg = {Token, done}, + Final ! Msg; + {Token, Cnt} -> + sendloop(Loops), + NextCnt = Cnt-1, + Msg = {Token, NextCnt}, + Next ! Msg, + pass_token(Token, Final, Next, NextCnt, Loops) end. sendloop(Loops) -> diff --git a/lib/runtime_tools/test/dyntrace_SUITE.erl b/lib/runtime_tools/test/dyntrace_SUITE.erl index f3c1cce8f8..7be2f49a8b 100644 --- a/lib/runtime_tools/test/dyntrace_SUITE.erl +++ b/lib/runtime_tools/test/dyntrace_SUITE.erl @@ -20,26 +20,14 @@ -module(dyntrace_SUITE). -include_lib("common_test/include/ct.hrl"). --export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, - init_per_group/2,end_per_group/2]). --export([init_per_testcase/2, end_per_testcase/2]). +-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1]). %% Test cases -export([smoke/1,process/1]). -%% Default timetrap timeout (set in init_per_testcase) --define(default_timeout, ?t:minutes(1)). - -init_per_testcase(_Case, Config) -> - Dog = test_server:timetrap(?default_timeout), - [{watchdog,Dog}|Config]. - -end_per_testcase(_Case, Config) -> - Dog = ?config(watchdog, Config), - ?t:timetrap_cancel(Dog), - ok. - -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> + [{ct_hooks,[ts_install_cth]}, + {timetrap, {minutes, 1}}]. all() -> case erlang:system_info(dynamic_trace) of @@ -73,14 +61,8 @@ init_per_suite(Config) -> end_per_suite(_Config) -> ok. -init_per_group(_GroupName, Config) -> - Config. - -end_per_group(_GroupName, Config) -> - Config. - smoke(Config) -> - Emu = ?t:lookup_config(emu_name, Config), + Emu = test_server:lookup_config(emu_name, Config), BinEmu = list_to_binary(Emu), case erlang:system_info(dynamic_trace) of dtrace -> @@ -107,8 +89,7 @@ process(_Config) -> {probe,"process-hibernate"}, {action,[{printf,["hibernate %s %s\n",{arg,0},{arg,1}]}]}, {probe,"process-exit"}, - {action,[{printf,["exit %s %s\n",{arg,0},{arg,1}]}]} - ], + {action,[{printf,["exit %s %s\n",{arg,0},{arg,1}]}]}], F = fun() -> {Pid,Ref} = spawn_monitor(fun my_process/0), Pid ! hibernate, diff --git a/lib/runtime_tools/test/erts_alloc_config_SUITE.erl b/lib/runtime_tools/test/erts_alloc_config_SUITE.erl index 4fb96a0c1b..6ae51d9a26 100644 --- a/lib/runtime_tools/test/erts_alloc_config_SUITE.erl +++ b/lib/runtime_tools/test/erts_alloc_config_SUITE.erl @@ -25,9 +25,7 @@ -include_lib("common_test/include/ct.hrl"). %-compile(export_all). --export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, - init_per_group/2,end_per_group/2, - init_per_testcase/2, end_per_testcase/2]). +-export([all/0, suite/0, init_per_testcase/2, end_per_testcase/2]). %% Testcases -export([basic/1]). @@ -35,83 +33,63 @@ %% internal export -export([make_basic_config/1]). --define(DEFAULT_TIMEOUT, ?t:minutes(2)). - -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> + [{ct_hooks,[ts_install_cth]}, + {timetrap, {minutes, 2}}]. all() -> [basic]. -groups() -> - []. - -init_per_suite(Config) -> - Config. - -end_per_suite(_Config) -> - ok. - -init_per_group(_GroupName, Config) -> - Config. - -end_per_group(_GroupName, Config) -> - Config. - - init_per_testcase(Case, Config) when is_list(Config) -> [{testcase, Case}, - {watchdog, ?t:timetrap(?DEFAULT_TIMEOUT)}, {erl_flags_env, save_env()} | Config]. end_per_testcase(_Case, Config) when is_list(Config) -> - ?t:timetrap_cancel(?config(watchdog, Config)), - restore_env(?config(erl_flags_env, Config)), + restore_env(proplists:get_value(erl_flags_env, Config)), ok. %%% %%% The test cases ------------------------------------------------------------ %%% -basic(doc) -> []; -basic(suite) -> []; basic(Config) when is_list(Config) -> - ?line ErtsAllocConfig = privfile("generated", Config), + ErtsAllocConfig = privfile("generated", Config), SbctMod = " +MBsbct 1024 +MHsbct 4096", %% Make sure we have enabled allocators ZFlgs = os:getenv("ERL_ZFLAGS", "") ++ " +Mea max +Mea config", - ?line os:putenv("ERL_ZFLAGS", ZFlgs ++ SbctMod), + os:putenv("ERL_ZFLAGS", ZFlgs ++ SbctMod), - ?line {ok, Node1} = start_node(Config), - ?line ok = rpc:call(Node1, ?MODULE, make_basic_config, [ErtsAllocConfig]), - ?line stop_node(Node1), + {ok, Node1} = start_node(Config), + ok = rpc:call(Node1, ?MODULE, make_basic_config, [ErtsAllocConfig]), + stop_node(Node1), - ?line display_file(ErtsAllocConfig), + display_file(ErtsAllocConfig), - ?line ManualConfig = privfile("manual", Config), - ?line {ok, IOD} = file:open(ManualConfig, [write]), - ?line io:format(IOD, "~s", ["+MBsbct 2048"]), - ?line file:close(IOD), - ?line display_file(ManualConfig), + ManualConfig = privfile("manual", Config), + {ok, IOD} = file:open(ManualConfig, [write]), + io:format(IOD, "~s", ["+MBsbct 2048"]), + file:close(IOD), + display_file(ManualConfig), - ?line os:putenv("ERL_ZFLAGS", ZFlgs), + os:putenv("ERL_ZFLAGS", ZFlgs), - ?line {ok, Node2} = start_node(Config, - "-args_file " ++ ErtsAllocConfig - ++ " -args_file " ++ ManualConfig), + {ok, Node2} = start_node(Config, + "-args_file " ++ ErtsAllocConfig + ++ " -args_file " ++ ManualConfig), - ?line {_, _, _, Cfg} = rpc:call(Node2, erlang, system_info, [allocator]), + {_, _, _, Cfg} = rpc:call(Node2, erlang, system_info, [allocator]), - ?line stop_node(Node2), + stop_node(Node2), - ?line {value,{binary_alloc, BCfg}} = lists:keysearch(binary_alloc, 1, Cfg), - ?line {value,{sbct, 2097152}} = lists:keysearch(sbct, 1, BCfg), - ?line {value,{eheap_alloc, HCfg}} = lists:keysearch(eheap_alloc, 1, Cfg), - ?line {value,{sbct, 4194304}} = lists:keysearch(sbct, 1, HCfg), + {value,{binary_alloc, BCfg}} = lists:keysearch(binary_alloc, 1, Cfg), + {value,{sbct, 2097152}} = lists:keysearch(sbct, 1, BCfg), + {value,{eheap_alloc, HCfg}} = lists:keysearch(eheap_alloc, 1, Cfg), + {value,{sbct, 4194304}} = lists:keysearch(sbct, 1, HCfg), - ?line ok. + ok. make_basic_config(ErtsAllocConfig) -> %% Save some different scenarios @@ -119,35 +97,35 @@ make_basic_config(ErtsAllocConfig) -> SSBegun = make_ref(), SSDone = make_ref(), SSFun = fun (F) -> - receive - SSDone -> - ok = erts_alloc_config:save_scenario(), - Tester ! SSDone - after 500 -> - ok = erts_alloc_config:save_scenario(), - F(F) - end - end, + receive + SSDone -> + ok = erts_alloc_config:save_scenario(), + Tester ! SSDone + after 500 -> + ok = erts_alloc_config:save_scenario(), + F(F) + end + end, SS = spawn_link(fun () -> - ok = erts_alloc_config:save_scenario(), - Tester ! SSBegun, - SSFun(SSFun) - end), + ok = erts_alloc_config:save_scenario(), + Tester ! SSBegun, + SSFun(SSFun) + end), receive SSBegun -> ok end, Ref = make_ref(), Tab = ets:new(?MODULE, [bag, public]), Ps = lists:map( - fun (_) -> - spawn_link( - fun () -> - ets:insert(Tab, - {self(), - lists:seq(1, 1000)}), - receive after 1000 -> ok end, - Tester ! {Ref, self()} - end) - end, - lists:seq(1, 10000)), + fun (_) -> + spawn_link( + fun () -> + ets:insert(Tab, + {self(), + lists:seq(1, 1000)}), + receive after 1000 -> ok end, + Tester ! {Ref, self()} + end) + end, + lists:seq(1, 10000)), lists:foreach(fun (P) -> receive {Ref, P} -> ok end end, Ps), ets:delete(Tab), SS ! SSDone, @@ -162,35 +140,35 @@ make_basic_config(ErtsAllocConfig) -> %% display_file(FileName) -> - ?t:format("filename: ~s~n", [FileName]), + io:format("filename: ~s~n", [FileName]), {ok, Bin} = file:read_file(FileName), io:format("~s", [binary_to_list(Bin)]), - ?t:format("eof: ~s~n", [FileName]), + io:format("eof: ~s~n", [FileName]), ok. mk_name(Config) when is_list(Config) -> {A, B, C} = now(), list_to_atom(atom_to_list(?MODULE) - ++ "-" ++ atom_to_list(?config(testcase, Config)) - ++ "-" ++ integer_to_list(A) - ++ "-" ++ integer_to_list(B) - ++ "-" ++ integer_to_list(C)). + ++ "-" ++ atom_to_list(proplists:get_value(testcase, Config)) + ++ "-" ++ integer_to_list(A) + ++ "-" ++ integer_to_list(B) + ++ "-" ++ integer_to_list(C)). start_node(Config) -> start_node(Config, ""). start_node(Config, Args) -> - ?line Pa = filename:dirname(code:which(?MODULE)), - ?line ?t:start_node(mk_name(Config), - slave, - [{args, "-pa " ++ Pa ++ " " ++ Args}]). + Pa = filename:dirname(code:which(?MODULE)), + test_server:start_node(mk_name(Config), + slave, + [{args, "-pa " ++ Pa ++ " " ++ Args}]). stop_node(Node) -> - ?line true = ?t:stop_node(Node). + true = test_server:stop_node(Node). privfile(Name, Config) -> - filename:join([?config(priv_dir, Config), - atom_to_list(?config(testcase, Config)) ++ "." ++ Name]). + filename:join([proplists:get_value(priv_dir, Config), + atom_to_list(proplists:get_value(testcase, Config)) ++ "." ++ Name]). save_env() -> {erl_flags, @@ -203,15 +181,15 @@ restore_env(EVar, false) when is_list(EVar) -> restore_env(EVar, ""); restore_env(EVar, "") when is_list(EVar) -> case os:getenv(EVar) of - false -> ok; - "" -> ok; - " " -> ok; - _ -> os:putenv(EVar, " ") + false -> ok; + "" -> ok; + " " -> ok; + _ -> os:putenv(EVar, " ") end; restore_env(EVar, Value) when is_list(EVar), is_list(Value) -> case os:getenv(EVar) of - Value -> ok; - _ -> os:putenv(EVar, Value) + Value -> ok; + _ -> os:putenv(EVar, Value) end. restore_env({erl_flags, AFlgs, Flgs, RFlgs, ZFlgs}) -> diff --git a/lib/runtime_tools/test/runtime_tools_SUITE.erl b/lib/runtime_tools/test/runtime_tools_SUITE.erl index 374d7f6694..6877e1a379 100644 --- a/lib/runtime_tools/test/runtime_tools_SUITE.erl +++ b/lib/runtime_tools/test/runtime_tools_SUITE.erl @@ -21,54 +21,27 @@ -include_lib("common_test/include/ct.hrl"). %% Test server specific exports --export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, - init_per_group/2,end_per_group/2]). --export([init_per_testcase/2, end_per_testcase/2]). +-export([all/0, suite/0]). %% Test cases -export([app_file/1, appup_file/1, start_stop_app/1]). -%% Default timetrap timeout (set in init_per_testcase) --define(default_timeout, ?t:minutes(1)). - -init_per_testcase(_Case, Config) -> - Dog = test_server:timetrap(?default_timeout), - [{watchdog, Dog} | Config]. - -end_per_testcase(_Case, Config) -> - Dog = ?config(watchdog, Config), - ?t:timetrap_cancel(Dog), - ok. - -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> + [{ct_hooks,[ts_install_cth]}, + {timetrap, {minutes, 1}}]. all() -> [app_file, appup_file, start_stop_app]. -groups() -> - []. - -init_per_suite(Config) -> - Config. - -end_per_suite(_Config) -> - ok. - -init_per_group(_GroupName, Config) -> - Config. - -end_per_group(_GroupName, Config) -> - Config. - app_file(_Config) -> - ?line ok = ?t:app_test(runtime_tools), + ok = test_server:app_test(runtime_tools), ok. appup_file(_Config) -> - ok = ?t:appup_test(runtime_tools). + ok = test_server:appup_test(runtime_tools). start_stop_app(_Config) -> ok = application:start(runtime_tools), diff --git a/lib/runtime_tools/test/system_information_SUITE.erl b/lib/runtime_tools/test/system_information_SUITE.erl index 5e2e0d17ac..a5a025a1cf 100644 --- a/lib/runtime_tools/test/system_information_SUITE.erl +++ b/lib/runtime_tools/test/system_information_SUITE.erl @@ -230,19 +230,19 @@ api_report(_Config) -> ok. api_to_file(Config) -> - DataDir = ?config(data_dir, Config), + DataDir = proplists:get_value(data_dir, Config), Filename = filename:join([DataDir, "system_information_report_1.dat"]), ok = system_information:to_file(Filename), {ok, _} = file:consult(Filename), {save_config, [{report_name, Filename}]}. api_from_file(Config) -> - {api_to_file, Saved} = ?config(saved_config, Config), - DataDir = ?config(data_dir, Config), + {api_to_file, Saved} = proplists:get_value(saved_config, Config), + DataDir = proplists:get_value(data_dir, Config), Fname1 = filename:join([DataDir, "information_test_report.dat"]), Report1 = system_information:from_file(Fname1), ok = validate_report(Report1), - Fname2 = ?config(report_name, Saved), + Fname2 = proplists:get_value(report_name, Saved), Report2 = system_information:from_file(Fname2), ok = validate_report(Report2), ok. @@ -253,7 +253,7 @@ api_start_stop(_Config) -> ok. validate_server_interface(Config) -> - DataDir = ?config(data_dir, Config), + DataDir = proplists:get_value(data_dir, Config), Fname1 = filename:join([DataDir, "information_test_report.dat"]), %% load old report ok = system_information:load_report(file, Fname1), diff --git a/lib/sasl/doc/src/appup.xml b/lib/sasl/doc/src/appup.xml index a57068492c..6fbdcb9f5b 100644 --- a/lib/sasl/doc/src/appup.xml +++ b/lib/sasl/doc/src/appup.xml @@ -137,7 +137,8 @@ code change. If it is set to <c>{advanced,Extra}</c>, implemented processes using <seealso marker="stdlib:gen_server"><c>gen_server</c></seealso>, - <seealso marker="stdlib:gen_fsm"><c>gen_fsm</c></seealso>, or + <seealso marker="stdlib:gen_fsm"><c>gen_fsm</c></seealso>, + <seealso marker="stdlib:gen_statem"><c>gen_statem</c></seealso>, or <seealso marker="stdlib:gen_event"><c>gen_event</c></seealso> transform their internal state by calling the callback function <c>code_change</c>. Special processes call the callback diff --git a/lib/sasl/doc/src/error_logging.xml b/lib/sasl/doc/src/error_logging.xml index 86d0c811e9..8464a41ff9 100644 --- a/lib/sasl/doc/src/error_logging.xml +++ b/lib/sasl/doc/src/error_logging.xml @@ -90,8 +90,9 @@ a process terminates with an unexpected reason, which is any reason other than <c>normal</c>, <c>shutdown</c>, or <c>{shutdown,Term}</c>. Processes using behaviors - <seealso marker="stdlib:gen_server"><c>gen_server</c></seealso> or - <seealso marker="stdlib:gen_fsm"><c>gen_fsm</c></seealso> + <seealso marker="stdlib:gen_server"><c>gen_server</c></seealso>, + <seealso marker="stdlib:gen_fsm"><c>gen_fsm</c></seealso> or + <seealso marker="stdlib:gen_statem"><c>gen_statem</c></seealso> are examples of such processes. A crash report contains the following items:</p> <taglist> <tag><c>Crasher</c></tag> diff --git a/lib/sasl/test/release_handler_SUITE.erl b/lib/sasl/test/release_handler_SUITE.erl index ee620dcdb4..4dcaec03a7 100644 --- a/lib/sasl/test/release_handler_SUITE.erl +++ b/lib/sasl/test/release_handler_SUITE.erl @@ -54,7 +54,7 @@ unix_cases() -> end, [target_system, target_system_unicode] ++ RunErlCases ++ cases(). -win32_cases() -> +win32_cases() -> [{group,release} | cases()]. %% Cases that can be run on all platforms @@ -89,11 +89,16 @@ groups() -> %% {group,release} %% Top group for all cases using run_erl init_per_group(release, Config) -> - Dog = ?t:timetrap(?default_timeout), - P1gInstall = filename:join(priv_dir(Config),p1g_install), - ok = create_p1g(Config,P1gInstall), - ok = create_p1h(Config), - ?t:timetrap_cancel(Dog); + case {os:type(), os:version()} of + {{win32, nt}, Vsn} when Vsn > {6,1,999999} -> + {skip, "Requires admin privileges on Win 8 and later"}; + _ -> + Dog = ?t:timetrap(?default_timeout), + P1gInstall = filename:join(priv_dir(Config),p1g_install), + ok = create_p1g(Config,P1gInstall), + ok = create_p1h(Config), + ?t:timetrap_cancel(Dog) + end; %% {group,release_single} %% Subgroup of {group,release}, contains all cases that are not diff --git a/lib/ssh/test/ssh_benchmark_SUITE.erl b/lib/ssh/test/ssh_benchmark_SUITE.erl index 8ec1017642..6e1d18cc95 100644 --- a/lib/ssh/test/ssh_benchmark_SUITE.erl +++ b/lib/ssh/test/ssh_benchmark_SUITE.erl @@ -104,7 +104,7 @@ init_sftp_dirs(Config) -> DstDir = filename:join(UserDir, "sftp_dst"), ok = file:make_dir(DstDir), N = 100 * 1024*1024, - ok = file:write_file(filename:join(SrcDir,SrcFile), crypto:rand_bytes(N)), + ok = file:write_file(filename:join(SrcDir,SrcFile), crypto:strong_rand_bytes(N)), [{sftp_src_dir,SrcDir}, {sftp_dst_dir,DstDir}, {src_file,SrcFile}, {sftp_size,N} | Config]. diff --git a/lib/ssh/test/ssh_sftp_SUITE.erl b/lib/ssh/test/ssh_sftp_SUITE.erl index c4bb02841b..cd6c5f82b9 100644 --- a/lib/ssh/test/ssh_sftp_SUITE.erl +++ b/lib/ssh/test/ssh_sftp_SUITE.erl @@ -869,7 +869,7 @@ aes_cbc256_crypto_tar(Config) -> {"d1",fn("d1",Config)} % Dir ]), Key = <<"This is a 256 bit key. Boring...">>, - Ivec0 = crypto:rand_bytes(16), + Ivec0 = crypto:strong_rand_bytes(16), DataSize = 1024, % data_size rem 16 = 0 for aes_cbc Cinitw = fun() -> {ok, Ivec0, DataSize} end, @@ -914,7 +914,7 @@ aes_ctr_stream_crypto_tar(Config) -> {"d1",fn("d1",Config)} % Dir ]), Key = <<"This is a 256 bit key. Boring...">>, - Ivec0 = crypto:rand_bytes(16), + Ivec0 = crypto:strong_rand_bytes(16), Cinitw = Cinitr = fun() -> {ok, crypto:stream_init(aes_ctr,Key,Ivec0)} end, diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl index abbd4857c9..4db7d09ccd 100644 --- a/lib/ssh/test/ssh_test_lib.erl +++ b/lib/ssh/test/ssh_test_lib.erl @@ -100,7 +100,7 @@ std_simple_sftp(Host, Port, Config, Opts) -> DataFile = filename:join(UserDir, "test.data"), ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, Opts), {ok, ChannelRef} = ssh_sftp:start_channel(ConnectionRef), - Data = crypto:rand_bytes(proplists:get_value(std_simple_sftp_size,Config,10)), + Data = crypto:strong_rand_bytes(proplists:get_value(std_simple_sftp_size,Config,10)), ok = ssh_sftp:write_file(ChannelRef, DataFile, Data), {ok,ReadData} = file:read_file(DataFile), ok = ssh:close(ConnectionRef), @@ -354,7 +354,7 @@ setup_rsa_pass_pharse(DataDir, UserDir, Phrase) -> setup_pass_pharse(KeyBin, OutFile, Phrase) -> [{KeyType, _,_} = Entry0] = public_key:pem_decode(KeyBin), Key = public_key:pem_entry_decode(Entry0), - Salt = crypto:rand_bytes(8), + Salt = crypto:strong_rand_bytes(8), Entry = public_key:pem_entry_encode(KeyType, Key, {{"DES-CBC", Salt}, Phrase}), Pem = public_key:pem_encode([Entry]), diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml index 7c55dd4c96..00419b63db 100644 --- a/lib/ssl/doc/src/notes.xml +++ b/lib/ssl/doc/src/notes.xml @@ -28,6 +28,36 @@ <p>This document describes the changes made to the SSL application.</p> +<section><title>SSL 7.3.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Corrections to cipher suite handling using the 3 and 4 + tuple format in addition to commit + 89d7e21cf4ae988c57c8ef047bfe85127875c70c</p> + <p> + Own Id: OTP-13511</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Make values for the TLS-1.2 signature_algorithms + extension configurable</p> + <p> + Own Id: OTP-13261</p> + </item> + </list> + </section> + +</section> + <section><title>SSL 7.3</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src index 057906bcb3..11728128c4 100644 --- a/lib/ssl/src/ssl.appup.src +++ b/lib/ssl/src/ssl.appup.src @@ -1,9 +1,6 @@ %% -*- erlang -*- {"%VSN%", [ - {<<"7\\.2">>, [{load_module, tls_connection, soft_purge, soft_purge, []}, - {load_module, ssl_tls_dist_proxy, soft_purge, soft_purge, []} - ]}, {<<"7\\..*">>, [{restart_application, ssl}]}, {<<"6\\..*">>, [{restart_application, ssl}]}, {<<"5\\..*">>, [{restart_application, ssl}]}, @@ -11,9 +8,6 @@ {<<"3\\..*">>, [{restart_application, ssl}]} ], [ - {<<"7\\.2">>, [{load_module, tls_connection, soft_purge, soft_purge, []}, - {load_module, ssl_tls_dist_proxy, soft_purge, soft_purge, []} - ]}, {<<"7\\..*">>, [{restart_application, ssl}]}, {<<"6\\..*">>, [{restart_application, ssl}]}, {<<"5\\..*">>, [{restart_application, ssl}]}, diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 4bcd6ddb0e..025e8cea61 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -42,7 +42,7 @@ renegotiate/1, prf/5, negotiated_protocol/1, negotiated_next_protocol/1, connection_information/1, connection_information/2]). %% Misc --export([random_bytes/1, handle_options/2]). +-export([handle_options/2]). -deprecated({negotiated_next_protocol, 1, next_major_release}). -deprecated({connection_info, 1, next_major_release}). @@ -581,22 +581,6 @@ format_error(Error) -> Other end. -%%-------------------------------------------------------------------- --spec random_bytes(integer()) -> binary(). - -%% -%% Description: Generates cryptographically secure random sequence if possible -%% fallbacks on pseudo random function -%%-------------------------------------------------------------------- -random_bytes(N) -> - try crypto:strong_rand_bytes(N) of - RandBytes -> - RandBytes - catch - error:low_entropy -> - crypto:rand_bytes(N) - end. - %%%-------------------------------------------------------------- %%% Internal functions %%%-------------------------------------------------------------------- @@ -1105,10 +1089,7 @@ binary_cipher_suites(Version, []) -> %% Defaults to all supported suites that does %% not require explicit configuration ssl_cipher:filter_suites(ssl_cipher:suites(Version)); -binary_cipher_suites(Version, [{_,_,_,_}| _] = Ciphers0) -> %% Backwards compatibility - Ciphers = [{KeyExchange, Cipher, Hash} || {KeyExchange, Cipher, Hash, _} <- Ciphers0], - binary_cipher_suites(Version, Ciphers); -binary_cipher_suites(Version, [{_,_,_}| _] = Ciphers0) -> +binary_cipher_suites(Version, [Tuple|_] = Ciphers0) when is_tuple(Tuple) -> Ciphers = [ssl_cipher:suite(C) || C <- Ciphers0], binary_cipher_suites(Version, Ciphers); diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index e66f253a70..544d9b41cd 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -1,4 +1,4 @@ -%% +% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2007-2016. All Rights Reserved. @@ -39,7 +39,8 @@ suite/1, suites/1, all_suites/1, ec_keyed_suites/0, anonymous_suites/1, psk_suites/1, srp_suites/0, rc4_suites/1, des_suites/1, openssl_suite/1, openssl_suite_name/1, filter/2, filter_suites/1, - hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1]). + hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1, + random_bytes/1]). -export_type([cipher_suite/0, erl_cipher_suite/0, openssl_cipher_suite/0, @@ -49,7 +50,8 @@ | aes_128_cbc | aes_256_cbc | aes_128_gcm | aes_256_gcm | chacha20_poly1305. -type hash() :: null | sha | md5 | sha224 | sha256 | sha384 | sha512. -type sign_algo() :: rsa | dsa | ecdsa. --type key_algo() :: null | rsa | dhe_rsa | dhe_dss | ecdhe_ecdsa| ecdh_ecdsa | ecdh_rsa| srp_rsa| srp_dss | psk | dhe_psk | rsa_psk | dh_anon | ecdh_anon | srp_anon. +-type key_algo() :: null | rsa | dhe_rsa | dhe_dss | ecdhe_ecdsa| ecdh_ecdsa | ecdh_rsa| srp_rsa| srp_dss | + psk | dhe_psk | rsa_psk | dh_anon | ecdh_anon | srp_anon. -type erl_cipher_suite() :: {key_algo(), cipher(), hash()} % Pre TLS 1.2 %% TLS 1.2, internally PRE TLS 1.2 will use default_prf | {key_algo(), cipher(), hash(), hash() | default_prf}. @@ -102,7 +104,7 @@ cipher_init(?RC4, IV, Key) -> State = crypto:stream_init(rc4, Key), #cipher_state{iv = IV, key = Key, state = State}; cipher_init(?AES_GCM, IV, Key) -> - <<Nonce:64>> = ssl:random_bytes(8), + <<Nonce:64>> = random_bytes(8), #cipher_state{iv = IV, key = Key, nonce = Nonce}; cipher_init(_BCA, IV, Key) -> #cipher_state{iv = IV, key = Key}. @@ -853,17 +855,17 @@ suite({rsa_psk, aes_256_cbc,sha}) -> %%% TLS 1.2 PSK Cipher Suites RFC 5487 -suite({psk, aes_128_gcm, null}) -> +suite({psk, aes_128_gcm, null, sha256}) -> ?TLS_PSK_WITH_AES_128_GCM_SHA256; -suite({psk, aes_256_gcm, null}) -> +suite({psk, aes_256_gcm, null, sha384}) -> ?TLS_PSK_WITH_AES_256_GCM_SHA384; -suite({dhe_psk, aes_128_gcm, null}) -> +suite({dhe_psk, aes_128_gcm, null, sha256}) -> ?TLS_DHE_PSK_WITH_AES_128_GCM_SHA256; -suite({dhe_psk, aes_256_gcm, null}) -> +suite({dhe_psk, aes_256_gcm, null, sha384}) -> ?TLS_DHE_PSK_WITH_AES_256_GCM_SHA384; -suite({rsa_psk, aes_128_gcm, null}) -> +suite({rsa_psk, aes_128_gcm, null, sha256}) -> ?TLS_RSA_PSK_WITH_AES_128_GCM_SHA256; -suite({rsa_psk, aes_256_gcm, null}) -> +suite({rsa_psk, aes_256_gcm, null, sha384}) -> ?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384; suite({psk, aes_128_cbc, sha256}) -> @@ -970,74 +972,74 @@ suite({ecdh_anon, aes_256_cbc, sha}) -> ?TLS_ECDH_anon_WITH_AES_256_CBC_SHA; %%% RFC 5289 EC TLS suites -suite({ecdhe_ecdsa, aes_128_cbc, sha256}) -> +suite({ecdhe_ecdsa, aes_128_cbc, sha256, sha256}) -> ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256; -suite({ecdhe_ecdsa, aes_256_cbc, sha384}) -> +suite({ecdhe_ecdsa, aes_256_cbc, sha384, sha384}) -> ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384; -suite({ecdh_ecdsa, aes_128_cbc, sha256}) -> +suite({ecdh_ecdsa, aes_128_cbc, sha256, sha256}) -> ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256; -suite({ecdh_ecdsa, aes_256_cbc, sha384}) -> +suite({ecdh_ecdsa, aes_256_cbc, sha384, sha384}) -> ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384; -suite({ecdhe_rsa, aes_128_cbc, sha256}) -> +suite({ecdhe_rsa, aes_128_cbc, sha256, sha256}) -> ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256; -suite({ecdhe_rsa, aes_256_cbc, sha384}) -> +suite({ecdhe_rsa, aes_256_cbc, sha384, sha384}) -> ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384; -suite({ecdh_rsa, aes_128_cbc, sha256}) -> +suite({ecdh_rsa, aes_128_cbc, sha256, sha256}) -> ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256; -suite({ecdh_rsa, aes_256_cbc, sha384}) -> +suite({ecdh_rsa, aes_256_cbc, sha384, sha384}) -> ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384; %% RFC 5288 AES-GCM Cipher Suites -suite({rsa, aes_128_gcm, null}) -> +suite({rsa, aes_128_gcm, null, sha256}) -> ?TLS_RSA_WITH_AES_128_GCM_SHA256; suite({rsa, aes_256_gcm, null}) -> ?TLS_RSA_WITH_AES_256_GCM_SHA384; -suite({dhe_rsa, aes_128_gcm, null}) -> +suite({dhe_rsa, aes_128_gcm, null, sha384}) -> ?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256; -suite({dhe_rsa, aes_256_gcm, null}) -> +suite({dhe_rsa, aes_256_gcm, null, sha256}) -> ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384; -suite({dh_rsa, aes_128_gcm, null}) -> +suite({dh_rsa, aes_128_gcm, null, sha384}) -> ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256; -suite({dh_rsa, aes_256_gcm, null}) -> +suite({dh_rsa, aes_256_gcm, null, sha256}) -> ?TLS_DH_RSA_WITH_AES_256_GCM_SHA384; -suite({dhe_dss, aes_128_gcm, null}) -> +suite({dhe_dss, aes_128_gcm, null, sha384}) -> ?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256; -suite({dhe_dss, aes_256_gcm, null}) -> +suite({dhe_dss, aes_256_gcm, null, sha256}) -> ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384; -suite({dh_dss, aes_128_gcm, null}) -> +suite({dh_dss, aes_128_gcm, null, sha384}) -> ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256; -suite({dh_dss, aes_256_gcm, null}) -> +suite({dh_dss, aes_256_gcm, null, sha384}) -> ?TLS_DH_DSS_WITH_AES_256_GCM_SHA384; -suite({dh_anon, aes_128_gcm, null}) -> +suite({dh_anon, aes_128_gcm, null, sha256}) -> ?TLS_DH_anon_WITH_AES_128_GCM_SHA256; -suite({dh_anon, aes_256_gcm, null}) -> +suite({dh_anon, aes_256_gcm, null, sha384}) -> ?TLS_DH_anon_WITH_AES_256_GCM_SHA384; %% RFC 5289 ECC AES-GCM Cipher Suites -suite({ecdhe_ecdsa, aes_128_gcm, null}) -> +suite({ecdhe_ecdsa, aes_128_gcm, null, sha256}) -> ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256; -suite({ecdhe_ecdsa, aes_256_gcm, null}) -> +suite({ecdhe_ecdsa, aes_256_gcm, null, sha384}) -> ?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384; -suite({ecdh_ecdsa, aes_128_gcm, null}) -> +suite({ecdh_ecdsa, aes_128_gcm, null, sha256}) -> ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256; -suite({ecdh_ecdsa, aes_256_gcm, null}) -> +suite({ecdh_ecdsa, aes_256_gcm, null, sha384}) -> ?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384; -suite({ecdhe_rsa, aes_128_gcm, null}) -> +suite({ecdhe_rsa, aes_128_gcm, null, sha256}) -> ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256; -suite({ecdhe_rsa, aes_256_gcm, null}) -> +suite({ecdhe_rsa, aes_256_gcm, null, sha384}) -> ?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384; -suite({ecdh_rsa, aes_128_gcm, null}) -> +suite({ecdh_rsa, aes_128_gcm, null, sha256}) -> ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256; -suite({ecdh_rsa, aes_256_gcm, null}) -> +suite({ecdh_rsa, aes_256_gcm, null, sha384}) -> ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384; %% draft-agl-tls-chacha20poly1305-04 Chacha20/Poly1305 Suites -suite({ecdhe_rsa, chacha20_poly1305, null}) -> +suite({ecdhe_rsa, chacha20_poly1305, null, sha256}) -> ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256; -suite({ecdhe_ecdsa, chacha20_poly1305, null}) -> +suite({ecdhe_ecdsa, chacha20_poly1305, null, sha256}) -> ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256; -suite({dhe_rsa, chacha20_poly1305, null}) -> +suite({dhe_rsa, chacha20_poly1305, null, sha256}) -> ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256. %%-------------------------------------------------------------------- @@ -1472,6 +1474,16 @@ is_acceptable_prf(Prf, Algos) -> is_fallback(CipherSuites)-> lists:member(?TLS_FALLBACK_SCSV, CipherSuites). + +%%-------------------------------------------------------------------- +-spec random_bytes(integer()) -> binary(). + +%% +%% Description: Generates cryptographically secure random sequence +%%-------------------------------------------------------------------- +random_bytes(N) -> + crypto:strong_rand_bytes(N). + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -1712,7 +1724,7 @@ get_padding_aux(BlockSize, PadLength) -> random_iv(IV) -> IVSz = byte_size(IV), - ssl:random_bytes(IVSz). + random_bytes(IVSz). next_iv(Bin, IV) -> BinSz = byte_size(Bin), diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 1568e8559f..0073e86e26 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -502,7 +502,7 @@ certify(#server_hello_done{}, role = client, key_algorithm = Alg} = State0, Connection) when Alg == rsa_psk -> - Rand = ssl:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), + Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), RSAPremasterSecret = <<?BYTE(Major), ?BYTE(Minor), Rand/binary>>, case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup, RSAPremasterSecret) of #alert{} = Alert -> @@ -1885,7 +1885,7 @@ handle_resumed_session(SessId, #state{connection_states = ConnectionStates0, end. make_premaster_secret({MajVer, MinVer}, rsa) -> - Rand = ssl:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), + Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>; make_premaster_secret(_, _) -> undefined. diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index e273581de9..2349158b11 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -551,7 +551,7 @@ last_delay_timer({_,_}, TRef, {_, LastClient}) -> new_id(_, 0, _, _) -> <<>>; new_id(Port, Tries, Cache, CacheCb) -> - Id = crypto:rand_bytes(?NUM_OF_SESSION_ID_BYTES), + Id = ssl_cipher:random_bytes(?NUM_OF_SESSION_ID_BYTES), case CacheCb:lookup(Cache, {Port, Id}) of undefined -> Now = erlang:monotonic_time(), diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index ecff950668..866bfcef7e 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -460,7 +460,7 @@ empty_security_params(ConnectionEnd = ?SERVER) -> random() -> Secs_since_1970 = calendar:datetime_to_gregorian_seconds( calendar:universal_time()) - 62167219200, - Random_28_bytes = crypto:rand_bytes(28), + Random_28_bytes = ssl_cipher:random_bytes(28), <<?UINT32(Secs_since_1970), Random_28_bytes/binary>>. dtls_next_epoch(#connection_state{epoch = undefined}) -> %% SSL/TLS diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 50313e6a22..0c3c7aa39b 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -168,6 +168,7 @@ renegotiate_tests() -> cipher_tests() -> [cipher_suites, + cipher_suites_mix, ciphers_rsa_signed_certs, ciphers_rsa_signed_certs_openssl_names, ciphers_dsa_signed_certs, @@ -913,6 +914,31 @@ cipher_suites(Config) when is_list(Config) -> [_|_] =ssl:cipher_suites(openssl). %%-------------------------------------------------------------------- +cipher_suites_mix() -> + [{doc,"Test to have old and new cipher suites at the same time"}]. + +cipher_suites_mix(Config) when is_list(Config) -> + CipherSuites = [{ecdh_rsa,aes_128_cbc,sha256,sha256}, {rsa,aes_128_cbc,sha}], + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{ciphers, CipherSuites} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- socket_options() -> [{doc,"Test API function getopts/2 and setopts/2"}]. @@ -1555,7 +1581,7 @@ tcp_connect_big(Config) when is_list(Config) -> {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), TcpOpts = [binary, {reuseaddr, true}], - Rand = crypto:rand_bytes(?MAX_CIPHER_TEXT_LENGTH+1), + Rand = crypto:strong_rand_bytes(?MAX_CIPHER_TEXT_LENGTH+1), Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {timeout, 5000}, diff --git a/lib/ssl/test/ssl_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl index bd0ddde090..e7cbfa63f4 100644 --- a/lib/ssl/test/ssl_dist_SUITE.erl +++ b/lib/ssl/test/ssl_dist_SUITE.erl @@ -194,7 +194,7 @@ payload(Config) when is_list(Config) -> ok = apply_on_ssl_node( NH2, fun () -> - Msg = crypto:rand_bytes(100000), + Msg = crypto:strong_rand_bytes(100000), SslPid ! {self(), Msg}, receive {SslPid, Msg} -> diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index 2cd23eb3b8..db9e1c3d38 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -349,7 +349,7 @@ wait_for_result(Pid, Msg) -> user_lookup(psk, _Identity, UserState) -> {ok, UserState}; user_lookup(srp, Username, _UserState) -> - Salt = ssl:random_bytes(16), + Salt = ssl_cipher:random_bytes(16), UserPassHash = crypto:hash(sha, [Salt, crypto:hash(sha, [Username, <<$:>>, <<"secret">>])]), {ok, {srp_1024, Salt, UserPassHash}}. @@ -905,8 +905,8 @@ anonymous_suites() -> {dh_anon, '3des_ede_cbc', sha}, {dh_anon, aes_128_cbc, sha}, {dh_anon, aes_256_cbc, sha}, - {dh_anon, aes_128_gcm, null}, - {dh_anon, aes_256_gcm, null}, + {dh_anon, aes_128_gcm, null, sha256}, + {dh_anon, aes_256_gcm, null, sha384}, {ecdh_anon,rc4_128,sha}, {ecdh_anon,'3des_ede_cbc',sha}, {ecdh_anon,aes_128_cbc,sha}, @@ -933,12 +933,12 @@ psk_suites() -> {rsa_psk, aes_256_cbc, sha}, {rsa_psk, aes_128_cbc, sha256}, {rsa_psk, aes_256_cbc, sha384}, - {psk, aes_128_gcm, null}, - {psk, aes_256_gcm, null}, - {dhe_psk, aes_128_gcm, null}, - {dhe_psk, aes_256_gcm, null}, - {rsa_psk, aes_128_gcm, null}, - {rsa_psk, aes_256_gcm, null}], + {psk, aes_128_gcm, null, sha256}, + {psk, aes_256_gcm, null, sha384}, + {dhe_psk, aes_128_gcm, null, sha256}, + {dhe_psk, aes_256_gcm, null, sha384}, + {rsa_psk, aes_128_gcm, null, sha256}, + {rsa_psk, aes_256_gcm, null, sha384}], ssl_cipher:filter_suites(Suites). psk_anon_suites() -> diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index 47cb2909fb..8c732002de 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -1 +1 @@ -SSL_VSN = 7.3 +SSL_VSN = 7.3.1 diff --git a/lib/stdlib/doc/src/Makefile b/lib/stdlib/doc/src/Makefile index 196c86748f..26602764a6 100644 --- a/lib/stdlib/doc/src/Makefile +++ b/lib/stdlib/doc/src/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 1997-2015. All Rights Reserved. +# Copyright Ericsson AB 1997-2016. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -68,6 +68,7 @@ XML_REF3_FILES = \ gen_event.xml \ gen_fsm.xml \ gen_server.xml \ + gen_statem.xml \ io.xml \ io_lib.xml \ lib.xml \ diff --git a/lib/stdlib/doc/src/binary.xml b/lib/stdlib/doc/src/binary.xml index 5402fd0163..933157fc34 100644 --- a/lib/stdlib/doc/src/binary.xml +++ b/lib/stdlib/doc/src/binary.xml @@ -366,7 +366,7 @@ <code> 1> binary:matches(<<"abcde">>, - [<<"bcde">>,<<"bc">>>,<<"de">>],[]). + [<<"bcde">>,<<"bc">>,<<"de">>],[]). [{1,4}] </code> diff --git a/lib/stdlib/doc/src/gen_fsm.xml b/lib/stdlib/doc/src/gen_fsm.xml index 37e7c6ed17..835e252704 100644 --- a/lib/stdlib/doc/src/gen_fsm.xml +++ b/lib/stdlib/doc/src/gen_fsm.xml @@ -31,12 +31,23 @@ <module>gen_fsm</module> <modulesummary>Generic Finite State Machine Behaviour</modulesummary> <description> + <note> + <p> + There is a new behaviour + <seealso marker="gen_statem"><c>gen_statem</c></seealso> + that is intended to replace <c>gen_fsm</c> for new code. + It has the same features and add some really useful. + This module will not be removed for the foreseeable future + to keep old state machine implementations running. + </p> + </note> <p>A behaviour module for implementing a finite state machine. A generic finite state machine process (gen_fsm) implemented using this module will have a standard set of interface functions and include functionality for tracing and error reporting. It will also fit into an OTP supervision tree. Refer to - <seealso marker="doc/design_principles:fsm">OTP Design Principles</seealso> for more information.</p> + <seealso marker="doc/design_principles:fsm">OTP Design Principles</seealso> for more information. + </p> <p>A gen_fsm assumes all specific parts to be located in a callback module exporting a pre-defined set of functions. The relationship between the behaviour functions and the callback functions can be @@ -848,6 +859,7 @@ gen_fsm:sync_send_all_state_event -----> Module:handle_sync_event/4 <title>SEE ALSO</title> <p><seealso marker="gen_event">gen_event(3)</seealso>, <seealso marker="gen_server">gen_server(3)</seealso>, + <seealso marker="gen_statem">gen_statem(3)</seealso>, <seealso marker="supervisor">supervisor(3)</seealso>, <seealso marker="proc_lib">proc_lib(3)</seealso>, <seealso marker="sys">sys(3)</seealso></p> diff --git a/lib/stdlib/doc/src/gen_server.xml b/lib/stdlib/doc/src/gen_server.xml index 02da11250a..10dc978afc 100644 --- a/lib/stdlib/doc/src/gen_server.xml +++ b/lib/stdlib/doc/src/gen_server.xml @@ -715,6 +715,7 @@ gen_server:abcast -----> Module:handle_cast/2 <title>SEE ALSO</title> <p><seealso marker="gen_event">gen_event(3)</seealso>, <seealso marker="gen_fsm">gen_fsm(3)</seealso>, + <seealso marker="gen_statem">gen_statem(3)</seealso>, <seealso marker="supervisor">supervisor(3)</seealso>, <seealso marker="proc_lib">proc_lib(3)</seealso>, <seealso marker="sys">sys(3)</seealso></p> diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml new file mode 100644 index 0000000000..ec7f267c64 --- /dev/null +++ b/lib/stdlib/doc/src/gen_statem.xml @@ -0,0 +1,1724 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE erlref SYSTEM "erlref.dtd"> + +<erlref> + <header> + <copyright> + <year>2016</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + </legalnotice> + + <title>gen_statem</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + </header> + <module>gen_statem</module> + <modulesummary>Generic State Machine Behaviour</modulesummary> + <description> + <p> + A behaviour module for implementing a state machine. Two + <seealso marker="#type-callback_mode"><em>callback modes</em></seealso> + are supported. One for finite state machines + (<seealso marker="gen_fsm"><c>gen_fsm</c></seealso> like) + that requires the state to be an atom and uses that state as + the name of the current callback function, + and one without restriction on the state data type + that uses one callback function for all states. + </p> + <p> + This is a new behaviour in OTP-19.0. + It has been thoroughly reviewed, is stable enough + to be used by at least two heavy OTP applications, and is here to stay. + But depending on user feedback, we do not expect + but might find it necessary to make minor + not backwards compatible changes into OTP-20.0, + so its state can be designated as "not quite experimental"... + </p> + <p> + The <c>gen_statem</c> behaviour is intended to replace + <seealso marker="gen_fsm"><c>gen_fsm</c></seealso> for new code. + It has the same features and add some really useful: + </p> + <list type="bulleted"> + <item>State code is gathered</item> + <item>The state can be any term</item> + <item>Events can be postponed</item> + <item>Events can be self generated</item> + <item>A reply can be sent from a later state</item> + <item>There can be multiple sys traceable replies</item> + </list> + <p> + The callback model(s) for <c>gen_statem</c> differs from + the one for <seealso marker="gen_fsm"><c>gen_fsm</c></seealso>, + but it is still fairly easy to rewrite + from <c>gen_fsm</c> to <c>gen_statem</c>. + </p> + <p> + A generic state machine process (<c>gen_statem</c>) implemented + using this module will have a standard set of interface functions + and include functionality for tracing and error reporting. + It will also fit into an OTP supervision tree. Refer to + <seealso marker="doc/design_principles:statem"> + OTP Design Principles + </seealso> + for more information. + </p> + <p> + A <c>gen_statem</c> assumes all specific parts to be located in a + callback module exporting a pre-defined set of functions. + The relationship between the behaviour functions and the callback + functions can be illustrated as follows:</p> + <pre> +gen_statem module Callback module +----------------- --------------- +gen_statem:start +gen_statem:start_link -----> Module:init/1 + +gen_statem:stop -----> Module:terminate/3 + +gen_statem:call +gen_statem:cast +erlang:send +erlang:'!' -----> Module:StateName/3 + Module:handle_event/4 + +- -----> Module:terminate/3 + +- -----> Module:code_change/4</pre> + <p> + Events are of different + <seealso marker="#type-event_type">types</seealso> + so the callback functions can know the origin of an event + and how to respond. + </p> + <p> + If a callback function fails or returns a bad value, + the <c>gen_statem</c> will terminate. An exception of class + <seealso marker="erts:erlang#throw/1"><c>throw</c></seealso>, + however, is not regarded as an error but as a valid return. + </p> + <marker id="state_function" /> + <p> + The "<em>state function</em>" for a specific + <seealso marker="#type-state">state</seealso> + in a <c>gen_statem</c> is the callback function that is called + for all events in this state, and is selected depending on which + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> + that the implementation specifies when the the server starts. + </p> + <p> + When the + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> + is <c>state_functions</c>, the state has to be an atom and + is used as the state function name. See + <seealso marker="#Module:StateName/3"> + <c>Module:StateName/3</c> + </seealso>. + This gathers all code for a specific state + in one function and hence dispatches on state first. + Note that in this mode the fact that there is + a mandatory callback function + <seealso marker="#Module:terminate/3"> + <c>Module:terminate/3</c> + </seealso> makes the state name <c>terminate</c> unusable. + </p> + <p> + When the + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> + is <c>handle_event_function</c> the state can be any term + and the state function name is + <seealso marker="#Module:handle_event/4"> + <c>Module:handle_event/4</c> + </seealso>. + This makes it easy to dispatch on state or on event as you desire. + Be careful about which events you handle in which + states so you do not accidentally postpone one event + forever creating an infinite busy loop. + </p> + <p> + The <c>gen_statem</c> enqueues incoming events in order of arrival + and presents these to the + <seealso marker="#state_function">state function</seealso> + in that order. The state function can postpone an event + so it is not retried in the current state. + After a state change the queue restarts with the postponed events. + </p> + <p> + The <c>gen_statem</c> event queue model is sufficient + to emulate the normal process message queue with selective receive. + Postponing an event corresponds to not matching it + in a receive statement and changing states corresponds + to entering a new receive statement. + </p> + <p> + The <seealso marker="#state_function">state function</seealso> + can insert events using the + <seealso marker="#type-action"> + <c>action()</c> <c>next_event</c> + </seealso> + and such an event is inserted as the next to present + to the state function. That is: as if it is + the oldest incoming event. There is a dedicated + <seealso marker="#type-event_type"> + <c>event_type()</c> + </seealso> + <c>internal</c> that can be used for such events making them impossible + to mistake for external events. + </p> + <p> + Inserting an event replaces the trick of calling your own + state handling functions that you often would have to + resort to in for example <seealso marker="gen_fsm"><c>gen_fsm</c></seealso> + to force processing an inserted event before others. + A warning, though: if you in <c>gen_statem</c> for example + postpone an event in one state and then call some other state function of yours, + you have not changed states and hence the postponed event will not be retried, + which is logical but might be confusing. + </p> + <p> + See the type + <seealso marker="#type-transition_option"> + <c>transition_option()</c> + </seealso> + for the details of a state transition. + </p> + <p> + A <c>gen_statem</c> handles system messages as documented in + <seealso marker="sys"><c>sys</c></seealso>. + The <c>sys</c>module can be used for debugging a <c>gen_statem</c>. + </p> + <p> + Note that a <c>gen_statem</c> does not trap exit signals + automatically, this must be explicitly initiated in + the callback module (by calling + <seealso marker="erts:erlang#process_flag/2"> + <c>process_flag(trap_exit, true)</c></seealso>. + </p> + <p> + Unless otherwise stated, all functions in this module fail if + the specified <c>gen_statem</c> does not exist or + if bad arguments are given. + </p> + <p> + The <c>gen_statem</c> process can go into hibernation; see + <seealso marker="proc_lib#hibernate/3"> + <c>proc_lib:hibernate/3</c>. + </seealso> + It is done when a + <seealso marker="#state_function">state function</seealso> or + <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> + specifies <c>hibernate</c> in the returned + <seealso marker="#type-action"><c>Actions</c></seealso> + list. This feature might be useful to reclaim process heap memory + while the server is expected to be idle for a long time. + However, use this feature with care + since hibernation can be too costly + to use after every event; see + <seealso marker="erts:erlang#hibernate/3"> + <c>erlang:hibernate/3</c>. + </seealso> + </p> + </description> + + <section> + <title>EXAMPLE</title> + <p> + This example shows a simple pushbutton model + for a toggling pushbutton implemented with + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> + <c>state_functions</c>. + You can push the button and it replies if it went on or off, + and you can ask for a count of how many times it has been + pushed to on. + </p> + <p>This is the complete callback module file <c>pushbutton.erl</c>:</p> + <code type="erl"> +-module(pushbutton). +-behaviour(gen_statem). + +-export([start/0,push/0,get_count/0,stop/0]). +-export([terminate/3,code_change/4,init/1]). +-export([on/3,off/3]). + +name() -> pushbutton_statem. % The registered server name +callback_mode() -> state_functions. + +%% API. This example uses a registered name name() +%% and does not link to the caller. +start() -> + gen_statem:start({local,name()}, ?MODULE, [], []). +push() -> + gen_statem:call(name(), push). +get_count() -> + gen_statem:call(name(), get_count). +stop() -> + gen_statem:stop(name()). + +%% Mandatory callback functions +terminate(_Reason, _State, _Data) -> + void. +code_change(_Vsn, State, Data, _Extra) -> + {callback_mode(),State,Data}. +init([]) -> + %% Set the callback mode and initial state + data. + %% Data is used only as a counter. + State = off, Data = 0, + {callback_mode(),State,Data}. + + +%%% State functions + +off({call,From}, push, Data) -> + %% Go to 'on', increment count and reply + %% that the resulting status is 'on' + {next_state,on,Data+1,[{reply,From,on}]}; +off(EventType, EventContent, Data) -> + handle_event(EventType, EventContent, Data). + +on({call,From}, push, Data) -> + %% Go to 'off' and reply that the resulting status is 'off' + {next_state,off,Data,[{reply,From,off}]}; +on(EventType, EventContent, Data) -> + handle_event(EventType, EventContent, Data). + +%% Handle events common to all states +handle_event({call,From}, get_count, Data) -> + %% Reply with the current count + {keep_state,Data,[{reply,From,Data}]}; +handle_event(_, _, Data) -> + %% Ignore all other events + {keep_state,Data}. + </code> + <p>And this is a shell session when running it:</p> + <pre> +1> pushbutton:start(). +{ok,<0.36.0>} +2> pushbutton:get_count(). +0 +3> pushbutton:push(). +on +4> pushbutton:get_count(). +1 +5> pushbutton:push(). +off +6> pushbutton:get_count(). +1 +7> pushbutton:stop(). +ok +8> pushbutton:push(). +** exception exit: {noproc,{gen_statem,call,[pushbutton_statem,push,infinity]}} + in function gen:do_for_proc/2 (gen.erl, line 261) + in call from gen_statem:call/3 (gen_statem.erl, line 386) + </pre> + + <p> + And just to compare styles here is the same example using + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> + <c>state_functions</c>, or rather here is code to replace + from the <c>init/1</c> function of the <c>pushbutton.erl</c> + example file above: + </p> + <code type="erl"> +init([]) -> + %% Set the callback mode and initial state + data. + %% Data is used only as a counter. + State = off, Data = 0, + {handle_event_function,State,Data}. + + +%%% Event handling + +handle_event({call,From}, push, off, Data) -> + %% Go to 'on', increment count and reply + %% that the resulting status is 'on' + {next_state,on,Data+1,[{reply,From,on}]}; +handle_event({call,From}, push, on, Data) -> + %% Go to 'off' and reply that the resulting status is 'off' + {next_state,off,Data,[{reply,From,off}]}; +%% +%% Event handling common to all states +handle_event({call,From}, get_count, State, Data) -> + %% Reply with the current count + {next_state,State,Data,[{reply,From,Data}]}; +handle_event(_, _, State, Data) -> + %% Ignore all other events + {next_state,State,Data}. + </code> + </section> + + <datatypes> + <datatype> + <name name="server_name" /> + <desc> + <p> + Name specification to use when starting + a <c>gen_statem</c> server. See + <seealso marker="#start_link/3"> + <c>start_link/3</c> + </seealso> + and + <seealso marker="#type-server_ref"> + <c>server_ref()</c> + </seealso> below. + </p> + </desc> + </datatype> + <datatype> + <name name="server_ref" /> + <desc> + <p> + Server specification to use when addressing + a <c>gen_statem</c> server. + See <seealso marker="#call/2"><c>call/2</c></seealso> and + <seealso marker="#type-server_name"> + <c>server_name()</c> + </seealso> + above. + </p> + <p>It can be:</p> + <taglist> + <tag><c>pid()</c><br /> + <c>LocalName</c></tag> + <item>The <c>gen_statem</c> is locally registered.</item> + <tag><c>Name, Node</c></tag> + <item> + The <c>gen_statem</c> is locally registered + on another node. + </item> + <tag><c>GlobalName</c></tag> + <item> + The <c>gen_statem</c> is globally registered + in <seealso marker="kernel:global"><c>global</c></seealso>. + </item> + <tag><c>RegMod, ViaName</c></tag> + <item> + The <c>gen_statem</c> is registered through + an alternative process registry. + The registry callback module <c>RegMod</c> + should export the functions + <c>register_name/2</c>, <c>unregister_name/1</c>, + <c>whereis_name/1</c> and <c>send/2</c>, + which should behave like the corresponding functions + in <seealso marker="kernel:global"><c>global</c></seealso>. + Thus, <c>{via,global,GlobalName}</c> is the same as + <c>{global,GlobalName}</c>. + </item> + </taglist> + </desc> + </datatype> + <datatype> + <name name="debug_opt" /> + <desc> + <p> + Debug option that can be used when starting + a <c>gen_statem</c> server through for example + <seealso marker="#enter_loop/5"><c>enter_loop/5</c></seealso>. + </p> + <p> + For every entry in <c><anno>Dbgs</anno></c> + the corresponding function in + <seealso marker="sys"><c>sys</c></seealso> will be called. + </p> + </desc> + </datatype> + <datatype> + <name name="start_opt" /> + <desc> + <p> + Options that can be used when starting + a <c>gen_statem</c> server through for example + <seealso marker="#start_link/3"><c>start_link/3</c></seealso>. + </p> + </desc> + </datatype> + <datatype> + <name name="start_ret" /> + <desc> + <p> + Return value from the start functions for_example + <seealso marker="#start_link/3"><c>start_link/3</c></seealso>. + </p> + </desc> + </datatype> + + <datatype> + <name name="from" /> + <desc> + <p> + Destination to use when replying through for example the + <seealso marker="#type-action"> + <c>action()</c> + </seealso> + <c>{reply,From,Reply}</c> + to a process that has called the <c>gen_statem</c> server using + <seealso marker="#call/2"><c>call/2</c></seealso>. + </p> + </desc> + </datatype> + <datatype> + <name name="state" /> + <desc> + <p> + After a state change (<c>NextState =/= State</c>) + all postponed events are retried. + </p> + </desc> + </datatype> + <datatype> + <name name="state_name" /> + <desc> + <p> + If the + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> + is <c>state_functions</c>, + the state has to be of this type. + </p> + </desc> + </datatype> + <datatype> + <name name="data" /> + <desc> + <p> + A term in which the state machine implementation + should store any server data it needs. The difference between + this and the <seealso marker="#type-state"><c>state()</c></seealso> + itself is that a change in this data does not cause + postponed events to be retried. Hence if a change + in this data would change the set of events that + are handled than that data item should be made + a part of the state. + </p> + </desc> + </datatype> + <datatype> + <name name="event_type" /> + <desc> + <p> + External events are of 3 different type: + <c>{call,<anno>From</anno>}</c>, <c>cast</c> or <c>info</c>. + <seealso marker="#call/2">Calls</seealso> + (synchronous) and + <seealso marker="#cast/2">casts</seealso> + originate from the corresponding API functions. + For calls the event contain whom to reply to. + Type <c>info</c> originates from regular process messages sent + to the <c>gen_statem</c>. It is also possible for the state machine + implementation to generate events of types + <c>timeout</c> and <c>internal</c> to itself. + </p> + </desc> + </datatype> + <datatype> + <name name="callback_mode" /> + <desc> + <p> + The <em>callback mode</em> is selected when starting the + <c>gen_statem</c> using the return value from + <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> + or when calling + <seealso marker="#enter_loop/5"><c>enter_loop/5-7</c></seealso>, + and with the return value from + <seealso marker="#Module:code_change/4"> + <c>Module:code_change/4</c>. + </seealso> + </p> + <taglist> + <tag><c>state_functions</c></tag> + <item> + The state has to be of type + <seealso marker="#type-state_name"><c>state_name()</c></seealso> + and one callback function per state that is + <seealso marker="#Module:StateName/3"> + <c>Module:StateName/3</c> + </seealso> + is used. + </item> + <tag><c>handle_event_function</c></tag> + <item> + The state can be any term and the callback function + <seealso marker="#Module:handle_event/4"> + <c>Module:handle_event/4</c> + </seealso> + is used for all states. + </item> + </taglist> + </desc> + </datatype> + <datatype> + <name name="transition_option" /> + <desc> + <p> + Transition options may be set by + <seealso marker="#type-action">actions</seealso> + and they modify some details below in how + the state transition is done: + </p> + <list type="ordered"> + <item> + All + <seealso marker="#type-action">actions</seealso> + are processed in order of appearance. + </item> + <item> + If + <seealso marker="#type-postpone"> + <c>postpone()</c> + </seealso> + is <c>true</c> + the current event is postponed. + </item> + <item> + If the state changes the queue of incoming events + is reset to start with the oldest postponed. + </item> + <item> + All events stored with + <seealso marker="#type-action"> + <c>action()</c> + </seealso> + <c>next_event</c> + are inserted in the queue to be processed before + all other events. + </item> + <item> + If an + <seealso marker="#type-event_timeout"> + <c>event_timeout()</c> + </seealso> + is set through + <seealso marker="#type-action"> + <c>action()</c> + </seealso> + <c>timeout</c> + an event timer may be started or a timeout zero event + may be enqueued. + </item> + <item> + The (possibly new) + <seealso marker="#state_function">state function</seealso> + is called with the oldest enqueued event if there is any, + otherwise the <c>gen_statem</c> goes into <c>receive</c> + or hibernation + (if + <seealso marker="#type-hibernate"> + <c>hibernate()</c> + </seealso> + is <c>true</c>) + to wait for the next message. In hibernation the next + non-system event awakens the <c>gen_statem</c>, or rather + the next incoming message awakens the <c>gen_statem</c> + but if it is a system event + it goes right back into hibernation. + </item> + </list> + </desc> + </datatype> + <datatype> + <name name="postpone" /> + <desc> + <p> + If <c>true</c> postpone the current event and retry + it when the state changes + (<c>NextState =/= State</c>). + </p> + </desc> + </datatype> + <datatype> + <name name="hibernate" /> + <desc> + <p> + If <c>true</c> hibernate the <c>gen_statem</c> + by calling + <seealso marker="proc_lib#hibernate/3"> + <c>proc_lib:hibernate/3</c> + </seealso> + before going into <c>receive</c> + to wait for a new external event. + If there are enqueued events, + to prevent receiving any new event; a + <seealso marker="erts:erlang#garbage_collect/0"> + <c>garbage_collect/0</c> + </seealso> is done instead to simulate + that the <c>gen_statem</c> entered hibernation + and immediately got awakened by the oldest enqueued event. + </p> + </desc> + </datatype> + <datatype> + <name name="event_timeout" /> + <desc> + <p> + Generate an event of + <seealso marker="#type-event_type"><c>event_type()</c></seealso> + <c>timeout</c> + after this time (in milliseconds) unless some other + event arrives in which case this timeout is cancelled. + Note that a retried or inserted event + counts just like a new in this respect. + </p> + <p> + If the value is <c>infinity</c> no timer is started since + it will never trigger anyway. + </p> + <p> + If the value is <c>0</c> the timeout event is immediately enqueued + unless there already are enqueued events since then the + timeout is immediately cancelled. + This is a feature ensuring that a timeout <c>0</c> event + will be processed before any not yet received external event. + </p> + <p> + Note that it is not possible nor needed to cancel this timeout + since it is cancelled automatically by any other event. + </p> + </desc> + </datatype> + <datatype> + <name name="action" /> + <desc> + <p> + These state transition actions may be invoked by + returning them from the + <seealso marker="#state_function">state function</seealso>, + from <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> + or by giving them to + <seealso marker="#enter_loop/6"><c>enter_loop/6,7</c></seealso>. + </p> + <p> + Actions are executed in the containing list order. + </p> + <p> + Actions that set + <seealso marker="#type-transition_option"> + transition options + </seealso> + overrides any previous of the same type, + so the last in the containing list wins. + For example the last + <seealso marker="#type-event_timeout"> + <c>event_timeout()</c> + </seealso> + overrides any other <c>event_timeout()</c> in the list. + </p> + <taglist> + <tag><c>postpone</c></tag> + <item> + Set the + <seealso marker="#type-transition_option"> + <c>transition_option()</c> + </seealso> + <seealso marker="#type-postpone"> + <c>postpone()</c> + </seealso> + for this state transition. + This action is ignored when returned from + <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> + or given to + <seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso> + since there is no event to postpone in those cases. + </item> + <tag><c>hibernate</c></tag> + <item> + Set the + <seealso marker="#type-transition_option"> + <c>transition_option()</c> + </seealso> + <seealso marker="#type-hibernate"> + <c>hibernate()</c> + </seealso> + for this state transition. + </item> + <tag><c>Timeout</c></tag> + <item> + Short for <c>{timeout,Timeout,Timeout}</c> that is + the timeout message is the timeout time. + This form exists to make the + <seealso marker="#state_function">state function</seealso> + return value <c>{next_state,NextState,NewData,Timeout}</c> + allowed like for + <seealso marker="gen_fsm#Module:StateName/2"> + <c>gen_fsm Module:StateName/2</c>. + </seealso> + </item> + <tag><c>timeout</c></tag> + <item> + Set the + <seealso marker="#type-transition_option"> + <c>transition_option()</c> + </seealso> + <seealso marker="#type-event_timeout"> + <c>event_timeout()</c> + </seealso> + to <c><anno>Time</anno></c> with <c><anno>EventContent</anno></c>. + </item> + <tag><c>reply_action()</c></tag> + <item>Reply to a caller.</item> + <tag><c>next_event</c></tag> + <item> + Store the given <c><anno>EventType</anno></c> + and <c><anno>EventContent</anno></c> for insertion after all + actions have been executed. + </item> + <item> + <p> + The stored events are inserted in the queue as the next to process + before any already queued events. The order of these stored events + is preserved so the first <c>next_event</c> in the containing list + will become the first to process. + </p> + </item> + <item> + <p> + An event of type + <seealso marker="#type-event_type"> + <c>internal</c> + </seealso> + should be used when you want to reliably distinguish + an event inserted this way from any external event. + </p> + </item> + </taglist> + </desc> + </datatype> + <datatype> + <name name="reply_action" /> + <desc> + <p> + Reply to a caller waiting for a reply in + <seealso marker="#call/2"><c>call/2</c></seealso>. + <c><anno>From</anno></c> must be the term from the + <seealso marker="#type-event_type"> + <c>{call,<anno>From</anno>}</c> + </seealso> + argument to the + <seealso marker="#state_function">state function</seealso>. + </p> + </desc> + </datatype> + <datatype> + <name name="state_function_result" /> + <desc> + <taglist> + <tag><c>next_state</c></tag> + <item> + The <c>gen_statem</c> will do a state transition to + <c><anno>NextStateName</anno></c> + (which may be the same as the current state), + set <c><anno>NewData</anno></c> + and execute all <c><anno>Actions</anno></c> + </item> + </taglist> + <p> + All these terms are tuples or atoms and this property + will hold in any future version of <c>gen_statem</c>, + just in case you need such a promise. + </p> + </desc> + </datatype> + <datatype> + <name name="handle_event_result" /> + <desc> + <taglist> + <tag><c>next_state</c></tag> + <item> + The <c>gen_statem</c> will do a state transition to + <c><anno>NextState</anno></c> + (which may be the same as the current state), + set <c><anno>NewData</anno></c> + and execute all <c><anno>Actions</anno></c> + </item> + </taglist> + <p> + All these terms are tuples or atoms and this property + will hold in any future version of <c>gen_statem</c>, + just in case you need such a promise. + </p> + </desc> + </datatype> + <datatype> + <name name="common_state_callback_result" /> + <desc> + <taglist> + <tag><c>stop</c></tag> + <item> + Terminate the <c>gen_statem</c> by calling + <seealso marker="#Module:terminate/3"> + <c>Module:terminate/3</c> + </seealso> + with <c>Reason</c> and + <c><anno>NewData</anno></c>, if given. + </item> + <tag><c>stop_and_reply</c></tag> + <item> + Send all <c><anno>Replies</anno></c> + then terminate the <c>gen_statem</c> by calling + <seealso marker="#Module:terminate/3"> + <c>Module:terminate/3</c> + </seealso> + with <c>Reason</c> and + <c><anno>NewData</anno></c>, if given. + </item> + <tag><c>keep_state</c></tag> + <item> + The <c>gen_statem</c> will keep the current state, or + do a state transition to the current state if you like, + set <c><anno>NewData</anno></c> + and execute all <c><anno>Actions</anno></c>. + This is the same as + <c>{next_state,CurrentState,<anno>NewData</anno>,<anno>Actions</anno>}</c>. + </item> + <tag><c>keep_state_and_data</c></tag> + <item> + The <c>gen_statem</c> will keep the current state or + do a state transition to the current state if you like, + keep the current server data, + and execute all <c><anno>Actions</anno></c>. + This is the same as + <c>{next_state,CurrentState,CurrentData,<anno>Actions</anno>}</c>. + </item> + </taglist> + <p> + All these terms are tuples or atoms and this property + will hold in any future version of <c>gen_statem</c>, + just in case you need such a promise. + </p> + </desc> + </datatype> + </datatypes> + + <funcs> + + <func> + <name name="start_link" arity="3" /> + <name name="start_link" arity="4" /> + <fsummary>Create a linked <c>gen_statem</c> process</fsummary> + <desc> + <p> + Creates a <c>gen_statem</c> process according + to OTP design principles + (using + <seealso marker="proc_lib"><c>proc_lib</c></seealso> + primitives) + that is linked to the calling process. + This is essential when the <c>gen_statem</c> shall be part of + a supervision tree so it gets linked to its supervisor. + </p> + <p> + The <c>gen_statem</c> process calls + <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> + to initialize the server. To ensure a synchronized start-up + procedure, <c>start_link/3,4</c> does not return until + <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> + has returned. + </p> + <p> + <c><anno>ServerName</anno></c> specifies the + <seealso marker="#type-server_name"> + <c>server_name()</c> + </seealso> + to register for the <c>gen_statem</c>. + If the <c>gen_statem</c> is started with <c>start_link/3</c> + no <c><anno>ServerName</anno></c> is provided and + the <c>gen_statem</c> is not registered. + </p> + <p><c><anno>Module</anno></c> is the name of the callback module.</p> + <p> + <c><anno>Args</anno></c> is an arbitrary term which is passed as + the argument to + <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>. + </p> + <p> + If the option <c>{timeout,Time}</c> is present in + <c><anno>Opts</anno></c>, the <c>gen_statem</c> + is allowed to spend <c>Time</c> milliseconds initializing + or it will be terminated and the start function will return + <seealso marker="#type-start_ret"><c>{error,timeout}</c></seealso>. + </p> + <p> + If the option + <seealso marker="#type-debug_opt"><c>{debug,Dbgs}</c></seealso> + is present in <c><anno>Opts</anno></c>, debugging through + <seealso marker="sys"><c>sys</c></seealso> is activated. + </p> + <p> + If the option <c>{spawn_opt,SpawnOpts}</c> is present in + <c><anno>Opts</anno></c>, <c>SpawnOpts</c> will be passed + as option list to + <seealso marker="erts:erlang#spawn_opt/2"><c>spawn_opt/2</c></seealso> + which is used to spawn the <c>gen_statem</c> process. + </p> + <note> + <p> + Using the spawn option <c>monitor</c> is currently not + allowed, but will cause this function to fail with reason + <c>badarg</c>. + </p> + </note> + <p> + If the <c>gen_statem</c> is successfully created + and initialized this function returns + <seealso marker="#type-start_ret"> + <c>{ok,Pid}</c>, + </seealso> + where <c>Pid</c> is the <c>pid()</c> + of the <c>gen_statem</c>. + If there already exists a process with the specified + <c><anno>ServerName</anno></c> this function returns + <seealso marker="#type-start_ret"><c>{error,{already_started,Pid}}</c></seealso>, + where <c>Pid</c> is the <c>pid()</c> of that process. + </p> + <p> + If <c>Module:init/1</c> fails with <c>Reason</c>, + this function returns + <seealso marker="#type-start_ret"><c>{error,Reason}</c></seealso>. + If <c>Module:init/1</c> returns + <seealso marker="#type-start_ret"> + <c>{stop,Reason}</c> + </seealso> + or + <seealso marker="#type-start_ret"><c>ignore</c></seealso>, + the process is terminated and this function + returns + <seealso marker="#type-start_ret"> + <c>{error,Reason}</c> + </seealso> + or + <seealso marker="#type-start_ret"><c>ignore</c></seealso>, + respectively. + </p> + </desc> + </func> + + + <func> + <name name="start" arity="3" /> + <name name="start" arity="4" /> + <fsummary>Create a stand-alone <c>gen_statem</c> process</fsummary> + <desc> + <p> + Creates a stand-alone <c>gen_statem</c> process according to + OTP design principles (using + <seealso marker="proc_lib"><c>proc_lib</c></seealso> + primitives). + Since it does not get linked to the calling process + this start function can not be used by a supervisor + to start a child. + </p> + <p> + See <seealso marker="#start_link/3"><c>start_link/3,4</c></seealso> + for a description of arguments and return values. + </p> + </desc> + </func> + + <func> + <name name="stop" arity="1" /> + <fsummary>Synchronously stop a generic server</fsummary> + <desc> + <p> + The same as + <seealso marker="#stop/3"> + <c>stop(<anno>ServerRef</anno>, normal, infinity)</c>. + </seealso> + </p> + </desc> + </func> + <func> + <name name="stop" arity="3" /> + <fsummary>Synchronously stop a generic server</fsummary> + <desc> + <p> + Orders the <c>gen_statem</c> + <seealso marker="#type-server_ref"> + <c><anno>ServerRef</anno></c> + </seealso> + to exit with the given <c><anno>Reason</anno></c> + and waits for it to terminate. + The <c>gen_statem</c> will call + <seealso marker="#Module:terminate/3"> + <c>Module:terminate/3</c> + </seealso> + before exiting. + </p> + <p> + This function returns <c>ok</c> if the server terminates + with the expected reason. Any other reason than <c>normal</c>, + <c>shutdown</c>, or <c>{shutdown,Term}</c> will cause an + error report to be issued through + <seealso marker="kernel:error_logger#format/2"> + <c>error_logger:format/2</c>. + </seealso> + The default <c><anno>Reason</anno></c> is <c>normal</c>. + </p> + <p> + <c><anno>Timeout</anno></c> is an integer greater than zero + which specifies how many milliseconds to wait for the server to + terminate, or the atom <c>infinity</c> to wait indefinitely. + The default value is <c>infinity</c>. + If the server has not terminated within the specified time, + a <c>timeout</c> exception is raised. + </p> + <p> + If the process does not exist, a <c>noproc</c> exception + is raised. + </p> + </desc> + </func> + + <func> + <name name="call" arity="2" /> + <name name="call" arity="3" /> + <fsummary>Make a synchronous call to a <c>gen_statem</c></fsummary> + <desc> + <p> + Makes a synchronous call to the <c>gen_statem</c> + <seealso marker="#type-server_ref"> + <c><anno>ServerRef</anno></c> + </seealso> + by sending a request + and waiting until its reply arrives. + The <c>gen_statem</c> will call the + <seealso marker="#state_function">state function</seealso> with + <seealso marker="#type-event_type"><c>event_type()</c></seealso> + <c>{call,From}</c> and event content + <c><anno>Request</anno></c>. + </p> + <p> + A <c><anno>Reply</anno></c> is generated when a + <seealso marker="#state_function">state function</seealso> + returns with + <c>{reply,From,<anno>Reply</anno>}</c> as one + <seealso marker="#type-action"><c>action()</c></seealso>, + and that <c><anno>Reply</anno></c> becomes the return value + of this function. + </p> + <p> + <c><anno>Timeout</anno></c> is an integer greater than zero + which specifies how many milliseconds to wait for a reply, + or the atom <c>infinity</c> to wait indefinitely, + which is the default. If no reply is received within + the specified time, the function call fails. + </p> + <note> + <p> + To avoid getting a late reply in the caller's + inbox this function spawns a proxy process that + does the call. A late reply gets delivered to the + dead proxy process hence gets discarded. This is + less efficient than using + <c><anno>Timeout</anno> =:= infinity</c>. + </p> + </note> + <p> + The call may fail for example if the <c>gen_statem</c> dies + before or during this function call. + </p> + </desc> + </func> + + <func> + <name name="cast" arity="2" /> + <fsummary>Send an asynchronous event to a <c>gen_statem</c></fsummary> + <desc> + <p> + Sends an asynchronous event to the <c>gen_statem</c> + <seealso marker="#type-server_ref"> + <c><anno>ServerRef</anno></c> + </seealso> + and returns <c>ok</c> immediately, + ignoring if the destination node or <c>gen_statem</c> + does not exist. + The <c>gen_statem</c> will call the + <seealso marker="#state_function">state function</seealso> with + <seealso marker="#type-event_type"><c>event_type()</c></seealso> + <c>cast</c> and event content + <c><anno>Msg</anno></c>. + </p> + </desc> + </func> + + <func> + <name name="reply" arity="1" /> + <name name="reply" arity="2" /> + <fsummary>Reply to a caller</fsummary> + <desc> + <p> + This function can be used by a <c>gen_statem</c> + to explicitly send a reply to a process that waits in + <seealso marker="#call/2"><c>call/2</c></seealso> + when the reply cannot be defined in + the return value of a + <seealso marker="#state_function">state function</seealso>. + </p> + <p> + <c><anno>From</anno></c> must be the term from the + <seealso marker="#type-event_type"> + <c>{call,<anno>From</anno>}</c> + </seealso> + argument to the + <seealso marker="#state_function">state function</seealso>. + <c><anno>From</anno></c> and <c><anno>Reply</anno></c> + can also be specified using a + <seealso marker="#type-reply_action"> + <c>reply_action()</c> + </seealso> + and multiple replies with a list of them. + </p> + <note> + <p> + A reply sent with this function will not be visible + in <seealso marker="sys"><c>sys</c></seealso> debug output. + </p> + </note> + </desc> + </func> + + <func> + <name name="enter_loop" arity="5" /> + <fsummary>Enter the <c>gen_statem</c> receive loop</fsummary> + <desc> + <p> + The same as + <seealso marker="#enter_loop/7"><c>enter_loop/7</c></seealso> + except that no + <seealso marker="#type-server_name"> + <c>server_name()</c> + </seealso> + must have been registered. + </p> + </desc> + </func> + <func> + <name name="enter_loop" arity="6" /> + <fsummary>Enter the <c>gen_statem</c> receive loop</fsummary> + <desc> + <p> + If <c><anno>Server_or_Actions</anno></c> is a <c>list()</c> + the same as + <seealso marker="#enter_loop/7"><c>enter_loop/7</c></seealso> + except that no + <seealso marker="#type-server_name"> + <c>server_name()</c> + </seealso> + must have been registered and + <c>Actions = <anno>Server_or_Actions</anno></c>. + </p> + <p> + Otherwise the same as + <seealso marker="#enter_loop/7"><c>enter_loop/7</c></seealso> + with + <c>Server = <anno>Server_or_Actions</anno></c> and + <c>Actions = []</c>. + </p> + </desc> + </func> + <func> + <name name="enter_loop" arity="7" /> + <fsummary>Enter the <c>gen_statem</c> receive loop</fsummary> + <desc> + <p> + Makes an the calling process become a <c>gen_statem</c>. + Does not return, instead the calling process will enter + the <c>gen_statem</c> receive loop and become + a <c>gen_statem</c> server. + The process <em>must</em> have been started + using one of the start functions in + <seealso marker="proc_lib"><c>proc_lib</c></seealso>. + The user is responsible for any initialization of the process, + including registering a name for it. + </p> + <p> + This function is useful when a more complex initialization + procedure is needed than + the <c>gen_statem</c> behaviour provides. + </p> + <p> + <c><anno>Module</anno></c>, <c><anno>Opts</anno></c> and + <c><anno>Server</anno></c> have the same meanings + as when calling + <seealso marker="#start_link/3"> + <c>gen_statem:start[_link]/3,4</c>. + </seealso> + However, the + <seealso marker="#type-server_name"> + <c>server_name()</c> + </seealso> + name must have been registered accordingly + <em>before</em> this function is called.</p> + <p> + <c><anno>CallbackMode</anno></c>, <c><anno>State</anno></c>, + <c><anno>Data</anno></c> and <c><anno>Actions</anno></c> + have the same meanings as in the return value of + <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>. + Also, the callback module <c><anno>Module</anno></c> + does not need to export an <c>init/1</c> function. + </p> + <p> + Failure: If the calling process was not started by a + <seealso marker="proc_lib"><c>proc_lib</c></seealso> + start function, or if it is not registered + according to + <seealso marker="#type-server_name"><c>server_name()</c></seealso>. + </p> + </desc> + </func> + + </funcs> + + + + <section> + <title>CALLBACK FUNCTIONS</title> + <p> + The following functions should be exported from a + <c>gen_statem</c> callback module. + </p> + </section> + <funcs> + + <func> + <name>Module:init(Args) -> Result</name> + <fsummary>Initialize process and internal state</fsummary> + <type> + <v>Args = term()</v> + <v>Result = {CallbackMode,State,Data}</v> + <v> | {CallbackMode,State,Data,Actions}</v> + <v> | {stop,Reason} | ignore</v> + <v> + CallbackMode = + <seealso marker="#type-callback_mode">callback_mode()</seealso> + </v> + <v>State = <seealso marker="#type-state">state()</seealso></v> + <v> + Data = <seealso marker="#type-data">data()</seealso> + </v> + <v> + Actions = + [<seealso marker="#type-action">action()</seealso>] | + <seealso marker="#type-action">action()</seealso> + </v> + <v>Reason = term()</v> + </type> + <desc> + <marker id="Module:init-1" /> + <p> + Whenever a <c>gen_statem</c> is started using + <seealso marker="#start_link/3"><c>start_link/3,4</c></seealso> + or + <seealso marker="#start/3"><c>start/3,4</c></seealso>, + this function is called by the new process to initialize + the implementation state and server data. + </p> + <p> + <c>Args</c> is the <c>Args</c> argument provided to the start + function. + </p> + <p> + If the initialization is successful, the function should + return <c>{CallbackMode,State,Data}</c> or + <c>{CallbackMode,State,Data,Actions}</c>. + <c>CallbackMode</c> selects the + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>. + of the <c>gen_statem</c>. + <c>State</c> is the initial + <seealso marker="#type-state"><c>state()</c></seealso> + and <c>Data</c> the initial server + <seealso marker="#type-data"><c>data()</c></seealso>. + </p> + <p> + The <seealso marker="#type-action"><c>Actions</c></seealso> + are executed when entering the first + <seealso marker="#type-state">state</seealso> just as for a + <seealso marker="#state_function">state function</seealso>. + </p> + <p> + If something goes wrong during the initialization + the function should return <c>{stop,Reason}</c> + or <c>ignore</c>. See + <seealso marker="#start_link/3"><c>start_link/3,4</c></seealso>. + </p> + <p> + This function may use + <seealso marker="erts:erlang#throw/1"><c>throw/1</c></seealso> + to return <c>Result</c>. + </p> + </desc> + </func> + + <func> + <name>Module:StateName(EventType, EventContent, Data) -> + StateFunctionResult + </name> + <name>Module:handle_event(EventType, EventContent, + State, Data) -> HandleEventResult + </name> + <fsummary>Handle an event</fsummary> + <type> + <v> + EventType = + <seealso marker="#type-event_type">event_type()</seealso> + </v> + <v>EventContent = term()</v> + <v> + State = + <seealso marker="#type-state">state()</seealso> + </v> + <v> + Data = NewData = + <seealso marker="#type-data">data()</seealso> + </v> + <v> + StateFunctionResult = + <seealso marker="#type-state_function_result"> + state_function_result() + </seealso> + </v> + <v> + HandleEventResult = + <seealso marker="#type-handle_event_result"> + handle_event_result() + </seealso> + </v> + </type> + <desc> + <p> + Whenever a <c>gen_statem</c> receives an event from + <seealso marker="#call/2"><c>call/2</c></seealso>, + <seealso marker="#cast/2"><c>cast/2</c></seealso> or + as a normal process message one of these functions is called. If + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> + is <c>state_functions</c> then <c>Module:StateName/3</c> is called, + and if it is <c>handle_event_function</c> + then <c>Module:handle_event/4</c> is called. + </p> + <p> + If <c>EventType</c> is + <seealso marker="#type-event_type"><c>{call,From}</c></seealso> + the caller is waiting for a reply. The reply can be sent + from this or from any other + <seealso marker="#state_function">state function</seealso> + by returning with <c>{reply,From,Reply}</c> in + <seealso marker="#type-action"><c>Actions</c></seealso>, in + <seealso marker="#type-reply_action"><c>Replies</c></seealso> + or by calling + <seealso marker="#reply/2"><c>reply(From, Reply)</c></seealso>. + </p> + <p> + If this function returns with a next state that + does not match equal (<c>=/=</c>) to the current state + all postponed events will be retried in the next state. + </p> + <p> + The only difference between <c>StateFunctionResult</c> and + <c>HandleEventResult</c> is that for <c>StateFunctionResult</c> + the next state has to be an atom but for <c>HandleEventResult</c> + there is no restriction on the next state. + </p> + <p> + See <seealso marker="#type-action"><c>action()</c></seealso> + for options that can be set and actions that can be done + by <c>gen_statem</c> after returning from this function. + </p> + <p> + These functions may use + <seealso marker="erts:erlang#throw/1"><c>throw/1</c></seealso>, + to return the result. + </p> + </desc> + </func> + + <func> + <name>Module:terminate(Reason, State, Data) -> Ignored</name> + <fsummary>Clean up before termination</fsummary> + <type> + <v>Reason = normal | shutdown | {shutdown,term()} | term()</v> + <v>State = <seealso marker="#type-state">state()</seealso></v> + <v>Data = <seealso marker="#type-data">data()</seealso></v> + <v>Ignored = term()</v> + </type> + <desc> + <p> + This function is called by a <c>gen_statem</c> + when it is about to terminate. It should be the opposite of + <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> + and do any necessary cleaning up. When it returns, + the <c>gen_statem</c> terminates with <c>Reason</c>. The return + value is ignored.</p> + <p> + <c>Reason</c> is a term denoting the stop reason and + <seealso marker="#type-state"><c>State</c></seealso> + is the internal state of the <c>gen_statem</c>. + </p> + <p> + <c>Reason</c> depends on why the <c>gen_statem</c> + is terminating. + If it is because another callback function has returned a + stop tuple <c>{stop,Reason}</c> in + <seealso marker="#type-action"><c>Actions</c></seealso>, + <c>Reason</c> will have the value specified in that tuple. + If it is due to a failure, <c>Reason</c> is the error reason. + </p> + <p> + If the <c>gen_statem</c> is part of a supervision tree and is + ordered by its supervisor to terminate, this function will be + called with <c>Reason = shutdown</c> if the following + conditions apply:</p> + <list type="bulleted"> + <item> + the <c>gen_statem</c> has been set + to trap exit signals, and + </item> + <item> + the shutdown strategy as defined in the supervisor's + child specification is an integer timeout value, not + <c>brutal_kill</c>. + </item> + </list> + <p> + Even if the <c>gen_statem</c> is <em>not</em> + part of a supervision tree, this function will be called + if it receives an <c>'EXIT'</c> message from its parent. + <c>Reason</c> will be the same as + in the <c>'EXIT'</c> message. + </p> + <p> + Otherwise, the <c>gen_statem</c> will be immediately terminated. + </p> + <p> + Note that for any other reason than <c>normal</c>, + <c>shutdown</c>, or <c>{shutdown,Term}</c> + the <c>gen_statem</c> is assumed to terminate due to an error + and an error report is issued using + <seealso marker="kernel:error_logger#format/2"> + <c>error_logger:format/2</c>. + </seealso> + </p> + <p> + This function may use + <seealso marker="erts:erlang#throw/1"><c>throw/1</c></seealso> + to return <c>Ignored</c>, which is ignored anyway. + </p> + </desc> + </func> + + <func> + <name>Module:code_change(OldVsn, OldState, OldData, Extra) -> + Result + </name> + <fsummary>Update the internal state during upgrade/downgrade</fsummary> + <type> + <v>OldVsn = Vsn | {down,Vsn}</v> + <v> Vsn = term()</v> + <v>OldState = NewState = term()</v> + <v>Extra = term()</v> + <v>Result = {NewCallbackMode,NewState,NewData} | Reason</v> + <v> + NewCallbackMode = + <seealso marker="#type-callback_mode">callback_mode()</seealso> + </v> + <v> + OldState = NewState = + <seealso marker="#type-state">state()</seealso> + </v> + <v> + OldData = NewData = + <seealso marker="#type-data">data()</seealso> + </v> + <v>Reason = term()</v> + </type> + <desc> + <p> + This function is called by a <c>gen_statem</c> when it should + update its internal state during a release upgrade/downgrade, + that is when the instruction <c>{update,Module,Change,...}</c> + where <c>Change={advanced,Extra}</c> is given in the + <seealso marker="sasl:appup"><c>appup</c></seealso> + file. See + <seealso marker="doc/design_principles:release_handling#instr"> + OTP Design Principles + </seealso> + for more information. + </p> + <p> + In the case of an upgrade, <c>OldVsn</c> is <c>Vsn</c>, and + in the case of a downgrade, <c>OldVsn</c> is + <c>{down,Vsn}</c>. <c>Vsn</c> is defined by the <c>vsn</c> + attribute(s) of the old version of the callback module + <c>Module</c>. If no such attribute is defined, the version + is the checksum of the BEAM file. + </p> + <note> + <p> + If you would dare to change + <seealso marker="#type-callback_mode"> + <em>callback mode</em> + </seealso> + during release upgrade/downgrade, the upgrade is no problem + since the new code surely knows what <em>callback mode</em> + it needs, but for a downgrade this function will have to + know from the <c>Extra</c> argument that comes from the + <seealso marker="sasl:appup"><c>appup</c></seealso> + file what <em>callback mode</em> the old code did use. + It may also be possible to figure this out + from the <c>{down,Vsn}</c> argument since <c>Vsn</c> + in effect defines the old callback module version. + </p> + </note> + <p> + <c>OldState</c> and <c>OldData</c> is the internal state + of the <c>gen_statem</c>. + </p> + <p> + <c>Extra</c> is passed as-is from the <c>{advanced,Extra}</c> + part of the update instruction. + </p> + <p> + If successful, the function shall return the updated + internal state in an + <c>{NewCallbackMode,NewState,NewData}</c> tuple. + </p> + <p> + If the function returns <c>Reason</c>, the ongoing + upgrade will fail and roll back to the old release.</p> + <p> + This function may use + <seealso marker="erts:erlang#throw/1"><c>throw/1</c></seealso> + to return <c>Result</c> or <c>Reason</c>. + </p> + </desc> + </func> + + <func> + <name>Module:format_status(Opt, [PDict,State,Data]) -> + Status + </name> + <fsummary>Optional function for providing a term describing the + current <c>gen_statem</c> status</fsummary> + <type> + <v>Opt = normal | terminate</v> + <v>PDict = [{Key, Value}]</v> + <v> + State = + <seealso marker="#type-state">state()</seealso> + </v> + <v> + Data = + <seealso marker="#type-data">data()</seealso> + </v> + <v>Key = term()</v> + <v>Value = term()</v> + <v>Status = term()</v> + </type> + <desc> + <note> + <p> + This callback is optional, so a callback module need not + export it. The <c>gen_statem</c> module provides a default + implementation of this function that returns + <c>{State,Data}</c>. If this callback fails the default + function will return <c>{State,Info}</c> + where <c>Info</c> informs of the crash but no details, + to hide possibly sensitive data. + </p> + </note> + <p>This function is called by a <c>gen_statem</c> process when:</p> + <list type="bulleted"> + <item> + One of + <seealso marker="sys#get_status/1"> + <c>sys:get_status/1,2</c> + </seealso> + is invoked to get the <c>gen_statem</c> status. <c>Opt</c> is set + to the atom <c>normal</c> for this case. + </item> + <item> + The <c>gen_statem</c> terminates abnormally and logs an error. + <c>Opt</c> is set to the atom <c>terminate</c> for this case. + </item> + </list> + <p> + This function is useful for customising the form and + appearance of the <c>gen_statem</c> status for these cases. A + callback module wishing to customise the + <seealso marker="sys#get_status/1"> + <c>sys:get_status/1,2</c> + </seealso> + return value as well as how + its status appears in termination error logs exports an + instance of <c>format_status/2</c> that returns a term + describing the current status of the <c>gen_statem</c>. + </p> + <p> + <c>PDict</c> is the current value of the <c>gen_statem</c>'s + process dictionary. + </p> + <p> + <seealso marker="#type-state"><c>State</c></seealso> + is the internal state of the <c>gen_statem</c>. + </p> + <p> + <seealso marker="#type-data"><c>Data</c></seealso> + is the internal server data of the <c>gen_statem</c>. + </p> + <p> + The function should return <c>Status</c>, a term that + customises the details of the current state and status of + the <c>gen_statem</c>. There are no restrictions on the + form <c>Status</c> can take, but for the + <seealso marker="sys#get_status/1"> + <c>sys:get_status/1,2</c> + </seealso> + case (when <c>Opt</c> + is <c>normal</c>), the recommended form for + the <c>Status</c> value is <c>[{data, [{"State", + Term}]}]</c> where <c>Term</c> provides relevant details of + the <c>gen_statem</c> state. Following this recommendation isn't + required, but doing so will make the callback module status + consistent with the rest of the + <seealso marker="sys#get_status/1"> + <c>sys:get_status/1,2</c> + </seealso> + return value. + </p> + <p> + One use for this function is to return compact alternative + state representations to avoid having large state terms + printed in logfiles. Another is to hide sensitive data from + being written to the error log. + </p> + <p> + This function may use + <seealso marker="erts:erlang#throw/1"><c>throw/1</c></seealso> + to return <c>Status</c>. + </p> + </desc> + </func> + + </funcs> + + <section> + <title>SEE ALSO</title> + <p><seealso marker="gen_event"><c>gen_event(3)</c></seealso>, + <seealso marker="gen_fsm"><c>gen_fsm(3)</c></seealso>, + <seealso marker="gen_server"><c>gen_server(3)</c></seealso>, + <seealso marker="supervisor"><c>supervisor(3)</c></seealso>, + <seealso marker="proc_lib"><c>proc_lib(3)</c></seealso>, + <seealso marker="sys"><c>sys(3)</c></seealso></p> + </section> +</erlref> diff --git a/lib/stdlib/doc/src/proc_lib.xml b/lib/stdlib/doc/src/proc_lib.xml index bef037076b..245580b1ba 100644 --- a/lib/stdlib/doc/src/proc_lib.xml +++ b/lib/stdlib/doc/src/proc_lib.xml @@ -34,9 +34,9 @@ <p>This module is used to start processes adhering to the <seealso marker="doc/design_principles:des_princ">OTP Design Principles</seealso>. Specifically, the functions in this module are used by the OTP standard behaviors (<c>gen_server</c>, - <c>gen_fsm</c>, ...) when starting new processes. The functions - can also be used to start <em>special processes</em>, user - defined processes which comply to the OTP design principles. See + <c>gen_fsm</c>, <c>gen_statem</c>, ...) when starting new processes. + The functions can also be used to start <em>special processes</em>, + user defined processes which comply to the OTP design principles. See <seealso marker="doc/design_principles:spec_proc">Sys and Proc_Lib</seealso> in OTP Design Principles for an example.</p> <p>Some useful information is initialized when a process starts. The registered names, or the process identifiers, of the parent diff --git a/lib/stdlib/doc/src/ref_man.xml b/lib/stdlib/doc/src/ref_man.xml index 82ad78e675..404873ea32 100644 --- a/lib/stdlib/doc/src/ref_man.xml +++ b/lib/stdlib/doc/src/ref_man.xml @@ -4,7 +4,7 @@ <application xmlns:xi="http://www.w3.org/2001/XInclude"> <header> <copyright> - <year>1996</year><year>2015</year> + <year>1996</year><year>2016</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -66,6 +66,7 @@ <xi:include href="gen_event.xml"/> <xi:include href="gen_fsm.xml"/> <xi:include href="gen_server.xml"/> + <xi:include href="gen_statem.xml"/> <xi:include href="io.xml"/> <xi:include href="io_lib.xml"/> <xi:include href="lib.xml"/> diff --git a/lib/stdlib/doc/src/specs.xml b/lib/stdlib/doc/src/specs.xml index 0418bf7b22..45b207b13d 100644 --- a/lib/stdlib/doc/src/specs.xml +++ b/lib/stdlib/doc/src/specs.xml @@ -30,6 +30,7 @@ <xi:include href="../specs/specs_gen_event.xml"/> <xi:include href="../specs/specs_gen_fsm.xml"/> <xi:include href="../specs/specs_gen_server.xml"/> + <xi:include href="../specs/specs_gen_statem.xml"/> <xi:include href="../specs/specs_io.xml"/> <xi:include href="../specs/specs_io_lib.xml"/> <xi:include href="../specs/specs_lib.xml"/> diff --git a/lib/stdlib/doc/src/supervisor.xml b/lib/stdlib/doc/src/supervisor.xml index 421306283c..29e5a732d5 100644 --- a/lib/stdlib/doc/src/supervisor.xml +++ b/lib/stdlib/doc/src/supervisor.xml @@ -34,8 +34,8 @@ <p>A behaviour module for implementing a supervisor, a process which supervises other processes called child processes. A child process can either be another supervisor or a worker process. - Worker processes are normally implemented using one of - the <c>gen_event</c>, <c>gen_fsm</c>, or <c>gen_server</c> + Worker processes are normally implemented using one of the + <c>gen_event</c>, <c>gen_fsm</c>, <c>gen_statem</c> or <c>gen_server</c> behaviours. A supervisor implemented using this module will have a standard set of interface functions and include functionality for tracing and error reporting. Supervisors are used to build a @@ -221,7 +221,8 @@ <p><c>modules</c> is used by the release handler during code replacement to determine which processes are using a certain module. As a rule of thumb, if the child process is a - <c>supervisor</c>, <c>gen_server</c>, or <c>gen_fsm</c>, + <c>supervisor</c>, <c>gen_server</c>, + <c>gen_fsm</c> or <c>gen_statem</c> this should be a list with one element <c>[Module]</c>, where <c>Module</c> is the callback module. If the child process is an event manager (<c>gen_event</c>) with a @@ -636,6 +637,7 @@ <title>SEE ALSO</title> <p><seealso marker="gen_event">gen_event(3)</seealso>, <seealso marker="gen_fsm">gen_fsm(3)</seealso>, + <seealso marker="gen_statem">gen_statem(3)</seealso>, <seealso marker="gen_server">gen_server(3)</seealso>, <seealso marker="sys">sys(3)</seealso></p> </section> diff --git a/lib/stdlib/doc/src/sys.xml b/lib/stdlib/doc/src/sys.xml index d80ec4dfa6..2255395f46 100644 --- a/lib/stdlib/doc/src/sys.xml +++ b/lib/stdlib/doc/src/sys.xml @@ -217,14 +217,18 @@ processes. For example, a <c>gen_server</c> process returns the callback module's state, a <c>gen_fsm</c> process returns information such as its current state name and state data, - and a <c>gen_event</c> process returns information about each of its + a <c>gen_statem</c> process returns information about + its current state and data, and a <c>gen_event</c> process + returns information about each of its registered handlers. Callback modules for <c>gen_server</c>, - <c>gen_fsm</c>, and <c>gen_event</c> can also customise the value + <c>gen_fsm</c>, <c>gen_statem</c> and <c>gen_event</c> + can also customise the value of <c><anno>Misc</anno></c> by exporting a <c>format_status/2</c> function that contributes module-specific information; - see <seealso marker="gen_server#Module:format_status/2">gen_server:format_status/2</seealso>, - <seealso marker="gen_fsm#Module:format_status/2">gen_fsm:format_status/2</seealso>, and - <seealso marker="gen_event#Module:format_status/2">gen_event:format_status/2</seealso> + see <seealso marker="gen_server#Module:format_status/2">gen_server format_status/2</seealso>, + <seealso marker="gen_fsm#Module:format_status/2">gen_fsm format_status/2</seealso>, + <seealso marker="gen_statem#Module:format_status/2">gen_statem format_status/2</seealso>, and + <seealso marker="gen_event#Module:format_status/2">gen_event format_status/2</seealso> for more details.</p> </desc> </func> @@ -245,6 +249,8 @@ processes. For a <c>gen_server</c> process, the returned <c><anno>State</anno></c> is simply the callback module's state. For a <c>gen_fsm</c> process, <c><anno>State</anno></c> is the tuple <c>{CurrentStateName, CurrentStateData}</c>. + For a <c>gen_statem</c> process <c><anno>State</anno></c> is + the tuple <c>{CurrentState,CurrentData}.</c> For a <c>gen_event</c> process, <c><anno>State</anno></c> a list of tuples, where each tuple corresponds to an event handler registered in the process and contains <c>{Module, Id, HandlerState}</c>, where <c>Module</c> is the event handler's module name, @@ -263,8 +269,9 @@ details of the exception.</p> <p>The <c>system_get_state/1</c> function is primarily useful for user-defined behaviours and modules that implement OTP <seealso marker="#special_process">special - processes</seealso>. The <c>gen_server</c>, <c>gen_fsm</c>, and <c>gen_event</c> OTP - behaviour modules export this function, and so callback modules for those behaviours + processes</seealso>. The <c>gen_server</c>, <c>gen_fsm</c>, + <c>gen_statem</c> and <c>gen_event</c> OTP + behaviour modules export this function, so callback modules for those behaviours need not supply their own.</p> <p>To obtain more information about a process, including its state, see <seealso marker="#get_status-1">get_status/1</seealso> and @@ -290,6 +297,8 @@ <c>gen_fsm</c> process, <c><anno>State</anno></c> is the tuple <c>{CurrentStateName, CurrentStateData}</c>, and <c><anno>NewState</anno></c> is a similar tuple that may contain a new state name, new state data, or both. + The same applies for a <c>gen_statem</c> process but + it names the tuple fields <c>{CurrentState,CurrentData}</c>. For a <c>gen_event</c> process, <c><anno>State</anno></c> is the tuple <c>{Module, Id, HandlerState}</c> where <c>Module</c> is the event handler's module name, <c>Id</c> is the handler's ID (which is the value <c>false</c> if it was registered without @@ -304,7 +313,8 @@ state, then regardless of process type, it may simply return its <c><anno>State</anno></c> argument.</p> <p>If a <c><anno>StateFun</anno></c> function crashes or throws an exception, then - for <c>gen_server</c> and <c>gen_fsm</c> processes, the original state of the process is + for <c>gen_server</c>, <c>gen_fsm</c> or <c>gen_statem</c> processes, + the original state of the process is unchanged. For <c>gen_event</c> processes, a crashing or failing <c><anno>StateFun</anno></c> function means that only the state of the particular event handler it was working on when it failed or crashed is unchanged; it can still succeed in changing the states of other event @@ -329,7 +339,8 @@ <c>{callback_failed, StateFun, {Class, Reason}}</c>.</p> <p>The <c>system_replace_state/2</c> function is primarily useful for user-defined behaviours and modules that implement OTP <seealso marker="#special_process">special processes</seealso>. The - <c>gen_server</c>, <c>gen_fsm</c>, and <c>gen_event</c> OTP behaviour modules export this function, + <c>gen_server</c>, <c>gen_fsm</c>, <c>gen_statem</c> and + <c>gen_event</c> OTP behaviour modules export this function, and so callback modules for those behaviours need not supply their own.</p> </desc> </func> diff --git a/lib/stdlib/include/assert.hrl b/lib/stdlib/include/assert.hrl index 151794d114..9e5d4eb598 100644 --- a/lib/stdlib/include/assert.hrl +++ b/lib/stdlib/include/assert.hrl @@ -59,19 +59,22 @@ -define(assert(BoolExpr),ok). -else. %% The assert macro is written the way it is so as not to cause warnings -%% for clauses that cannot match, even if the expression is a constant. +%% for clauses that cannot match, even if the expression is a constant or +%% is known to be boolean-only. -define(assert(BoolExpr), begin ((fun () -> + __T = is_process_alive(self()), % cheap source of truth case (BoolExpr) of - true -> ok; + __T -> ok; __V -> erlang:error({assert, [{module, ?MODULE}, {line, ?LINE}, {expression, (??BoolExpr)}, {expected, true}, - case __V of false -> {value, __V}; - _ -> {not_boolean,__V} + case not __T of + __V -> {value, false}; + _ -> {not_boolean, __V} end]}) end end)()) @@ -85,15 +88,17 @@ -define(assertNot(BoolExpr), begin ((fun () -> + __F = not is_process_alive(self()), case (BoolExpr) of - false -> ok; + __F -> ok; __V -> erlang:error({assert, [{module, ?MODULE}, {line, ?LINE}, {expression, (??BoolExpr)}, {expected, false}, - case __V of true -> {value, __V}; - _ -> {not_boolean,__V} + case not __F of + __V -> {value, true}; + _ -> {not_boolean, __V} end]}) end end)()) @@ -149,7 +154,8 @@ -else. -define(assertEqual(Expect, Expr), begin - ((fun (__X) -> + ((fun () -> + __X = (Expect), case (Expr) of __X -> ok; __V -> erlang:error({assertEqual, @@ -159,7 +165,7 @@ {expected, __X}, {value, __V}]}) end - end)(Expect)) + end)()) end). -endif. @@ -169,7 +175,8 @@ -else. -define(assertNotEqual(Unexpected, Expr), begin - ((fun (__X) -> + ((fun () -> + __X = (Unexpected), case (Expr) of __X -> erlang:error({assertNotEqual, [{module, ?MODULE}, @@ -178,7 +185,7 @@ {value, __X}]}); _ -> ok end - end)(Unexpected)) + end)()) end). -endif. diff --git a/lib/stdlib/src/Makefile b/lib/stdlib/src/Makefile index 9f4a446ea0..302834f9d0 100644 --- a/lib/stdlib/src/Makefile +++ b/lib/stdlib/src/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 1996-2015. All Rights Reserved. +# Copyright Ericsson AB 1996-2016. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -85,6 +85,7 @@ MODULES= \ gen_event \ gen_fsm \ gen_server \ + gen_statem \ io \ io_lib \ io_lib_format \ diff --git a/lib/stdlib/src/gen.erl b/lib/stdlib/src/gen.erl index 9811a211ae..597830cf9a 100644 --- a/lib/stdlib/src/gen.erl +++ b/lib/stdlib/src/gen.erl @@ -26,7 +26,8 @@ %%% %%% The standard behaviour should export init_it/6. %%%----------------------------------------------------------------- --export([start/5, start/6, debug_options/1, +-export([start/5, start/6, debug_options/2, + name/1, unregister_name/1, get_proc_name/1, get_parent/0, call/3, call/4, reply/2, stop/1, stop/3]). -export([init_it/6, init_it/7]). @@ -124,7 +125,7 @@ init_it(GenMod, Starter, Parent, Mod, Args, Options) -> init_it2(GenMod, Starter, Parent, self(), Mod, Args, Options). init_it(GenMod, Starter, Parent, Name, Mod, Args, Options) -> - case name_register(Name) of + case register_name(Name) of true -> init_it2(GenMod, Starter, Parent, Name, Mod, Args, Options); {false, Pid} -> @@ -297,19 +298,19 @@ where({global, Name}) -> global:whereis_name(Name); where({via, Module, Name}) -> Module:whereis_name(Name); where({local, Name}) -> whereis(Name). -name_register({local, Name} = LN) -> +register_name({local, Name} = LN) -> try register(Name, self()) of true -> true catch error:_ -> {false, where(LN)} end; -name_register({global, Name} = GN) -> +register_name({global, Name} = GN) -> case global:register_name(Name, self()) of yes -> true; no -> {false, where(GN)} end; -name_register({via, Module, Name} = GN) -> +register_name({via, Module, Name} = GN) -> case Module:register_name(Name, self()) of yes -> true; @@ -317,34 +318,108 @@ name_register({via, Module, Name} = GN) -> {false, where(GN)} end. +name({local,Name}) -> Name; +name({global,Name}) -> Name; +name({via,_, Name}) -> Name; +name(Pid) when is_pid(Pid) -> Pid. + +unregister_name({local,Name}) -> + try unregister(Name) of + _ -> ok + catch + _:_ -> ok + end; +unregister_name({global,Name}) -> + _ = global:unregister_name(Name), + ok; +unregister_name({via, Mod, Name}) -> + _ = Mod:unregister_name(Name), + ok; +unregister_name(Pid) when is_pid(Pid) -> + ok. + +get_proc_name(Pid) when is_pid(Pid) -> + Pid; +get_proc_name({local, Name}) -> + case process_info(self(), registered_name) of + {registered_name, Name} -> + Name; + {registered_name, _Name} -> + exit(process_not_registered); + [] -> + exit(process_not_registered) + end; +get_proc_name({global, Name}) -> + case global:whereis_name(Name) of + undefined -> + exit(process_not_registered_globally); + Pid when Pid =:= self() -> + Name; + _Pid -> + exit(process_not_registered_globally) + end; +get_proc_name({via, Mod, Name}) -> + case Mod:whereis_name(Name) of + undefined -> + exit({process_not_registered_via, Mod}); + Pid when Pid =:= self() -> + Name; + _Pid -> + exit({process_not_registered_via, Mod}) + end. + +get_parent() -> + case get('$ancestors') of + [Parent | _] when is_pid(Parent) -> + Parent; + [Parent | _] when is_atom(Parent) -> + name_to_pid(Parent); + _ -> + exit(process_was_not_started_by_proc_lib) + end. + +name_to_pid(Name) -> + case whereis(Name) of + undefined -> + case global:whereis_name(Name) of + undefined -> + exit(could_not_find_registered_name); + Pid -> + Pid + end; + Pid -> + Pid + end. + timeout(Options) -> - case opt(timeout, Options) of - {ok, Time} -> + case lists:keyfind(timeout, 1, Options) of + {_,Time} -> Time; - _ -> + false -> infinity end. spawn_opts(Options) -> - case opt(spawn_opt, Options) of - {ok, Opts} -> + case lists:keyfind(spawn_opt, 1, Options) of + {_,Opts} -> Opts; - _ -> + false -> [] end. -opt(Op, [{Op, Value}|_]) -> - {ok, Value}; -opt(Op, [_|Options]) -> - opt(Op, Options); -opt(_, []) -> - false. - -debug_options(Opts) -> - case opt(debug, Opts) of - {ok, Options} -> sys:debug_options(Options); - _ -> [] +debug_options(Name, Opts) -> + case lists:keyfind(debug, 1, Opts) of + {_,Options} -> + try sys:debug_options(Options) + catch _:_ -> + error_logger:format( + "~p: ignoring erroneous debug options - ~p~n", + [Name,Options]), + [] + end; + false -> + [] end. format_status_header(TagLine, Pid) when is_pid(Pid) -> diff --git a/lib/stdlib/src/gen_event.erl b/lib/stdlib/src/gen_event.erl index 1303036168..ccacf658e9 100644 --- a/lib/stdlib/src/gen_event.erl +++ b/lib/stdlib/src/gen_event.erl @@ -147,16 +147,11 @@ init_it(Starter, self, Name, Mod, Args, Options) -> init_it(Starter, self(), Name, Mod, Args, Options); init_it(Starter, Parent, Name0, _, _, Options) -> process_flag(trap_exit, true), - Debug = gen:debug_options(Options), + Name = gen:name(Name0), + Debug = gen:debug_options(Name, Options), proc_lib:init_ack(Starter, {ok, self()}), - Name = name(Name0), loop(Parent, Name, [], Debug, false). -name({local,Name}) -> Name; -name({global,Name}) -> Name; -name({via,_, Name}) -> Name; -name(Pid) when is_pid(Pid) -> Pid. - -spec add_handler(emgr_ref(), handler(), term()) -> term(). add_handler(M, Handler, Args) -> rpc(M, {add_handler, Handler, Args}). diff --git a/lib/stdlib/src/gen_fsm.erl b/lib/stdlib/src/gen_fsm.erl index 2b4b76105e..6e7528fd98 100644 --- a/lib/stdlib/src/gen_fsm.erl +++ b/lib/stdlib/src/gen_fsm.erl @@ -305,64 +305,11 @@ enter_loop(Mod, Options, StateName, StateData, Timeout) -> enter_loop(Mod, Options, StateName, StateData, self(), Timeout). enter_loop(Mod, Options, StateName, StateData, ServerName, Timeout) -> - Name = get_proc_name(ServerName), - Parent = get_parent(), - Debug = gen:debug_options(Options), + Name = gen:get_proc_name(ServerName), + Parent = gen:get_parent(), + Debug = gen:debug_options(Name, Options), loop(Parent, Name, StateName, StateData, Mod, Timeout, Debug). -get_proc_name(Pid) when is_pid(Pid) -> - Pid; -get_proc_name({local, Name}) -> - case process_info(self(), registered_name) of - {registered_name, Name} -> - Name; - {registered_name, _Name} -> - exit(process_not_registered); - [] -> - exit(process_not_registered) - end; -get_proc_name({global, Name}) -> - case global:whereis_name(Name) of - undefined -> - exit(process_not_registered_globally); - Pid when Pid =:= self() -> - Name; - _Pid -> - exit(process_not_registered_globally) - end; -get_proc_name({via, Mod, Name}) -> - case Mod:whereis_name(Name) of - undefined -> - exit({process_not_registered_via, Mod}); - Pid when Pid =:= self() -> - Name; - _Pid -> - exit({process_not_registered_via, Mod}) - end. - -get_parent() -> - case get('$ancestors') of - [Parent | _] when is_pid(Parent) -> - Parent; - [Parent | _] when is_atom(Parent) -> - name_to_pid(Parent); - _ -> - exit(process_was_not_started_by_proc_lib) - end. - -name_to_pid(Name) -> - case whereis(Name) of - undefined -> - case global:whereis_name(Name) of - undefined -> - exit(could_not_find_registered_name); - Pid -> - Pid - end; - Pid -> - Pid - end. - %%% --------------------------------------------------- %%% Initiate the new process. %%% Register the name using the Rfunc function @@ -373,8 +320,8 @@ name_to_pid(Name) -> init_it(Starter, self, Name, Mod, Args, Options) -> init_it(Starter, self(), Name, Mod, Args, Options); init_it(Starter, Parent, Name0, Mod, Args, Options) -> - Name = name(Name0), - Debug = gen:debug_options(Options), + Name = gen:name(Name0), + Debug = gen:debug_options(Name, Options), case catch Mod:init(Args) of {ok, StateName, StateData} -> proc_lib:init_ack(Starter, {ok, self()}), @@ -383,15 +330,15 @@ init_it(Starter, Parent, Name0, Mod, Args, Options) -> proc_lib:init_ack(Starter, {ok, self()}), loop(Parent, Name, StateName, StateData, Mod, Timeout, Debug); {stop, Reason} -> - unregister_name(Name0), + gen:unregister_name(Name0), proc_lib:init_ack(Starter, {error, Reason}), exit(Reason); ignore -> - unregister_name(Name0), + gen:unregister_name(Name0), proc_lib:init_ack(Starter, ignore), exit(normal); {'EXIT', Reason} -> - unregister_name(Name0), + gen:unregister_name(Name0), proc_lib:init_ack(Starter, {error, Reason}), exit(Reason); Else -> @@ -400,20 +347,6 @@ init_it(Starter, Parent, Name0, Mod, Args, Options) -> exit(Error) end. -name({local,Name}) -> Name; -name({global,Name}) -> Name; -name({via,_, Name}) -> Name; -name(Pid) when is_pid(Pid) -> Pid. - -unregister_name({local,Name}) -> - _ = (catch unregister(Name)); -unregister_name({global,Name}) -> - _ = global:unregister_name(Name); -unregister_name({via, Mod, Name}) -> - _ = Mod:unregister_name(Name); -unregister_name(Pid) when is_pid(Pid) -> - Pid. - %%----------------------------------------------------------------- %% The MAIN loop %%----------------------------------------------------------------- diff --git a/lib/stdlib/src/gen_server.erl b/lib/stdlib/src/gen_server.erl index fa6fff6dca..5800aca66f 100644 --- a/lib/stdlib/src/gen_server.erl +++ b/lib/stdlib/src/gen_server.erl @@ -304,9 +304,9 @@ enter_loop(Mod, Options, State, Timeout) -> enter_loop(Mod, Options, State, self(), Timeout). enter_loop(Mod, Options, State, ServerName, Timeout) -> - Name = get_proc_name(ServerName), - Parent = get_parent(), - Debug = debug_options(Name, Options), + Name = gen:get_proc_name(ServerName), + Parent = gen:get_parent(), + Debug = gen:debug_options(Name, Options), loop(Parent, Name, State, Mod, Timeout, Debug). %%%======================================================================== @@ -323,8 +323,8 @@ enter_loop(Mod, Options, State, ServerName, Timeout) -> init_it(Starter, self, Name, Mod, Args, Options) -> init_it(Starter, self(), Name, Mod, Args, Options); init_it(Starter, Parent, Name0, Mod, Args, Options) -> - Name = name(Name0), - Debug = debug_options(Name, Options), + Name = gen:name(Name0), + Debug = gen:debug_options(Name, Options), case catch Mod:init(Args) of {ok, State} -> proc_lib:init_ack(Starter, {ok, self()}), @@ -339,15 +339,15 @@ init_it(Starter, Parent, Name0, Mod, Args, Options) -> %% (Otherwise, the parent process could get %% an 'already_started' error if it immediately %% tried starting the process again.) - unregister_name(Name0), + gen:unregister_name(Name0), proc_lib:init_ack(Starter, {error, Reason}), exit(Reason); ignore -> - unregister_name(Name0), + gen:unregister_name(Name0), proc_lib:init_ack(Starter, ignore), exit(normal); {'EXIT', Reason} -> - unregister_name(Name0), + gen:unregister_name(Name0), proc_lib:init_ack(Starter, {error, Reason}), exit(Reason); Else -> @@ -356,20 +356,6 @@ init_it(Starter, Parent, Name0, Mod, Args, Options) -> exit(Error) end. -name({local,Name}) -> Name; -name({global,Name}) -> Name; -name({via,_, Name}) -> Name; -name(Pid) when is_pid(Pid) -> Pid. - -unregister_name({local,Name}) -> - _ = (catch unregister(Name)); -unregister_name({global,Name}) -> - _ = global:unregister_name(Name); -unregister_name({via, Mod, Name}) -> - _ = Mod:unregister_name(Name); -unregister_name(Pid) when is_pid(Pid) -> - Pid. - %%%======================================================================== %%% Internal functions %%%======================================================================== @@ -858,86 +844,6 @@ error_info(Reason, Name, Msg, State, Debug) -> sys:print_log(Debug), ok. -%%% --------------------------------------------------- -%%% Misc. functions. -%%% --------------------------------------------------- - -opt(Op, [{Op, Value}|_]) -> - {ok, Value}; -opt(Op, [_|Options]) -> - opt(Op, Options); -opt(_, []) -> - false. - -debug_options(Name, Opts) -> - case opt(debug, Opts) of - {ok, Options} -> dbg_opts(Name, Options); - _ -> [] - end. - -dbg_opts(Name, Opts) -> - case catch sys:debug_options(Opts) of - {'EXIT',_} -> - format("~p: ignoring erroneous debug options - ~p~n", - [Name, Opts]), - []; - Dbg -> - Dbg - end. - -get_proc_name(Pid) when is_pid(Pid) -> - Pid; -get_proc_name({local, Name}) -> - case process_info(self(), registered_name) of - {registered_name, Name} -> - Name; - {registered_name, _Name} -> - exit(process_not_registered); - [] -> - exit(process_not_registered) - end; -get_proc_name({global, Name}) -> - case global:whereis_name(Name) of - undefined -> - exit(process_not_registered_globally); - Pid when Pid =:= self() -> - Name; - _Pid -> - exit(process_not_registered_globally) - end; -get_proc_name({via, Mod, Name}) -> - case Mod:whereis_name(Name) of - undefined -> - exit({process_not_registered_via, Mod}); - Pid when Pid =:= self() -> - Name; - _Pid -> - exit({process_not_registered_via, Mod}) - end. - -get_parent() -> - case get('$ancestors') of - [Parent | _] when is_pid(Parent)-> - Parent; - [Parent | _] when is_atom(Parent)-> - name_to_pid(Parent); - _ -> - exit(process_was_not_started_by_proc_lib) - end. - -name_to_pid(Name) -> - case whereis(Name) of - undefined -> - case global:whereis_name(Name) of - undefined -> - exit(could_not_find_registered_name); - Pid -> - Pid - end; - Pid -> - Pid - end. - %%----------------------------------------------------------------- %% Status information %%----------------------------------------------------------------- diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl new file mode 100644 index 0000000000..f9e2e5f7d2 --- /dev/null +++ b/lib/stdlib/src/gen_statem.erl @@ -0,0 +1,1267 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +-module(gen_statem). + +%% API +-export( + [start/3,start/4,start_link/3,start_link/4, + stop/1,stop/3, + cast/2,call/2,call/3, + enter_loop/5,enter_loop/6,enter_loop/7, + reply/1,reply/2]). + +%% gen callbacks +-export( + [init_it/6]). + +%% sys callbacks +-export( + [system_continue/3, + system_terminate/4, + system_code_change/4, + system_get_state/1, + system_replace_state/2, + format_status/2]). + +%% Internal callbacks +-export( + [wakeup_from_hibernate/3]). + +%% Type exports for templates +-export_type( + [event_type/0, + callback_mode/0, + state_function_result/0, + handle_event_result/0, + action/0]). + +%% Fix problem for doc build +-export_type([transition_option/0]). + +%%%========================================================================== +%%% Interface functions. +%%%========================================================================== + +-type from() :: + {To :: pid(), Tag :: term()}. % Reply-to specifier for call + +-type state() :: + state_name() | % For state callback function StateName/5 + term(). % For state callback function handle_event/5 + +-type state_name() :: atom(). + +-type data() :: term(). + +-type event_type() :: + {'call',From :: from()} | 'cast' | + 'info' | 'timeout' | 'internal'. + +-type callback_mode() :: 'state_functions' | 'handle_event_function'. + +-type transition_option() :: + postpone() | hibernate() | event_timeout(). +-type postpone() :: + %% If 'true' postpone the current event + %% and retry it when the state changes (=/=) + boolean(). +-type hibernate() :: + %% If 'true' hibernate the server instead of going into receive + boolean(). +-type event_timeout() :: + %% Generate a ('timeout', EventContent, ...) event after Time + %% unless some other event is delivered + Time :: timeout(). + +-type action() :: + %% During a state change: + %% * NextState and NewData are set. + %% * All action()s are executed in order of apperance. + %% * Postponing the current event is performed + %% iff 'postpone' is 'true'. + %% * A state timer is started iff 'timeout' is set. + %% * Pending events are processed or if there are + %% no pending events the server goes into receive + %% or hibernate (iff 'hibernate' is 'true') + %% + %% These action()s are executed in order of appearence + %% in the containing list. The ones that set options + %% will override any previous so the last of each kind wins. + %% + 'postpone' | % Set the postpone option + {'postpone', Postpone :: postpone()} | + %% + 'hibernate' | % Set the hibernate option + {'hibernate', Hibernate :: hibernate()} | + %% + (Timeout :: event_timeout()) | % {timeout,Timeout} + {'timeout', % Set the event timeout option + Time :: event_timeout(), EventContent :: term()} | + %% + reply_action() | + %% + %% All 'next_event' events are kept in a list and then + %% inserted at state changes so the first in the + %% action() list is the first to be delivered. + {'next_event', % Insert event as the next to handle + EventType :: event_type(), + EventContent :: term()}. +-type reply_action() :: + {'reply', % Reply to a caller + From :: from(), Reply :: term()}. + +-type state_function_result() :: + {'next_state', % {next_state,NextStateName,NewData,[]} + NextStateName :: state_name(), + NewData :: data()} | + {'next_state', % State transition, maybe to the same state + NextStateName :: state_name(), + NewData :: data(), + Actions :: [action()] | action()} | + common_state_callback_result(). +-type handle_event_result() :: + {'next_state', % {next_state,NextState,NewData,[]} + NextState :: state(), + NewData :: data()} | + {'next_state', % State transition, maybe to the same state + NextState :: state(), + NewData :: data(), + Actions :: [action()] | action()} | + common_state_callback_result(). +-type common_state_callback_result() :: + 'stop' | % {stop,normal} + {'stop', % Stop the server + Reason :: term()} | + {'stop', % Stop the server + Reason :: term(), + NewData :: data()} | + {'stop_and_reply', % Reply then stop the server + Reason :: term(), + Replies :: [reply_action()] | reply_action()} | + {'stop_and_reply', % Reply then stop the server + Reason :: term(), + Replies :: [reply_action()] | reply_action(), + NewData :: data()} | + {'keep_state', % {keep_state,NewData,[]} + NewData :: data()} | + {'keep_state', % Keep state, change data + NewData :: data(), + Actions :: [action()] | action()} | + 'keep_state_and_data' | % {keep_state_and_data,[]} + {'keep_state_and_data', % Keep state and data -> only actions + Actions :: [action()] | action()}. + + +%% The state machine init function. It is called only once and +%% the server is not running until this function has returned +%% an {ok, ...} tuple. Thereafter the state callbacks are called +%% for all events to this server. +-callback init(Args :: term()) -> + {callback_mode(), state(), data()} | + {callback_mode(), state(), data(), [action()] | action()} | + 'ignore' | + {'stop', Reason :: term()}. + +%% Example state callback for callback_mode() =:= state_functions +%% state name 'state_name'. +%% +%% In this mode all states has to be type state_name() i.e atom(). +%% +%% Note that state callbacks and only state callbacks have arity 5 +%% and that is intended. +-callback state_name( + event_type(), + EventContent :: term(), + Data :: data()) -> + state_function_result(). +%% +%% State callback for callback_mode() =:= handle_event_function. +%% +%% Note that state callbacks and only state callbacks have arity 5 +%% and that is intended. +-callback handle_event( + event_type(), + EventContent :: term(), + State :: state(), % Current state + Data :: data()) -> + handle_event_result(). + +%% Clean up before the server terminates. +-callback terminate( + Reason :: 'normal' | 'shutdown' | {'shutdown', term()} + | term(), + State :: state(), + Data :: data()) -> + any(). + +%% Note that the new code can expect to get an OldState from +%% the old code version not only in code_change/4 but in the first +%% state callback function called thereafter +-callback code_change( + OldVsn :: term() | {'down', term()}, + OldState :: state(), + OldData :: data(), + Extra :: term()) -> + {NewCallbackMode :: callback_mode(), + NewState :: state(), + NewData :: data()}. + +%% Format the callback module state in some sensible that is +%% often condensed way. For StatusOption =:= 'normal' the perferred +%% return term is [{data,[{"State",FormattedState}]}], and for +%% StatusOption =:= 'terminate' it is just FormattedState. +-callback format_status( + StatusOption, + [ [{Key :: term(), Value :: term()}] | + state() | + data()]) -> + Status :: term() when + StatusOption :: 'normal' | 'terminate'. + +-optional_callbacks( + [init/1, % One may use enter_loop/5,6,7 instead + format_status/2, % Has got a default implementation + %% + state_name/3, % Example for callback_mode =:= state_functions: + %% there has to be a StateName/5 callback function for every StateName. + %% + handle_event/4]). % For callback_mode =:= handle_event_function + +%% Type validation functions +callback_mode(CallbackMode) -> + case CallbackMode of + state_functions -> + true; + handle_event_function -> + true; + _ -> + false + end. +%% +from({Pid,_}) when is_pid(Pid) -> + true; +from(_) -> + false. +%% +event_type({call,From}) -> + from(From); +event_type(Type) -> + case Type of + cast -> + true; + info -> + true; + timeout -> + true; + internal -> + true; + _ -> + false + end. + + + +-define( + STACKTRACE(), + try throw(ok) catch _ -> erlang:get_stacktrace() end). + +-define( + TERMINATE(Class, Reason, Debug, S, Q), + terminate( + begin Class end, + begin Reason end, + ?STACKTRACE(), + begin Debug end, + begin S end, + begin Q end)). + +%%%========================================================================== +%%% API + +-type server_name() :: + {'global', GlobalName :: term()} + | {'via', RegMod :: module(), Name :: term()} + | {'local', atom()}. +-type server_ref() :: + {'global', GlobalName :: term()} + | {'via', RegMod :: module(), ViaName :: term()} + | (LocalName :: atom()) + | {Name :: atom(), Node :: atom()} + | pid(). +-type debug_opt() :: + {'debug', + Dbgs :: + ['trace' | 'log' | 'statistics' | 'debug' + | {'logfile', string()}]}. +-type start_opt() :: + debug_opt() + | {'timeout', Time :: timeout()} + | {'spawn_opt', [proc_lib:spawn_option()]}. +-type start_ret() :: {'ok', pid()} | 'ignore' | {'error', term()}. + + + +%% Start a state machine +-spec start( + Module :: module(), Args :: term(), Opts :: [start_opt()]) -> + start_ret(). +start(Module, Args, Opts) -> + gen:start(?MODULE, nolink, Module, Args, Opts). +%% +-spec start( + ServerName :: server_name(), + Module :: module(), Args :: term(), Opts :: [start_opt()]) -> + start_ret(). +start(ServerName, Module, Args, Opts) -> + gen:start(?MODULE, nolink, ServerName, Module, Args, Opts). + +%% Start and link to a state machine +-spec start_link( + Module :: module(), Args :: term(), Opts :: [start_opt()]) -> + start_ret(). +start_link(Module, Args, Opts) -> + gen:start(?MODULE, link, Module, Args, Opts). +%% +-spec start_link( + ServerName :: server_name(), + Module :: module(), Args :: term(), Opts :: [start_opt()]) -> + start_ret(). +start_link(ServerName, Module, Args, Opts) -> + gen:start(?MODULE, link, ServerName, Module, Args, Opts). + +%% Stop a state machine +-spec stop(ServerRef :: server_ref()) -> ok. +stop(ServerRef) -> + gen:stop(ServerRef). +%% +-spec stop( + ServerRef :: server_ref(), + Reason :: term(), + Timeout :: timeout()) -> ok. +stop(ServerRef, Reason, Timeout) -> + gen:stop(ServerRef, Reason, Timeout). + +%% Send an event to a state machine that arrives with type 'event' +-spec cast(ServerRef :: server_ref(), Msg :: term()) -> ok. +cast({global,Name}, Msg) -> + try global:send(Name, wrap_cast(Msg)) of + _ -> ok + catch + _:_ -> ok + end; +cast({via,RegMod,Name}, Msg) -> + try RegMod:send(Name, wrap_cast(Msg)) of + _ -> ok + catch + _:_ -> ok + end; +cast({Name,Node} = ServerRef, Msg) when is_atom(Name), is_atom(Node) -> + send(ServerRef, wrap_cast(Msg)); +cast(ServerRef, Msg) when is_atom(ServerRef) -> + send(ServerRef, wrap_cast(Msg)); +cast(ServerRef, Msg) when is_pid(ServerRef) -> + send(ServerRef, wrap_cast(Msg)). + +%% Call a state machine (synchronous; a reply is expected) that +%% arrives with type {call,From} +-spec call(ServerRef :: server_ref(), Request :: term()) -> Reply :: term(). +call(ServerRef, Request) -> + call(ServerRef, Request, infinity). +%% +-spec call( + ServerRef :: server_ref(), + Request :: term(), + Timeout :: timeout()) -> + Reply :: term(). +call(ServerRef, Request, infinity) -> + try gen:call(ServerRef, '$gen_call', Request, infinity) of + {ok,Reply} -> + Reply + catch + Class:Reason -> + erlang:raise( + Class, + {Reason,{?MODULE,call,[ServerRef,Request,infinity]}}, + erlang:get_stacktrace()) + end; +call(ServerRef, Request, Timeout) -> + %% Call server through proxy process to dodge any late reply + Ref = make_ref(), + Self = self(), + Pid = spawn( + fun () -> + Self ! + try gen:call( + ServerRef, '$gen_call', Request, Timeout) of + Result -> + {Ref,Result} + catch Class:Reason -> + {Ref,Class,Reason,erlang:get_stacktrace()} + end + end), + Mref = monitor(process, Pid), + receive + {Ref,Result} -> + demonitor(Mref, [flush]), + case Result of + {ok,Reply} -> + Reply + end; + {Ref,Class,Reason,Stacktrace} -> + demonitor(Mref, [flush]), + erlang:raise( + Class, + {Reason,{?MODULE,call,[ServerRef,Request,Timeout]}}, + Stacktrace); + {'DOWN',Mref,_,_,Reason} -> + %% There is a theoretical possibility that the + %% proxy process gets killed between try--of and ! + %% so this clause is in case of that + exit(Reason) + end. + +%% Reply from a state machine callback to whom awaits in call/2 +-spec reply([reply_action()] | reply_action()) -> ok. +reply({reply,From,Reply}) -> + reply(From, Reply); +reply(Replies) when is_list(Replies) -> + replies(Replies). +%% +-spec reply(From :: from(), Reply :: term()) -> ok. +reply({To,Tag}, Reply) when is_pid(To) -> + Msg = {Tag,Reply}, + try To ! Msg of + _ -> + ok + catch + _:_ -> ok + end. + +%% Instead of starting the state machine through start/3,4 +%% or start_link/3,4 turn the current process presumably +%% started by proc_lib into a state machine using +%% the same arguments as you would have returned from init/1 +-spec enter_loop( + Module :: module(), Opts :: [debug_opt()], + CallbackMode :: callback_mode(), + State :: state(), Data :: data()) -> + no_return(). +enter_loop(Module, Opts, CallbackMode, State, Data) -> + enter_loop(Module, Opts, CallbackMode, State, Data, self()). +%% +-spec enter_loop( + Module :: module(), Opts :: [debug_opt()], + CallbackMode :: callback_mode(), + State :: state(), Data :: data(), + Server_or_Actions :: + server_name() | pid() | [action()]) -> + no_return(). +enter_loop(Module, Opts, CallbackMode, State, Data, Server_or_Actions) -> + if + is_list(Server_or_Actions) -> + enter_loop( + Module, Opts, CallbackMode, State, Data, + self(), Server_or_Actions); + true -> + enter_loop( + Module, Opts, CallbackMode, State, Data, + Server_or_Actions, []) + end. +%% +-spec enter_loop( + Module :: module(), Opts :: [debug_opt()], + CallbackMode :: callback_mode(), + State :: state(), Data :: data(), + Server :: server_name() | pid(), + Actions :: [action()] | action()) -> + no_return(). +enter_loop(Module, Opts, CallbackMode, State, Data, Server, Actions) -> + is_atom(Module) orelse error({atom,Module}), + callback_mode(CallbackMode) orelse error({callback_mode,CallbackMode}), + Parent = gen:get_parent(), + enter(Module, Opts, CallbackMode, State, Data, Server, Actions, Parent). + +%%--------------------------------------------------------------------------- +%% API helpers + +wrap_cast(Event) -> + {'$gen_cast',Event}. + +replies([{reply,From,Reply}|Replies]) -> + reply(From, Reply), + replies(Replies); +replies([]) -> + ok. + +%% Might actually not send the message in case of caught exception +send(Proc, Msg) -> + try erlang:send(Proc, Msg, [noconnect]) of + noconnect -> + _ = spawn(erlang, send, [Proc,Msg]), + ok; + ok -> + ok + catch + _:_ -> + ok + end. + +%% Here init_it/6 and enter_loop/5,6,7 functions converge +enter(Module, Opts, CallbackMode, State, Data, Server, Actions, Parent) -> + %% The values should already have been type checked + Name = gen:get_proc_name(Server), + Debug = gen:debug_options(Name, Opts), + PrevState = make_ref(), % Will be discarded by loop_event_actions/9 + NewActions = + if + is_list(Actions) -> + Actions ++ [{postpone,false}]; + true -> + [Actions,{postpone,false}] + end, + S = #{ + callback_mode => CallbackMode, + module => Module, + name => Name, + state => PrevState, + data => Data, + timer => undefined, + postponed => [], + hibernate => false}, + loop_event_actions( + Parent, Debug, S, [], + {event,undefined}, % Will be discarded thanks to {postpone,false} + PrevState, State, Data, NewActions). + +%%%========================================================================== +%%% gen callbacks + +init_it(Starter, self, ServerRef, Module, Args, Opts) -> + init_it(Starter, self(), ServerRef, Module, Args, Opts); +init_it(Starter, Parent, ServerRef, Module, Args, Opts) -> + try Module:init(Args) of + Result -> + init_result(Starter, Parent, ServerRef, Module, Result, Opts) + catch + Result -> + init_result(Starter, Parent, ServerRef, Module, Result, Opts); + Class:Reason -> + gen:unregister_name(ServerRef), + proc_lib:init_ack(Starter, {error,Reason}), + erlang:raise(Class, Reason, erlang:get_stacktrace()) + end. + +%%--------------------------------------------------------------------------- +%% gen callbacks helpers + +init_result(Starter, Parent, ServerRef, Module, Result, Opts) -> + case Result of + {CallbackMode,State,Data} -> + case callback_mode(CallbackMode) of + true -> + proc_lib:init_ack(Starter, {ok,self()}), + enter( + Module, Opts, CallbackMode, State, Data, + ServerRef, [], Parent); + false -> + Error = {callback_mode,CallbackMode}, + proc_lib:init_ack(Starter, {error,Error}), + exit(Error) + end; + {CallbackMode,State,Data,Actions} -> + case callback_mode(CallbackMode) of + true -> + proc_lib:init_ack(Starter, {ok,self()}), + enter( + Module, Opts, CallbackMode, State, Data, + ServerRef, Actions, Parent); + false -> + Error = {callback_mode,CallbackMode}, + proc_lib:init_ack(Starter, {error,Error}), + exit(Error) + end; + {stop,Reason} -> + gen:unregister_name(ServerRef), + proc_lib:init_ack(Starter, {error,Reason}), + exit(Reason); + ignore -> + gen:unregister_name(ServerRef), + proc_lib:init_ack(Starter, ignore), + exit(normal); + _ -> + Error = {bad_return_value,Result}, + proc_lib:init_ack(Starter, {error,Error}), + exit(Error) + end. + +%%%========================================================================== +%%% sys callbacks + +system_continue(Parent, Debug, S) -> + loop(Parent, Debug, S). + +system_terminate(Reason, _Parent, Debug, S) -> + ?TERMINATE(exit, Reason, Debug, S, []). + +system_code_change( + #{module := Module, + state := State, + data := Data} = S, + _Mod, OldVsn, Extra) -> + case + try Module:code_change(OldVsn, State, Data, Extra) + catch + Result -> Result + end + of + {NewCallbackMode,NewState,NewData} -> + callback_mode(NewCallbackMode) orelse + error({callback_mode,NewCallbackMode}), + {ok,S#{state := NewState, data := NewData}}; + {ok,_} = Error -> + error({case_clause,Error}); + Error -> + Error + end. + +system_get_state(#{state := State, data := Data}) -> + {ok,{State,Data}}. + +system_replace_state( + StateFun, + #{state := State, + data := Data} = S) -> + {NewState,NewData} = Result = StateFun({State,Data}), + {ok,Result,S#{state := NewState, data := NewData}}. + +format_status( + Opt, + [PDict,SysState,Parent,Debug, + #{name := Name, postponed := P} = S]) -> + Header = gen:format_status_header("Status for state machine", Name), + Log = sys:get_debug(log, Debug, []), + [{header,Header}, + {data, + [{"Status",SysState}, + {"Parent",Parent}, + {"Logged Events",Log}, + {"Postponed",P}]} | + case format_status(Opt, PDict, S) of + L when is_list(L) -> L; + T -> [T] + end]. + +%%--------------------------------------------------------------------------- +%% Format debug messages. Print them as the call-back module sees +%% them, not as the real erlang messages. Use trace for that. +%%--------------------------------------------------------------------------- + +print_event(Dev, {in,Event}, #{name := Name}) -> + io:format( + Dev, "*DBG* ~p received ~s~n", + [Name,event_string(Event)]); +print_event(Dev, {out,Reply,{To,_Tag}}, #{name := Name}) -> + io:format( + Dev, "*DBG* ~p sent ~p to ~p~n", + [Name,Reply,To]); +print_event(Dev, {Tag,Event,NewState}, #{name := Name, state := State}) -> + StateString = + case NewState of + State -> + io_lib:format("~p", [State]); + _ -> + io_lib:format("~p => ~p", [State,NewState]) + end, + io:format( + Dev, "*DBG* ~p ~w ~s in state ~s~n", + [Name,Tag,event_string(Event),StateString]). + +event_string(Event) -> + case Event of + {{call,{Pid,_Tag}},Request} -> + io_lib:format("call ~p from ~w", [Request,Pid]); + {Tag,Content} -> + io_lib:format("~w ~p", [Tag,Content]) + end. + +sys_debug(Debug, S, Entry) -> + case Debug of + [] -> + Debug; + _ -> + sys:handle_debug(Debug, fun print_event/3, S, Entry) + end. + +%%%========================================================================== +%%% Internal callbacks + +wakeup_from_hibernate(Parent, Debug, S) -> + %% It is a new message that woke us up so we have to receive it now + loop_receive(Parent, Debug, S). + +%%%========================================================================== +%%% State Machine engine implementation of proc_lib/gen server + +%% Server loop, consists of all loop* functions +%% and some detours through sys and proc_lib + +%% Entry point for system_continue/3 +loop(Parent, Debug, #{hibernate := Hibernate} = S) -> + case Hibernate of + true -> + %% Does not return but restarts process at + %% wakeup_from_hibernate/3 that jumps to loop_receive/3 + proc_lib:hibernate( + ?MODULE, wakeup_from_hibernate, [Parent,Debug,S]), + error( + {should_not_have_arrived_here_but_instead_in, + {wakeup_from_hibernate,3}}); + false -> + loop_receive(Parent, Debug, S) + end. + +%% Entry point for wakeup_from_hibernate/3 +loop_receive(Parent, Debug, #{timer := Timer} = S) -> + receive + Msg -> + case Msg of + {system,Pid,Req} -> + #{hibernate := Hibernate} = S, + %% Does not return but tail recursively calls + %% system_continue/3 that jumps to loop/3 + sys:handle_system_msg( + Req, Pid, Parent, ?MODULE, Debug, S, Hibernate); + {'EXIT',Parent,Reason} = EXIT -> + %% EXIT is not a 2-tuple and therefore + %% not an event and has no event_type(), + %% but this will stand out in the crash report... + ?TERMINATE(exit, Reason, Debug, S, [EXIT]); + {timeout,Timer,Content} when Timer =/= undefined -> + loop_event(Parent, Debug, S, {timeout,Content}); + _ -> + %% Cancel Timer if running + case Timer of + undefined -> + ok; + _ -> + case erlang:cancel_timer(Timer) of + TimeLeft when is_integer(TimeLeft) -> + ok; + false -> + receive + {timeout,Timer,_} -> + ok + after 0 -> + ok + end + end + end, + Event = + case Msg of + {'$gen_call',From,Request} -> + {{call,From},Request}; + {'$gen_cast',E} -> + {cast,E}; + _ -> + {info,Msg} + end, + loop_event(Parent, Debug, S, Event) + end + end. + +loop_event(Parent, Debug, S, Event) -> + %% The timer field and the hibernate flag in S + %% are now invalid and ignored until we get back to loop/3 + NewDebug = sys_debug(Debug, S, {in,Event}), + %% Here the queue of not yet processed events is created + loop_events(Parent, NewDebug, S, [Event], false). + +%% Process first the event queue, or if it is empty +%% loop back to receive a new event +loop_events(Parent, Debug, S, [], _Hibernate) -> + loop(Parent, Debug, S); +loop_events( + Parent, Debug, + #{callback_mode := CallbackMode, + module := Module, + state := State, + data := Data} = S, + [{Type,Content} = Event|Events] = Q, + Hibernate) -> + %% If the Hibernate flag is true here it can only be + %% because it was set from an event action + %% and we did not go into hibernation since there + %% were events in queue, so we do what the user + %% might depend on i.e collect garbage which + %% would have happened if we actually hibernated + %% and immediately was awakened + Hibernate andalso garbage_collect(), + try + case CallbackMode of + state_functions -> + Module:State(Type, Content, Data); + handle_event_function -> + Module:handle_event(Type, Content, State, Data) + end of + Result -> + loop_event_result( + Parent, Debug, S, Events, Event, Result) + catch + Result -> + loop_event_result( + Parent, Debug, S, Events, Event, Result); + error:badarg when CallbackMode =:= state_functions -> + case erlang:get_stacktrace() of + [{erlang,apply,[Module,State,_],_}|Stacktrace] -> + Args = [Type,Content,Data], + terminate( + error, + {undef_state_function,{Module,State,Args}}, + Stacktrace, + Debug, S, Q); + Stacktrace -> + terminate(error, badarg, Stacktrace, Debug, S, Q) + end; + error:undef -> + %% Process an undef to check for the simple mistake + %% of calling a nonexistent state function + case erlang:get_stacktrace() of + [{Module,State, + [Type,Content,Data]=Args, + _} + |Stacktrace] + when CallbackMode =:= state_functions -> + terminate( + error, + {undef_state_function,{Module,State,Args}}, + Stacktrace, + Debug, S, Q); + [{Module,handle_event, + [Type,Content,State,Data]=Args, + _} + |Stacktrace] + when CallbackMode =:= handle_event_function -> + terminate( + error, + {undef_state_function, + {Module,handle_event,Args}}, + Stacktrace, + Debug, S, Q); + Stacktrace -> + terminate(error, undef, Stacktrace, Debug, S, Q) + end; + Class:Reason -> + Stacktrace = erlang:get_stacktrace(), + terminate(Class, Reason, Stacktrace, Debug, S, Q) + end. + +%% Interpret all callback return variants +loop_event_result( + Parent, Debug, + #{state := State, data := Data} = S, + Events, Event, Result) -> + %% From now until we loop back to the loop_events/4 + %% the state and data fields in S are old + case Result of + stop -> + ?TERMINATE(exit, normal, Debug, S, [Event|Events]); + {stop,Reason} -> + ?TERMINATE(exit, Reason, Debug, S, [Event|Events]); + {stop,Reason,NewData} -> + NewS = S#{data := NewData}, + Q = [Event|Events], + ?TERMINATE(exit, Reason, Debug, NewS, Q); + {stop_and_reply,Reason,Replies} -> + Q = [Event|Events], + [Class,NewReason,Stacktrace,NewDebug] = + reply_then_terminate( + exit, Reason, ?STACKTRACE(), Debug, S, Q, Replies), + %% Since we got back here Replies was bad + terminate(Class, NewReason, Stacktrace, NewDebug, S, Q); + {stop_and_reply,Reason,Replies,NewData} -> + NewS = S#{data := NewData}, + Q = [Event|Events], + [Class,NewReason,Stacktrace,NewDebug] = + reply_then_terminate( + exit, Reason, ?STACKTRACE(), Debug, NewS, Q, Replies), + %% Since we got back here Replies was bad + terminate(Class, NewReason, Stacktrace, NewDebug, NewS, Q); + {next_state,NextState,NewData} -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, []); + {next_state,NextState,NewData,Actions} -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, Actions); + {keep_state,NewData} -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, State, NewData, []); + {keep_state,NewData,Actions} -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, State, NewData, Actions); + keep_state_and_data -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, State, Data, []); + {keep_state_and_data,Actions} -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, State, Data, Actions); + _ -> + ?TERMINATE( + error, {bad_return_value,Result}, Debug, S, [Event|Events]) + end. + +loop_event_actions( + Parent, Debug, S, Events, Event, State, NextState, NewData, Actions) -> + Postpone = false, % Shall we postpone this event, true or false + Hibernate = false, + Timeout = undefined, + NextEvents = [], + loop_event_actions( + Parent, Debug, S, Events, Event, State, NextState, NewData, + if + is_list(Actions) -> + Actions; + true -> + [Actions] + end, + Postpone, Hibernate, Timeout, NextEvents). +%% +%% Process all action()s +loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, [Action|Actions], + Postpone, Hibernate, Timeout, NextEvents) -> + case Action of + %% Actual actions + {reply,From,Reply} -> + case from(From) of + true -> + NewDebug = do_reply(Debug, S, From, Reply), + loop_event_actions( + Parent, NewDebug, S, Events, Event, + State, NextState, NewData, Actions, + Postpone, Hibernate, Timeout, NextEvents); + false -> + ?TERMINATE( + error, {bad_action,Action}, Debug, S, [Event|Events]) + end; + {next_event,Type,Content} -> + case event_type(Type) of + true -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, Actions, + Postpone, Hibernate, Timeout, + [{Type,Content}|NextEvents]); + false -> + ?TERMINATE( + error, {bad_action,Action}, Debug, S, [Event|Events]) + end; + %% Actions that set options + {postpone,NewPostpone} when is_boolean(NewPostpone) -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, Actions, + NewPostpone, Hibernate, Timeout, NextEvents); + {postpone,_} -> + ?TERMINATE( + error, {bad_action,Action}, Debug, S, [Event|Events]); + postpone -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, Actions, + true, Hibernate, Timeout, NextEvents); + {hibernate,NewHibernate} when is_boolean(NewHibernate) -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, Actions, + Postpone, NewHibernate, Timeout, NextEvents); + {hibernate,_} -> + ?TERMINATE( + error, {bad_action,Action}, Debug, S, [Event|Events]); + hibernate -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, Actions, + Postpone, true, Timeout, NextEvents); + {timeout,infinity,_} -> % Clear timer - it will never trigger + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, Actions, + Postpone, Hibernate, undefined, NextEvents); + {timeout,Time,_} = NewTimeout when is_integer(Time), Time >= 0 -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, Actions, + Postpone, Hibernate, NewTimeout, NextEvents); + {timeout,_,_} -> + ?TERMINATE( + error, {bad_action,Action}, Debug, S, [Event|Events]); + infinity -> % Clear timer - it will never trigger + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, Actions, + Postpone, Hibernate, undefined, NextEvents); + Time when is_integer(Time), Time >= 0 -> + NewTimeout = {timeout,Time,Time}, + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, Actions, + Postpone, Hibernate, NewTimeout, NextEvents); + _ -> + ?TERMINATE( + error, {bad_action,Action}, Debug, S, [Event|Events]) + end; +%% +%% End of actions list +loop_event_actions( + Parent, Debug, #{postponed := P0} = S, Events, Event, + State, NextState, NewData, [], + Postpone, Hibernate, Timeout, NextEvents) -> + %% + %% All options have been collected and next_events are buffered. + %% Do the actual state transition. + %% + P1 = % Move current event to postponed if Postpone + case Postpone of + true -> + [Event|P0]; + false -> + P0 + end, + {Q2,P} = % Move all postponed events to queue if state change + if + NextState =:= State -> + {Events,P1}; + true -> + {lists:reverse(P1, Events),[]} + end, + %% Place next events first in queue + Q3 = lists:reverse(NextEvents, Q2), + %% + NewDebug = + sys_debug( + Debug, S, + case Postpone of + true -> + {postpone,Event,NextState}; + false -> + {consume,Event,NextState} + end), + %% Have a peek on the event queue so we can avoid starting + %% the state timer unless we have to + {Q,Timer} = + case Timeout of + undefined -> + %% No state timeout has been requested + {Q3,undefined}; + {timeout,Time,EventContent} -> + %% A state timeout has been requested + case Q3 of + [] when Time =:= 0 -> + %% Immediate timeout - simulate it + %% so we do not get the timeout message + %% after any received event + {[{timeout,EventContent}],undefined}; + [] -> + %% Actually start a timer + {Q3,erlang:start_timer(Time, self(), EventContent)}; + _ -> + %% Do not start a timer since any queued + %% event cancels the state timer so we pretend + %% that the timer has been started and cancelled + {Q3,undefined} + end + end, + %% Loop to top of event queue loop; process next event + loop_events( + Parent, NewDebug, + S#{ + state := NextState, + data := NewData, + timer := Timer, + postponed := P, + hibernate := Hibernate}, + Q, Hibernate). + +%%--------------------------------------------------------------------------- +%% Server helpers + +reply_then_terminate(Class, Reason, Stacktrace, Debug, S, Q, Replies) -> + if + is_list(Replies) -> + do_reply_then_terminate( + Class, Reason, Stacktrace, Debug, S, Q, Replies); + true -> + do_reply_then_terminate( + Class, Reason, Stacktrace, Debug, S, Q, [Replies]) + end. +%% +do_reply_then_terminate(Class, Reason, Stacktrace, Debug, S, Q, []) -> + terminate(Class, Reason, Stacktrace, Debug, S, Q); +do_reply_then_terminate( + Class, Reason, Stacktrace, Debug, S, Q, [R|Rs]) -> + case R of + {reply,{_To,_Tag}=From,Reply} -> + NewDebug = do_reply(Debug, S, From, Reply), + do_reply_then_terminate( + Class, Reason, Stacktrace, NewDebug, S, Q, Rs); + _ -> + [error,{bad_action,R},?STACKTRACE(),Debug] + end. + +do_reply(Debug, S, From, Reply) -> + reply(From, Reply), + sys_debug(Debug, S, {out,Reply,From}). + + +terminate( + Class, Reason, Stacktrace, Debug, + #{module := Module, + state := State, data := Data} = S, + Q) -> + try Module:terminate(Reason, State, Data) of + _ -> ok + catch + _ -> ok; + C:R -> + ST = erlang:get_stacktrace(), + error_info( + C, R, ST, Debug, S, Q, + format_status(terminate, get(), S)), + erlang:raise(C, R, ST) + end, + case Reason of + normal -> ok; + shutdown -> ok; + {shutdown,_} -> ok; + _ -> + error_info( + Class, Reason, Stacktrace, Debug, S, Q, + format_status(terminate, get(), S)) + end, + case Stacktrace of + [] -> + erlang:Class(Reason); + _ -> + erlang:raise(Class, Reason, Stacktrace) + end. + +error_info( + Class, Reason, Stacktrace, Debug, + #{name := Name, callback_mode := CallbackMode, + state := State, postponed := P}, + Q, FmtData) -> + {FixedReason,FixedStacktrace} = + case Stacktrace of + [{M,F,Args,_}|ST] + when Class =:= error, Reason =:= undef -> + case code:is_loaded(M) of + false -> + {{'module could not be loaded',M},ST}; + _ -> + Arity = + if + is_list(Args) -> + length(Args); + is_integer(Args) -> + Args + end, + case erlang:function_exported(M, F, Arity) of + true -> + {Reason,Stacktrace}; + false -> + {{'function not exported',{M,F,Arity}}, + ST} + end + end; + _ -> {Reason,Stacktrace} + end, + error_logger:format( + "** State machine ~p terminating~n" ++ + case Q of + [] -> + ""; + _ -> + "** Last event = ~p~n" + end ++ + "** When Server state = ~p~n" ++ + "** Reason for termination = ~w:~p~n" ++ + "** State = ~p~n" ++ + "** Callback mode = ~p~n" ++ + "** Queued/Postponed = ~w/~w~n" ++ + case FixedStacktrace of + [] -> + ""; + _ -> + "** Stacktrace =~n" + "** ~p~n" + end, + [Name | + case Q of + [] -> + []; + [Event|_] -> + [Event] + end] ++ + [FmtData,Class,FixedReason, + State,CallbackMode,length(Q),length(P)] ++ + case FixedStacktrace of + [] -> + []; + _ -> + [FixedStacktrace] + end), + sys:print_log(Debug), + ok. + + +%% Call Module:format_status/2 or return a default value +format_status( + Opt, PDict, + #{module := Module, state := State, data := Data}) -> + case erlang:function_exported(Module, format_status, 2) of + true -> + try Module:format_status(Opt, [PDict,State,Data]) + catch + Result -> Result; + _:_ -> + format_status_default( + Opt, State, + "Module:format_status/2 crashed") + end; + false -> + format_status_default(Opt, State, Data) + end. + +%% The default Module:format_status/2 +format_status_default(Opt, State, Data) -> + SSD = {State,Data}, + case Opt of + terminate -> + SSD; + _ -> + [{data,[{"State",SSD}]}] + end. diff --git a/lib/stdlib/src/otp_internal.erl b/lib/stdlib/src/otp_internal.erl index 052dffdbfd..7a59523f06 100644 --- a/lib/stdlib/src/otp_internal.erl +++ b/lib/stdlib/src/otp_internal.erl @@ -58,7 +58,12 @@ obsolete_1(erlang, now, 0) -> obsolete_1(calendar, local_time_to_universal_time, 1) -> {deprecated, {calendar, local_time_to_universal_time_dst, 1}}; -%% *** CRYPTO add in R16B01 *** +%% *** CRYPTO added in OTP 19 *** + +obsolete_1(crypto, rand_bytes, 1) -> + {deprecated, {crypto, strong_rand_bytes, 1}}; + +%% *** CRYPTO added in R16B01 *** obsolete_1(crypto, md4, 1) -> {deprecated, {crypto, hash, 2}}; diff --git a/lib/stdlib/src/proc_lib.erl b/lib/stdlib/src/proc_lib.erl index c47d30c8b8..3f79ed0f87 100644 --- a/lib/stdlib/src/proc_lib.erl +++ b/lib/stdlib/src/proc_lib.erl @@ -472,13 +472,15 @@ trans_init(gen,init_it,[gen_server,_,_,supervisor_bridge,[Module|_],_]) -> {supervisor_bridge,Module,1}; trans_init(gen,init_it,[gen_server,_,_,_,supervisor_bridge,[Module|_],_]) -> {supervisor_bridge,Module,1}; -trans_init(gen,init_it,[gen_server,_,_,Module,_,_]) -> +trans_init(gen,init_it,[GenMod,_,_,Module,_,_]) + when GenMod =:= gen_server; + GenMod =:= gen_statem; + GenMod =:= gen_fsm -> {Module,init,1}; -trans_init(gen,init_it,[gen_server,_,_,_,Module|_]) -> - {Module,init,1}; -trans_init(gen,init_it,[gen_fsm,_,_,Module,_,_]) -> - {Module,init,1}; -trans_init(gen,init_it,[gen_fsm,_,_,_,Module|_]) -> +trans_init(gen,init_it,[GenMod,_,_,_,Module|_]) + when GenMod =:= gen_server; + GenMod =:= gen_statem; + GenMod =:= gen_fsm -> {Module,init,1}; trans_init(gen,init_it,[gen_event|_]) -> {gen_event,init_it,6}; diff --git a/lib/stdlib/src/stdlib.app.src b/lib/stdlib/src/stdlib.app.src index e3950ee795..32bcdc4f2a 100644 --- a/lib/stdlib/src/stdlib.app.src +++ b/lib/stdlib/src/stdlib.app.src @@ -65,6 +65,7 @@ gen_event, gen_fsm, gen_server, + gen_statem, io, io_lib, io_lib_format, diff --git a/lib/stdlib/test/Makefile b/lib/stdlib/test/Makefile index 287f63b2be..28c35aed55 100644 --- a/lib/stdlib/test/Makefile +++ b/lib/stdlib/test/Makefile @@ -46,6 +46,7 @@ MODULES= \ gen_event_SUITE \ gen_fsm_SUITE \ gen_server_SUITE \ + gen_statem_SUITE \ id_transform_SUITE \ io_SUITE \ io_proto_SUITE \ diff --git a/lib/stdlib/test/error_logger_forwarder.erl b/lib/stdlib/test/error_logger_forwarder.erl index 0aecd41ba9..6bbf180585 100644 --- a/lib/stdlib/test/error_logger_forwarder.erl +++ b/lib/stdlib/test/error_logger_forwarder.erl @@ -20,7 +20,7 @@ -module(error_logger_forwarder). %% API. --export([register/0]). +-export([register/0, unregister/0]). %% Internal export for error_logger. -export([init/1, @@ -33,6 +33,10 @@ register() -> error_logger:add_report_handler(?MODULE, self()). +unregister() -> + Self = self(), + Self = error_logger:delete_report_handler(?MODULE). + init(Tester) -> {ok,Tester}. diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl new file mode 100644 index 0000000000..3deb5fd986 --- /dev/null +++ b/lib/stdlib/test/gen_statem_SUITE.erl @@ -0,0 +1,1578 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +-module(gen_statem_SUITE). + +-include_lib("common_test/include/ct.hrl"). + +-compile(export_all). +-behaviour(gen_statem). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +suite() -> + [{ct_hooks,[ts_install_cth]}, + {timetrap,{minutes,1}}]. + +all() -> + [{group, start}, + {group, start_handle_event}, + {group, stop}, + {group, stop_handle_event}, + {group, abnormal}, + {group, abnormal_handle_event}, + shutdown, stop_and_reply, event_order, + {group, sys}, + hibernate, enter_loop]. + +groups() -> + [{start, [], + [start1, start2, start3, start4, start5, start6, start7, + start8, start9, start10, start11, start12, next_events]}, + {start_handle_event, [], + [start1, start2, start3, start4, start5, start6, start7, + start8, start9, start10, start11, start12, next_events]}, + {stop, [], + [stop1, stop2, stop3, stop4, stop5, stop6, stop7, stop8, stop9, stop10]}, + {stop_handle_event, [], + [stop1, stop2, stop3, stop4, stop5, stop6, stop7, stop8, stop9, stop10]}, + {abnormal, [], [abnormal1, abnormal2]}, + {abnormal_handle_event, [], [abnormal1, abnormal2]}, + {sys, [], + [sys1, code_change, + call_format_status, + error_format_status, terminate_crash_format, + get_state, replace_state]}, + {sys_handle_event, [], + [sys1, + call_format_status, + error_format_status, terminate_crash_format, + get_state, replace_state]}]. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_group(GroupName, Config) + when GroupName =:= start_handle_event; + GroupName =:= stop_handle_event; + GroupName =:= abnormal_handle_event; + GroupName =:= sys_handle_event -> + [{callback_mode,handle_event_function}|Config]; +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + +init_per_testcase(_CaseName, Config) -> + flush(), +%%% dbg:tracer(), +%%% dbg:p(all, c), +%%% dbg:tpl(gen_statem, cx), +%%% dbg:tpl(proc_lib, cx), +%%% dbg:tpl(gen, cx), +%%% dbg:tpl(sys, cx), + Config. + +end_per_testcase(_CaseName, Config) -> +%%% dbg:stop(), + Config. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-define(EXPECT_FAILURE(Code, Reason), + try begin Code end of + Reason -> + ct:fail({unexpected,Reason}) + catch + error:Reason -> Reason; + exit:Reason -> Reason + end). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% anonymous +start1(Config) -> + %%OldFl = process_flag(trap_exit, true), + + {ok,Pid0} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), + ok = do_func_test(Pid0), + ok = do_sync_func_test(Pid0), + stop_it(Pid0), +%% stopped = gen_statem:call(Pid0, stop), +%% timeout = +%% ?EXPECT_FAILURE(gen_statem:call(Pid0, hej), Reason), + + %%process_flag(trap_exit, OldFl), + ok = verify_empty_msgq(). + +%% anonymous w. shutdown +start2(Config) -> + %% Dont link when shutdown + {ok,Pid0} = gen_statem:start(?MODULE, start_arg(Config, []), []), + ok = do_func_test(Pid0), + ok = do_sync_func_test(Pid0), + stopped = gen_statem:call(Pid0, {stop,shutdown}), + check_stopped(Pid0), + ok = verify_empty_msgq(). + +%% anonymous with timeout +start3(Config) -> + %%OldFl = process_flag(trap_exit, true), + + {ok,Pid0} = + gen_statem:start(?MODULE, start_arg(Config, []), [{timeout,5}]), + ok = do_func_test(Pid0), + ok = do_sync_func_test(Pid0), + stop_it(Pid0), + + {error,timeout} = + gen_statem:start( + ?MODULE, start_arg(Config, sleep), [{timeout,5}]), + + %%process_flag(trap_exit, OldFl), + ok = verify_empty_msgq(). + +%% anonymous with ignore +start4(Config) -> + OldFl = process_flag(trap_exit, true), + + ignore = gen_statem:start(?MODULE, start_arg(Config, ignore), []), + + process_flag(trap_exit, OldFl), + ok = verify_empty_msgq(). + +%% anonymous with stop +start5(Config) -> + OldFl = process_flag(trap_exit, true), + + {error,stopped} = gen_statem:start(?MODULE, start_arg(Config, stop), []), + + process_flag(trap_exit, OldFl), + ok = verify_empty_msgq(). + +%% anonymous linked +start6(Config) -> + {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), + ok = do_func_test(Pid), + ok = do_sync_func_test(Pid), + stop_it(Pid), + + ok = verify_empty_msgq(). + +%% global register linked +start7(Config) -> + STM = {global,my_stm}, + + {ok,Pid} = + gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []), + {error,{already_started,Pid}} = + gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []), + {error,{already_started,Pid}} = + gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), + + ok = do_func_test(Pid), + ok = do_sync_func_test(Pid), + ok = do_func_test(STM), + ok = do_sync_func_test(STM), + stop_it(STM), + + ok = verify_empty_msgq(). + + +%% local register +start8(Config) -> + %%OldFl = process_flag(trap_exit, true), + Name = my_stm, + STM = {local,Name}, + + {ok,Pid} = + gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), + {error,{already_started,Pid}} = + gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), + + ok = do_func_test(Pid), + ok = do_sync_func_test(Pid), + ok = do_func_test(Name), + ok = do_sync_func_test(Name), + stop_it(Pid), + + %%process_flag(trap_exit, OldFl), + ok = verify_empty_msgq(). + +%% local register linked +start9(Config) -> + %%OldFl = process_flag(trap_exit, true), + Name = my_stm, + STM = {local,Name}, + + {ok,Pid} = + gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []), + {error,{already_started,Pid}} = + gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), + + ok = do_func_test(Pid), + ok = do_sync_func_test(Pid), + ok = do_func_test(Name), + ok = do_sync_func_test(Name), + stop_it(Pid), + + %%process_flag(trap_exit, OldFl), + ok = verify_empty_msgq(). + +%% global register +start10(Config) -> + STM = {global,my_stm}, + + {ok,Pid} = + gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), + {error,{already_started,Pid}} = + gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), + {error,{already_started,Pid}} = + gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []), + + ok = do_func_test(Pid), + ok = do_sync_func_test(Pid), + ok = do_func_test(STM), + ok = do_sync_func_test(STM), + stop_it(STM), + + ok = verify_empty_msgq(). + +%% Stop registered processes +start11(Config) -> + Name = my_stm, + LocalSTM = {local,Name}, + GlobalSTM = {global,Name}, + + {ok,Pid} = + gen_statem:start_link(LocalSTM, ?MODULE, start_arg(Config, []), []), + stop_it(Pid), + + {ok,_Pid1} = + gen_statem:start_link(LocalSTM, ?MODULE, start_arg(Config, []), []), + stop_it(Name), + + {ok,Pid2} = + gen_statem:start(GlobalSTM, ?MODULE, start_arg(Config, []), []), + stop_it(Pid2), + receive after 1 -> true end, + Result = + gen_statem:start(GlobalSTM, ?MODULE, start_arg(Config, []), []), + ct:log("Result = ~p~n",[Result]), + {ok,_Pid3} = Result, + stop_it(GlobalSTM), + + ok = verify_empty_msgq(). + +%% Via register linked +start12(Config) -> + dummy_via:reset(), + VIA = {via,dummy_via,my_stm}, + + {ok,Pid} = + gen_statem:start_link(VIA, ?MODULE, start_arg(Config, []), []), + {error,{already_started,Pid}} = + gen_statem:start_link(VIA, ?MODULE, start_arg(Config, []), []), + {error,{already_started,Pid}} = + gen_statem:start(VIA, ?MODULE, start_arg(Config, []), []), + + ok = do_func_test(Pid), + ok = do_sync_func_test(Pid), + ok = do_func_test(VIA), + ok = do_sync_func_test(VIA), + stop_it(VIA), + + ok = verify_empty_msgq(). + + +%% Anonymous, reason 'normal' +stop1(Config) -> + {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), + ok = gen_statem:stop(Pid), + false = erlang:is_process_alive(Pid), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason). + +%% Anonymous, other reason +stop2(Config) -> + {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), + ok = gen_statem:stop(Pid, other_reason, infinity), + false = erlang:is_process_alive(Pid), + ok. + +%% Anonymous, invalid timeout +stop3(Config) -> + {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), + _ = + ?EXPECT_FAILURE( + gen_statem:stop(Pid, other_reason, invalid_timeout), + Reason), + true = erlang:is_process_alive(Pid), + ok = gen_statem:stop(Pid), + false = erlang:is_process_alive(Pid), + ok. + +%% Registered name +stop4(Config) -> + {ok,Pid} = + gen_statem:start( + {local,to_stop},?MODULE, start_arg(Config, []), []), + ok = gen_statem:stop(to_stop), + false = erlang:is_process_alive(Pid), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(to_stop), Reason), + ok. + +%% Registered name and local node +stop5(Config) -> + Name = to_stop, + {ok,Pid} = + gen_statem:start( + {local,Name},?MODULE, start_arg(Config, []), []), + ok = gen_statem:stop({Name,node()}), + false = erlang:is_process_alive(Pid), + noproc = + ?EXPECT_FAILURE(gen_statem:stop({Name,node()}), Reason), + ok. + +%% Globally registered name +stop6(Config) -> + STM = {global,to_stop}, + {ok,Pid} = gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), + ok = gen_statem:stop(STM), + false = erlang:is_process_alive(Pid), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(STM), Reason), + ok. + +%% 'via' registered name +stop7(Config) -> + VIA = {via,dummy_via,to_stop}, + dummy_via:reset(), + {ok,Pid} = gen_statem:start(VIA, ?MODULE, start_arg(Config, []), []), + ok = gen_statem:stop(VIA), + false = erlang:is_process_alive(Pid), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(VIA), Reason), + ok. + +%% Anonymous on remote node +stop8(Config) -> + Node = gen_statem_stop8, + {ok,NodeName} = ct_slave:start(Node), + Dir = filename:dirname(code:which(?MODULE)), + rpc:call(NodeName, code, add_path, [Dir]), + {ok,Pid} = + rpc:call( + NodeName, gen_statem,start, + [?MODULE,start_arg(Config, []),[]]), + ok = gen_statem:stop(Pid), + false = rpc:call(NodeName, erlang, is_process_alive, [Pid]), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason1), + {ok,NodeName} = ct_slave:stop(Node), + {{nodedown,NodeName},{sys,terminate,_}} = + ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason2), + ok. + +%% Registered name on remote node +stop9(Config) -> + Name = to_stop, + LocalSTM = {local,Name}, + Node = gen_statem__stop9, + {ok,NodeName} = ct_slave:start(Node), + STM = {Name,NodeName}, + Dir = filename:dirname(code:which(?MODULE)), + rpc:call(NodeName, code, add_path, [Dir]), + {ok,Pid} = + rpc:call( + NodeName, gen_statem, start, + [LocalSTM,?MODULE,start_arg(Config, []),[]]), + ok = gen_statem:stop(STM), + undefined = rpc:call(NodeName,erlang,whereis,[Name]), + false = rpc:call(NodeName,erlang,is_process_alive,[Pid]), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1), + {ok,NodeName} = ct_slave:stop(Node), + {{nodedown,NodeName},{sys,terminate,_}} = + ?EXPECT_FAILURE(gen_statem:stop(STM), Reason2), + ok. + +%% Globally registered name on remote node +stop10(Config) -> + Node = gen_statem_stop10, + STM = {global,to_stop}, + {ok,NodeName} = ct_slave:start(Node), + Dir = filename:dirname(code:which(?MODULE)), + rpc:call(NodeName,code,add_path,[Dir]), + {ok,Pid} = + rpc:call( + NodeName, gen_statem, start, + [STM,?MODULE,start_arg(Config, []),[]]), + global:sync(), + ok = gen_statem:stop(STM), + false = rpc:call(NodeName, erlang, is_process_alive, [Pid]), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1), + {ok,NodeName} = ct_slave:stop(Node), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(STM), Reason2), + ok. + +%% Check that time outs in calls work +abnormal1(Config) -> + Name = abnormal1, + LocalSTM = {local,Name}, + + {ok, _Pid} = + gen_statem:start(LocalSTM, ?MODULE, start_arg(Config, []), []), + + %% timeout call. + delayed = gen_statem:call(Name, {delayed_answer,1}, 100), + {timeout,_} = + ?EXPECT_FAILURE( + gen_statem:call(Name, {delayed_answer,1000}, 10), + Reason), + ok = gen_statem:stop(Name), + ok = verify_empty_msgq(). + +%% Check that bad return values makes the stm crash. Note that we must +%% trap exit since we must link to get the real bad_return_ error +abnormal2(Config) -> + OldFl = process_flag(trap_exit, true), + {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), + + %% bad return value in the gen_statem loop + {{bad_return_value,badreturn},_} = + ?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason), + receive + {'EXIT',Pid,{bad_return_value,badreturn}} -> ok + after 5000 -> + ct:fail(gen_statem_did_not_die) + end, + + process_flag(trap_exit, OldFl), + ok = verify_empty_msgq(). + +shutdown(Config) -> + process_flag(trap_exit, true), + + {ok,Pid0} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), + ok = do_func_test(Pid0), + ok = do_sync_func_test(Pid0), + stopped = gen_statem:call(Pid0, {stop,{shutdown,reason}}), + receive {'EXIT',Pid0,{shutdown,reason}} -> ok end, + process_flag(trap_exit, false), + + {noproc,_} = + ?EXPECT_FAILURE(gen_statem:call(Pid0, hej), Reason), + + receive + Any -> + ct:log("Unexpected: ~p", [Any]), + ct:fail({unexpected,Any}) + after 500 -> + ok + end. + + + +stop_and_reply(_Config) -> + process_flag(trap_exit, true), + + Machine = + %% Abusing the internal format of From... + #{init => + fun () -> + {ok,start,undefined} + end, + start => + fun (cast, {echo,From1,Reply1}, undefined) -> + {next_state,wait,{reply,From1,Reply1}} + end, + wait => + fun (cast, {stop_and_reply,Reason,From2,Reply2},R1) -> + {stop_and_reply,Reason, + [R1,{reply,From2,Reply2}]} + end}, + {ok,STM} = gen_statem:start_link(?MODULE, {map_statem,Machine}, []), + + Self = self(), + Tag1 = make_ref(), + gen_statem:cast(STM, {echo,{Self,Tag1},reply1}), + Tag2 = make_ref(), + gen_statem:cast(STM, {stop_and_reply,reason,{Self,Tag2},reply2}), + case flush() of + [{Tag1,reply1},{Tag2,reply2},{'EXIT',STM,reason}] -> + ok; + Other1 -> + ct:fail({unexpected,Other1}) + end, + + {noproc,_} = + ?EXPECT_FAILURE(gen_statem:call(STM, hej), Reason), + case flush() of + [] -> + ok; + Other2 -> + ct:fail({unexpected,Other2}) + end. + + + +event_order(_Config) -> + process_flag(trap_exit, true), + + Machine = + %% Abusing the internal format of From... + #{init => + fun () -> + {ok,start,undefined} + end, + start => + fun (cast, _, _) -> + {keep_state_and_data,postpone}; %% Handled in 'buffer' + ({call,From}, {buffer,Pid,[Tag3,Tag4,Tag5]}, + undefined) -> + {next_state,buffer,[], + [{next_event,internal,{reply,{Pid,Tag3},ok3}}, + {next_event,internal,{reply,{Pid,Tag4},ok4}}, + {timeout,0,{reply,{Pid,Tag5},ok5}}, + %% The timeout should not happen since there + %% are events that cancel it i.e next_event + %% and postponed + {reply,From,ok}]} + end, + buffer => + fun (internal, Reply, Replies) -> + {keep_state,[Reply|Replies]}; + (timeout, Reply, Replies) -> + {keep_state,[Reply|Replies]}; + (cast, Reply, Replies) -> + {keep_state,[Reply|Replies]}; + ({call,From}, {stop,Reason}, Replies) -> + {next_state,stop,undefined, + lists:reverse( + Replies, + [{reply,From,ok}, + {next_event,internal,{stop,Reason}}])} + end, + stop => + fun (internal, Result, undefined) -> + Result + end}, + + {ok,STM} = gen_statem:start_link(?MODULE, {map_statem,Machine}, []), + Self = self(), + Tag1 = make_ref(), + gen_statem:cast(STM, {reply,{Self,Tag1},ok1}), + Tag2 = make_ref(), + gen_statem:cast(STM, {reply,{Self,Tag2},ok2}), + Tag3 = make_ref(), + Tag4 = make_ref(), + Tag5 = make_ref(), + ok = gen_statem:call(STM, {buffer,Self,[Tag3,Tag4,Tag5]}), + ok = gen_statem:call(STM, {stop,reason}), + case flush() of + [{Tag3,ok3},{Tag4,ok4},{Tag1,ok1},{Tag2,ok2}, + {'EXIT',STM,reason}] -> + ok; + Other1 -> + ct:fail({unexpected,Other1}) + end, + + {noproc,_} = + ?EXPECT_FAILURE(gen_statem:call(STM, hej), Reason), + case flush() of + [] -> + ok; + Other2 -> + ct:fail({unexpected,Other2}) + end. + + + +sys1(Config) -> + {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), + {status, Pid, {module,gen_statem}, _} = sys:get_status(Pid), + sys:suspend(Pid), + Parent = self(), + Tag = make_ref(), + Caller = + spawn( + fun () -> + Parent ! {Tag,gen_statem:call(Pid, hej)} + end), + receive + {Tag,_} -> + ct:fail(should_be_suspended) + after 3000 -> + exit(Caller, ok) + end, + + %% {timeout,_} = + %% ?EXPECT_FAILURE(gen_statem:call(Pid, hej), Reason), + sys:resume(Pid), + stop_it(Pid). + +code_change(Config) -> + {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), + {idle,data} = sys:get_state(Pid), + sys:suspend(Pid), + sys:change_code(Pid, ?MODULE, old_vsn, state_functions), + sys:resume(Pid), + {idle,{old_vsn,data,state_functions}} = sys:get_state(Pid), + stop_it(Pid). + +call_format_status(Config) -> + {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), + Status = sys:get_status(Pid), + {status,Pid,_Mod,[_PDict,running,_,_, Data]} = Status, + [format_status_called|_] = lists:reverse(Data), + stop_it(Pid), + + %% check that format_status can handle a name being an atom (pid is + %% already checked by the previous test) + {ok, Pid2} = + gen_statem:start( + {local, gstm}, ?MODULE, start_arg(Config, []), []), + Status2 = sys:get_status(gstm), + {status,Pid2,_Mod,[_PDict2,running,_,_,Data2]} = Status2, + [format_status_called|_] = lists:reverse(Data2), + stop_it(Pid2), + + %% check that format_status can handle a name being a term other than a + %% pid or atom + GlobalName1 = {global,"CallFormatStatus"}, + {ok,Pid3} = + gen_statem:start( + GlobalName1, ?MODULE, start_arg(Config, []), []), + Status3 = sys:get_status(GlobalName1), + {status,Pid3,_Mod,[_PDict3,running,_,_,Data3]} = Status3, + [format_status_called|_] = lists:reverse(Data3), + stop_it(Pid3), + GlobalName2 = {global,{name, "term"}}, + {ok,Pid4} = + gen_statem:start( + GlobalName2, ?MODULE, start_arg(Config, []), []), + Status4 = sys:get_status(GlobalName2), + {status,Pid4,_Mod,[_PDict4,running,_,_, Data4]} = Status4, + [format_status_called|_] = lists:reverse(Data4), + stop_it(Pid4), + + %% check that format_status can handle a name being a term other than a + %% pid or atom + dummy_via:reset(), + ViaName1 = {via,dummy_via,"CallFormatStatus"}, + {ok,Pid5} = gen_statem:start(ViaName1, ?MODULE, start_arg(Config, []), []), + Status5 = sys:get_status(ViaName1), + {status,Pid5,_Mod, [_PDict5,running,_,_, Data5]} = Status5, + [format_status_called|_] = lists:reverse(Data5), + stop_it(Pid5), + ViaName2 = {via,dummy_via,{name,"term"}}, + {ok, Pid6} = + gen_statem:start( + ViaName2, ?MODULE, start_arg(Config, []), []), + Status6 = sys:get_status(ViaName2), + {status,Pid6,_Mod,[_PDict6,running,_,_,Data6]} = Status6, + [format_status_called|_] = lists:reverse(Data6), + stop_it(Pid6). + + + +error_format_status(Config) -> + error_logger_forwarder:register(), + OldFl = process_flag(trap_exit, true), + Data = "called format_status", + {ok,Pid} = + gen_statem:start( + ?MODULE, start_arg(Config, {data,Data}), []), + %% bad return value in the gen_statem loop + {{bad_return_value,badreturn},_} = + ?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason), + receive + {error,_, + {Pid, + "** State machine"++_, + [Pid,{{call,_},badreturn}, + {formatted,idle,Data}, + error,{bad_return_value,badreturn}|_]}} -> + ok; + Other when is_tuple(Other), element(1, Other) =:= error -> + error_logger_forwarder:unregister(), + ct:fail({unexpected,Other}) + after 1000 -> + error_logger_forwarder:unregister(), + ct:fail(timeout) + end, + process_flag(trap_exit, OldFl), + error_logger_forwarder:unregister(), + receive + %% Comes with SASL + {error_report,_,{Pid,crash_report,_}} -> + ok + after 500 -> + ok + end, + ok = verify_empty_msgq(). + +terminate_crash_format(Config) -> + error_logger_forwarder:register(), + OldFl = process_flag(trap_exit, true), + Data = crash_terminate, + {ok,Pid} = + gen_statem:start( + ?MODULE, start_arg(Config, {data,Data}), []), + stop_it(Pid), + Self = self(), + receive + {error,_GroupLeader, + {Pid, + "** State machine"++_, + [Pid, + {{call,{Self,_}},stop}, + {formatted,idle,Data}, + exit,{crash,terminate}|_]}} -> + ok; + Other when is_tuple(Other), element(1, Other) =:= error -> + error_logger_forwarder:unregister(), + ct:fail({unexpected,Other}) + after 1000 -> + error_logger_forwarder:unregister(), + ct:fail(timeout) + end, + process_flag(trap_exit, OldFl), + error_logger_forwarder:unregister(), + receive + %% Comes with SASL + {error_report,_,{Pid,crash_report,_}} -> + ok + after 500 -> + ok + end, + ok = verify_empty_msgq(). + + +get_state(Config) -> + State = self(), + {ok,Pid} = + gen_statem:start( + ?MODULE, start_arg(Config, {data,State}), []), + {idle,State} = sys:get_state(Pid), + {idle,State} = sys:get_state(Pid, 5000), + stop_it(Pid), + + %% check that get_state can handle a name being an atom (pid is + %% already checked by the previous test) + {ok,Pid2} = + gen_statem:start( + {local,gstm}, ?MODULE, start_arg(Config, {data,State}), []), + {idle,State} = sys:get_state(gstm), + {idle,State} = sys:get_state(gstm, 5000), + stop_it(Pid2), + + %% check that get_state works when pid is sys suspended + {ok,Pid3} = + gen_statem:start( + ?MODULE, start_arg(Config, {data,State}), []), + {idle,State} = sys:get_state(Pid3), + ok = sys:suspend(Pid3), + {idle,State} = sys:get_state(Pid3, 5000), + ok = sys:resume(Pid3), + stop_it(Pid3), + ok = verify_empty_msgq(). + +replace_state(Config) -> + State = self(), + {ok, Pid} = + gen_statem:start( + ?MODULE, start_arg(Config, {data,State}), []), + {idle,State} = sys:get_state(Pid), + NState1 = "replaced", + Replace1 = fun({StateName, _}) -> {StateName,NState1} end, + {idle,NState1} = sys:replace_state(Pid, Replace1), + {idle,NState1} = sys:get_state(Pid), + NState2 = "replaced again", + Replace2 = fun({idle, _}) -> {state0,NState2} end, + {state0,NState2} = sys:replace_state(Pid, Replace2, 5000), + {state0,NState2} = sys:get_state(Pid), + %% verify no change in state if replace function crashes + Replace3 = fun(_) -> error(fail) end, + {callback_failed, + {gen_statem,system_replace_state},{error,fail}} = + ?EXPECT_FAILURE(sys:replace_state(Pid, Replace3), Reason), + {state0, NState2} = sys:get_state(Pid), + %% verify state replaced if process sys suspended + ok = sys:suspend(Pid), + Suffix2 = " and again", + NState3 = NState2 ++ Suffix2, + Replace4 = fun({StateName, _}) -> {StateName, NState3} end, + {state0,NState3} = sys:replace_state(Pid, Replace4), + ok = sys:resume(Pid), + {state0,NState3} = sys:get_state(Pid, 5000), + stop_it(Pid), + ok = verify_empty_msgq(). + +%% Hibernation +hibernate(Config) -> + OldFl = process_flag(trap_exit, true), + + {ok,Pid0} = + gen_statem:start_link( + ?MODULE, start_arg(Config, hiber_now), []), + is_in_erlang_hibernate(Pid0), + stop_it(Pid0), + receive + {'EXIT',Pid0,normal} -> ok + after 5000 -> + ct:fail(gen_statem_did_not_die) + end, + + {ok,Pid} = + gen_statem:start_link(?MODULE, start_arg(Config, hiber), []), + true = ({current_function,{erlang,hibernate,3}} =/= + erlang:process_info(Pid,current_function)), + hibernating = gen_statem:call(Pid, hibernate_sync), + is_in_erlang_hibernate(Pid), + good_morning = gen_statem:call(Pid, wakeup_sync), + is_not_in_erlang_hibernate(Pid), + hibernating = gen_statem:call(Pid, hibernate_sync), + is_in_erlang_hibernate(Pid), + please_just_five_more = gen_statem:call(Pid, snooze_sync), + is_in_erlang_hibernate(Pid), + good_morning = gen_statem:call(Pid, wakeup_sync), + is_not_in_erlang_hibernate(Pid), + ok = gen_statem:cast(Pid, hibernate_async), + is_in_erlang_hibernate(Pid), + ok = gen_statem:cast(Pid, wakeup_async), + is_not_in_erlang_hibernate(Pid), + ok = gen_statem:cast(Pid, hibernate_async), + is_in_erlang_hibernate(Pid), + ok = gen_statem:cast(Pid, snooze_async), + is_in_erlang_hibernate(Pid), + ok = gen_statem:cast(Pid, wakeup_async), + is_not_in_erlang_hibernate(Pid), + + Pid ! hibernate_later, + true = + ({current_function,{erlang,hibernate,3}} =/= + erlang:process_info(Pid, current_function)), + is_in_erlang_hibernate(Pid), + + 'alive!' = gen_statem:call(Pid, 'alive?'), + true = + ({current_function,{erlang,hibernate,3}} =/= + erlang:process_info(Pid, current_function)), + Pid ! hibernate_now, + is_in_erlang_hibernate(Pid), + + 'alive!' = gen_statem:call(Pid, 'alive?'), + true = + ({current_function,{erlang,hibernate,3}} =/= + erlang:process_info(Pid, current_function)), + + hibernating = gen_statem:call(Pid, hibernate_sync), + is_in_erlang_hibernate(Pid), + good_morning = gen_statem:call(Pid, wakeup_sync), + is_not_in_erlang_hibernate(Pid), + hibernating = gen_statem:call(Pid, hibernate_sync), + is_in_erlang_hibernate(Pid), + please_just_five_more = gen_statem:call(Pid, snooze_sync), + is_in_erlang_hibernate(Pid), + good_morning = gen_statem:call(Pid, wakeup_sync), + is_not_in_erlang_hibernate(Pid), + ok = gen_statem:cast(Pid, hibernate_async), + is_in_erlang_hibernate(Pid), + ok = gen_statem:cast(Pid, wakeup_async), + is_not_in_erlang_hibernate(Pid), + ok = gen_statem:cast(Pid, hibernate_async), + is_in_erlang_hibernate(Pid), + ok = gen_statem:cast(Pid, snooze_async), + is_in_erlang_hibernate(Pid), + ok = gen_statem:cast(Pid, wakeup_async), + is_not_in_erlang_hibernate(Pid), + + hibernating = gen_statem:call(Pid, hibernate_sync), + is_in_erlang_hibernate(Pid), + sys:suspend(Pid), + is_in_erlang_hibernate(Pid), + sys:resume(Pid), + is_in_erlang_hibernate(Pid), + receive after 1000 -> ok end, + is_in_erlang_hibernate(Pid), + + good_morning = gen_statem:call(Pid, wakeup_sync), + is_not_in_erlang_hibernate(Pid), + stop_it(Pid), + process_flag(trap_exit, OldFl), + receive + {'EXIT',Pid,normal} -> ok + after 5000 -> + ct:fail(gen_statem_did_not_die) + end, + ok = verify_empty_msgq(). + +is_in_erlang_hibernate(Pid) -> + receive after 1 -> ok end, + is_in_erlang_hibernate_1(200, Pid). + +is_in_erlang_hibernate_1(0, Pid) -> + ct:log("~p\n", [erlang:process_info(Pid, current_function)]), + ct:fail(not_in_erlang_hibernate_3); +is_in_erlang_hibernate_1(N, Pid) -> + {current_function,MFA} = erlang:process_info(Pid, current_function), + case MFA of + {erlang,hibernate,3} -> + ok; + _ -> + receive after 10 -> ok end, + is_in_erlang_hibernate_1(N-1, Pid) + end. + +is_not_in_erlang_hibernate(Pid) -> + receive after 1 -> ok end, + is_not_in_erlang_hibernate_1(200, Pid). + +is_not_in_erlang_hibernate_1(0, Pid) -> + ct:log("~p\n", [erlang:process_info(Pid, current_function)]), + ct:fail(not_in_erlang_hibernate_3); +is_not_in_erlang_hibernate_1(N, Pid) -> + {current_function,MFA} = erlang:process_info(Pid, current_function), + case MFA of + {erlang,hibernate,3} -> + receive after 10 -> ok end, + is_not_in_erlang_hibernate_1(N-1, Pid); + _ -> + ok + end. + + +enter_loop(_Config) -> + OldFlag = process_flag(trap_exit, true), + + dummy_via:reset(), + + %% Locally registered process + {local,Name} + {ok,Pid1a} = + proc_lib:start_link(?MODULE, enter_loop, [local,local]), + yes = gen_statem:call(Pid1a, 'alive?'), + stopped = gen_statem:call(Pid1a, stop), + receive + {'EXIT',Pid1a,normal} -> + ok + after 5000 -> + ct:fail(gen_statem_did_not_die) + end, + + %% Unregistered process + {local,Name} + {ok,Pid1b} = + proc_lib:start_link(?MODULE, enter_loop, [anon,local]), + receive + {'EXIT',Pid1b,process_not_registered} -> + ok + after 5000 -> + ct:fail(gen_statem_did_not_die) + end, + + %% Globally registered process + {global,Name} + {ok,Pid2a} = + proc_lib:start_link(?MODULE, enter_loop, [global,global]), + yes = gen_statem:call(Pid2a, 'alive?'), + stopped = gen_statem:call(Pid2a, stop), + receive + {'EXIT',Pid2a,normal} -> + ok + after 5000 -> + ct:fail(gen_statem_did_not_die) + end, + + %% Unregistered process + {global,Name} + {ok,Pid2b} = + proc_lib:start_link(?MODULE, enter_loop, [anon,global]), + receive + {'EXIT',Pid2b,process_not_registered_globally} -> + ok + after 5000 -> + ct:fail(gen_statem_did_not_die) + end, + + %% Unregistered process + no name + {ok,Pid3} = + proc_lib:start_link(?MODULE, enter_loop, [anon,anon]), + yes = gen_statem:call(Pid3, 'alive?'), + stopped = gen_statem:call(Pid3, stop), + receive + {'EXIT',Pid3,normal} -> + ok + after 5000 -> + ct:fail(gen_statem_did_not_die) + end, + + %% Process not started using proc_lib + CallbackMode = state_functions, + Pid4 = + spawn_link( + gen_statem, enter_loop, + [?MODULE,[],CallbackMode,state0,[]]), + receive + {'EXIT',Pid4,process_was_not_started_by_proc_lib} -> + ok + after 5000 -> + ct:fail(gen_statem_did_not_die) + end, + + %% Make sure I am the parent, ie that ordering a shutdown will + %% result in the process terminating with Reason==shutdown + {ok,Pid5} = + proc_lib:start_link(?MODULE, enter_loop, [anon,anon]), + yes = gen_statem:call(Pid5, 'alive?'), + exit(Pid5, shutdown), + receive + {'EXIT',Pid5,shutdown} -> + ok + after 5000 -> + ct:fail(gen_statem_did_not_die) + end, + + %% Make sure gen_statem:enter_loop does not accept {local,Name} + %% when it's another process than the calling one which is + %% registered under that name + register(armitage, self()), + {ok,Pid6a} = + proc_lib:start_link(?MODULE, enter_loop, [anon,local]), + receive + {'EXIT',Pid6a,process_not_registered} -> + ok + after 1000 -> + ct:fail(gen_statem_started) + end, + unregister(armitage), + + %% Make sure gen_statem:enter_loop does not accept {global,Name} + %% when it's another process than the calling one which is + %% registered under that name + global:register_name(armitage, self()), + {ok,Pid6b} = + proc_lib:start_link(?MODULE, enter_loop, [anon,global]), + receive + {'EXIT',Pid6b,process_not_registered_globally} -> + ok + after 1000 -> + ct:fail(gen_statem_started) + end, + global:unregister_name(armitage), + + dummy_via:register_name(armitage, self()), + {ok,Pid6c} = + proc_lib:start_link(?MODULE, enter_loop, [anon,via]), + receive + {'EXIT',Pid6c,{process_not_registered_via,dummy_via}} -> + ok + after 1000 -> + ct:fail( + {gen_statem_started, + process_info(self(), messages)}) + end, + dummy_via:unregister_name(armitage), + + process_flag(trap_exit, OldFlag), + ok = verify_empty_msgq(). + +enter_loop(Reg1, Reg2) -> + process_flag(trap_exit, true), + case Reg1 of + local -> register(armitage, self()); + global -> global:register_name(armitage, self()); + via -> dummy_via:register_name(armitage, self()); + anon -> ignore + end, + proc_lib:init_ack({ok, self()}), + CallbackMode = state_functions, + case Reg2 of + local -> + gen_statem:enter_loop( + ?MODULE, [], CallbackMode, state0, [], {local,armitage}); + global -> + gen_statem:enter_loop( + ?MODULE, [], CallbackMode, state0, [], {global,armitage}); + via -> + gen_statem:enter_loop( + ?MODULE, [], CallbackMode, state0, [], + {via, dummy_via, armitage}); + anon -> + gen_statem:enter_loop( + ?MODULE, [], CallbackMode, state0, []) + end. + + +%% Test the order for multiple {next_event,T,C} +next_events(Config) -> + {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), + ok = gen_statem:cast(Pid, next_event), + {state,next_events,[]} = gen_statem:call(Pid, get), + ok = gen_statem:stop(Pid), + false = erlang:is_process_alive(Pid), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason). + + +%% +%% Functionality check +%% + +wfor(Msg) -> + receive + Msg -> ok + after 5000 -> + error(timeout) + end. + + +stop_it(STM) -> + stopped = gen_statem:call(STM, stop), + check_stopped(STM). + + +check_stopped(STM) -> + Call = there_you_are, + {_,{gen_statem,call,[_,Call,infinity]}} = + ?EXPECT_FAILURE(gen_statem:call(STM, Call), Reason), + ok. + + +do_func_test(STM) -> + ok = gen_statem:cast(STM, {'alive?',self()}), + wfor(yes), + ok = do_connect(STM), + ok = gen_statem:cast(STM, {'alive?',self()}), + wfor(yes), + ?t:do_times(3, ?MODULE, do_msg, [STM]), + ok = gen_statem:cast(STM, {'alive?',self()}), + wfor(yes), + ok = do_disconnect(STM), + ok = gen_statem:cast(STM, {'alive?',self()}), + wfor(yes), + ok. + + +do_connect(STM) -> + check_state(STM, idle), + gen_statem:cast(STM, {connect,self()}), + wfor(accept), + check_state(STM, wfor_conf), + Tag = make_ref(), + gen_statem:cast(STM, {ping,self(),Tag}), + gen_statem:cast(STM, confirm), + wfor({pong,Tag}), + check_state(STM, connected), + ok. + +do_msg(STM) -> + check_state(STM, connected), + R = make_ref(), + ok = gen_statem:cast(STM, {msg,self(),R}), + wfor({ack,R}). + + +do_disconnect(STM) -> + ok = gen_statem:cast(STM, disconnect), + check_state(STM, idle). + +check_state(STM, State) -> + case gen_statem:call(STM, get) of + {state, State, _} -> ok + end. + +do_sync_func_test(STM) -> + yes = gen_statem:call(STM, 'alive?'), + ok = do_sync_connect(STM), + yes = gen_statem:call(STM, 'alive?'), + ?t:do_times(3, ?MODULE, do_sync_msg, [STM]), + yes = gen_statem:call(STM, 'alive?'), + ok = do_sync_disconnect(STM), + yes = gen_statem:call(STM, 'alive?'), + check_state(STM, idle), + ok = gen_statem:call(STM, {timeout,200}), + yes = gen_statem:call(STM, 'alive?'), + check_state(STM, idle), + ok. + + +do_sync_connect(STM) -> + check_state(STM, idle), + accept = gen_statem:call(STM, connect), + check_state(STM, wfor_conf), + Tag = make_ref(), + gen_statem:cast(STM, {ping,self(),Tag}), + yes = gen_statem:call(STM, confirm), + wfor({pong,Tag}), + check_state(STM, connected), + ok. + +do_sync_msg(STM) -> + check_state(STM, connected), + R = make_ref(), + {ack,R} = gen_statem:call(STM, {msg,R}), + ok. + +do_sync_disconnect(STM) -> + yes = gen_statem:call(STM, disconnect), + check_state(STM, idle). + + +verify_empty_msgq() -> + [] = flush(), + ok. + +start_arg(Config, Arg) -> + case lists:keyfind(callback_mode, 1, Config) of + {_,CallbackMode} -> + {callback_mode,CallbackMode,Arg}; + false -> + Arg + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%% The State Machine +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init(ignore) -> + ignore; +init(stop) -> + {stop,stopped}; +init(stop_shutdown) -> + {stop,shutdown}; +init(sleep) -> + ?t:sleep(1000), + {state_functions,idle,data}; +init(hiber) -> + {state_functions,hiber_idle,[]}; +init(hiber_now) -> + {state_functions,hiber_idle,[],[hibernate]}; +init({data, Data}) -> + {state_functions,idle,Data}; +init({callback_mode,CallbackMode,Arg}) -> + case init(Arg) of + {_,State,Data,Ops} -> + {CallbackMode,State,Data,Ops}; + {_,State,Data} -> + {CallbackMode,State,Data}; + Other -> + Other + end; +init({map_statem,#{init := Init}=Machine}) -> + case Init() of + {ok,State,Data,Ops} -> + {handle_event_function,State,[Data|Machine],Ops}; + {ok,State,Data} -> + {handle_event_function,State,[Data|Machine]}; + Other -> + Other + end; +init([]) -> + {state_functions,idle,data}. + +terminate(_, _State, crash_terminate) -> + exit({crash,terminate}); +terminate({From,stopped}, State, _Data) -> + From ! {self(),{stopped,State}}, + ok; +terminate(_Reason, _State, _Data) -> + ok. + + +%% State functions + +idle(cast, {connect,Pid}, Data) -> + Pid ! accept, + {next_state,wfor_conf,Data,infinity}; % NoOp timeout just to test API +idle({call,From}, connect, Data) -> + gen_statem:reply(From, accept), + {next_state,wfor_conf,Data,infinity}; % NoOp timeout just to test API +idle(cast, badreturn, _Data) -> + badreturn; +idle({call,_From}, badreturn, _Data) -> + badreturn; +idle({call,From}, {delayed_answer,T}, Data) -> + receive + after T -> + gen_statem:reply({reply,From,delayed}), + throw({keep_state,Data}) + end; +idle({call,From}, {timeout,Time}, _Data) -> + {next_state,timeout,{From,Time}, + {timeout,Time,idle}}; +idle(cast, next_event, _Data) -> + {next_state,next_events,[a,b,c], + [{next_event,internal,a}, + {next_event,internal,b}, + {next_event,internal,c}]}; +idle(Type, Content, Data) -> + case handle_common_events(Type, Content, idle, Data) of + undefined -> + case Type of + {call,From} -> + throw({keep_state,Data,[{reply,From,'eh?'}]}); + _ -> + throw( + {stop,{unexpected,idle,Type,Content}}) + end; + Result -> + Result + end. + +timeout(timeout, idle, {From,Time}) -> + TRef = erlang:start_timer(Time, self(), ok), + {keep_state,{From,TRef},0}; % Immediate timeout 0 +timeout(timeout, 0, {From,TRef}) -> + {next_state,timeout2,{From,TRef}, + [{timeout,1,should_be_cancelled}, + postpone]}; % Should cancel state timeout +timeout(_, _, _) -> + keep_state_and_data. + +timeout2(timeout, 0, _) -> + keep_state_and_data; +timeout2(timeout, Reason, _) -> + {stop,Reason}; +timeout2(info, {timeout,TRef,Result}, {From,TRef}) -> + gen_statem:reply([{reply,From,Result}]), + {next_state,idle,state}; +timeout2(_, _, _) -> + {keep_state_and_data,[]}. + +wfor_conf({call,From}, confirm, Data) -> + {next_state,connected,Data, + {reply,From,yes}}; +wfor_conf(cast, {ping,_,_}, _) -> + {keep_state_and_data,[postpone]}; +wfor_conf(cast, confirm, Data) -> + {next_state,connected,Data}; +wfor_conf(Type, Content, Data) -> + case handle_common_events(Type, Content, wfor_conf, Data) of + undefined -> + case Type of + {call,From} -> + {next_state,idle,Data, + [{reply,From,'eh?'}]}; + _ -> + throw(keep_state_and_data) + end; + Result -> + Result + end. + +connected({call,From}, {msg,Ref}, Data) -> + {keep_state,Data, + {reply,From,{ack,Ref}}}; +connected(cast, {msg,From,Ref}, Data) -> + From ! {ack,Ref}, + {keep_state,Data}; +connected({call,From}, disconnect, Data) -> + {next_state,idle,Data, + [{reply,From,yes}]}; +connected(cast, disconnect, Data) -> + {next_state,idle,Data}; +connected(cast, {ping,Pid,Tag}, Data) -> + Pid ! {pong,Tag}, + {keep_state,Data}; +connected(Type, Content, Data) -> + case handle_common_events(Type, Content, connected, Data) of + undefined -> + case Type of + {call,From} -> + {keep_state,Data, + [{reply,From,'eh?'}]}; + _ -> + {keep_state,Data} + end; + Result -> + Result + end. + +state0({call,From}, stop, Data) -> + {stop_and_reply,normal,[{reply,From,stopped}],Data}; +state0(Type, Content, Data) -> + case handle_common_events(Type, Content, state0, Data) of + undefined -> + {keep_state,Data}; + Result -> + Result + end. + +hiber_idle({call,From}, 'alive?', Data) -> + {keep_state,Data, + [{reply,From,'alive!'}]}; +hiber_idle({call,From}, hibernate_sync, Data) -> + {next_state,hiber_wakeup,Data, + [{reply,From,hibernating}, + hibernate]}; +hiber_idle(info, hibernate_later, _) -> + Tref = erlang:start_timer(1000, self(), hibernate), + {keep_state,Tref}; +hiber_idle(info, hibernate_now, Data) -> + {keep_state,Data, + [hibernate]}; +hiber_idle(info, {timeout,Tref,hibernate}, Tref) -> + {keep_state,[], + [hibernate]}; +hiber_idle(cast, hibernate_async, Data) -> + {next_state,hiber_wakeup,Data, + [hibernate]}; +hiber_idle(Type, Content, Data) -> + case handle_common_events(Type, Content, hiber_idle, Data) of + undefined -> + {keep_state,Data}; + Result -> + Result + end. + +hiber_wakeup({call,From}, wakeup_sync, Data) -> + {next_state,hiber_idle,Data, + [{reply,From,good_morning}]}; +hiber_wakeup({call,From}, snooze_sync, Data) -> + {keep_state,Data, + [{reply,From,please_just_five_more}, + hibernate]}; +hiber_wakeup(cast, wakeup_async, Data) -> + {next_state,hiber_idle,Data}; +hiber_wakeup(cast, snooze_async, Data) -> + {keep_state,Data, + [hibernate]}; +hiber_wakeup(Type, Content, Data) -> + case handle_common_events(Type, Content, hiber_wakeup, Data) of + undefined -> + {keep_state,Data}; + Result -> + Result + end. + +next_events(internal, Msg, [Msg|Msgs]) -> + {keep_state,Msgs}; +next_events(Type, Content, Data) -> + case handle_common_events(Type, Content, next_events, Data) of + undefined -> + {keep_state,Data}; + Result -> + Result + end. + + +handle_common_events({call,From}, get, State, Data) -> + {keep_state,Data, + [{reply,From,{state,State,Data}}]}; +handle_common_events(cast, {get,Pid}, State, Data) -> + Pid ! {state,State,Data}, + {keep_state,Data}; +handle_common_events({call,From}, stop, _, Data) -> + {stop_and_reply,normal,[{reply,From,stopped}],Data}; +handle_common_events(cast, stop, _, _) -> + stop; +handle_common_events({call,From}, {stop,Reason}, _, Data) -> + {stop_and_reply,Reason,{reply,From,stopped},Data}; +handle_common_events(cast, {stop,Reason}, _, _) -> + {stop,Reason}; +handle_common_events({call,From}, 'alive?', _, Data) -> + {keep_state,Data, + [{reply,From,yes}]}; +handle_common_events(cast, {'alive?',Pid}, _, Data) -> + Pid ! yes, + {keep_state,Data}; +handle_common_events(_, _, _, _) -> + undefined. + +%% Wrapper state machine that uses a map state machine spec +handle_event( + Type, Event, State, [Data|Machine]) + when is_map(Machine) -> + #{State := HandleEvent} = Machine, + case + try HandleEvent(Type, Event, Data) of + Result -> + Result + catch + Result -> + Result + end of + {stop,Reason,NewData} -> + {stop,Reason,[NewData|Machine]}; + {next_state,NewState,NewData} -> + {next_state,NewState,[NewData|Machine]}; + {next_state,NewState,NewData,Ops} -> + {next_state,NewState,[NewData|Machine],Ops}; + {keep_state,NewData} -> + {keep_state,[NewData|Machine]}; + {keep_state,NewData,Ops} -> + {keep_state,[NewData|Machine],Ops}; + Other -> + Other + end; +%% +%% Dispatcher to test callback_mode handle_event_function +%% +%% Wrap the state in a 1 element list just to test non-atom +%% states. Note that the state from init/1 is not wrapped +%% so both atom and non-atom states are tested. +handle_event(Type, Event, State, Data) -> + StateName = unwrap_state(State), + try ?MODULE:StateName(Type, Event, Data) of + Result -> + wrap_result(Result) + catch + throw:Result -> + erlang:raise( + throw, wrap_result(Result), erlang:get_stacktrace()) + end. + +unwrap_state([State]) -> + State; +unwrap_state(State) -> + State. + +wrap_result(Result) -> + case Result of + {next_state,NewState,NewData} -> + {next_state,[NewState],NewData}; + {next_state,NewState,NewData,StateOps} -> + {next_state,[NewState],NewData,StateOps}; + Other -> + Other + end. + + + +code_change(OldVsn, State, Data, CallbackMode) -> + {CallbackMode,State,{OldVsn,Data,CallbackMode}}. + +format_status(terminate, [_Pdict,State,Data]) -> + {formatted,State,Data}; +format_status(normal, [_Pdict,_State,_Data]) -> + [format_status_called]. + +flush() -> + receive + Msg -> + [Msg|flush()] + after 500 -> + [] + end. diff --git a/lib/stdlib/test/rand_SUITE.erl b/lib/stdlib/test/rand_SUITE.erl index 3fd5ed4ccf..1bcdc3ccd0 100644 --- a/lib/stdlib/test/rand_SUITE.erl +++ b/lib/stdlib/test/rand_SUITE.erl @@ -377,7 +377,7 @@ crypto_seed() -> crypto_next(<<Num:64, Bin/binary>>) -> {Num, Bin}; crypto_next(_) -> - crypto_next(crypto:rand_bytes((64 div 8)*100)). + crypto_next(crypto:strong_rand_bytes((64 div 8)*100)). crypto_uniform({Api, Data0}) -> {Int, Data} = crypto_next(Data0), diff --git a/lib/tools/doc/src/erlang_mode.xml b/lib/tools/doc/src/erlang_mode.xml index 0ab272fb2f..00cf5196b4 100644 --- a/lib/tools/doc/src/erlang_mode.xml +++ b/lib/tools/doc/src/erlang_mode.xml @@ -252,6 +252,7 @@ behavior</item> <item>gen_event - skeleton for the OTP gen_event behavior</item> <item>gen_fsm - skeleton for the OTP gen_fsm behavior</item> + <item>gen_statem - skeleton for the OTP gen_statem behavior</item> <item>Library module - skeleton for a module that does not implement a process.</item> <item>Corba callback - skeleton for a Corba callback module.</item> diff --git a/lib/tools/emacs/erlang-skels.el b/lib/tools/emacs/erlang-skels.el index 3bb1dda6db..ce26c83295 100644 --- a/lib/tools/emacs/erlang-skels.el +++ b/lib/tools/emacs/erlang-skels.el @@ -56,6 +56,8 @@ erlang-skel-gen-event erlang-skel-header) ("gen_fsm" "gen-fsm" erlang-skel-gen-fsm erlang-skel-header) + ("gen_statem" "gen-statem" + erlang-skel-gen-statem erlang-skel-header) ("wx_object" "wx-object" erlang-skel-wx-object erlang-skel-header) ("Library module" "gen-lib" @@ -858,6 +860,122 @@ Please see the function `tempo-define-template'.") "*The template of a gen_fsm. Please see the function `tempo-define-template'.") +(defvar erlang-skel-gen-statem + '((erlang-skel-include erlang-skel-large-header) + "-behaviour(gen_statem)." n n + + "%% API" n + "-export([start_link/0])." n + n + "%% gen_statem callbacks" n + "-export([init/1, terminate/3, code_change/4])." n + "-export([state_name/3])." n + "-export([handle_event/4])." n + n + "-define(SERVER, ?MODULE)." n + n + "-record(data, {})." n + n + (erlang-skel-double-separator-start 3) + "%%% API" n + (erlang-skel-double-separator-end 3) n + (erlang-skel-separator-start 2) + "%% @doc" n + "%% Creates a gen_statem process which calls Module:init/1 to" n + "%% initialize. To ensure a synchronized start-up procedure, this" n + "%% function does not return until Module:init/1 has returned." n + "%%" n + (erlang-skel-separator-end 2) + "-spec start_link() ->" n> + "{ok, Pid :: pid()} |" n> + "ignore |" n> + "{error, Error :: term()}." n + "start_link() ->" n> + "gen_statem:start_link({local, ?SERVER}, ?MODULE, [], [])." n + n + (erlang-skel-double-separator-start 3) + "%%% gen_statem callbacks" n + (erlang-skel-double-separator-end 3) n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n + "%% Whenever a gen_statem is started using gen_statem:start/[3,4] or" n + "%% gen_statem:start_link/[3,4], this function is called by the new" n + "%% process to initialize." n + (erlang-skel-separator-end 2) + "-spec init(Args :: term()) -> " n> + "{gen_statem:callback_mode()," n> + "State :: term(), Data :: term()} |" n> + "{gen_statem:callback_mode()," n> + "State :: term(), Data :: term()," n> + "[gen_statem:action()] | gen_statem:action()} |" n> + "ignore |" n> + "{stop, Reason :: term()}." n + "init([]) ->" n> + "{state_functions, state_name, #data{}}." n + n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n + "%% If the gen_statem runs with CallbackMode =:= state_functions" n + "%% there should be one instance of this function for each possible" n + "%% state name. Whenever a gen_statem receives an event," n + "%% the instance of this function with the same name" n + "%% as the current state name StateName is called to" n + "%% handle the event." n + (erlang-skel-separator-end 2) + "-spec state_name(" n> + "gen_statem:event_type(), Msg :: term()," n> + "Data :: term()) ->" n> + "gen_statem:state_function_result(). " n + "state_name({call,Caller}, _Msg, Data) ->" n> + "{next_state, state_name, Data, [{reply,Caller,ok}]}." n + n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n + "%% If the gen_statem runs with CallbackMode =:= handle_event_function" n + "%% this function is called for every event a gen_statem receives." n + (erlang-skel-separator-end 2) + "-spec handle_event(" n> + "gen_statem:event_type(), Msg :: term()," n> + "State :: term(), Data :: term()) ->" n> + "gen_statem:handle_event_result(). " n + "handle_event({call,From}, _Msg, State, Data) ->" n> + "{next_state, State, Data, [{reply,From,ok}]}." n + n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n + "%% This function is called by a gen_statem when it is about to" n + "%% terminate. It should be the opposite of Module:init/1 and do any" n + "%% necessary cleaning up. When it returns, the gen_statem terminates with" n + "%% Reason. The return value is ignored." n + (erlang-skel-separator-end 2) + "-spec terminate(Reason :: term(), State :: term(), Data :: term()) ->" n> + "any()." n + "terminate(_Reason, _State, _Data) ->" n> + "void." n + n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n + "%% Convert process state when code is changed" n + (erlang-skel-separator-end 2) + "-spec code_change(" n> + "OldVsn :: term() | {down,term()}," n> + "State :: term(), Data :: term(), Extra :: term()) ->" n> + "{ok, NewState :: term(), NewData :: term()}." n + "code_change(_OldVsn, State, Data, _Extra) ->" n> + "{ok, State, Data}." n + n + (erlang-skel-double-separator-start 3) + "%%% Internal functions" n + (erlang-skel-double-separator-end 3) + ) + "*The template of a gen_statem. +Please see the function `tempo-define-template'.") + (defvar erlang-skel-wx-object '((erlang-skel-include erlang-skel-large-header) "-behaviour(wx_object)." n n diff --git a/lib/wx/api_gen/wx_extra/wxEvtHandler.c_src b/lib/wx/api_gen/wx_extra/wxEvtHandler.c_src index 08fef1c2ff..b9cb4f08cc 100644 --- a/lib/wx/api_gen/wx_extra/wxEvtHandler.c_src +++ b/lib/wx/api_gen/wx_extra/wxEvtHandler.c_src @@ -43,7 +43,7 @@ case 101: { // wxEvtHandler::Disconnect int eventType = wxeEventTypeFromAtom(bp); bp += *eventTypeLen; if(eventType > 0) { if(recurse_level > 1) { - delayed_delete->Append(Ecmd.Save()); + delayed_delete->Append(Ecmd.Save(op)); } else { bool Result = This->Disconnect((int) *winid,(int) *lastId,eventType, (wxObjectEventFunction)(wxEventFunction) diff --git a/lib/wx/api_gen/wx_gen_cpp.erl b/lib/wx/api_gen/wx_gen_cpp.erl index 07486e801b..55c179142d 100644 --- a/lib/wx/api_gen/wx_gen_cpp.erl +++ b/lib/wx/api_gen/wx_gen_cpp.erl @@ -195,11 +195,13 @@ gen_funcs(Defs) -> w("void WxeApp::wxe_dispatch(wxeCommand& Ecmd)~n{~n"), w(" char * bp = Ecmd.buffer;~n"), + w(" int op = Ecmd.op;~n"), + w(" Ecmd.op = -1;~n"), w(" wxeMemEnv *memenv = getMemEnv(Ecmd.port);~n"), %% w(" wxMBConvUTF32 UTFconverter;~n"), - w(" wxeReturn rt = wxeReturn(WXE_DRV_PORT, Ecmd.caller, true);~n"), + w(" wxeReturn rt = wxeReturn(WXE_DRV_PORT, Ecmd.caller, true);~n"), w(" try {~n"), - w(" switch (Ecmd.op)~n{~n"), + w(" switch (op)~n{~n"), %% w(" case WXE_CREATE_PORT:~n", []), %% w(" { newMemEnv(Ecmd.port); } break;~n", []), %% w(" case WXE_REMOVE_PORT:~n", []), @@ -209,7 +211,7 @@ gen_funcs(Defs) -> w(" wxeRefData *refd = getRefData(This);~n"), w(" if(This && refd) {~n"), w(" if(recurse_level > 1 && refd->type != 4) {~n"), - w(" delayed_delete->Append(Ecmd.Save());~n"), + w(" delayed_delete->Append(Ecmd.Save(op));~n"), w(" } else {~n"), w(" delete_object(This, refd);~n"), w(" ((WxeApp *) wxTheApp)->clearPtr(This);}~n"), @@ -228,7 +230,7 @@ gen_funcs(Defs) -> w(" default: {~n"), w(" wxeReturn error = wxeReturn(WXE_DRV_PORT, Ecmd.caller, false);"), w(" error.addAtom(\"_wxe_error_\");~n"), - w(" error.addInt((int) Ecmd.op);~n"), + w(" error.addInt((int) op);~n"), w(" error.addAtom(\"not_supported\");~n"), w(" error.addTupleCount(3);~n"), w(" error.send();~n"), @@ -239,7 +241,7 @@ gen_funcs(Defs) -> w("} catch (wxe_badarg badarg) { // try~n"), w(" wxeReturn error = wxeReturn(WXE_DRV_PORT, Ecmd.caller, false);"), w(" error.addAtom(\"_wxe_error_\");~n"), - w(" error.addInt((int) Ecmd.op);~n"), + w(" error.addInt((int) op);~n"), w(" error.addAtom(\"badarg\");~n"), w(" error.addInt((int) badarg.ref);~n"), w(" error.addTupleCount(2);~n"), diff --git a/lib/wx/c_src/gen/wxe_funcs.cpp b/lib/wx/c_src/gen/wxe_funcs.cpp index 059cee59f4..283e97f4e2 100644 --- a/lib/wx/c_src/gen/wxe_funcs.cpp +++ b/lib/wx/c_src/gen/wxe_funcs.cpp @@ -40,17 +40,19 @@ void WxeApp::wxe_dispatch(wxeCommand& Ecmd) { char * bp = Ecmd.buffer; + int op = Ecmd.op; + Ecmd.op = -1; wxeMemEnv *memenv = getMemEnv(Ecmd.port); - wxeReturn rt = wxeReturn(WXE_DRV_PORT, Ecmd.caller, true); + wxeReturn rt = wxeReturn(WXE_DRV_PORT, Ecmd.caller, true); try { - switch (Ecmd.op) + switch (op) { case DESTROY_OBJECT: { void *This = getPtr(bp,memenv); wxeRefData *refd = getRefData(This); if(This && refd) { if(recurse_level > 1 && refd->type != 4) { - delayed_delete->Append(Ecmd.Save()); + delayed_delete->Append(Ecmd.Save(op)); } else { delete_object(This, refd); ((WxeApp *) wxTheApp)->clearPtr(This);} @@ -114,7 +116,7 @@ case 101: { // wxEvtHandler::Disconnect int eventType = wxeEventTypeFromAtom(bp); bp += *eventTypeLen; if(eventType > 0) { if(recurse_level > 1) { - delayed_delete->Append(Ecmd.Save()); + delayed_delete->Append(Ecmd.Save(op)); } else { bool Result = This->Disconnect((int) *winid,(int) *lastId,eventType, (wxObjectEventFunction)(wxEventFunction) @@ -32077,7 +32079,7 @@ case wxDCOverlay_Clear: { // wxDCOverlay::Clear } default: { wxeReturn error = wxeReturn(WXE_DRV_PORT, Ecmd.caller, false); error.addAtom("_wxe_error_"); - error.addInt((int) Ecmd.op); + error.addInt((int) op); error.addAtom("not_supported"); error.addTupleCount(3); error.send(); @@ -32087,7 +32089,7 @@ case wxDCOverlay_Clear: { // wxDCOverlay::Clear rt.send(); } catch (wxe_badarg badarg) { // try wxeReturn error = wxeReturn(WXE_DRV_PORT, Ecmd.caller, false); error.addAtom("_wxe_error_"); - error.addInt((int) Ecmd.op); + error.addInt((int) op); error.addAtom("badarg"); error.addInt((int) badarg.ref); error.addTupleCount(2); diff --git a/lib/wx/c_src/wxe_helpers.cpp b/lib/wx/c_src/wxe_helpers.cpp index 76958a346f..4798e605e8 100644 --- a/lib/wx/c_src/wxe_helpers.cpp +++ b/lib/wx/c_src/wxe_helpers.cpp @@ -47,8 +47,8 @@ void wxeCommand::Delete() if(len > 64) driver_free(buffer); buffer = NULL; - op = -1; } + op = -1; } /* **************************************************************************** @@ -226,7 +226,7 @@ unsigned int wxeFifo::Cleanup(unsigned int def) // Realloced we need to start from the beginning return 0; } else { - return def; + return def < cb_start? def : cb_start; } } diff --git a/lib/wx/c_src/wxe_helpers.h b/lib/wx/c_src/wxe_helpers.h index 3f66b6d97a..70ffccdc13 100644 --- a/lib/wx/c_src/wxe_helpers.h +++ b/lib/wx/c_src/wxe_helpers.h @@ -46,7 +46,7 @@ class wxeCommand wxeCommand(); virtual ~wxeCommand(); // Use Delete() - wxeCommand * Save() { return this; }; + wxeCommand * Save(int Op) { op = Op; return this; }; void Delete(); ErlDrvTermData caller; diff --git a/lib/wx/c_src/wxe_impl.cpp b/lib/wx/c_src/wxe_impl.cpp index f899839782..175bcfce54 100644 --- a/lib/wx/c_src/wxe_impl.cpp +++ b/lib/wx/c_src/wxe_impl.cpp @@ -238,9 +238,10 @@ void WxeApp::dispatch_cmds() if(wxe_status != WXE_INITIATED) return; recurse_level++; - // fprintf(stderr, "\r\ndispatch_normal %d\r\n", level);fflush(stderr); + // fprintf(stderr, "\r\ndispatch_normal %d\r\n", recurse_level);fflush(stderr); + wxe_queue->cb_start = 0; dispatch(wxe_queue); - // fprintf(stderr, "\r\ndispatch_done \r\n");fflush(stderr); + // fprintf(stderr, "\r\ndispatch_done %d\r\n", recurse_level);fflush(stderr); recurse_level--; // Cleanup old memenv's and deleted objects diff --git a/lib/wx/src/wx.erl b/lib/wx/src/wx.erl index a1a9344316..6498b58cda 100644 --- a/lib/wx/src/wx.erl +++ b/lib/wx/src/wx.erl @@ -66,7 +66,7 @@ get_env/0,set_env/1, debug/1, batch/1,foreach/2,map/2,foldl/3,foldr/3, getObjectType/1, typeCast/2, - null/0, is_null/1]). + null/0, is_null/1, equal/2]). -export([create_memory/1, get_memory_bin/1, retain_memory/1, release_memory/1]). @@ -153,6 +153,10 @@ null() -> -spec is_null(wx_object()) -> boolean(). is_null(#wx_ref{ref=NULL}) -> NULL =:= 0. +%% @doc Returns true if both arguments references the same object, false otherwise +-spec equal(wx_object(), wx_object()) -> boolean(). +equal(#wx_ref{ref=Ref1}, #wx_ref{ref=Ref2}) -> Ref1 =:= Ref2. + %% @doc Returns the object type -spec getObjectType(wx_object()) -> atom(). getObjectType(#wx_ref{type=Type}) -> diff --git a/lib/wx/src/wx_object.erl b/lib/wx/src/wx_object.erl index a2acbb0a63..c22a083eb9 100644 --- a/lib/wx/src/wx_object.erl +++ b/lib/wx/src/wx_object.erl @@ -107,7 +107,8 @@ call/2, call/3, cast/2, reply/2, - get_pid/1 + get_pid/1, + set_pid/2 ]). %% -export([behaviour_info/1]). @@ -306,6 +307,11 @@ cast(Name, Request) when is_atom(Name) orelse is_pid(Name) -> get_pid(#wx_ref{state=Pid}) when is_pid(Pid) -> Pid. +%% @spec (Ref::wxObject(), pid()) -> wxObject() +%% @doc Sets the controlling process of the object handle. +set_pid(#wx_ref{}=R, Pid) when is_pid(Pid) -> + R#wx_ref{state=Pid}. + %% ----------------------------------------------------------------- %% Send a reply to the client. %% ----------------------------------------------------------------- diff --git a/lib/wx/test/wx_app_SUITE.erl b/lib/wx/test/wx_app_SUITE.erl index 0b885a78b8..3fd5bf689d 100644 --- a/lib/wx/test/wx_app_SUITE.erl +++ b/lib/wx/test/wx_app_SUITE.erl @@ -49,7 +49,7 @@ end_per_testcase(Func,Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,5}}]. all() -> [fields, modules, exportall, app_depend, undef_funcs, appup]. @@ -221,12 +221,10 @@ check_apps([App|Apps]) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -undef_funcs(suite) -> - []; -undef_funcs(doc) -> - []; +undef_funcs() -> + [{timetrap,{minutes,10}}]. + undef_funcs(Config) when is_list(Config) -> - catch test_server:timetrap(timer:minutes(10)), App = wx, AppFile = key1search(app_file, Config), Mods = key1search(modules, AppFile), diff --git a/lib/wx/test/wx_basic_SUITE.erl b/lib/wx/test/wx_basic_SUITE.erl index 5dffdea6be..f89f25274a 100644 --- a/lib/wx/test/wx_basic_SUITE.erl +++ b/lib/wx/test/wx_basic_SUITE.erl @@ -45,7 +45,7 @@ end_per_testcase(Func,Config) -> wx_test_lib:end_per_testcase(Func,Config). %% SUITE specification -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,2}}]. all() -> [silent_start, create_window, several_apps, wx_api, wx_misc, @@ -344,13 +344,13 @@ data_types(_Config) -> ImgRGB = ?mt(wxImage, wxImage:new(128, 64, Colors)), ?m(true, wxImage:ok(ImgRGB)), ?m(false, wxImage:hasAlpha(ImgRGB)), - ?m(Colors, wxImage:getData(ImgRGB)), + ?m(ok, case wxImage:getData(ImgRGB) of Colors -> ok; Other -> Other end), ImgRGBA = ?mt(wxImage, wxImage:new(128, 64, Colors, Alpha)), ?m(true, wxImage:ok(ImgRGBA)), ?m(true, wxImage:hasAlpha(ImgRGBA)), - ?m(Colors, wxImage:getData(ImgRGBA)), - ?m(Alpha, wxImage:getAlpha(ImgRGBA)), + ?m(ok, case wxImage:getData(ImgRGBA) of Colors -> ok; Other -> Other end), + ?m(ok, case wxImage:getAlpha(ImgRGBA) of Alpha -> ok; Other -> Other end), wxClientDC:destroy(CDC), %%wx_test_lib:wx_destroy(Frame,Config). @@ -361,7 +361,8 @@ wx_object(Config) -> wx:new(), Me = self(), Init = fun() -> - Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Test wx_object", [{size, {500, 400}}]), + Frame0 = wxFrame:new(wx:null(), ?wxID_ANY, "Test wx_object", [{size, {500, 400}}]), + Frame = wx_object:set_pid(Frame0, self()), Sz = wxBoxSizer:new(?wxHORIZONTAL), Panel = wxPanel:new(Frame), wxSizer:add(Sz, Panel, [{flag, ?wxEXPAND}, {proportion, 1}]), @@ -371,6 +372,7 @@ wx_object(Config) -> {Frame, {Frame, Panel}} end, Frame = ?mt(wxFrame, wx_obj_test:start([{init, Init}])), + timer:sleep(500), ?m(ok, check_events(flush())), @@ -378,6 +380,11 @@ wx_object(Config) -> ?m({call, foobar, {Me, _}}, wx_object:call(Frame, foobar)), ?m(ok, wx_object:cast(Frame, foobar2)), ?m([{cast, foobar2}|_], flush()), + + ?m(Frame, wx_obj_test:who_are_you(Frame)), + {call, {Frame,Panel}, _} = wx_object:call(Frame, fun(US) -> US end), + ?m(false, wxWindow:getParent(Panel) =:= Frame), + ?m(true, wx:equal(wxWindow:getParent(Panel),Frame)), FramePid = wx_object:get_pid(Frame), io:format("wx_object pid ~p~n",[FramePid]), FramePid ! foo3, diff --git a/lib/wx/test/wx_class_SUITE.erl b/lib/wx/test/wx_class_SUITE.erl index e23fd901f5..f88c10f987 100644 --- a/lib/wx/test/wx_class_SUITE.erl +++ b/lib/wx/test/wx_class_SUITE.erl @@ -46,12 +46,12 @@ end_per_testcase(Func,Config) -> wx_test_lib:end_per_testcase(Func,Config). %% SUITE specification -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,2}}]. all() -> [calendarCtrl, treeCtrl, notebook, staticBoxSizer, clipboard, helpFrame, htmlWindow, listCtrlSort, listCtrlVirtual, - radioBox, systemSettings, taskBarIcon, toolbar, popup]. + radioBox, systemSettings, taskBarIcon, toolbar, popup, modal]. groups() -> []. @@ -621,3 +621,67 @@ lang_env() -> format_env({match, List}) -> [io:format(" ~ts~n",[L]) || L <- List]; format_env(nomatch) -> ok. + +%% Add a testcase that tests that we can recurse in showModal +%% because it hangs in observer if object are not destroyed correctly +%% when popping the stack + +modal(Config) -> + Wx = wx:new(), + case {?wxMAJOR_VERSION, ?wxMINOR_VERSION, ?wxRELEASE_NUMBER} of + {2, Min, Rel} when Min < 8 orelse (Min =:= 8 andalso Rel < 11) -> + {skip, "old wxWidgets version"}; + _ -> + Frame = wxFrame:new(Wx, -1, "Test Modal windows"), + wxFrame:show(Frame), + Env = wx:get_env(), + Tester = self(), + ets:new(test_state, [named_table, public]), + Upd = wxUpdateUIEvent:getUpdateInterval(), + wxUpdateUIEvent:setUpdateInterval(500), + _Pid = spawn(fun() -> + wx:set_env(Env), + modal_dialog(Frame, 1, Tester) + end), + receive {dialog, M1, 1} -> timer:sleep(200), ets:insert(test_state, {M1, ready}) end, + receive {dialog, M2, 2} -> timer:sleep(200), ets:insert(test_state, {M2, ready}) end, + + receive done -> ok end, + receive {dialog_done, M2, 2} -> M2 end, + receive {dialog_done, M1, 1} -> M1 end, + + wxUpdateUIEvent:setUpdateInterval(Upd), + wx_test_lib:wx_destroy(Frame,Config) + end. + +modal_dialog(Parent, Level, Tester) when Level < 3 -> + M1 = wxTextEntryDialog:new(Parent, "Dialog " ++ integer_to_list(Level)), + io:format("Creating dialog ~p ~p~n",[Level, M1]), + wxDialog:connect(M1, show, [{callback, fun(#wx{event=Ev},_) -> + case Ev of + #wxShow{show=true} -> + Tester ! {dialog, M1, Level}; + _ -> ignore + end + end}]), + DoOnce = fun(_,_) -> + case ets:take(test_state, M1) of + [] -> ignore; + [_] -> modal_dialog(M1, Level+1, Tester) + end + end, + wxDialog:connect(M1, update_ui, [{callback, DoOnce}]), + ?wxID_OK = wxDialog:showModal(M1), + wxDialog:destroy(M1), + case Level > 1 of + true -> + io:format("~p: End dialog ~p ~p~n",[?LINE, Level-1, Parent]), + wxDialog:endModal(Parent, ?wxID_OK); + false -> ok + end, + Tester ! {dialog_done, M1, Level}, + ok; +modal_dialog(Parent, Level, Tester) -> + io:format("~p: End dialog ~p ~p~n",[?LINE, Level-1, Parent]), + wxDialog:endModal(Parent, ?wxID_OK), + Tester ! done. diff --git a/lib/wx/test/wx_event_SUITE.erl b/lib/wx/test/wx_event_SUITE.erl index 62fcf44033..6512cedaf2 100644 --- a/lib/wx/test/wx_event_SUITE.erl +++ b/lib/wx/test/wx_event_SUITE.erl @@ -44,7 +44,7 @@ end_per_testcase(Func,Config) -> wx_test_lib:end_per_testcase(Func,Config). %% SUITE specification -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,2}}]. all() -> [connect, disconnect, disconnect_cb, connect_msg_20, connect_cb_20, diff --git a/lib/wx/test/wx_obj_test.erl b/lib/wx/test/wx_obj_test.erl index cf99728c1a..23142e28b2 100644 --- a/lib/wx/test/wx_obj_test.erl +++ b/lib/wx/test/wx_obj_test.erl @@ -19,13 +19,13 @@ -module(wx_obj_test). -include_lib("wx/include/wx.hrl"). --export([start/1, stop/1]). +-export([start/1, stop/1, who_are_you/1]). %% wx_object callbacks -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, handle_sync_event/3, handle_event/2, handle_cast/2]). --record(state, {parent, opts, user_state}). +-record(state, {parent, me, opts, user_state}). start(Opts) -> wx_object:start_link(?MODULE, [{parent, self()}| Opts], []). @@ -33,12 +33,15 @@ start(Opts) -> stop(Object) -> wx_object:stop(Object). +who_are_you(Object) -> + wx_object:call(Object, who_are_you). + init(Opts) -> Parent = proplists:get_value(parent, Opts), put(parent_pid, Parent), Init = proplists:get_value(init, Opts), {Obj, UserState} = Init(), - {Obj, #state{parent=Parent, opts=Opts, user_state=UserState}}. + {Obj, #state{me=Obj, parent=Parent, opts=Opts, user_state=UserState}}. handle_sync_event(Event = #wx{obj=Panel, event=#wxPaint{}}, WxEvent, #state{parent=Parent, user_state=US, opts=Opts}) -> @@ -59,6 +62,8 @@ handle_event(Event, State = #state{parent=Parent}) -> Parent ! {event, Event}, {noreply, State}. +handle_call(who_are_you, _From, State = #state{me=Me}) -> + {reply, Me, State}; handle_call(What, From, State = #state{user_state=US}) when is_function(What) -> Result = What(US), {reply, {call, Result, From}, State}; diff --git a/lib/wx/test/wx_opengl_SUITE.erl b/lib/wx/test/wx_opengl_SUITE.erl index 5162078dbf..643a0df6a3 100644 --- a/lib/wx/test/wx_opengl_SUITE.erl +++ b/lib/wx/test/wx_opengl_SUITE.erl @@ -52,7 +52,7 @@ end_per_testcase(Func,Config) -> wx_test_lib:end_per_testcase(Func,Config). %% SUITE specification -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,2}}]. all() -> [canvas, glu_tesselation]. diff --git a/lib/wx/test/wx_xtra_SUITE.erl b/lib/wx/test/wx_xtra_SUITE.erl index 7aba17ee74..c6268a7f46 100644 --- a/lib/wx/test/wx_xtra_SUITE.erl +++ b/lib/wx/test/wx_xtra_SUITE.erl @@ -45,7 +45,7 @@ end_per_testcase(Func,Config) -> wx_test_lib:end_per_testcase(Func,Config). %% SUITE specification -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,2}}]. all() -> [destroy_app, multiple_add_in_sizer, app_dies, diff --git a/lib/wx/test/wxt.erl b/lib/wx/test/wxt.erl index fc828e47e8..265cd5c981 100644 --- a/lib/wx/test/wxt.erl +++ b/lib/wx/test/wxt.erl @@ -16,13 +16,9 @@ %% limitations under the License. %% %% %CopyrightEnd% -%%%------------------------------------------------------------------- -%%% File : wxt.erl -%%% Author : Dan Gudmundsson <[email protected]> -%%% Description : Shortcuts for starting test with wx internal test_server -%%% -%%% Created : 4 Nov 2008 by Dan Gudmundsson <[email protected]> -%%%------------------------------------------------------------------- +%% +%% Description : Shortcuts for running tests with wx internal test_server +%%------------------------------------------------------------------- -module(wxt). -compile(export_all). @@ -40,7 +36,7 @@ t(Mod, TC) when is_atom(Mod), is_atom(TC) -> t({Mod,TC}, []); t(all, Config) when is_list(Config) -> Fs = filelib:wildcard("wx_*_SUITE.erl"), - t([list_to_atom(filename:rootname(File)) || File <- Fs], Config); + t([list_to_atom(filename:rootname(File)) || File <- Fs, File =/= "wx_app_SUITE.erl"], Config); t(Test,Config) when is_list(Config) -> Tests = resolve(Test), write_test_case(Test), diff --git a/otp_versions.table b/otp_versions.table index f339eab796..79e1c14c49 100644 --- a/otp_versions.table +++ b/otp_versions.table @@ -1,3 +1,4 @@ +OTP-18.3.2 : inets-6.2.2 ssl-7.3.1 # asn1-4.0.2 common_test-1.12 compiler-6.0.3 cosEvent-2.2 cosEventDomain-1.2 cosFileTransfer-1.2 cosNotification-1.2.1 cosProperty-1.2 cosTime-1.2.1 cosTransactions-1.3.1 crypto-3.6.3 debugger-4.1.2 dialyzer-2.9 diameter-1.11.2 edoc-0.7.18 eldap-1.2.1 erl_docgen-0.4.2 erl_interface-3.8.2 erts-7.3.1 et-1.5.1 eunit-2.2.13 gs-1.6 hipe-3.15 ic-4.4 jinterface-1.6.1 kernel-4.2 megaco-3.18 mnesia-4.13.4 observer-2.1.2 odbc-2.11.1 orber-3.8.1 os_mon-2.4 ose-1.1 otp_mibs-1.1 parsetools-2.1.1 percept-0.8.11 public_key-1.1.1 reltool-0.7 runtime_tools-1.9.3 sasl-2.7 snmp-5.2.2 ssh-4.2.2 stdlib-2.8 syntax_tools-1.7 test_server-3.10 tools-2.8.3 typer-0.9.10 webtool-0.9.1 wx-1.6.1 xmerl-1.3.10 : OTP-18.3.1 : erts-7.3.1 inets-6.2.1 mnesia-4.13.4 # asn1-4.0.2 common_test-1.12 compiler-6.0.3 cosEvent-2.2 cosEventDomain-1.2 cosFileTransfer-1.2 cosNotification-1.2.1 cosProperty-1.2 cosTime-1.2.1 cosTransactions-1.3.1 crypto-3.6.3 debugger-4.1.2 dialyzer-2.9 diameter-1.11.2 edoc-0.7.18 eldap-1.2.1 erl_docgen-0.4.2 erl_interface-3.8.2 et-1.5.1 eunit-2.2.13 gs-1.6 hipe-3.15 ic-4.4 jinterface-1.6.1 kernel-4.2 megaco-3.18 observer-2.1.2 odbc-2.11.1 orber-3.8.1 os_mon-2.4 ose-1.1 otp_mibs-1.1 parsetools-2.1.1 percept-0.8.11 public_key-1.1.1 reltool-0.7 runtime_tools-1.9.3 sasl-2.7 snmp-5.2.2 ssh-4.2.2 ssl-7.3 stdlib-2.8 syntax_tools-1.7 test_server-3.10 tools-2.8.3 typer-0.9.10 webtool-0.9.1 wx-1.6.1 xmerl-1.3.10 : OTP-18.3 : asn1-4.0.2 common_test-1.12 compiler-6.0.3 cosNotification-1.2.1 cosTime-1.2.1 cosTransactions-1.3.1 crypto-3.6.3 debugger-4.1.2 dialyzer-2.9 diameter-1.11.2 edoc-0.7.18 eldap-1.2.1 erl_docgen-0.4.2 erl_interface-3.8.2 erts-7.3 eunit-2.2.13 hipe-3.15 inets-6.2 kernel-4.2 mnesia-4.13.3 observer-2.1.2 orber-3.8.1 public_key-1.1.1 runtime_tools-1.9.3 sasl-2.7 snmp-5.2.2 ssh-4.2.2 ssl-7.3 stdlib-2.8 test_server-3.10 tools-2.8.3 webtool-0.9.1 wx-1.6.1 xmerl-1.3.10 # cosEvent-2.2 cosEventDomain-1.2 cosFileTransfer-1.2 cosProperty-1.2 et-1.5.1 gs-1.6 ic-4.4 jinterface-1.6.1 megaco-3.18 odbc-2.11.1 os_mon-2.4 ose-1.1 otp_mibs-1.1 parsetools-2.1.1 percept-0.8.11 reltool-0.7 syntax_tools-1.7 typer-0.9.10 : OTP-18.2.4 : common_test-1.11.2 # asn1-4.0.1 compiler-6.0.2 cosEvent-2.2 cosEventDomain-1.2 cosFileTransfer-1.2 cosNotification-1.2 cosProperty-1.2 cosTime-1.2 cosTransactions-1.3 crypto-3.6.2 debugger-4.1.1 dialyzer-2.8.2 diameter-1.11.1 edoc-0.7.17 eldap-1.2 erl_docgen-0.4.1 erl_interface-3.8.1 erts-7.2.1 et-1.5.1 eunit-2.2.12 gs-1.6 hipe-3.14 ic-4.4 inets-6.1.1 jinterface-1.6.1 kernel-4.1.1 megaco-3.18 mnesia-4.13.2 observer-2.1.1 odbc-2.11.1 orber-3.8 os_mon-2.4 ose-1.1 otp_mibs-1.1 parsetools-2.1.1 percept-0.8.11 public_key-1.1 reltool-0.7 runtime_tools-1.9.2 sasl-2.6.1 snmp-5.2.1 ssh-4.2.1 ssl-7.2 stdlib-2.7 syntax_tools-1.7 test_server-3.9.1 tools-2.8.2 typer-0.9.10 webtool-0.9 wx-1.6 xmerl-1.3.9 : diff --git a/system/doc/definitions/term.defs b/system/doc/definitions/term.defs index 6091a46a20..921175a7f0 100644 --- a/system/doc/definitions/term.defs +++ b/system/doc/definitions/term.defs @@ -76,6 +76,7 @@ the module Erlang in the application kernel","kenneth"}, {"gen_event","gen_event","A behaviour used for programming event handling mechanisms, such as alarm handlers, error loggers, and plug-and-play handlers.","mbj"}, {"gen_fsm","gen_fsm","A behaviour used for programming finite state machines.","mbj"}, {"gen_server","gen_server","A behaviour used for programming client-server processes.","mbj"}, +{"gen_statem","gen_statem","A behaviour used for programming generic state machines.","raimo"}, {"gterm","Global Glossary Database","A glossary database used to list common acronymns and defintions etc.","jocke"}, {"xref","xref","A cross reference tool that can be used for finding dependencies between functions, modules, applications and releases. Part of the Tools application.","gunilla"}, {"GSlong","Graphics System","A library module which provides a graphics interface for Erlang.","mbj"}, diff --git a/system/doc/design_principles/Makefile b/system/doc/design_principles/Makefile index 653072bc65..937b3e28c8 100644 --- a/system/doc/design_principles/Makefile +++ b/system/doc/design_principles/Makefile @@ -58,6 +58,12 @@ GIF_FILES = \ sup5.gif \ sup6.gif +PNG_FILES = \ + code_lock.png \ + code_lock_2.png + +IMAGE_FILES = $(GIF_FILES) $(PNG_FILES) + XML_FILES = \ $(BOOK_FILES) $(XML_CHAPTER_FILES) \ $(XML_PART_FILES) @@ -85,13 +91,16 @@ _create_dirs := $(shell mkdir -p $(HTMLDIR)) $(HTMLDIR)/%.gif: %.gif $(INSTALL_DATA) $< $@ +$(HTMLDIR)/%.png: %.png + $(INSTALL_DATA) $< $@ + docs: html local_docs: PDFDIR=../../pdf -html: $(HTML_UG_FILE) gifs +html: $(HTML_UG_FILE) images -gifs: $(GIF_FILES:%=$(HTMLDIR)/%) +images: $(IMAGE_FILES:%=$(HTMLDIR)/%) debug opt: @@ -109,7 +118,7 @@ release_docs_spec: docs # $(INSTALL_DIR) "$(RELEASE_PATH)/pdf" # $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELEASE_PATH)/pdf" $(INSTALL_DIR) $(RELSYSDIR) - $(INSTALL_DATA) $(GIF_FILES) $(HTMLDIR)/*.html \ + $(INSTALL_DATA) $(IMAGE_FILES) $(HTMLDIR)/*.html \ $(RELSYSDIR) diff --git a/system/doc/design_principles/appup_cookbook.xml b/system/doc/design_principles/appup_cookbook.xml index 417b482fba..4f23c42c59 100644 --- a/system/doc/design_principles/appup_cookbook.xml +++ b/system/doc/design_principles/appup_cookbook.xml @@ -50,7 +50,8 @@ <p>In a system implemented according to the OTP design principles, all processes, except system processes and special processes, reside in one of the behaviours <c>supervisor</c>, - <c>gen_server</c>, <c>gen_fsm</c>, or <c>gen_event</c>. These + <c>gen_server</c>, <c>gen_fsm</c>, + <c>gen_statem</c> or <c>gen_event</c>. These belong to the STDLIB application and upgrading/downgrading normally requires an emulator restart.</p> <p>OTP thus provides no support for changing residence modules except diff --git a/system/doc/design_principles/code_lock.dia b/system/doc/design_principles/code_lock.dia Binary files differnew file mode 100644 index 0000000000..bed6d8ee86 --- /dev/null +++ b/system/doc/design_principles/code_lock.dia diff --git a/system/doc/design_principles/code_lock.png b/system/doc/design_principles/code_lock.png Binary files differnew file mode 100644 index 0000000000..e40f0320aa --- /dev/null +++ b/system/doc/design_principles/code_lock.png diff --git a/system/doc/design_principles/code_lock_2.dia b/system/doc/design_principles/code_lock_2.dia Binary files differnew file mode 100644 index 0000000000..4e82a9e1d6 --- /dev/null +++ b/system/doc/design_principles/code_lock_2.dia diff --git a/system/doc/design_principles/code_lock_2.png b/system/doc/design_principles/code_lock_2.png Binary files differnew file mode 100644 index 0000000000..138fbdef6c --- /dev/null +++ b/system/doc/design_principles/code_lock_2.png diff --git a/system/doc/design_principles/des_princ.xml b/system/doc/design_principles/des_princ.xml index 0cf9f28bdc..8ab8661c2d 100644 --- a/system/doc/design_principles/des_princ.xml +++ b/system/doc/design_principles/des_princ.xml @@ -226,7 +226,9 @@ free(Ch, {Alloc, Free} = Channels) -> <item><p><seealso marker="gen_server_concepts">gen_server</seealso></p> <p>For implementing the server of a client-server relation</p></item> <item><p><seealso marker="fsm">gen_fsm</seealso></p> - <p>For implementing finite-state machines</p></item> + <p>For implementing finite-state machines (Old)</p></item> + <item><p><seealso marker="statem">gen_statem</seealso></p> + <p>For implementing state machines (New)</p></item> <item><p><seealso marker="events">gen_event</seealso></p> <p>For implementing event handling functionality</p></item> <item><p><seealso marker="sup_princ">supervisor</seealso></p> diff --git a/system/doc/design_principles/fsm.xml b/system/doc/design_principles/fsm.xml index f20d20fb7e..3468f93ae0 100644 --- a/system/doc/design_principles/fsm.xml +++ b/system/doc/design_principles/fsm.xml @@ -30,6 +30,16 @@ <file>fsm.xml</file> </header> <marker id="gen_fsm behaviour"></marker> + <note> + <p> + There is a new behaviour + <seealso marker="gen_statem"><c>gen_statem</c></seealso> + that is intended to replace <c>gen_fsm</c> for new code. + It has the same features and add some really useful. + This module will not be removed for the foreseeable future + to keep old state machine implementations running. + </p> + </note> <p>This section is to be read with the <c>gen_fsm(3)</c> manual page in STDLIB, where all interface functions and callback functions are described in detail.</p> diff --git a/system/doc/design_principles/part.xml b/system/doc/design_principles/part.xml index 7862a2d650..6495211e04 100644 --- a/system/doc/design_principles/part.xml +++ b/system/doc/design_principles/part.xml @@ -31,6 +31,7 @@ <xi:include href="des_princ.xml"/> <xi:include href="gen_server_concepts.xml"/> <xi:include href="fsm.xml"/> + <xi:include href="statem.xml"/> <xi:include href="events.xml"/> <xi:include href="sup_princ.xml"/> <xi:include href="spec_proc.xml"/> diff --git a/system/doc/design_principles/release_handling.xml b/system/doc/design_principles/release_handling.xml index c38cca248f..4f71ad4437 100644 --- a/system/doc/design_principles/release_handling.xml +++ b/system/doc/design_principles/release_handling.xml @@ -249,7 +249,7 @@ <p>If <c>Modules=dynamic</c>, which is the case for event managers, the event manager process informs the release handler about the list of currently installed event handlers - (<c>gen_fsm</c>), and it is checked if the module name is in + (<c>gen_event</c>), and it is checked if the module name is in this list instead.</p> <p>The release handler suspends, asks for code change, and resumes processes by calling the functions diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml new file mode 100644 index 0000000000..ca0fce55e2 --- /dev/null +++ b/system/doc/design_principles/statem.xml @@ -0,0 +1,1522 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2016</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + </legalnotice> + + <title>gen_statem Behaviour</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + <file>statem.xml</file> + </header> + <marker id="gen_statem behaviour"></marker> + <p> + This section is to be read with the + <seealso marker="stdlib:gen_statem"><c>gen_statem(3)</c></seealso> + manual page in STDLIB, where all interface functions and callback + functions are described in detail. + </p> + <p> + This is a new behaviour in OTP-19.0. + It has been thoroughly reviewed, is stable enough + to be used by at least two heavy OTP applications, and is here to stay. + But depending on user feedback, we do not expect + but might find it necessary to make minor + not backwards compatible changes into OTP-20.0, + so its state can be designated as "not quite experimental"... + </p> + +<!-- =================================================================== --> + + <section> + <title>Event Driven State Machines</title> + <p> + Established Automata theory does not deal much with + how a state transition is triggered, + but in general assumes that the output is a function + of the input (and the state) and that they are + some kind of values. + </p> + <p> + For an Event Driven State Machine the input is an event + that triggers a state transition and the output + is actions executed during the state transition. + It can analogously to the mathematical model of a + Finite State Machine be described as + a set of relations of the form: + </p> + <pre> +State(S) x Event(E) -> Actions(A), State(S')</pre> + <p>These relations are interpreted as meaning:</p> + <p> + If we are in state <c>S</c> and event <c>E</c> occurs, we + are to perform actions <c>A</c> and make a transition to + state <c>S'</c>. + </p> + <p> + Note that <c>S'</c> may be equal to <c>S</c>. + </p> + <p> + Since <c>A</c> and <c>S'</c> depend only on + <c>S</c> and <c>E</c> the kind of state machine described + here is a Mealy Machine. + (See for example the corresponding Wikipedia article) + </p> + <p> + Like most <c>gen_</c> behaviours, <c>gen_statem</c> keeps + a server <c>Data</c> besides the state. This and the fact that + there is no restriction on the number of states + (assuming enough virtual machine memory) + or on the number of distinct input events actually makes + a state machine implemented with this behaviour Turing complete. + But it feels mostly like an Event Driven Mealy Machine. + </p> + </section> + +<!-- =================================================================== --> + + <section> + <marker id="callback_modes" /> + <title>Callback Modes</title> + <p> + The <c>gen_statem</c> behaviour supports two different callback modes. + In the mode + <seealso marker="stdlib:gen_statem#type-callback_mode"> + <c>state_functions</c>, + </seealso> + the state transition rules are written as a number of Erlang + functions, which conform to the following convention: + </p> + <pre> +StateName(EventType, EventContent, Data) -> + .. code for actions here ... + {next_state, NewStateName, NewData}.</pre> + <p> + In the mode + <seealso marker="stdlib:gen_statem#type-callback_mode"> + <c>handle_event_function</c> + </seealso> + there is only one + Erlang function that implements all state transition rules: + </p> + <pre> +handle_event(EventType, EventContent, State, Data) -> + .. code for actions here ... + {next_state, State', Data'}</pre> + <p> + Both these modes allow other return tuples + that you can find in the + <seealso marker="stdlib:gen_statem#Module:StateName/3"> + reference manual. + </seealso> + These other return tuples can for example stop the machine, + execute state transition actions on the machine engine itself + and send replies. + </p> + + <section> + <title>Choosing the Callback Mode</title> + <p> + The two + <seealso marker="#callback_modes">callback modes</seealso> + gives different possibilities + and restrictions, but one goal remains: + you want to handle all possible combinations of + events and states. + </p> + <p> + You can for example do this by focusing on one state at the time + and for every state ensure that all events are handled, + or the other way around focus on one event at the time + and ensure that it is handled in every state, + or mix these strategies. + </p> + <p> + With <c>state_functions</c> you are restricted to use + atom only states, and the <c>gen_statem</c> engine dispatches + on state name for you. This encourages the callback module + to gather the implementation of all event actions particular + to one state in the same place in the code + hence to focus on one state at the time. + </p> + <p> + This mode fits well when you have a regular state diagram + like the ones in this chapter that describes all events and actions + belonging to a state visually around that state, + and each state has its unique name. + </p> + <p> + With <c>handle_event_function</c> you are free to mix strategies + as you like because all events and states + are handled in the the same callback function. + </p> + <p> + This mode works equally well when you want to focus on + one event at the time or when you want to focus on + one state at the time, but the <c>handle_event/4</c> function + quickly grows too large to handle without introducing dispatching. + </p> + <p> + The mode enables the use of non-atom states for example + complex states or even hiearchical states. + If, for example, a state diagram is largely alike + for the client and for the server side of a protocol; + then you can have a state <c>{StateName,server}</c> or + <c>{StateName,client}</c> and since you do the dispatching + yourself you make <c>StateName</c> decide where in the code + to handle most events in the state. + The second element of the tuple is then used to select + whether to handle special client side or server side events. + </p> + </section> + </section> + +<!-- =================================================================== --> + + <section> + <title>Example</title> + <p> + This is an example starting off as equivalent to the the example in the + <seealso marker="fsm"><c>gen_fsm</c> behaviour</seealso> + description. In later chapters additions and tweaks are made + using features in <c>gen_statem</c> that <c>gen_fsm</c> does not have. + At the end of this section you can find the example again + with all the added features. + </p> + <p> + A door with a code lock can be viewed as a state machine. + Initially, the door is locked. Anytime someone presses a button, + this generates an event. + Depending on what buttons have been pressed before, + the sequence so far can be correct, incomplete, or wrong. + </p> + <p> + If it is correct, the door is unlocked for 10 seconds (10000 ms). + If it is incomplete, we wait for another button to be pressed. If + it is is wrong, we start all over, + waiting for a new button sequence. + </p> + <image file="../design_principles/code_lock.png"> + <icaption>Code lock state diagram</icaption> + </image> + <p> + We can implement such a code lock state machine using + <c>gen_statem</c> with the following callback module: + </p> + <marker id="ex"></marker> + <code type="erl"><![CDATA[ +-module(code_lock). +-behaviour(gen_statem). +-define(NAME, code_lock). +-define(CALLBACK_MODE, state_functions). + +-export([start_link/1]). +-export([button/1]). +-export([init/1,terminate/3,code_change/4]). +-export([locked/3,open/3]). + +start_link(Code) -> + gen_statem:start_link({local,?NAME}, ?MODULE, Code, []). + +button(Digit) -> + gen_statem:cast(?NAME, {button,Digit}). + + +init(Code) -> + do_lock(), + Data = #{code => Code, remaining => Code}, + {?CALLBACK_MODE,locked,Data}. + +locked( + cast, {button,Digit}, + #{code := Code, remaining := Remaining} = Data) -> + case Remaining of + [Digit] -> + do_unlock(), + {next_state,open,Data#{remaining := Code},10000}; + [Digit|Rest] -> % Incomplete + {next_state,locked,Data#{remaining := Rest}}; + _Wrong -> + {next_state,locked,Data#{remaining := Code}} + end. + +open(timeout, _, Data) -> + do_lock(), + {next_state,locked,Data}; +open(cast, {button,_}, Data) -> + do_lock(), + {next_state,locked,Data}. + +do_lock() -> + io:format("Lock~n", []). +do_unlock() -> + io:format("Unlock~n", []). + +terminate(_Reason, State, _Data) -> + State =/= locked andalso do_lock(), + ok. +code_change(_Vsn, State, Data, _Extra) -> + {?CALLBACK_MODE,State,Data}. + ]]></code> + <p>The code is explained in the next sections.</p> + </section> + +<!-- =================================================================== --> + + <section> + <title>Starting gen_statem</title> + <p> + In the example in the previous section, the <c>gen_statem</c> is + started by calling <c>code_lock:start_link(Code)</c>: + </p> + <code type="erl"><![CDATA[ +start_link(Code) -> + gen_statem:start_link({local,?NAME}, ?MODULE, Code, []). + ]]></code> + <p> + <c>start_link</c> calls the function + <seealso marker="stdlib:gen_statem#start_link/4"> + <c>gen_statem:start_link/4</c> + </seealso> + which spawns and links to a new process; a <c>gen_statem</c>. + </p> + <list type="bulleted"> + <item> + <p> + The first argument, <c>{local,?NAME}</c>, specifies + the name. In this case, the <c>gen_statem</c> is locally + registered as <c>code_lock</c> through the macro <c>?NAME</c>. + </p> + <p> + If the name is omitted, the <c>gen_statem</c> is not registered. + Instead its pid must be used. The name can also be given + as <c>{global,Name}</c>, in which case the <c>gen_statem</c> is + registered using + <seealso marker="kernel:global#register_name/2"> + <c>global:register_name/2</c>. + </seealso> + </p> + </item> + <item> + <p> + The second argument, <c>?MODULE</c>, is the name of + the callback module, that is; the module where the callback + functions are located, which is this module. + </p> + <p> + The interface functions (<c>start_link/1</c> and <c>button/1</c>) + are located in the same module as the callback functions + (<c>init/1</c>, <c>locked/3</c>, and <c>open/3</c>). + It is normally good programming practice to have the client + side and the server side code contained in one module. + </p> + </item> + <item> + <p> + The third argument, <c>Code</c>, is a list of digits that + is the correct unlock code which is passsed + to the callback function <c>init/1</c>. + </p> + </item> + <item> + <p> + The fourth argument, <c>[]</c>, is a list of options. See the + <seealso marker="stdlib:gen_statem#start_link/3"> + <c>gen_statem:start_link/3</c> + </seealso> + manual page for available options. + </p> + </item> + </list> + <p> + If name registration succeeds, the new <c>gen_statem</c> process + calls the callback function <c>code_lock:init(Code)</c>. + This function is expected to return <c>{CallbackMode,State,Data}</c>, + where + <seealso marker="#callback_modes"> + <c>CallbackMode</c> + </seealso> + selects callback module state function mode, in this case + <seealso marker="stdlib:gen_statem#type-callback_mode"> + <c>state_functions</c> + </seealso> + through the macro <c>?CALLBACK_MODE</c> that is; each state + has got its own handler function. + <c>State</c> is the initial state of the <c>gen_statem</c>, + in this case <c>locked</c>; assuming the door is locked to begin with. + <c>Data</c> is the internal server data of the <c>gen_statem</c>. + Here the server data is a <seealso marker="stdlib:maps">map</seealso> + with the key <c>code</c> that stores + the correct button sequence and the key <c>remaining</c> + that stores the remaining correct button sequence + (the same as the <c>code</c> to begin with). + </p> + <code type="erl"><![CDATA[ +init(Code) -> + do_lock(), + Data = #{code => Code, remaining => Code}, + {?CALLBACK_MODE,locked,Data}. + ]]></code> + <p> + <seealso marker="stdlib:gen_statem#start_link/3"> + <c>gen_statem:start_link</c> + </seealso> + is synchronous. It does not return until the <c>gen_statem</c> + has been initialized and is ready to receive events. + </p> + <p> + <seealso marker="stdlib:gen_statem#start_link/3"> + <c>gen_statem:start_link</c> + </seealso> + must be used if the <c>gen_statem</c> + is part of a supervision tree, that is; started by a supervisor. + There is another function; + <seealso marker="stdlib:gen_statem#start/3"> + <c>gen_statem:start</c> + </seealso> + to start a standalone <c>gen_statem</c>, that is; + a <c>gen_statem</c> that is not part of a supervision tree. + </p> + </section> + +<!-- =================================================================== --> + + <section> + <title>Events and Handling them</title> + <p>The function notifying the code lock about a button event is + implemented using + <seealso marker="stdlib:gen_statem#cast/2"> + <c>gen_statem:cast/2</c>: + </seealso> + </p> + <code type="erl"><![CDATA[ +button(Digit) -> + gen_statem:cast(?NAME, {button,Digit}). + ]]></code> + <p> + The first argument is the name of the <c>gen_statem</c> and must + agree with the name used to start it so therefore we use the + same macro <c>?NAME</c> as when starting. + <c>{button,Digit}</c> is the actual event content. + </p> + <p> + The event is made into a message and sent to the <c>gen_statem</c>. + When the event is received, the <c>gen_statem</c> calls + <c>StateName(cast, Event, Data)</c>, which is expected to + return a tuple <c>{next_state,NewStateName,NewData}</c>. + <c>StateName</c> is the name of the current state and + <c>NewStateName</c> is the name of the next state to go to. + <c>NewData</c> is a new value for the server data of + the <c>gen_statem</c>. + </p> + <code type="erl"><![CDATA[ +locked( + cast, {button,Digit}, + #{code := Code, remaining := Remaining} = Data) -> + case Remaining of + [Digit] -> % Complete + do_unlock(), + {next_state,open,Data#{remaining := Code},10000}; + [Digit|Rest] -> % Incomplete + {next_state,locked,Data#{remaining := Rest}}; + [_|_] -> % Wrong + {next_state,locked,Data#{remaining := Code}} + end. + +open(timeout, _, Data) -> + do_lock(), + {next_state,locked,Data}; +open(cast, {button,_}, Data) -> + do_lock(), + {next_state,locked,Data}. + ]]></code> + <p> + If the door is locked and a button is pressed, the pressed + button is compared with the next correct button and, + depending on the result, the door is either unlocked + and the <c>gen_statem</c> goes to state <c>open</c>, + or the door remains in state <c>locked</c>. + </p> + <p> + If the pressed button is incorrect the server data + restarts from the start of the code sequence. + </p> + <p> + In state <c>open</c> any button locks the door since + any event cancels the event timer so we will not get + a timeout event after a button event. + </p> + </section> + + <section> + <title>Event Time-Outs</title> + <p> + When a correct code has been given, the door is unlocked and + the following tuple is returned from <c>locked/2</c>: + </p> + <code type="erl"><![CDATA[ +{next_state,open,Data#{remaining := Code},10000}; + ]]></code> + <p> + 10000 is a time-out value in milliseconds. + After this time, that is; 10 seconds, a time-out occurs. + Then, <c>StateName(timeout, 10000, Data)</c> is called. + The time-out occurs when the door has been in state <c>open</c> + for 10 seconds. After that the door is locked again: + </p> + <code type="erl"><![CDATA[ +open(timeout, _, Data) -> + do_lock(), + {next_state,locked,Data}; + ]]></code> + </section> + +<!-- =================================================================== --> + + <section> + <title>All State Events</title> + <p> + Sometimes an event can arrive in any state of the <c>gen_statem</c>. + It is convenient to handle these in a common state handler function + that all state functions call for events not specific to the state. + </p> + <p> + Let's introduce a <c>code_length/0</c> function that returns + the length of the correct code + (that should not be sensitive to reveal...). + We'll dispatch all events that are not state specific + to the common function <c>handle_event/3</c>. + </p> + <code type="erl"><![CDATA[ +... +-export([button/1,code_length/0]). +... + +code_length() -> + gen_statem:call(?NAME, code_length). + +... +locked(...) -> ... ; +locked(EventType, EventContent, Data) -> + handle_event(EventType, EventContent, Data). + +... +open(...) -> ... ; +open(EventType, EventContent, Data) -> + handle_event(EventType, EventContent, Data). + +handle_event({call,From}, code_length, #{code := Code} = Data) -> + {keep_state,Data,[{reply,From,length(Code)}]}. + ]]></code> + <p> + This example uses + <seealso marker="stdlib:gen_statem#call/2"> + <c>gen_statem:call/2</c> + </seealso> + which waits for a reply from the server. + The reply is sent with a <c>{reply,From,Reply}</c> tuple + in an action list in the <c>{keep_state,...}</c> tuple + that retains the current state. + </p> + </section> + +<!-- =================================================================== --> + + <section> + <title>One Event Handler</title> + <p> + If you use the mode <c>handle_event_function</c> + all events are handled in <c>handle_event/4</c> and we + may (but do not have to) use an event-centered approach + where we dispatch on event first and then state: + </p> + <code type="erl"><![CDATA[ +... +-define(CALLBACK_MODE, state_functions). + +... +-export([handle_event/4]). + +... + +handle_event(cast, {button,Digit}, State, #{code := Code} = Data) -> + case State of + locked -> + case maps:get(remaining, Data) of + [Digit] -> % Complete + do_unlock(), + {next_state,open,Data#{remaining := Code},10000}; + [Digit|Rest] -> % Incomplete + {keep_state,Data#{remaining := Rest}}; + [_|_] -> % Wrong + {keep_state,Data#{remaining := Code}} + end; + open -> + do_lock(), + {next_state,locked,Data} + end; +handle_event(timeout, _, open, Data) -> + do_lock(), + {next_state,locked,Data}. + +... + ]]></code> + </section> + +<!-- =================================================================== --> + + <section> + <title>Stopping</title> + + <section> + <title>In a Supervision Tree</title> + <p> + If the <c>gen_statem</c> is part of a supervision tree, + no stop function is needed. + The <c>gen_statem</c> is automatically terminated by its supervisor. + Exactly how this is done is defined by a + <seealso marker="sup_princ#shutdown">shutdown strategy</seealso> + set in the supervisor. + </p> + <p> + If it is necessary to clean up before termination, the shutdown + strategy must be a time-out value and the <c>gen_statem</c> must + in the <c>init/1</c> function set itself to trap exit signals + by calling + <seealso marker="erts:erlang#process_flag/2"> + <c>process_flag(trap_exit, true)</c>. + </seealso> + When ordered to shutdown, the <c>gen_statem</c> then calls + the callback function + <c>terminate(shutdown, State, Data)</c>: + </p> + <code type="erl"><![CDATA[ +init(Args) -> + process_flag(trap_exit, true), + do_lock(), + ... + ]]></code> + <p> + In this example we let the <c>terminate/3</c> function + lock the door if it is open so we do not accidentally leave the door + open when the supervision tree terminates. + </p> + <code type="erl"><![CDATA[ +terminate(_Reason, State, _Data) -> + State =/= locked andalso do_lock(), + ok. + ]]></code> + </section> + + <section> + <title>Standalone gen_statem</title> + <p> + If the <c>gen_statem</c> is not part of a supervision tree, + it can be stopped using + <seealso marker="stdlib:gen_statem#stop/1"> + <c>gen_statem:stop</c>, + </seealso> + preferably through an API function: + </p> + <code type="erl"><![CDATA[ +... +-export([start_link/1,stop/0]). + +... +stop() -> + gen_statem:stop(?NAME). + ]]></code> + <p> + This makes the <c>gen_statem</c> call the <c>terminate/3</c> + callback function just like for a supervised server + and waits for the process to terminate. + </p> + </section> + </section> + +<!-- =================================================================== --> + + <section> + <title>Actions</title> + <p> + In the first chapters we mentioned actions as a part of + the general state machine model, and these actions + are implemented with the code the <c>gen_statem</c> + callback module executes in an event handling + callback function before returning + to the <c>gen_statem</c> engine. + </p> + <p> + There are more specific state transition actions + that a callback function can order the <c>gen_statem</c> + engine to do after the callback function return. + These are ordered by returning a list of + <seealso marker="stdlib:gen_statem#type-action"> + actions + </seealso> + in the + <seealso marker="stdlib:gen_statem#type-state_function_result"> + return tuple + </seealso> + from the + <seealso marker="stdlib:gen_statem#Module:StateName/3"> + callback function. + </seealso> + These state transition actions affect the <c>gen_statem</c> + engine itself. They can: + </p> + <list type="bulleted"> + <item>Postpone the current event.</item> + <item>Hibernate the <c>gen_statem</c>.</item> + <item>Start an event timeout.</item> + <item>Reply to a caller.</item> + <item>Generate the next event to handle.</item> + </list> + <p> + We have mentioned the event timeout + and replying to a caller in the example above. + An example of event postponing comes in later in this chapter. + See the + <seealso marker="stdlib:gen_statem#type-action"> + reference manual + </seealso> + for details. You can for example actually reply to several callers + and generate multiple next events to handle. + </p> + </section> + +<!-- =================================================================== --> + + <section> + <title>Event Types</title> + <p> + So far we have mentioned a few + <seealso marker="stdlib:gen_statem#type-event_type"> + event types. + </seealso> + Events of all types are handled in the same callback function, + for a given state, and the function gets + <c>EventType</c> and <c>EventContent</c> as arguments. + </p> + <p> + Here is the complete list of event types and where + they come from: + </p> + <taglist> + <tag><c>cast</c></tag> + <item> + Generated by + <seealso marker="stdlib:gen_statem#cast/2"> + <c>gen_statem:cast</c>. + </seealso> + </item> + <tag><c>{call,From}</c></tag> + <item> + Generated by + <seealso marker="stdlib:gen_statem#call/2"> + <c>gen_statem:call</c> + </seealso> + where <c>From</c> is the reply address to use + when replying either through the state transition action + <c>{reply,From,Msg}</c> or by calling + <seealso marker="stdlib:gen_statem#reply/1"> + <c>gen_statem:reply</c>. + </seealso> + </item> + <tag><c>info</c></tag> + <item> + Generated by any regular process message sent to + the <c>gen_statem</c> process. + </item> + <tag><c>timeout</c></tag> + <item> + Generated by the state transition action + <c>{timeout,Time,EventContent}</c> (or its short form <c>Time</c>) + timer timing out. + </item> + <tag><c>internal</c></tag> + <item> + Generated by the state transition action + <c>{next_event,internal,EventContent}</c>. + In fact all event types above can be generated using + <c>{next_event,EventType,EventContent}</c>. + </item> + </taglist> + </section> + +<!-- =================================================================== --> + + <section> + <title>State Timeouts</title> + <p> + The timeout event generated by the state transition action + <c>{timeout,Time,EventContent}</c> is an event timeout, + that is; if an event arrives the timer is cancelled. + You get either an event or a timeout but not both. + </p> + <p> + Often you want a timer to not be cancelled by any event + or you want to start a timer in one state and respond + to the timeout in another. This can be accomplished + with a regular erlang timer: + <seealso marker="erts:erlang#start_timer/4"> + <c>erlang:start_timer</c>. + </seealso> + </p> + <p> + Looking at the example in this chapter so far; using the + <c>gen_statem</c> event timer has the consequence that + if a button event is generated while in the <c>open</c> state, + the timeout is cancelled and the button event is delivered. + Therefore we chose to lock the door if this happended. + </p> + <p> + Suppose we do not want a button to lock the door, + instead we want to ignore button events in the <c>open</c> state. + Then we start a timer when entering the <c>open</c> state + and wait for it to expire while ignoring button events: + </p> + <code type="erl"><![CDATA[ +... +locked( + cast, {button,Digit}, + #{code := Code, remaining := Remaining} = Data) -> + case Remaining of + [Digit] -> + do_unlock(), + Tref = erlang:start_timer(10000, self(), lock), + {next_state,open,Data#{remaining := Code, timer := Tref}}; +... + +open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) -> + do_lock(), + {next_state,locked,Data}; +open(cast, {button,_}, Data) -> + {keep_state,Data}; +... + ]]></code> + <p> + If you need to cancel a timer due to some other event you can use + <seealso marker="erts:erlang#cancel_timer/2"> + <c>erlang:cancel_timer(Tref)</c>. + </seealso> + Note that a timeout message can not arrive after this, + unless you have postponed it (see the next section) before, + so make sure you do not accidentally postpone such messages. + </p> + <p> + Another way to cancel a timer is to not cancel it, + but instead to ignore it if it arrives in a state + where it is known to be late. + </p> + </section> + +<!-- =================================================================== --> + + <section> + <title>Postponing Events</title> + <p> + If you want to ignore a particular event in the current state + and handle it in a future state, you can postpone the event. + A postponed event is retried after the state has + changed i.e <c>OldState =/= NewState</c>. + </p> + <p> + Postponing is ordered by the state transition + <seealso marker="stdlib:gen_statem#type-action"> + action + </seealso> + <c>postpone</c>. + </p> + <p> + In this example, instead of ignoring button events + while in the <c>open</c> state we can postpone them + and they will be queued and later handled in the <c>locked</c> state: + </p> + <code type="erl"><![CDATA[ +... +open(cast, {button,_}, Data) -> + {keep_state,Data,[postpone]}; +... + ]]></code> + <p> + The fact that a postponed event is only retried after a state change + translates into a requirement on the event and state space: + if you have a choice between storing a state data item + in the <c>State</c> or in the <c>Data</c>; + should a change in the item value affect which events that + are handled, then this item ought to be part of the state. + </p> + <p> + What you want to avoid is that you maybe much later decide + to postpone an event in one state and by misfortune it is never retried + because the code only changes the <c>Data</c> but not the <c>State</c>. + </p> + + <section> + <title>Fuzzy State Diagrams</title> + <p> + It is not uncommon that a state diagram does not specify + how to handle events that are not illustrated + in a particular state in the diagram. + Hopefully this is described in an associated text + or from the context. + </p> + <p> + Possible actions may be; ignore as in drop the event + (maybe log it) or deal with the event in some other state + as in postpone it. + </p> + </section> + + <section> + <title>Selective Receive</title> + <p> + Erlang's selective receive statement is often used to + describe simple state machine examples in straightforward + Erlang code. Here is a possible implementation of + the first example: + </p> + <code type="erl"><![CDATA[ +-module(code_lock). +-define(NAME, code_lock_1). +-export([start_link/1,button/1]). + +start_link(Code) -> + spawn( + fun () -> + true = register(?NAME, self()), + do_lock(), + locked(Code, Code) + end). + +button(Digit) -> + ?NAME ! {button,Digit}. + +locked(Code, [Digit|Remaining]) -> + receive + {button,Digit} when Remaining =:= [] -> + do_unlock(), + open(Code); + {button,Digit} -> + locked(Code, Remaining); + {button,_} -> + locked(Code, Code) + end. + +open(Code) -> + receive + after 10000 -> + do_lock(), + locked(Code, Code) + end. + +do_lock() -> + io:format("Locked~n", []). +do_unlock() -> + io:format("Open~n", []). + ]]></code> + <p> + The selective receive in this case causes <c>open</c> + to implicitly postpone any events to the <c>locked</c> state. + </p> + <p> + A selective receive can not be used from a <c>gen_statem</c> + behaviour just as for any <c>gen_*</c> behavior + since the receive statement is within the <c>gen_*</c> engine itself. + It has to be there because all + <seealso marker="stdlib:sys"><c>sys</c></seealso> + compatible behaviours must respond to system messages and therefore + do that in their engine receive loop, + passing non-system messages to the callback module. + </p> + <p> + The state transition + <seealso marker="stdlib:gen_statem#type-action"> + action + </seealso> + <c>postpone</c> is designed to be able to model + selective receives. A selective receive implicitly postpones + any not received events, but the <c>postpone</c> + state transition action explicitly postpones one received event. + </p> + <p> + Other than that both mechanisms have got the same theoretical + time and memory complexity, while the selective receive + language construct has got smaller constant factors. + </p> + </section> + </section> + +<!-- =================================================================== --> + + <section> + <title>Self Generated Events</title> + <p> + It may be beneficial in some cases to be able to generate events + to your own state machine. + This can be done with the state transition + <seealso marker="stdlib:gen_statem#type-action"> + action + </seealso> + <c>{next_event,EventType,EventContent}</c>. + </p> + <p> + You can generate events of any existing + <seealso marker="stdlib:gen_statem#type-action"> + type, + </seealso> + but the <c>internal</c> type can only be generated through the + <c>next_event</c> action and hence can not come from an external source, + so you can be certain that an <c>internal</c> event is an event + from your state machine to itself. + </p> + <p> + One example of using self generated events may be when you have + a state machine specification that uses state entry actions. + That you could code using a dedicated function + to do the state transition. But if you want that code to be + visible besides the other state logic you can insert + an <c>internal</c> event that does the entry actions. + This has the same unfortunate consequence as using + state transition functions that everywhere you go to + the state in question you will have to explicitly + insert the <c>internal</c> event + or use state transition function. + </p> + <p> + Here is an implementation of entry actions + using <c>internal</c> events with content <c>enter</c> + utilizing a helper function <c>enter/3</c> for state entry: + </p> + <code type="erl"><![CDATA[ +... +-define(CALLBACK_MODE, state_functions). + +... + +init(Code) -> + process_flag(trap_exit, true), + Data = #{code => Code}, + enter(?CALLBACK_MODE, locked, Data). + +... + +locked(internal, enter, _Data) -> + do_lock(), + {keep_state,Data#{remaining => Code}}; +locked( + cast, {button,Digit}, + #{code := Code, remaining := Remaining} = Data) -> + case Remaining of + [Digit] -> + enter(next_state, open, Data); +... + +open(internal, enter, _Data) -> + Tref = erlang:start_timer(10000, self(), lock), + do_unlock(), + {keep_state,Data#{timer => Tref}}; +open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) -> + enter(next_state, locked, Data); +... + +enter(Tag, State, Data) -> + {Tag,State,Data,[{next_event,internal,enter}]}. + ]]></code> + </section> + +<!-- =================================================================== --> + + <section> + <title>Example Revisited</title> + <p> + Here is the example after all mentioned modifications + and some more utilizing the entry actions, + which deserves a new state diagram: + </p> + <image file="../design_principles/code_lock_2.png"> + <icaption>Code lock state diagram revisited</icaption> + </image> + <p> + Note that this state diagram does not specify how to handle + a button event in the state <c>open</c>, so you will have to + read some other place that is here that unspecified events + shall be ignored as in not consumed but handled in some other state. + Nor does it show that the <c>code_length/0</c> call shall be + handled in every state. + </p> + + <section> + <title>Callback Mode: state_functions</title> + <p> + Using state functions: + </p> + <code type="erl"><![CDATA[ +-module(code_lock). +-behaviour(gen_statem). +-define(NAME, code_lock_2). +-define(CALLBACK_MODE, state_functions). + +-export([start_link/1,stop/0]). +-export([button/1,code_length/0]). +-export([init/1,terminate/3,code_change/4]). +-export([locked/3,open/3]). + +start_link(Code) -> + gen_statem:start_link({local,?NAME}, ?MODULE, Code, []). +stop() -> + gen_statem:stop(?NAME). + +button(Digit) -> + gen_statem:cast(?NAME, {button,Digit}). +code_length() -> + gen_statem:call(?NAME, code_length). + +init(Code) -> + process_flag(trap_exit, true), + Data = #{code => Code}, + enter(?CALLBACK_MODE, locked, Data). + +locked(internal, enter, #{code := Code} = Data) -> + do_lock(), + {keep_state,Data#{remaining => Code}}; +locked( + cast, {button,Digit}, + #{code := Code, remaining := Remaining} = Data) -> + case Remaining of + [Digit] -> % Complete + enter(next_state, open, Data); + [Digit|Rest] -> % Incomplete + {keep_state,Data#{remaining := Rest}}; + [_|_] -> % Wrong + {keep_state,Data#{remaining := Code}} + end; +locked(EventType, EventContent, Data) -> + handle_event(EventType, EventContent, Data). + +open(internal, enter, Data) -> + Tref = erlang:start_timer(10000, self(), lock), + do_unlock(), + {keep_state,Data#{timer => Tref}}; +open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) -> + enter(next_state, locked, Data); +open(cast, {button,_}, _) -> + {keep_state_and_data,[postpone]}; +open(EventType, EventContent, Data) -> + handle_event(EventType, EventContent, Data). + +handle_event({call,From}, code_length, #{code := Code}) -> + {keep_state_and_data,[{reply,From,length(Code)}]}. +enter(Tag, State, Data) -> + {Tag,State,Data,[{next_event,internal,enter}]}. + +do_lock() -> + io:format("Locked~n", []). +do_unlock() -> + io:format("Open~n", []). + +terminate(_Reason, State, _Data) -> + State =/= locked andalso do_lock(), + ok. +code_change(_Vsn, State, Data, _Extra) -> + {?CALLBACK_MODE,State,Data}. + ]]></code> + </section> + + <section> + <title>Callback Mode: handle_event_function</title> + <p> + What to change to use one <c>handle_event/4</c> function. + Here a clean first-dispatch-on-event approach + does not work that well due to the generated + entry actions: + </p> + <code type="erl"><![CDATA[ +... +-define(CALLBACK_MODE, handle_event_function). + +... +-export([handle_event/4]). + +... + +%% State: locked +handle_event(internal, enter, locked, #{code := Code} = Data) -> + do_lock(), + {keep_state,Data#{remaining => Code}}; +handle_event( + cast, {button,Digit}, locked, + #{code := Code, remaining := Remaining} = Data) -> + case Remaining of + [Digit] -> % Complete + enter(next_state, open, Data); + [Digit|Rest] -> % Incomplete + {keep_state,Data#{remaining := Rest}}; + [_|_] -> % Wrong + {keep_state,Data#{remaining := Code}} + end; +%% +%% State: open +handle_event(internal, enter, open, Data) -> + Tref = erlang:start_timer(10000, self(), lock), + do_unlock(), + {keep_state,Data#{timer => Tref}}; +handle_event(info, {timeout,Tref,lock}, open, #{timer := Tref} = Data) -> + enter(next_state, locked, Data); +handle_event(cast, {button,_}, open, _) -> + {keep_state_and_data,[postpone]}; +%% +%% Any state +handle_event({call,From}, code_length, _State, #{code := Code}) -> + {keep_state_and_data,[{reply,From,length(Code)}]}. + +... + ]]></code> + </section> + <p> + Note that postponing buttons from the <c>locked</c> state + to the <c>open</c> state feels like the wrong thing to do + for a code lock, but it at least illustrates event postponing. + </p> + </section> + +<!-- =================================================================== --> + + <section> + <title>Filter the State</title> + <p> + The example servers so far in this chapter will for example + when killed by an exit signal or due to an internal error + print out the full internal state in the error log. + This state contains both the code lock code + and which digits that remains to unlock. + </p> + <p> + This state data can be regarded as sensitive, + and maybe not what you want in the error log + because of something unpredictable happening. + </p> + <p> + Another reason to filter the state can be + that the state is too big to print out since it fills + the error log with uninteresting details. + </p> + <p> + To avoid this you can format the internal state + that gets in the error log and gets returned from + <seealso marker="stdlib:sys#get_status/1"> + <c>sys:get_status/1,2</c> + </seealso> + by implementing the + <seealso marker="stdlib:gen_statem#Module:format_status/2"> + <c>Module:format_status/2</c> + </seealso> + function, for example like this: + </p> + <code type="erl"><![CDATA[ +... +-export([init/1,terminate/3,code_change/4,format_status/2]). +... + +format_status(Opt, [_PDict,State,Data]) -> + StateData = + {State, + maps:filter( + fun (code, _) -> false; + (remaining, _) -> false; + (_, _) -> true + end, + Data)}, + case Opt of + terminate -> + StateData; + normal -> + [{data,[{"State",StateData}]}] + end. + ]]></code> + <p> + It is not mandatory to implement a + <seealso marker="stdlib:gen_statem#Module:format_status/2"> + <c>Module:format_status/2</c> + </seealso> + function. If you do not a default implementation is used that + does the same as this example function without filtering + the <c>Data</c> term that is: <c>StateData = {State,Data}</c>. + </p> + </section> + +<!-- =================================================================== --> + + <section> + <title>Complex State</title> + <p> + The callback mode + <seealso marker="stdlib:gen_statem#type-callback_mode"> + <c>handle_event_function</c> + </seealso> + enables using a non-atom state as described in + <seealso marker="#callback_modes"> + Callback Modes, + </seealso> + for example a complex state term like a tuple. + </p> + <p> + One reason to use this is when you have + a state item that affects the event handling + in particular when combining that with postponing events. + Let us complicate the previous example + by introducing a configurable lock button + (this is the state item in question) + that in the <c>open</c> state immediately locks the door, + and an API function <c>set_lock_button/1</c> to set the lock button. + </p> + <p> + Suppose now that we call <c>set_lock_button</c> + while the door is open, + and have already postponed a button event + that up until now was not the lock button; + the sensible thing might be to say that + the button was pressed too early so it should + not be recognized as the lock button, + but then it might be surprising that a button event + that now is the lock button event arrives (as retried postponed) + immediately after the state transits to <c>locked</c>. + </p> + <p> + So let us make the <c>button/1</c> function synchronous + by using <c>gen_statem:call</c>, + and still postpone its events in the <c>open</c> state. + Then a call to <c>button/1</c> during the <c>open</c> + state will not return until the state transits to <c>locked</c> + since it is there the event is handled and the reply is sent. + </p> + <p> + If now one process calls <c>set_lock_button/1</c> + to change the lock button while some other process + hangs in <c>button/1</c> with the new lock button + it could be expected that the hanging lock button call + immediately takes effect and locks the lock. + Therefore we make the current lock button a part of the state + so when we change the lock button the state will change + and all postponed events will be retried. + </p> + <p> + We define the state as <c>{StateName,LockButton}</c> + where <c>StateName</c> is as before + and <c>LockButton</c> is the current lock button: + </p> + <code type="erl"><![CDATA[ +-module(code_lock). +-behaviour(gen_statem). +-define(NAME, code_lock_3). +-define(CALLBACK_MODE, handle_event_function). + +-export([start_link/2,stop/0]). +-export([button/1,code_length/0,set_lock_button/1]). +-export([init/1,terminate/3,code_change/4,format_status/2]). +-export([handle_event/4]). + +start_link(Code, LockButton) -> + gen_statem:start_link( + {local,?NAME}, ?MODULE, {Code,LockButton}, []). +stop() -> + gen_statem:stop(?NAME). + +button(Digit) -> + gen_statem:call(?NAME, {button,Digit}). +code_length() -> + gen_statem:call(?NAME, code_length). +set_lock_button(LockButton) -> + gen_statem:call(?NAME, {set_lock_button,LockButton}). + +init({Code,LockButton}) -> + process_flag(trap_exit, true), + Data = #{code => Code, remaining => undefined, timer => undefined}, + enter(?CALLBACK_MODE, {locked,LockButton}, Data, []). + +handle_event( + {call,From}, {set_lock_button,NewLockButton}, + {StateName,OldLockButton}, Data) -> + {next_state,{StateName,NewLockButton},Data, + [{reply,From,OldLockButton}]}; +handle_event( + {call,From}, code_length, + {_StateName,_LockButton}, #{code := Code}) -> + {keep_state_and_data, + [{reply,From,length(Code)}]}; +handle_event( + EventType, EventContent, + {locked,LockButton}, #{code := Code, remaining := Remaining} = Data) -> + case {EventType,EventContent} of + {internal,enter} -> + do_lock(), + {keep_state,Data#{remaining := Code}}; + {{call,From},{button,Digit}} -> + case Remaining of + [Digit] -> % Complete + next_state( + {open,LockButton}, Data, + [{reply,From,ok}]); + [Digit|Rest] -> % Incomplete + {keep_state,Data#{remaining := Rest}, + [{reply,From,ok}]}; + [_|_] -> % Wrong + {keep_state,Data#{remaining := Code}, + [{reply,From,ok}]} + end + end; +handle_event( + EventType, EventContent, + {open,LockButton}, #{timer := Timer} = Data) -> + case {EventType,EventContent} of + {internal,enter} -> + Tref = erlang:start_timer(10000, self(), lock), + do_unlock(), + {keep_state,Data#{timer := Tref}}; + {info,{timeout,Timer,lock}} -> + next_state({locked,LockButton}, Data, []); + {{call,From},{button,Digit}} -> + if + Digit =:= LockButton -> + erlang:cancel_timer(Timer), + next_state( + {locked,LockButton}, Data, + [{reply,From,locked}]); + true -> + {keep_state_and_data, + [postpone]} + end + end. + +next_state(State, Data, Actions) -> + enter(next_state, State, Data, Actions). +enter(Tag, State, Data, Actions) -> + {Tag,State,Data,[{next_event,internal,enter}|Actions]}. + +do_lock() -> + io:format("Locked~n", []). +do_unlock() -> + io:format("Open~n", []). + +terminate(_Reason, State, _Data) -> + State =/= locked andalso do_lock(), + ok. +code_change(_Vsn, State, Data, _Extra) -> + {?CALLBACK_MODE,State,Data}. +format_status(Opt, [_PDict,State,Data]) -> + StateData = + {State, + maps:filter( + fun (code, _) -> false; + (remaining, _) -> false; + (_, _) -> true + end, + Data)}, + case Opt of + terminate -> + StateData; + normal -> + [{data,[{"State",StateData}]}] + end. + ]]></code> + <p> + It may be an ill-fitting model for a physical code lock + that the <c>button/1</c> call might hang until the lock + is locked. But for an API in general it is really not + that strange. + </p> + </section> + +<!-- =================================================================== --> + + <section> + <title>Hibernation</title> + <p> + If you have many servers in one node + and they have some state(s) in their lifetime in which + the servers can be expected to idle for a while, + and the amount of heap memory all these servers need + is a problem; then it is possible to minimize + the memory footprint of a server by hibernating it through + <seealso marker="stdlib:proc_lib#hibernate/3"> + <c>proc_lib:hibernate/3</c>. + </seealso> + </p> + <note> + <p> + To hibernate a process is rather costly. See + <seealso marker="erts:erlang#hibernate/3"> + <c>erlang:hibernate/3</c>. + </seealso> + It is in general not something you want to do + after every event. + </p> + </note> + <p> + We can in this example hibernate in the <c>{open,_}</c> state + since what normally happens in that state is that + the state timeout after a while + triggers a transition to <c>{locked,_}</c>: + </p> + <code type="erl"><![CDATA[ +... +handle_event( + EventType, EventContent, + {open,LockButton}, #{timer := Timer} = Data) -> + case {EventType,EventContent} of + {internal,enter} -> + Tref = erlang:start_timer(10000, self(), lock), + do_unlock(), + {keep_state,Data#{timer := Tref},[hibernate]}; +... + ]]></code> + <p> + The + <seealso marker="stdlib:gen_statem#type-hibernate"> + <c>[hibernate]</c> + </seealso> + action list on the last line + when entering the <c>{open,_}</c> state is the only change. + If any event arrives in the <c>{open,_},</c> state we + do not bother to re-hibernate, so the server stays + awake after any event. + </p> + <p> + To change that we would need to insert + the <c>hibernate</c> action in more places, + for example for the state independent <c>set_lock_button</c> + and <c>code_length</c> operations that then would have to + be aware of using <c>hibernate</c> while in the + <c>{open,_}</c> state which would clutter the code. + </p> + <p> + This server probably does not use an amount of + heap memory worth hibernating for. + To gain anything from hibernation your server would + have to actually produce some garbage during callback execution, + for which this example server may serve as a bad example. + </p> + </section> + +</chapter> diff --git a/system/doc/design_principles/sup_princ.xml b/system/doc/design_principles/sup_princ.xml index 7408a34442..a77b3964fc 100644 --- a/system/doc/design_principles/sup_princ.xml +++ b/system/doc/design_principles/sup_princ.xml @@ -213,6 +213,7 @@ child_spec() = #{id => child_id(), % mandatory <item><c>supervisor:start_link</c></item> <item><c>gen_server:start_link</c></item> <item><c>gen_fsm:start_link</c></item> + <item><c>gen_statem:start_link</c></item> <item><c>gen_event:start_link</c></item> <item>A function compliant with these functions. For details, see the <c>supervisor(3)</c> manual page.</item> @@ -276,7 +277,8 @@ child_spec() = #{id => child_id(), % mandatory <p><c>modules</c> are to be a list with one element <c>[Module]</c>, where <c>Module</c> is the name of the callback module, if the child process is a supervisor, - gen_server or gen_fsm. If the child process is a gen_event, + gen_server, gen_fsm or gen_statem. + If the child process is a gen_event, the value shall be <c>dynamic</c>.</p> <p>This information is used by the release handler during upgrades and downgrades, see @@ -400,8 +402,8 @@ supervisor:delete_child(Sup, Id)</code> restarts.</p> </section> - <marker id="simple"/> <section> + <marker id="simple"/> <title>Simplified one_for_one Supervisors</title> <p>A supervisor with restart strategy <c>simple_one_for_one</c> is a simplified <c>one_for_one</c> supervisor, where all child diff --git a/system/doc/design_principles/xmlfiles.mk b/system/doc/design_principles/xmlfiles.mk index 9c3836c8ac..e476255d62 100644 --- a/system/doc/design_principles/xmlfiles.mk +++ b/system/doc/design_principles/xmlfiles.mk @@ -25,6 +25,7 @@ DESIGN_PRINCIPLES_CHAPTER_FILES = \ distributed_applications.xml \ events.xml \ fsm.xml \ + statem.xml \ gen_server_concepts.xml \ included_applications.xml \ release_handling.xml \ diff --git a/system/doc/reference_manual/modules.xml b/system/doc/reference_manual/modules.xml index 5f2ac2a67d..96968b547e 100644 --- a/system/doc/reference_manual/modules.xml +++ b/system/doc/reference_manual/modules.xml @@ -4,7 +4,7 @@ <chapter> <header> <copyright> - <year>2003</year><year>2015</year> + <year>2003</year><year>2016</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -144,6 +144,7 @@ fact(0) -> % | <list type="bulleted"> <item><c>gen_server</c></item> <item><c>gen_fsm</c></item> + <item><c>gen_statem</c></item> <item><c>gen_event</c></item> <item><c>supervisor</c></item> </list> |