aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--erts/etc/unix/etp-commands32
-rw-r--r--lib/common_test/src/ct_logs.erl163
-rw-r--r--lib/common_test/src/cth_log_redirect.erl15
-rw-r--r--lib/common_test/test/ct_error_SUITE.erl10
-rw-r--r--lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl57
-rw-r--r--lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_helper.erl7
-rw-r--r--lib/ssh/src/ssh_sftpd.erl14
-rw-r--r--lib/ssh/test/ssh_sftp_SUITE.erl114
-rw-r--r--lib/stdlib/doc/src/digraph_utils.xml4
-rw-r--r--lib/stdlib/doc/src/supervisor.xml12
-rw-r--r--lib/stdlib/src/digraph_utils.erl4
-rw-r--r--lib/stdlib/src/supervisor.erl170
-rw-r--r--lib/stdlib/test/Makefile1
-rw-r--r--lib/stdlib/test/digraph_utils_SUITE.erl5
-rw-r--r--lib/stdlib/test/supervisor_SUITE.erl122
-rw-r--r--lib/stdlib/test/supervisor_deadlock.erl45
-rw-r--r--lib/test_server/src/test_server.erl14
-rw-r--r--lib/test_server/src/test_server_ctrl.erl7
-rw-r--r--lib/test_server/src/test_server_h.erl44
-rw-r--r--lib/tools/test/xref_SUITE.erl10
20 files changed, 631 insertions, 219 deletions
diff --git a/erts/etc/unix/etp-commands b/erts/etc/unix/etp-commands
index 6a01e0b7e0..79e5d6b1d8 100644
--- a/erts/etc/unix/etp-commands
+++ b/erts/etc/unix/etp-commands
@@ -1883,6 +1883,28 @@ document etp-ets-tables
%---------------------------------------------------------------------------
end
+define etp-ets-obj
+# Args: DbTerm*
+#
+ set $etp_ets_obj_i = 1
+ while $etp_ets_obj_i <= (($arg0)->tpl[0] >> 6)
+ if $etp_ets_obj_i == 1
+ printf "{"
+ else
+ printf ", "
+ end
+ set $etp_ets_elem = ($arg0)->tpl[$etp_ets_obj_i]
+ if ($etp_ets_elem & 3) == 0
+ printf "<compressed>"
+ else
+ etp-1 $etp_ets_elem 0
+ end
+ set $etp_ets_obj_i++
+ end
+ printf "}"
+end
+
+
define etp-ets-tabledump
# Args: int tableindex
#
@@ -1896,10 +1918,10 @@ define etp-ets-tabledump
if $etp_ets_tabledump_t->common.status & 0x130
# Hash table
set $etp_ets_tabledump_h = $etp_ets_tabledump_t->hash
- printf "%% nitems=%d\n", $etp_ets_tabledump_t->common.nitems
- while $etp_ets_tabledump_i < $etp_ets_tabledump_h->nactive
- set $etp_ets_tabledump_l = $etp_ets_tabledump_h->seg \
- [$etp_ets_tabledump_i>>8][$etp_ets_tabledump_i&0xFF]
+ printf "%% nitems=%d\n", (long) $etp_ets_tabledump_t->common.nitems
+ while $etp_ets_tabledump_i < (long) $etp_ets_tabledump_h->nactive
+ set $etp_ets_tabledump_seg = ((struct segment**)$etp_ets_tabledump_h->segtab)[$etp_ets_tabledump_i>>8]
+ set $etp_ets_tabledump_l = $etp_ets_tabledump_seg->buckets[$etp_ets_tabledump_i&0xFF]
if $etp_ets_tabledump_l
printf "%% Slot %d:\n", $etp_ets_tabledump_i
while $etp_ets_tabledump_l
@@ -1909,7 +1931,7 @@ define etp-ets-tabledump
printf "["
end
set $etp_ets_tabledump_n++
- etp-1 ((Eterm)($etp_ets_tabledump_l->dbterm.tpl)|0x2) 0
+ etp-ets-obj &($etp_ets_tabledump_l->dbterm)
if $etp_ets_tabledump_l->hvalue == ((unsigned long)-1)
printf "% *\n"
else
diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl
index 19ad7b26d8..5f0626c0b0 100644
--- a/lib/common_test/src/ct_logs.erl
+++ b/lib/common_test/src/ct_logs.erl
@@ -38,7 +38,7 @@
-export([get_ts_html_wrapper/3]).
%% Logging stuff directly from testcase
--export([tc_log/3,tc_print/3,tc_pal/3,ct_log/3,
+-export([tc_log/3,tc_log_async/3,tc_print/3,tc_pal/3,ct_log/3,
basic_html/0]).
%% Simulate logger process for use without ct environment running
@@ -239,7 +239,7 @@ end_tc(TCPid) ->
%%% activity it is. <code>Format</code> and <code>Args</code> is the
%%% data to log (as in <code>io:format(Format,Args)</code>).</p>
log(Heading,Format,Args) ->
- cast({log,self(),group_leader(),
+ cast({log,sync,self(),group_leader(),
[{int_header(),[log_timestamp(now()),Heading]},
{Format,Args},
{int_footer(),[]}]}),
@@ -261,7 +261,7 @@ log(Heading,Format,Args) ->
%%% @see cont_log/2
%%% @see end_log/0
start_log(Heading) ->
- cast({log,self(),group_leader(),
+ cast({log,sync,self(),group_leader(),
[{int_header(),[log_timestamp(now()),Heading]}]}),
ok.
@@ -276,7 +276,7 @@ cont_log([],[]) ->
ok;
cont_log(Format,Args) ->
maybe_log_timestamp(),
- cast({log,self(),group_leader(),[{Format,Args}]}),
+ cast({log,sync,self(),group_leader(),[{Format,Args}]}),
ok.
%%%-----------------------------------------------------------------
@@ -287,7 +287,7 @@ cont_log(Format,Args) ->
%%% @see start_log/1
%%% @see cont_log/2
end_log() ->
- cast({log,self(),group_leader(),[{int_footer(), []}]}),
+ cast({log,sync,self(),group_leader(),[{int_footer(), []}]}),
ok.
@@ -333,9 +333,29 @@ add_link(Heading,File,Type) ->
%%% stuff directly from a testcase (i.e. not from within the CT
%%% framework).</p>
tc_log(Category,Format,Args) ->
- cast({log,self(),group_leader(),[{div_header(Category),[]},
- {Format,Args},
- {div_footer(),[]}]}),
+ cast({log,sync,self(),group_leader(),[{div_header(Category),[]},
+ {Format,Args},
+ {div_footer(),[]}]}),
+ ok.
+
+
+%%%-----------------------------------------------------------------
+%%% @spec tc_log_async(Category,Format,Args) -> ok
+%%% Category = atom()
+%%% Format = string()
+%%% Args = list()
+%%%
+%%% @doc Internal use only.
+%%%
+%%% <p>This function is used to perform asynchronous printouts
+%%% towards the test server IO handler. This is necessary in order
+%%% to avoid deadlocks when e.g. the hook that handles SASL printouts
+%%% prints to the test case log file at the same time test server
+%%% asks ct_logs for an html wrapper.</p>
+tc_log_async(Category,Format,Args) ->
+ cast({log,async,self(),group_leader(),[{div_header(Category),[]},
+ {Format,Args},
+ {div_footer(),[]}]}),
ok.
%%%-----------------------------------------------------------------
@@ -377,9 +397,9 @@ print_heading(Category) ->
%%% log and on the console.</p>
tc_pal(Category,Format,Args) ->
tc_print(Category,Format,Args),
- cast({log,self(),group_leader(),[{div_header(Category),[]},
- {Format,Args},
- {div_footer(),[]}]}),
+ cast({log,sync,self(),group_leader(),[{div_header(Category),[]},
+ {Format,Args},
+ {div_footer(),[]}]}),
ok.
@@ -420,7 +440,7 @@ maybe_log_timestamp() ->
{MS,S,_} ->
ok;
_ ->
- cast({log,self(),group_leader(),
+ cast({log,sync,self(),group_leader(),
[{"<i>~s</i>",[log_timestamp({MS,S,US})]}]})
end.
@@ -441,7 +461,8 @@ log_timestamp({MS,S,US}) ->
orig_GL,
ct_log_fd,
tc_groupleaders,
- stylesheet}).
+ stylesheet,
+ async_print_jobs}).
logger(Parent,Mode) ->
register(?MODULE,self()),
@@ -520,50 +541,32 @@ logger(Parent,Mode) ->
start_time=Time,
orig_GL=group_leader(),
ct_log_fd=CtLogFd,
- tc_groupleaders=[]}).
+ tc_groupleaders=[],
+ async_print_jobs=[]}).
logger_loop(State) ->
receive
- {log,Pid,GL,List} ->
- case get_groupleader(Pid,GL,State) of
+ {log,SyncOrAsync,Pid,GL,List} ->
+ case get_groupleader(Pid, GL, State) of
{tc_log,TCGL,TCGLs} ->
case erlang:is_process_alive(TCGL) of
true ->
- %% we have to build one io-list of all strings
- %% before printing, or other io printouts (made in
- %% parallel) may get printed between this header
- %% and footer
- Fun =
- fun({Str,Args},IoList) ->
- case catch io_lib:format(Str,Args) of
- {'EXIT',_Reason} ->
- Fd = State#logger_state.ct_log_fd,
- io:format(Fd,
- "Logging fails! "
- "Str: ~p, Args: ~p~n",
- [Str,Args]),
- %% stop the testcase, we need
- %% to see the fault
- exit(Pid,{log_printout_error,Str,Args}),
- [];
- IoStr when IoList == [] ->
- [IoStr];
- IoStr ->
- [IoList,"\n",IoStr]
- end
- end,
- io:format(TCGL,"~s",[lists:foldl(Fun,[],List)]),
- logger_loop(State#logger_state{tc_groupleaders=TCGLs});
+ State1 = print_to_log(SyncOrAsync, Pid, TCGL,
+ List, State),
+ logger_loop(State1#logger_state{tc_groupleaders =
+ TCGLs});
false ->
- %% Group leader is dead, so write to the CtLog instead
+ %% Group leader is dead, so write to the
+ %% CtLog instead
Fd = State#logger_state.ct_log_fd,
[begin io:format(Fd,Str,Args),io:nl(Fd) end ||
{Str,Args} <- List],
logger_loop(State)
end;
{ct_log,Fd,TCGLs} ->
- [begin io:format(Fd,Str,Args),io:nl(Fd) end || {Str,Args} <- List],
- logger_loop(State#logger_state{tc_groupleaders=TCGLs})
+ [begin io:format(Fd,Str,Args),io:nl(Fd) end ||
+ {Str,Args} <- List],
+ logger_loop(State#logger_state{tc_groupleaders = TCGLs})
end;
{{init_tc,TCPid,GL,RefreshLog},From} ->
print_style(GL, State#logger_state.stylesheet),
@@ -575,11 +578,12 @@ logger_loop(State) ->
make_last_run_index(State#logger_state.start_time)
end,
return(From,ok),
- logger_loop(State#logger_state{tc_groupleaders=TCGLs});
+ logger_loop(State#logger_state{tc_groupleaders = TCGLs});
{{end_tc,TCPid},From} ->
set_evmgr_gl(State#logger_state.ct_log_fd),
return(From,ok),
- logger_loop(State#logger_state{tc_groupleaders=rm_tc_gl(TCPid,State)});
+ logger_loop(State#logger_state{tc_groupleaders =
+ rm_tc_gl(TCPid,State)});
{{get_log_dir,true},From} ->
return(From,{ok,State#logger_state.log_dir}),
logger_loop(State);
@@ -590,21 +594,35 @@ logger_loop(State) ->
make_last_run_index(State#logger_state.start_time),
return(From,filename:basename(State#logger_state.log_dir)),
logger_loop(State);
- {set_stylesheet,_,SSFile} when State#logger_state.stylesheet == SSFile ->
+ {set_stylesheet,_,SSFile} when State#logger_state.stylesheet ==
+ SSFile ->
logger_loop(State);
{set_stylesheet,TC,SSFile} ->
Fd = State#logger_state.ct_log_fd,
- io:format(Fd, "~p loading external style sheet: ~s~n", [TC,SSFile]),
- logger_loop(State#logger_state{stylesheet=SSFile});
+ io:format(Fd, "~p loading external style sheet: ~s~n",
+ [TC,SSFile]),
+ logger_loop(State#logger_state{stylesheet = SSFile});
{clear_stylesheet,_} when State#logger_state.stylesheet == undefined ->
logger_loop(State);
{clear_stylesheet,_} ->
- logger_loop(State#logger_state{stylesheet=undefined});
+ logger_loop(State#logger_state{stylesheet = undefined});
{ct_log, List} ->
Fd = State#logger_state.ct_log_fd,
[begin io:format(Fd,Str,Args),io:nl(Fd) end ||
{Str,Args} <- List],
logger_loop(State);
+ {'DOWN',Ref,_,_Pid,_} ->
+ %% there might be print jobs executing in parallel with ct_logs
+ %% and whenever one is finished (indicated by 'DOWN'), the
+ %% next job should be spawned
+ case lists:delete(Ref, State#logger_state.async_print_jobs) of
+ [] ->
+ logger_loop(State#logger_state{async_print_jobs = []});
+ Jobs ->
+ [Next|JobsRev] = lists:reverse(Jobs),
+ Jobs1 = [print_next(Next)|lists:reverse(JobsRev)],
+ logger_loop(State#logger_state{async_print_jobs = Jobs1})
+ end;
stop ->
io:format(State#logger_state.ct_log_fd,
int_header()++int_footer(),
@@ -613,6 +631,49 @@ logger_loop(State) ->
ok
end.
+create_io_fun(FromPid, State) ->
+ %% we have to build one io-list of all strings
+ %% before printing, or other io printouts (made in
+ %% parallel) may get printed between this header
+ %% and footer
+ Fd = State#logger_state.ct_log_fd,
+ fun({Str,Args}, IoList) ->
+ case catch io_lib:format(Str,Args) of
+ {'EXIT',_Reason} ->
+ io:format(Fd, "Logging fails! Str: ~p, Args: ~p~n",
+ [Str,Args]),
+ %% stop the testcase, we need to see the fault
+ exit(FromPid, {log_printout_error,Str,Args}),
+ [];
+ IoStr when IoList == [] ->
+ [IoStr];
+ IoStr ->
+ [IoList,"\n",IoStr]
+ end
+ end.
+
+print_to_log(sync, FromPid, TCGL, List, State) ->
+ IoFun = create_io_fun(FromPid, State),
+ io:format(TCGL, "~s", [lists:foldl(IoFun, [], List)]),
+ State;
+
+print_to_log(async, FromPid, TCGL, List, State) ->
+ IoFun = create_io_fun(FromPid, State),
+ Printer = fun() ->
+ io:format(TCGL, "~s", [lists:foldl(IoFun, [], List)])
+ end,
+ case State#logger_state.async_print_jobs of
+ [] ->
+ {_Pid,Ref} = spawn_monitor(Printer),
+ State#logger_state{async_print_jobs = [Ref]};
+ Queue ->
+ State#logger_state{async_print_jobs = [Printer|Queue]}
+ end.
+
+print_next(PrintFun) ->
+ {_Pid,Ref} = spawn_monitor(PrintFun),
+ Ref.
+
%% #logger_state.tc_groupleaders == [{Pid,{Type,GLPid}},...]
%% Type = tc | io
%%
@@ -1894,7 +1955,7 @@ simulate() ->
simulate_logger_loop() ->
receive
- {log,_,_,List} ->
+ {log,_,_,_,List} ->
S = [[io_lib:format(Str,Args),io_lib:nl()] || {Str,Args} <- List],
io:format("~s",[S]),
simulate_logger_loop();
diff --git a/lib/common_test/src/cth_log_redirect.erl b/lib/common_test/src/cth_log_redirect.erl
index 14663b7738..ea79251cfc 100644
--- a/lib/common_test/src/cth_log_redirect.erl
+++ b/lib/common_test/src/cth_log_redirect.erl
@@ -31,22 +31,22 @@
%% Event handler Callbacks
-export([init/1,
handle_event/2, handle_call/2, handle_info/2,
- terminate/2]).
+ terminate/1]).
id(_Opts) ->
?MODULE.
init(?MODULE, _Opts) ->
error_logger:add_report_handler(?MODULE),
- tc_log.
+ tc_log_async.
-post_init_per_group(Group, Config, Result, tc_log) ->
+post_init_per_group(Group, Config, Result, tc_log_async) ->
case lists:member(parallel,proplists:get_value(
tc_group_properties,Config,[])) of
true ->
{Result, {set_log_func(ct_log),Group}};
false ->
- {Result, tc_log}
+ {Result, tc_log_async}
end;
post_init_per_group(_Group, _Config, Result, State) ->
{Result, State}.
@@ -58,14 +58,14 @@ post_end_per_testcase(_TC, _Config, Result, State) ->
{Result, State}.
pre_end_per_group(Group, Config, {ct_log, Group}) ->
- {Config, set_log_func(tc_log)};
+ {Config, set_log_func(tc_log_async)};
pre_end_per_group(_Group, Config, State) ->
{Config, State}.
%% Copied and modified from sasl_report_tty_h.erl
init(_Type) ->
- {ok, tc_log}.
+ {ok, tc_log_async}.
handle_event({_Type, GL, _Msg}, State) when node(GL) /= node() ->
{ok, State};
@@ -101,7 +101,8 @@ handle_call({set_logfunc,NewLogFunc},_) ->
{ok, NewLogFunc, NewLogFunc};
handle_call(_Query, _State) -> {error, bad_query}.
-terminate(_Reason, _Type) ->
+terminate(_State) ->
+ error_logger:delete_report_handler(?MODULE),
[].
tag_event(Event) ->
diff --git a/lib/common_test/test/ct_error_SUITE.erl b/lib/common_test/test/ct_error_SUITE.erl
index 2b3157ff3b..484b0aac0c 100644
--- a/lib/common_test/test/ct_error_SUITE.erl
+++ b/lib/common_test/test/ct_error_SUITE.erl
@@ -682,7 +682,7 @@ test_events(timetrap_end_conf) ->
[
{?eh,start_logging,{'DEF','RUNDIR'}},
{?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
- {?eh,start_info,{1,1,6}},
+ {?eh,start_info,{1,1,8}},
{?eh,tc_start,{timetrap_1_SUITE,init_per_suite}},
{?eh,tc_done,{timetrap_1_SUITE,init_per_suite,ok}},
{?eh,tc_start,{timetrap_1_SUITE,tc1}},
@@ -709,6 +709,14 @@ test_events(timetrap_end_conf) ->
{?eh,tc_done,
{timetrap_1_SUITE,tc6,{failed,{testcase_aborted,testing_end_conf}}}},
{?eh,test_stats,{0,6,{0,0}}},
+ {?eh,tc_start,{timetrap_1_SUITE,tc7}},
+ {?eh,tc_done,
+ {timetrap_1_SUITE,tc7,{failed,{timetrap_timeout,1000}}}},
+ {?eh,test_stats,{0,7,{0,0}}},
+ {?eh,tc_start,{timetrap_1_SUITE,tc8}},
+ {?eh,tc_done,
+ {timetrap_1_SUITE,tc8,{failed,{timetrap_timeout,1000}}}},
+ {?eh,test_stats,{0,8,{0,0}}},
{?eh,tc_start,{timetrap_1_SUITE,end_per_suite}},
{?eh,tc_done,{timetrap_1_SUITE,end_per_suite,ok}},
{?eh,test_done,{'DEF','STOP_TIME'}},
diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl
index cb3109349b..faa0a7305c 100644
--- a/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl
+++ b/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl
@@ -83,23 +83,11 @@ init_per_testcase(TC, Config) ->
ets:insert(?MODULE, {last_case,fail}),
init_per_testcase1(TC, Config).
-init_per_testcase1(tc1, Config) ->
- [{tc,tc1}|Config];
-
-init_per_testcase1(tc2, Config) ->
- [{tc,tc2}|Config];
-
-init_per_testcase1(tc3, Config) ->
- [{tc,tc3}|Config];
-
init_per_testcase1(tc4, Config) ->
[{tc,tc4},{default_timeout,5000}|Config];
-init_per_testcase1(tc5, Config) ->
- [{tc,tc5}|Config];
-
-init_per_testcase1(tc6, Config) ->
- [{tc,tc6}|Config].
+init_per_testcase1(TC, Config) ->
+ [{tc,TC}|Config].
%%--------------------------------------------------------------------
%% Function: end_per_testcase(TestCase, Config0) ->
@@ -145,7 +133,19 @@ end_per_testcase1(tc5, Config) ->
end_per_testcase1(tc6, Config) ->
ct:pal("end_per_testcase(tc6): ~p", [Config]),
tc6 = ?config(tc, Config),
- exit(end_per_tc_fail_after_abort).
+ exit(end_per_tc_fail_after_abort);
+
+end_per_testcase1(tc7, Config) ->
+ ct:pal("end_per_testcase(tc7): ~p", [Config]),
+ tc7 = ?config(tc, Config),
+ {failed,timetrap_timeout} = ?config(tc_status, Config),
+ ok;
+
+end_per_testcase1(tc8, Config) ->
+ ct:pal("end_per_testcase(tc8): ~p", [Config]),
+ tc8 = ?config(tc, Config),
+ {failed,timetrap_timeout} = ?config(tc_status, Config),
+ ok.
%%--------------------------------------------------------------------
%% Function: groups() -> [Group]
@@ -170,25 +170,42 @@ groups() ->
%% Reason = term()
%%--------------------------------------------------------------------
all() ->
- [tc1, tc2, tc3, tc4, tc5, tc6].
+ [tc1, tc2, tc3, tc4, tc5, tc6, tc7, tc8].
tc1(_) ->
- timer:sleep(2000).
+ timer:sleep(2000),
+ ok.
tc2(_) ->
timer:sleep(2000).
tc3(_) ->
spawn(ct, abort_current_testcase, [testing_end_conf]),
- timer:sleep(2000).
+ timer:sleep(2000),
+ ok.
tc4(_) ->
spawn(ct, abort_current_testcase, [testing_end_conf]),
- timer:sleep(2000).
+ timer:sleep(2000),
+ ok.
tc5(_) ->
- timer:sleep(2000).
+ timer:sleep(2000),
+ ok.
tc6(_) ->
spawn(ct, abort_current_testcase, [testing_end_conf]),
timer:sleep(2000).
+
+tc7(_) ->
+ sleep(2000),
+ ok.
+
+tc8(_) ->
+ timetrap_helper:sleep(2000),
+ ok.
+
+%%%-----------------------------------------------------------------
+sleep(T) ->
+ timer:sleep(T),
+ ok.
diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_helper.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_helper.erl
new file mode 100644
index 0000000000..1389acca11
--- /dev/null
+++ b/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_helper.erl
@@ -0,0 +1,7 @@
+-module(timetrap_helper).
+
+-export([sleep/1]).
+
+sleep(T) ->
+ timer:sleep(T),
+ ok.
diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl
index 8cc414f83a..ec7b76b0b3 100644
--- a/lib/ssh/src/ssh_sftpd.erl
+++ b/lib/ssh/src/ssh_sftpd.erl
@@ -424,18 +424,18 @@ handle_op(?SSH_FXP_RENAME, ReqId,
State0
end;
handle_op(?SSH_FXP_SYMLINK, ReqId,
- <<?UINT32(PLen), BPath:PLen/binary, ?UINT32(PLen2),
- BPath2:PLen2/binary>>,
+ <<?UINT32(PLen), Link:PLen/binary, ?UINT32(PLen2),
+ Target:PLen2/binary>>,
State0 = #state{file_handler = FileMod, file_state = FS0}) ->
- Path = relate_file_name(BPath, State0),
- Path2 = relate_file_name(BPath2, State0),
- {Status, FS1} = FileMod:make_symlink(Path2, Path, FS0),
+ LinkPath = relate_file_name(Link, State0),
+ TargetPath = relate_file_name(Target, State0),
+ {Status, FS1} = FileMod:make_symlink(TargetPath, LinkPath, FS0),
State1 = State0#state{file_state = FS1},
send_status(Status, ReqId, State1).
new_handle([], H) ->
H;
-new_handle([{N, _} | Rest], H) when N > H ->
+new_handle([{N, _,_} | Rest], H) when N =< H ->
new_handle(Rest, N+1);
new_handle([_ | Rest], H) ->
new_handle(Rest, H).
@@ -444,6 +444,8 @@ add_handle(State, XF, ReqId, Type, DirFileTuple) ->
Handles = State#state.handles,
Handle = new_handle(Handles, 0),
ssh_xfer:xf_send_handle(XF, ReqId, integer_to_list(Handle)),
+ %% OBS: If you change handles-tuple also change new_handle!
+ %% Is this this the best way to implement new handle?
State#state{handles = [{Handle, Type, DirFileTuple} | Handles]}.
get_handle(Handles, BinHandle) ->
diff --git a/lib/ssh/test/ssh_sftp_SUITE.erl b/lib/ssh/test/ssh_sftp_SUITE.erl
index 7644db155d..d40b1d544d 100644
--- a/lib/ssh/test/ssh_sftp_SUITE.erl
+++ b/lib/ssh/test/ssh_sftp_SUITE.erl
@@ -76,42 +76,34 @@ end_per_suite(Config) ->
%% variable, but should NOT alter/remove any existing entries.
%% Description: Initiation before each test case
%%--------------------------------------------------------------------
-init_per_testcase(_Case, Config) ->
+init_per_testcase(Case, Config) ->
prep(Config),
TmpConfig0 = lists:keydelete(watchdog, 1, Config),
TmpConfig = lists:keydelete(sftp, 1, TmpConfig0),
Dog = test_server:timetrap(?default_timeout),
- PrivDir = ?config(priv_dir, Config),
- SysDir = ?config(data_dir, Config),
- Host = ssh_test_lib:hostname(),
- %% Run test against openssh server if available
- Sftp = case (catch ssh_sftp:start_channel(Host,
- [{user_interaction, false},
- {silently_accept_hosts, true}])) of
- {ok, ChannelPid, Connection} ->
- test_server:format("Running against openssh"),
- {ChannelPid, Connection};
- _Error -> %% Start own sftp
- test_server:format("Running against erlang ssh"),
- {_Sftpd, Host1, Port} =
- ssh_test_lib:daemon([{system_dir, SysDir},
- {user_dir, PrivDir},
- {user_passwords,
- [{?USER, ?PASSWD}]},
- {failfun,
- fun ssh_test_lib:failfun/2}]),
- Result = (catch ssh_sftp:start_channel(Host1, Port,
- [{user, ?USER},
- {password, ?PASSWD},
- {user_dir, PrivDir},
- {user_interaction, false},
- {silently_accept_hosts, true}])),
- {ok, ChannelPid, Connection} = Result,
- {ChannelPid, Connection}
- end,
-
- [{sftp, Sftp}, {watchdog, Dog} | TmpConfig].
+ case ?config(group, Config) of
+ erlang_server ->
+ {_,Host, Port} = ?config(sftpd, Config),
+ {ok, ChannelPid, Connection} =
+ ssh_sftp:start_channel(Host, Port,
+ [{user, ?USER},
+ {password, ?PASSWD},
+ {user_interaction, false},
+ {silently_accept_hosts, true}]),
+ Sftp = {ChannelPid, Connection},
+ [{sftp, Sftp}, {watchdog, Dog} | TmpConfig];
+ openssh_server when Case == links ->
+ {skip, "known bug in openssh"};
+ openssh_server ->
+ Host = ssh_test_lib:hostname(),
+ {ok, ChannelPid, Connection} =
+ ssh_sftp:start_channel(Host,
+ [{user_interaction, false},
+ {silently_accept_hosts, true}]),
+ Sftp = {ChannelPid, Connection},
+ [{sftp, Sftp}, {watchdog, Dog} | TmpConfig]
+ end.
%%--------------------------------------------------------------------
%% Function: end_per_testcase(TestCase, Config) -> _
@@ -121,7 +113,15 @@ init_per_testcase(_Case, Config) ->
%% A list of key/value pairs, holding the test case configuration.
%% Description: Cleanup after each test case
%%--------------------------------------------------------------------
-end_per_testcase(_Case, Config) ->
+end_per_testcase(rename_file, Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ NewFileName = filename:join(PrivDir, "test.txt"),
+ file:delete(NewFileName),
+ end_per_testcase(Config);
+end_per_testcase(_, Config) ->
+ end_per_testcase(Config).
+
+end_per_testcase(Config) ->
{Sftp, Connection} = ?config(sftp, Config),
ssh_sftp:stop_channel(Sftp),
ssh:close(Connection),
@@ -138,19 +138,47 @@ end_per_testcase(_Case, Config) ->
%% Description: Returns a list of all test cases in this test suite
%%--------------------------------------------------------------------
all() ->
- [open_close_file, open_close_dir, read_file, read_dir,
- write_file, rename_file, mk_rm_dir, remove_file, links,
- retrieve_attributes, set_attributes, async_read,
- async_write, position, pos_read, pos_write].
+ [{group, erlang_server},
+ {group, openssh_server}].
groups() ->
- [].
-
-init_per_group(_GroupName, Config) ->
- Config.
+ [{erlang_server, [], [open_close_file, open_close_dir, read_file, read_dir,
+ write_file, rename_file, mk_rm_dir, remove_file, links,
+ retrieve_attributes, set_attributes, async_read,
+ async_write, position, pos_read, pos_write]},
+ {openssh_server, [], [open_close_file, open_close_dir, read_file, read_dir,
+ write_file, rename_file, mk_rm_dir, remove_file, links,
+ retrieve_attributes, set_attributes, async_read,
+ async_write, position, pos_read, pos_write]}].
+
+init_per_group(erlang_server, Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ SysDir = ?config(data_dir, Config),
+ Sftpd =
+ ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, PrivDir},
+ {user_passwords,
+ [{?USER, ?PASSWD}]},
+ {failfun,
+ fun ssh_test_lib:failfun/2}]),
+ [{group, erlang_server}, {sftpd, Sftpd} | Config];
+
+init_per_group(openssh_server, Config) ->
+ Host = ssh_test_lib:hostname(),
+ case (catch ssh_sftp:start_channel(Host,
+ [{user_interaction, false},
+ {silently_accept_hosts, true}])) of
+ {ok, _ChannelPid, Connection} ->
+ ssh:close(Connection),
+ [{group, openssh_server} | Config];
+ _ ->
+ {skip, "No openssh server"}
+ end.
-end_per_group(_GroupName, Config) ->
- Config.
+end_per_group(erlang_server, Config) ->
+ Config;
+end_per_group(_, Config) ->
+ Config.
%% Test cases starts here.
@@ -332,7 +360,7 @@ links(Config) when is_list(Config) ->
FileName = filename:join(PrivDir, "sftp.txt"),
LinkFileName = filename:join(PrivDir, "link_test.txt"),
- ok = ssh_sftp:make_symlink(Sftp, FileName, LinkFileName),
+ ok = ssh_sftp:make_symlink(Sftp, LinkFileName, FileName),
{ok, FileName} = ssh_sftp:read_link(Sftp, LinkFileName),
ok
end.
diff --git a/lib/stdlib/doc/src/digraph_utils.xml b/lib/stdlib/doc/src/digraph_utils.xml
index e44632bfd2..ef6e1cb46f 100644
--- a/lib/stdlib/doc/src/digraph_utils.xml
+++ b/lib/stdlib/doc/src/digraph_utils.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2000</year><year>2011</year>
+ <year>2000</year><year>2012</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -156,7 +156,7 @@
<p>Creates a digraph where the vertices are
the <seealso marker="#strong_components">strongly connected
components</seealso> of <c><anno>Digraph</anno></c> as returned by
- <c>strong_components/1</c>. If X and Y are strongly
+ <c>strong_components/1</c>. If X and Y are two different strongly
connected components, and there exist vertices x and y in X
and Y respectively such that there is an
edge <seealso marker="#emanate">emanating</seealso> from x
diff --git a/lib/stdlib/doc/src/supervisor.xml b/lib/stdlib/doc/src/supervisor.xml
index d1e62230bc..35f4f82264 100644
--- a/lib/stdlib/doc/src/supervisor.xml
+++ b/lib/stdlib/doc/src/supervisor.xml
@@ -399,10 +399,11 @@ child_spec() = {Id,StartFunc,Restart,Shutdown,Type,Modules}
<c>SupRef</c>.</p>
<p>If successful, the function returns <c>ok</c>. If the child
specification identified by <c><anno>Id</anno></c> exists but
- the corresponding child process is running, the function
- returns <c>{error,running}</c>. If the child specification
- identified by <c><anno>Id</anno></c> does not exist, the function returns
- <c>{error,not_found}</c>.</p>
+ the corresponding child process is running or about to be restarted,
+ the function returns <c>{error,running}</c> or
+ <c>{error,restarting}</c> respectively. If the child specification
+ identified by <c><anno>Id</anno></c> does not exist, the function
+ returns <c>{error,not_found}</c>.</p>
</desc>
</func>
<func>
@@ -462,7 +463,8 @@ child_spec() = {Id,StartFunc,Restart,Shutdown,Type,Modules}
</item>
<item>
<p><c><anno>Child</anno></c> - the pid of the corresponding child
- process, or <c>undefined</c> if there is no such process.</p>
+ process, the atom <c>restarting</c> if the process is about to be
+ restarted or <c>undefined</c> if there is no such process.</p>
</item>
<item>
<p><c><anno>Type</anno></c> - as defined in the child specification.</p>
diff --git a/lib/stdlib/src/digraph_utils.erl b/lib/stdlib/src/digraph_utils.erl
index e221be15a1..807b5c12a1 100644
--- a/lib/stdlib/src/digraph_utils.erl
+++ b/lib/stdlib/src/digraph_utils.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2011. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2012. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -370,5 +370,5 @@ condense('$end_of_table', _T, _SC, _G, _SCG, _I2C) ->
condense(I, T, SC, G, SCG, I2C) ->
[{_,C}] = ets:lookup(I2C, I),
digraph:add_vertex(SCG, C),
- digraph:add_edge(SCG, SC, C),
+ [digraph:add_edge(SCG, SC, C) || C =/= SC],
condense(ets:next(T, I), T, SC, G, SCG, I2C).
diff --git a/lib/stdlib/src/supervisor.erl b/lib/stdlib/src/supervisor.erl
index ac5b078c29..f315064b03 100644
--- a/lib/stdlib/src/supervisor.erl
+++ b/lib/stdlib/src/supervisor.erl
@@ -28,8 +28,9 @@
check_childspecs/1]).
%% Internal exports
--export([init/1, handle_call/3, handle_info/2, terminate/2, code_change/3]).
--export([handle_cast/2]).
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+-export([try_again_restart/2]).
%%--------------------------------------------------------------------------
@@ -37,7 +38,7 @@
%%--------------------------------------------------------------------------
--type child() :: 'undefined' | pid() | [pid()].
+-type child() :: 'undefined' | pid().
-type child_id() :: term().
-type mfargs() :: {M :: module(), F :: atom(), A :: [term()] | undefined}.
-type modules() :: [module()] | 'dynamic'.
@@ -62,8 +63,8 @@
%%--------------------------------------------------------------------------
-record(child, {% pid is undefined when child is not running
- pid = undefined :: child(),
- name,
+ pid = undefined :: child() | {restarting,pid()} | [pid()],
+ name :: child_id(),
mfargs :: mfargs(),
restart_type :: restart(),
shutdown :: shutdown(),
@@ -95,6 +96,8 @@
[ChildSpec :: child_spec()]}}
| ignore.
+-define(restarting(_Pid_), {restarting,_Pid_}).
+
%%% ---------------------------------------------------
%%% This is a general process supervisor built upon gen_server.erl.
%%% Servers/processes should/could also be built using gen_server.erl.
@@ -139,7 +142,8 @@ start_child(Supervisor, ChildSpec) ->
Result :: {'ok', Child :: child()}
| {'ok', Child :: child(), Info :: term()}
| {'error', Error},
- Error :: 'running' | 'not_found' | 'simple_one_for_one' | term().
+ Error :: 'running' | 'restarting' | 'not_found' | 'simple_one_for_one' |
+ term().
restart_child(Supervisor, Name) ->
call(Supervisor, {restart_child, Name}).
@@ -147,7 +151,7 @@ restart_child(Supervisor, Name) ->
SupRef :: sup_ref(),
Id :: child_id(),
Result :: 'ok' | {'error', Error},
- Error :: 'running' | 'not_found' | 'simple_one_for_one'.
+ Error :: 'running' | 'restarting' | 'not_found' | 'simple_one_for_one'.
delete_child(Supervisor, Name) ->
call(Supervisor, {delete_child, Name}).
@@ -169,7 +173,7 @@ terminate_child(Supervisor, Name) ->
-spec which_children(SupRef) -> [{Id,Child,Type,Modules}] when
SupRef :: sup_ref(),
Id :: child_id() | undefined,
- Child :: child(),
+ Child :: child() | 'restarting',
Type :: worker(),
Modules :: modules().
which_children(Supervisor) ->
@@ -198,6 +202,17 @@ check_childspecs(ChildSpecs) when is_list(ChildSpecs) ->
end;
check_childspecs(X) -> {error, {badarg, X}}.
+%%%-----------------------------------------------------------------
+%%% Called by timer:apply_after from restart/2
+-spec try_again_restart(SupRef, Child) -> ok when
+ SupRef :: sup_ref(),
+ Child :: child_id() | pid().
+try_again_restart(Supervisor, Child) ->
+ cast(Supervisor, {try_again_restart, Child}).
+
+cast(Supervisor, Req) ->
+ gen_server:cast(Supervisor, Req).
+
%%% ---------------------------------------------------
%%%
%%% Initialize the supervisor.
@@ -384,6 +399,8 @@ handle_call({restart_child, Name}, _From, State) ->
Error ->
{reply, Error, State}
end;
+ {value, #child{pid=?restarting(_)}} ->
+ {reply, {error, restarting}, State};
{value, _} ->
{reply, {error, running}, State};
_ ->
@@ -395,6 +412,8 @@ handle_call({delete_child, Name}, _From, State) ->
{value, Child} when Child#child.pid =:= undefined ->
NState = remove_child(Child, State),
{reply, ok, NState};
+ {value, #child{pid=?restarting(_)}} ->
+ {reply, {error, restarting}, State};
{value, _} ->
{reply, {error, running}, State};
_ ->
@@ -413,13 +432,17 @@ handle_call(which_children, _From, #state{children = [#child{restart_type = RTyp
child_type = CT,
modules = Mods}]} =
State) when ?is_simple(State) ->
- Reply = lists:map(fun({Pid, _}) -> {undefined, Pid, CT, Mods} end,
+ Reply = lists:map(fun({?restarting(_),_}) -> {undefined,restarting,CT,Mods};
+ ({Pid, _}) -> {undefined, Pid, CT, Mods} end,
?DICT:to_list(dynamics_db(RType, State#state.dynamics))),
{reply, Reply, State};
handle_call(which_children, _From, State) ->
Resp =
- lists:map(fun(#child{pid = Pid, name = Name,
+ lists:map(fun(#child{pid = ?restarting(_), name = Name,
+ child_type = ChildType, modules = Mods}) ->
+ {Name, restarting, ChildType, Mods};
+ (#child{pid = Pid, name = Name,
child_type = ChildType, modules = Mods}) ->
{Name, Pid, ChildType, Mods}
end,
@@ -432,8 +455,11 @@ handle_call(count_children, _From, #state{children = [#child{restart_type = temp
when ?is_simple(State) ->
{Active, Count} =
?SETS:fold(fun(Pid, {Alive, Tot}) ->
- if is_pid(Pid) -> {Alive+1, Tot +1};
- true -> {Alive, Tot + 1} end
+ case is_pid(Pid) andalso is_process_alive(Pid) of
+ true ->{Alive+1, Tot +1};
+ false ->
+ {Alive, Tot + 1}
+ end
end, {0, 0}, dynamics_db(temporary, State#state.dynamics)),
Reply = case CT of
supervisor -> [{specs, 1}, {active, Active},
@@ -448,8 +474,12 @@ handle_call(count_children, _From, #state{children = [#child{restart_type = RTy
when ?is_simple(State) ->
{Active, Count} =
?DICT:fold(fun(Pid, _Val, {Alive, Tot}) ->
- if is_pid(Pid) -> {Alive+1, Tot +1};
- true -> {Alive, Tot + 1} end
+ case is_pid(Pid) andalso is_process_alive(Pid) of
+ true ->
+ {Alive+1, Tot +1};
+ false ->
+ {Alive, Tot + 1}
+ end
end, {0, 0}, dynamics_db(RType, State#state.dynamics)),
Reply = case CT of
supervisor -> [{specs, 1}, {active, Active},
@@ -486,14 +516,42 @@ count_child(#child{pid = Pid, child_type = supervisor},
end.
-%%% Hopefully cause a function-clause as there is no API function
-%%% that utilizes cast.
--spec handle_cast('null', state()) -> {'noreply', state()}.
+%%% If a restart attempt failed, this message is sent via
+%%% timer:apply_after(0,...) in order to give gen_server the chance to
+%%% check it's inbox before trying again.
+-spec handle_cast({try_again_restart, child_id() | pid()}, state()) ->
+ {'noreply', state()} | {stop, shutdown, state()}.
-handle_cast(null, State) ->
- error_logger:error_msg("ERROR: Supervisor received cast-message 'null'~n",
- []),
- {noreply, State}.
+handle_cast({try_again_restart,Pid}, #state{children=[Child]}=State)
+ when ?is_simple(State) ->
+ RT = Child#child.restart_type,
+ RPid = restarting(Pid),
+ case dynamic_child_args(RPid, dynamics_db(RT, State#state.dynamics)) of
+ {ok, Args} ->
+ {M, F, _} = Child#child.mfargs,
+ NChild = Child#child{pid = RPid, mfargs = {M, F, Args}},
+ case restart(NChild,State) of
+ {ok, State1} ->
+ {noreply, State1};
+ {shutdown, State1} ->
+ {stop, shutdown, State1}
+ end;
+ error ->
+ {noreply, State}
+ end;
+
+handle_cast({try_again_restart,Name}, State) ->
+ case lists:keyfind(Name,#child.name,State#state.children) of
+ Child = #child{pid=?restarting(_)} ->
+ case restart(Child,State) of
+ {ok, State1} ->
+ {noreply, State1};
+ {shutdown, State1} ->
+ {stop, shutdown, State1}
+ end;
+ _ ->
+ {noreply,State}
+ end.
%%
%% Take care of terminated children.
@@ -624,7 +682,7 @@ handle_start_child(Child, State) ->
{error, What} ->
{{error, {What, Child}}, State}
end;
- {value, OldChild} when OldChild#child.pid =/= undefined ->
+ {value, OldChild} when is_pid(OldChild#child.pid) ->
{{error, {already_started, OldChild#child.pid}}, State};
{value, _OldChild} ->
{{error, already_present}, State}
@@ -678,7 +736,21 @@ do_restart(temporary, Reason, Child, State) ->
restart(Child, State) ->
case add_restart(State) of
{ok, NState} ->
- restart(NState#state.strategy, Child, NState);
+ case restart(NState#state.strategy, Child, NState) of
+ {try_again,NState2} ->
+ %% Leaving control back to gen_server before
+ %% trying again. This way other incoming requsts
+ %% for the supervisor can be handled - e.g. a
+ %% shutdown request for the supervisor or the
+ %% child.
+ Id = if ?is_simple(State) -> Child#child.pid;
+ true -> Child#child.name
+ end,
+ timer:apply_after(0,?MODULE,try_again_restart,[self(),Id]),
+ {ok,NState2};
+ Other ->
+ Other
+ end;
{terminate, NState} ->
report_error(shutdown, reached_max_restart_intensity,
Child, State#state.name),
@@ -686,9 +758,9 @@ restart(Child, State) ->
end.
restart(simple_one_for_one, Child, State) ->
- #child{mfargs = {M, F, A}} = Child,
- Dynamics = ?DICT:erase(Child#child.pid, dynamics_db(Child#child.restart_type,
- State#state.dynamics)),
+ #child{pid = OldPid, mfargs = {M, F, A}} = Child,
+ Dynamics = ?DICT:erase(OldPid, dynamics_db(Child#child.restart_type,
+ State#state.dynamics)),
case do_start_child_i(M, F, A) of
{ok, Pid} ->
NState = State#state{dynamics = ?DICT:store(Pid, A, Dynamics)},
@@ -697,10 +769,13 @@ restart(simple_one_for_one, Child, State) ->
NState = State#state{dynamics = ?DICT:store(Pid, A, Dynamics)},
{ok, NState};
{error, Error} ->
+ NState = State#state{dynamics = ?DICT:store(restarting(OldPid), A,
+ Dynamics)},
report_error(start_error, Error, Child, State#state.name),
- restart(Child, State)
+ {try_again, NState}
end;
restart(one_for_one, Child, State) ->
+ OldPid = Child#child.pid,
case do_start_child(State#state.name, Child) of
{ok, Pid} ->
NState = replace_child(Child#child{pid = Pid}, State),
@@ -709,8 +784,9 @@ restart(one_for_one, Child, State) ->
NState = replace_child(Child#child{pid = Pid}, State),
{ok, NState};
{error, Reason} ->
+ NState = replace_child(Child#child{pid = restarting(OldPid)}, State),
report_error(start_error, Reason, Child, State#state.name),
- restart(Child, State)
+ {try_again, NState}
end;
restart(rest_for_one, Child, State) ->
{ChAfter, ChBefore} = split_child(Child#child.pid, State#state.children),
@@ -719,7 +795,9 @@ restart(rest_for_one, Child, State) ->
{ok, ChAfter3} ->
{ok, State#state{children = ChAfter3 ++ ChBefore}};
{error, ChAfter3} ->
- restart(Child, State#state{children = ChAfter3 ++ ChBefore})
+ NChild = Child#child{pid=restarting(Child#child.pid)},
+ NState = State#state{children = ChAfter3 ++ ChBefore},
+ {try_again, replace_child(NChild,NState)}
end;
restart(one_for_all, Child, State) ->
Children1 = del_child(Child#child.pid, State#state.children),
@@ -728,9 +806,14 @@ restart(one_for_all, Child, State) ->
{ok, NChs} ->
{ok, State#state{children = NChs}};
{error, NChs} ->
- restart(Child, State#state{children = NChs})
+ NChild = Child#child{pid=restarting(Child#child.pid)},
+ NState = State#state{children = NChs},
+ {try_again, replace_child(NChild,NState)}
end.
+restarting(Pid) when is_pid(Pid) -> ?restarting(Pid);
+restarting(RPid) -> RPid.
+
%%-----------------------------------------------------------------
%% Func: terminate_children/2
%% Args: Children = [child_rec()] in termination order
@@ -754,7 +837,7 @@ terminate_children([Child | Children], SupName, Res) ->
terminate_children([], _SupName, Res) ->
Res.
-do_terminate(Child, SupName) when Child#child.pid =/= undefined ->
+do_terminate(Child, SupName) when is_pid(Child#child.pid) ->
case shutdown(Child#child.pid, Child#child.shutdown) of
ok ->
ok;
@@ -765,7 +848,7 @@ do_terminate(Child, SupName) when Child#child.pid =/= undefined ->
end,
Child#child{pid = undefined};
do_terminate(Child, _SupName) ->
- Child.
+ Child#child{pid = undefined}.
%%-----------------------------------------------------------------
%% Shutdowns a child. We must check the EXIT value
@@ -866,7 +949,7 @@ terminate_dynamic_children(Child, Dynamics, SupName) ->
TRef = erlang:start_timer(Time, self(), kill),
wait_dynamic_children(Child, Pids, Sz, TRef, EStack0)
end,
- %% Unrool stacked errors and report them
+ %% Unroll stacked errors and report them
?DICT:fold(fun(Reason, Ls, _) ->
report_error(shutdown_error, Reason,
Child#child{pid=Ls}, SupName)
@@ -885,7 +968,7 @@ monitor_dynamic_children(#child{restart_type=temporary}, Dynamics) ->
end
end, {?SETS:new(), ?DICT:new()}, Dynamics);
monitor_dynamic_children(#child{restart_type=RType}, Dynamics) ->
- ?DICT:fold(fun(P, _, {Pids, EStack}) ->
+ ?DICT:fold(fun(P, _, {Pids, EStack}) when is_pid(P) ->
case monitor_child(P) of
ok ->
{?SETS:add_element(P, Pids), EStack};
@@ -893,7 +976,9 @@ monitor_dynamic_children(#child{restart_type=RType}, Dynamics) ->
{Pids, EStack};
{error, Reason} ->
{Pids, ?DICT:append(Reason, P, EStack)}
- end
+ end;
+ (?restarting(_), _, {Pids, EStack}) ->
+ {Pids, EStack}
end, {?SETS:new(), ?DICT:new()}, Dynamics).
@@ -1020,13 +1105,20 @@ get_child(Name, State, _) ->
lists:keysearch(Name, #child.name, State#state.children).
get_dynamic_child(Pid, #state{children=[Child], dynamics=Dynamics}) ->
- case is_dynamic_pid(Pid, dynamics_db(Child#child.restart_type, Dynamics)) of
+ DynamicsDb = dynamics_db(Child#child.restart_type, Dynamics),
+ case is_dynamic_pid(Pid, DynamicsDb) of
true ->
{value, Child#child{pid=Pid}};
false ->
- case erlang:is_process_alive(Pid) of
- true -> false;
- false -> {value, Child}
+ RPid = restarting(Pid),
+ case is_dynamic_pid(RPid, DynamicsDb) of
+ true ->
+ {value, Child#child{pid=RPid}};
+ false ->
+ case erlang:is_process_alive(Pid) of
+ true -> false;
+ false -> {value, Child}
+ end
end
end.
diff --git a/lib/stdlib/test/Makefile b/lib/stdlib/test/Makefile
index b36265302c..4de6ea3ee7 100644
--- a/lib/stdlib/test/Makefile
+++ b/lib/stdlib/test/Makefile
@@ -67,6 +67,7 @@ MODULES= \
string_SUITE \
supervisor_1 \
supervisor_2 \
+ supervisor_deadlock \
naughty_child \
shell_SUITE \
supervisor_SUITE \
diff --git a/lib/stdlib/test/digraph_utils_SUITE.erl b/lib/stdlib/test/digraph_utils_SUITE.erl
index 12c486c25f..6b554c2fb7 100644
--- a/lib/stdlib/test/digraph_utils_SUITE.erl
+++ b/lib/stdlib/test/digraph_utils_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2012. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -211,8 +211,7 @@ condensation(Config) when is_list(Config) ->
{lists:sort(V1), lists:sort(V2)}
end,
?line Es = lists:map(Fun, digraph:edges(CG)),
- ?line [{[b],[c]},{[b],[d]},{[e,f,g],[e,f,g]},{[h],[h]},{[i,j],[i,j]}] =
- lists:sort(Es),
+ ?line [{[b],[c]},{[b],[d]}] = lists:sort(Es),
?line true = digraph:delete(CG),
?line true = digraph:delete(G),
ok.
diff --git a/lib/stdlib/test/supervisor_SUITE.erl b/lib/stdlib/test/supervisor_SUITE.erl
index 71b76c093f..767ae3d62c 100644
--- a/lib/stdlib/test/supervisor_SUITE.erl
+++ b/lib/stdlib/test/supervisor_SUITE.erl
@@ -21,7 +21,7 @@
-module(supervisor_SUITE).
-include_lib("common_test/include/ct.hrl").
--define(TIMEOUT, 1000).
+-define(TIMEOUT, ?t:minutes(1)).
%% Testserver specific export
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
@@ -62,7 +62,8 @@
do_not_save_start_parameters_for_temporary_children/1,
do_not_save_child_specs_for_temporary_children/1,
simple_one_for_one_scale_many_temporary_children/1,
- simple_global_supervisor/1]).
+ simple_global_supervisor/1, hanging_restart_loop/1,
+ hanging_restart_loop_simple/1]).
%%-------------------------------------------------------------------------
@@ -82,7 +83,7 @@ all() ->
count_children_memory, do_not_save_start_parameters_for_temporary_children,
do_not_save_child_specs_for_temporary_children,
simple_one_for_one_scale_many_temporary_children, temporary_bystander,
- simple_global_supervisor].
+ simple_global_supervisor, hanging_restart_loop, hanging_restart_loop_simple].
groups() ->
[{sup_start, [],
@@ -111,10 +112,8 @@ groups() ->
{restart_rest_for_one, [],
[rest_for_one, rest_for_one_escalation]}].
-init_per_suite(Config0) ->
- Config = lists:keydelete(watchdog, 1, Config0),
- Dog = test_server:timetrap(?TIMEOUT),
- [{watchdog, Dog} | Config].
+init_per_suite(Config) ->
+ Config.
end_per_suite(_Config) ->
ok.
@@ -129,18 +128,21 @@ init_per_testcase(count_children_memory, Config) ->
try erlang:memory() of
_ ->
erts_debug:set_internal_state(available_internal_state, true),
- Config
+ Dog = ?t:timetrap(?TIMEOUT),
+ [{watchdog,Dog}|Config]
catch error:notsup ->
{skip, "+Meamin used during test; erlang:memory/1 not available"}
end;
init_per_testcase(_Case, Config) ->
- erlang:display(_Case),
- Config.
+ Dog = ?t:timetrap(?TIMEOUT),
+ [{watchdog,Dog}|Config].
-end_per_testcase(count_children_memory, _Config) ->
+end_per_testcase(count_children_memory, Config) ->
catch erts_debug:set_internal_state(available_internal_state, false),
+ ?t:timetrap_cancel(?config(watchdog,Config)),
ok;
-end_per_testcase(_Case, _Config) ->
+end_per_testcase(_Case, Config) ->
+ ?t:timetrap_cancel(?config(watchdog,Config)),
ok.
start_link(InitResult) ->
@@ -1455,6 +1457,102 @@ gen_server9212() ->
%%-------------------------------------------------------------------------
+%% Test that child and supervisor can be shutdown while hanging in restart loop.
+%% See OTP-9549.
+hanging_restart_loop(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ {ok, Pid} = start_link({ok, {{one_for_one, 8, 10}, []}}),
+ Child1 = {child1, {supervisor_deadlock, start_child, []},
+ permanent, brutal_kill, worker, []},
+
+ %% Ets table with state read by supervisor_deadlock.erl
+ ets:new(supervisor_deadlock,[set,named_table,public]),
+ ets:insert(supervisor_deadlock,{fail_start,false}),
+
+ {ok, CPid1} = supervisor:start_child(sup_test, Child1),
+ link(CPid1),
+
+ ets:insert(supervisor_deadlock,{fail_start,true}),
+ supervisor_deadlock:restart_child(),
+ timer:sleep(2000), % allow restart to happen before proceeding
+
+ {error, already_present} = supervisor:start_child(sup_test, Child1),
+ {error, restarting} = supervisor:restart_child(sup_test, child1),
+ {error, restarting} = supervisor:delete_child(sup_test, child1),
+ [{child1,restarting,worker,[]}] = supervisor:which_children(sup_test),
+ [1,0,0,1] = get_child_counts(sup_test),
+
+ ok = supervisor:terminate_child(sup_test, child1),
+ check_exit_reason(CPid1, error),
+ [{child1,undefined,worker,[]}] = supervisor:which_children(sup_test),
+
+ ets:insert(supervisor_deadlock,{fail_start,false}),
+ {ok, CPid2} = supervisor:restart_child(sup_test, child1),
+ link(CPid2),
+
+ ets:insert(supervisor_deadlock,{fail_start,true}),
+ supervisor_deadlock:restart_child(),
+ timer:sleep(2000), % allow restart to happen before proceeding
+
+ %% Terminating supervisor.
+ %% OTP-9549 fixes so this does not give a timetrap timeout -
+ %% i.e. that supervisor does not hang in restart loop.
+ terminate(Pid,shutdown),
+
+ %% Check that child died with reason from 'restart' request above
+ check_exit_reason(CPid2, error),
+ undefined = whereis(sup_test),
+ ok.
+
+%%-------------------------------------------------------------------------
+%% Test that child and supervisor can be shutdown while hanging in
+%% restart loop, simple_one_for_one.
+%% See OTP-9549.
+hanging_restart_loop_simple(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ Child1 = {child1, {supervisor_deadlock, start_child, []},
+ permanent, brutal_kill, worker, []},
+ {ok, Pid} = start_link({ok, {{simple_one_for_one, 8, 10}, [Child1]}}),
+
+ %% Ets table with state read by supervisor_deadlock.erl
+ ets:new(supervisor_deadlock,[set,named_table,public]),
+ ets:insert(supervisor_deadlock,{fail_start,false}),
+
+ {ok, CPid1} = supervisor:start_child(sup_test, []),
+ link(CPid1),
+
+ ets:insert(supervisor_deadlock,{fail_start,true}),
+ supervisor_deadlock:restart_child(),
+ timer:sleep(2000), % allow restart to happen before proceeding
+
+ {error, simple_one_for_one} = supervisor:restart_child(sup_test, child1),
+ {error, simple_one_for_one} = supervisor:delete_child(sup_test, child1),
+ [{undefined,restarting,worker,[]}] = supervisor:which_children(sup_test),
+ [1,0,0,1] = get_child_counts(sup_test),
+
+ ok = supervisor:terminate_child(sup_test, CPid1),
+ check_exit_reason(CPid1, error),
+ [] = supervisor:which_children(sup_test),
+
+ ets:insert(supervisor_deadlock,{fail_start,false}),
+ {ok, CPid2} = supervisor:start_child(sup_test, []),
+ link(CPid2),
+
+ ets:insert(supervisor_deadlock,{fail_start,true}),
+ supervisor_deadlock:restart_child(),
+ timer:sleep(2000), % allow restart to happen before proceeding
+
+ %% Terminating supervisor.
+ %% OTP-9549 fixes so this does not give a timetrap timeout -
+ %% i.e. that supervisor does not hang in restart loop.
+ terminate(Pid,shutdown),
+
+ %% Check that child died with reason from 'restart' request above
+ check_exit_reason(CPid2, error),
+ undefined = whereis(sup_test),
+ ok.
+
+%%-------------------------------------------------------------------------
terminate(Pid, Reason) when Reason =/= supervisor ->
terminate(dummy, Pid, dummy, Reason).
diff --git a/lib/stdlib/test/supervisor_deadlock.erl b/lib/stdlib/test/supervisor_deadlock.erl
new file mode 100644
index 0000000000..288547a972
--- /dev/null
+++ b/lib/stdlib/test/supervisor_deadlock.erl
@@ -0,0 +1,45 @@
+-module(supervisor_deadlock).
+-compile(export_all).
+
+
+%%%-----------------------------------------------------------------
+%%% gen_server callbacks
+init([child]) ->
+ case ets:lookup(supervisor_deadlock,fail_start) of
+ [{fail_start, false}] ->
+ %% we must not fail on the first init, otherwise supervisor
+ %% terminates immediately
+ {ok, []};
+ [{fail_start, true}] ->
+ %% Restart frequency is MaxR=8, MaxT=10, so this will
+ %% ensure that restart intensity is not reached -> restart
+ %% loop
+ timer:sleep(2000), % NOTE: this could be a gen_server call timeout
+
+ {stop, error}
+ end.
+
+handle_call(_Req, _From, State) ->
+ {reply, ok, State}.
+
+%% Force a restart
+handle_cast(restart, State) ->
+ {stop, error, State}.
+
+handle_info(_Msg, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+
+%%%-----------------------------------------------------------------
+%%% Start child
+start_child() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [child], []).
+
+restart_child() ->
+ gen_server:cast(supervisor_deadlock, restart).
diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl
index b0068eba6f..1433eef193 100644
--- a/lib/test_server/src/test_server.erl
+++ b/lib/test_server/src/test_server.erl
@@ -1553,8 +1553,18 @@ get_loc(Pid) ->
lists:foreach(fun({Key,Val}) -> put(Key, Val) end, Dict),
Stk = [rewrite_loc_item(Loc) || Loc <- Stk0],
case get(test_server_loc) of
- undefined -> put(test_server_loc, Stk);
- _ -> ok
+ undefined ->
+ put(test_server_loc, Stk);
+ {Suite,Case} ->
+ %% location info unknown, check if {Suite,Case,Line}
+ %% is available in stacktrace. and if so, use stacktrace
+ %% instead of currect test_server_loc
+ case [match || {S,C,_L} <- Stk, S == Suite, C == Case] of
+ [match|_] -> put(test_server_loc, Stk);
+ _ -> ok
+ end;
+ _ ->
+ ok
end,
get_loc().
diff --git a/lib/test_server/src/test_server_ctrl.erl b/lib/test_server/src/test_server_ctrl.erl
index 3432b3bc8e..3bea9e39ee 100644
--- a/lib/test_server/src/test_server_ctrl.erl
+++ b/lib/test_server/src/test_server_ctrl.erl
@@ -1301,7 +1301,12 @@ terminate(_Reason, State) ->
end,
kill_all_jobs(State#state.jobs),
test_server_node:stop(State#state.target_info),
- test_server_h:restore(),
+ case lists:keysearch(sasl, 1, application:which_applications()) of
+ {value,_} ->
+ test_server_h:restore();
+ _ ->
+ ok
+ end,
ok.
kill_all_jobs([{_Name,JobPid}|Jobs]) ->
diff --git a/lib/test_server/src/test_server_h.erl b/lib/test_server/src/test_server_h.erl
index e423863b99..6707f98109 100644
--- a/lib/test_server/src/test_server_h.erl
+++ b/lib/test_server/src/test_server_h.erl
@@ -79,10 +79,21 @@ set_group_leader() ->
handle_event({_Type, GL, _Msg}, State) when node(GL)/=node() ->
{ok, State};
handle_event({Tag, _GL, {_Pid, Type, _Report}} = Event, State) ->
- case report(Tag, Type) of
- sasl ->
- tag(State#state.testcase),
- sasl_report_tty_h:handle_event(Event, State#state.sasl);
+ SASL = lists:keyfind(sasl, 1, application:which_applications()),
+ case report_receiver(Tag, Type) of
+ sasl when SASL /= false ->
+ {ok,ErrLogType} = application:get_env(sasl, errlog_type),
+ SReport = sasl_report:format_report(group_leader(), ErrLogType,
+ tag_event(Event)),
+ if is_list(SReport) ->
+ tag(State#state.testcase),
+ sasl_report_tty_h:handle_event(Event,
+ State#state.sasl);
+ true -> %% Report is an atom if no logging is to be done
+ ignore
+ end;
+ sasl -> %% SASL not running
+ ignore;
kernel ->
tag(State#state.testcase),
error_logger_tty_h:handle_event(Event, State#state.kernel);
@@ -111,19 +122,22 @@ terminate(_Reason, _State) ->
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
-report(error_report, supervisor_report) -> sasl;
-report(error_report, crash_report) -> sasl;
-report(info_report, progress) -> sasl;
-report(error, _) -> kernel;
-report(error_report, _) -> kernel;
-report(warning_msg, _) -> kernel;
-report(warning_report, _) -> kernel;
-report(info, _) -> kernel;
-report(info_msg, _) -> kernel;
-report(info_report, _) -> kernel;
-report(_, _) -> none.
+report_receiver(error_report, supervisor_report) -> sasl;
+report_receiver(error_report, crash_report) -> sasl;
+report_receiver(info_report, progress) -> sasl;
+report_receiver(error, _) -> kernel;
+report_receiver(error_report, _) -> kernel;
+report_receiver(warning_msg, _) -> kernel;
+report_receiver(warning_report, _) -> kernel;
+report_receiver(info, _) -> kernel;
+report_receiver(info_msg, _) -> kernel;
+report_receiver(info_report, _) -> kernel;
+report_receiver(_, _) -> none.
tag({M,F,A}) when is_atom(M), is_atom(F), is_integer(A) ->
io:format(user, "~n=TESTCASE: ~p:~p/~p", [M,F,A]);
tag(Testcase) ->
io:format(user, "~n=TESTCASE: ~p", [Testcase]).
+
+tag_event(Event) ->
+ {calendar:local_time(), Event}.
diff --git a/lib/tools/test/xref_SUITE.erl b/lib/tools/test/xref_SUITE.erl
index e0876381ca..78e49044a5 100644
--- a/lib/tools/test/xref_SUITE.erl
+++ b/lib/tools/test/xref_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2012. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -2141,17 +2141,17 @@ basic(Conf) when is_list(Conf) ->
?line {ok, _} = eval("components (Mod) E", [[m1,m2,m3]], S),
?line {ok, _} = eval("components closure (Mod) E", [[m1,m2,m3]], S),
?line {ok, _} = eval("condensation (Mod) E",
- [{[m1,m2,m3],[m1,m2,m3]},{[m1,m2,m3],[m17]}], S),
+ [{[m1,m2,m3],[m17]}], S),
?line {ok, _} = eval("condensation closure (Mod) E",
- [{[m1,m2,m3],[m1,m2,m3]},{[m1,m2,m3],[m17]}], S),
+ [{[m1,m2,m3],[m17]}], S),
?line {ok, _} = eval("condensation closure closure closure (Mod) E",
- [{[m1,m2,m3],[m1,m2,m3]},{[m1,m2,m3],[m17]}], S),
+ [{[m1,m2,m3],[m17]}], S),
?line {ok, _} = eval("weak condensation (Mod) E",
[{[m1,m2,m3],[m1,m2,m3]},{[m1,m2,m3],[m17]},{[m17],[m17]}], S),
?line {ok, _} = eval("strict condensation (Mod) E",
[{[m1,m2,m3],[m17]}], S),
?line {ok, _} = eval("range condensation (Mod) E",
- [[m1,m2,m3],[m17]], S),
+ [[m17]], S),
?line {ok, _} = eval("domain condensation (Mod) E",
[[m1,m2,m3]], S),