aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile4
-rw-r--r--lib/common_test/doc/src/notes.xml60
-rw-r--r--lib/common_test/test_server/ts_erl_config.erl36
-rw-r--r--lib/common_test/vsn.mk2
-rw-r--r--lib/compiler/doc/src/notes.xml32
-rw-r--r--lib/compiler/src/beam_ssa_opt.erl32
-rw-r--r--lib/compiler/src/beam_validator.erl698
-rw-r--r--lib/compiler/test/beam_except_SUITE.erl13
-rw-r--r--lib/compiler/test/beam_validator_SUITE.erl29
-rw-r--r--lib/compiler/test/beam_validator_SUITE_data/merge_undefined.S4
-rw-r--r--lib/compiler/test/beam_validator_SUITE_data/receive_stacked.S4
-rw-r--r--lib/compiler/test/float_SUITE.erl23
-rw-r--r--lib/compiler/vsn.mk2
-rw-r--r--lib/crypto/c_src/atoms.c24
-rw-r--r--lib/crypto/c_src/atoms.h12
-rw-r--r--lib/crypto/c_src/cipher.c93
-rw-r--r--lib/crypto/c_src/cipher.h4
-rw-r--r--lib/crypto/c_src/crypto.c2
-rw-r--r--lib/crypto/c_src/hash.c26
-rw-r--r--lib/crypto/c_src/hash.h1
-rw-r--r--lib/crypto/c_src/openssl_config.h7
-rw-r--r--lib/crypto/c_src/poly1305.c2
-rw-r--r--lib/crypto/doc/src/notes.xml32
-rw-r--r--lib/crypto/src/crypto.erl15
-rw-r--r--lib/crypto/test/crypto_SUITE.erl21
-rw-r--r--lib/crypto/vsn.mk2
-rw-r--r--lib/debugger/doc/src/Makefile3
-rw-r--r--lib/dialyzer/doc/src/notes.xml30
-rw-r--r--lib/dialyzer/vsn.mk2
-rw-r--r--lib/diameter/doc/src/diameter.xml24
-rw-r--r--lib/diameter/doc/src/diameter_transport.xml31
-rw-r--r--lib/diameter/doc/src/notes.xml35
-rw-r--r--lib/diameter/src/base/diameter.erl4
-rw-r--r--lib/diameter/src/base/diameter_callback.erl4
-rw-r--r--lib/diameter/src/base/diameter_dist.erl525
-rw-r--r--lib/diameter/src/base/diameter_misc_sup.erl3
-rw-r--r--lib/diameter/src/base/diameter_traffic.erl97
-rw-r--r--lib/diameter/src/diameter.appup.src6
-rw-r--r--lib/diameter/src/modules.mk3
-rw-r--r--lib/diameter/test/diameter_dist_SUITE.erl332
-rw-r--r--lib/diameter/test/diameter_distribution_SUITE.erl7
-rw-r--r--lib/diameter/test/diameter_pool_SUITE.erl3
-rw-r--r--lib/diameter/test/diameter_traffic_SUITE.erl5
-rw-r--r--lib/diameter/test/modules.mk3
-rw-r--r--lib/diameter/vsn.mk2
-rw-r--r--lib/edoc/doc/src/notes.xml16
-rw-r--r--lib/edoc/vsn.mk2
-rw-r--r--lib/erl_docgen/doc/src/notes.xml26
-rw-r--r--lib/erl_docgen/vsn.mk2
-rw-r--r--lib/erl_interface/configure.in10
-rw-r--r--lib/erl_interface/doc/src/ei.xml2
-rw-r--r--lib/erl_interface/doc/src/ei_connect.xml10
-rw-r--r--lib/erl_interface/doc/src/notes.xml17
-rw-r--r--lib/erl_interface/include/ei.h17
-rw-r--r--lib/erl_interface/include/ei_config.h.in3
-rw-r--r--lib/erl_interface/src/Makefile2
-rw-r--r--lib/erl_interface/src/Makefile.in5
-rw-r--r--lib/erl_interface/src/misc/eidef.h1
-rw-r--r--lib/erl_interface/test/all_SUITE_data/Makefile.src4
-rw-r--r--lib/erl_interface/test/ei_accept_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/ei_connect_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/ei_decode_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/ei_decode_encode_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/ei_encode_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/ei_format_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/ei_print_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/ei_tmo_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/erl_connect_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/erl_eterm_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/erl_ext_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/erl_format_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/erl_global_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/erl_match_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/port_call_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/vsn.mk2
-rw-r--r--lib/ftp/doc/src/notes.xml18
-rw-r--r--lib/ftp/src/ftp.erl287
-rw-r--r--lib/ftp/test/ftp_SUITE.erl73
-rw-r--r--lib/ftp/vsn.mk2
-rw-r--r--lib/hipe/doc/src/notes.xml15
-rw-r--r--lib/hipe/vsn.mk2
-rw-r--r--lib/inets/doc/src/notes.xml38
-rw-r--r--lib/inets/src/http_server/mod_esi.erl6
-rw-r--r--lib/inets/test/httpd_SUITE.erl15
-rw-r--r--lib/inets/vsn.mk2
-rw-r--r--lib/jinterface/doc/src/Makefile10
-rw-r--r--lib/kernel/doc/src/application.xml4
-rw-r--r--lib/kernel/doc/src/kernel_app.xml18
-rw-r--r--lib/kernel/doc/src/logger.xml6
-rw-r--r--lib/kernel/doc/src/logger_chapter.xml14
-rw-r--r--lib/kernel/doc/src/logger_std_h.xml122
-rw-r--r--lib/kernel/doc/src/notes.xml198
-rw-r--r--lib/kernel/src/kernel.app.src2
-rw-r--r--lib/kernel/src/kernel.appup.src10
-rw-r--r--lib/kernel/src/kernel.erl72
-rw-r--r--lib/kernel/src/logger_formatter.erl71
-rw-r--r--lib/kernel/src/logger_h_common.erl35
-rw-r--r--lib/kernel/src/logger_olp.erl5
-rw-r--r--lib/kernel/src/logger_olp.hrl7
-rw-r--r--lib/kernel/src/logger_std_h.erl503
-rw-r--r--lib/kernel/test/kernel.spec1
-rw-r--r--lib/kernel/test/kernel_config_SUITE.erl21
-rw-r--r--lib/kernel/test/logger_SUITE.erl14
-rw-r--r--lib/kernel/test/logger_std_h_SUITE.erl428
-rw-r--r--lib/kernel/test/logger_stress_SUITE.erl94
-rw-r--r--lib/kernel/vsn.mk2
-rw-r--r--lib/mnesia/doc/src/notes.xml17
-rw-r--r--lib/mnesia/vsn.mk2
-rw-r--r--lib/observer/doc/src/notes.xml39
-rw-r--r--lib/observer/vsn.mk2
-rw-r--r--lib/odbc/doc/src/notes.xml17
-rw-r--r--lib/odbc/vsn.mk2
-rw-r--r--lib/public_key/doc/src/notes.xml15
-rw-r--r--lib/public_key/vsn.mk2
-rw-r--r--lib/runtime_tools/doc/src/notes.xml15
-rw-r--r--lib/runtime_tools/vsn.mk2
-rw-r--r--lib/ssh/doc/src/notes.xml17
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE.erl6
-rw-r--r--lib/ssh/vsn.mk2
-rw-r--r--lib/ssl/doc/src/notes.xml87
-rw-r--r--lib/ssl/src/ssl_connection.erl5
-rw-r--r--lib/ssl/vsn.mk2
-rw-r--r--lib/stdlib/doc/src/ets.xml16
-rw-r--r--lib/stdlib/doc/src/gen_statem.xml7
-rw-r--r--lib/stdlib/doc/src/notes.xml51
-rw-r--r--lib/stdlib/src/io_lib_pretty.erl46
-rw-r--r--lib/stdlib/src/stdlib.appup.src6
-rw-r--r--lib/stdlib/test/ets_SUITE.erl117
-rw-r--r--lib/stdlib/test/io_SUITE.erl39
-rw-r--r--lib/stdlib/vsn.mk2
-rw-r--r--lib/syntax_tools/doc/src/notes.xml14
-rw-r--r--lib/syntax_tools/vsn.mk2
-rw-r--r--lib/tools/c_src/Makefile.in7
-rw-r--r--lib/tools/doc/src/notes.xml39
-rw-r--r--lib/tools/vsn.mk2
-rw-r--r--lib/wx/doc/src/notes.xml19
-rw-r--r--lib/wx/vsn.mk2
137 files changed, 4200 insertions, 944 deletions
diff --git a/lib/Makefile b/lib/Makefile
index 6605c6145c..ea2827bef0 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -58,10 +58,10 @@ else
SUB_DIRECTORIES = hipe parsetools asn1/src
else
ifdef TERTIARY_BOOTSTRAP
- SUB_DIRECTORIES = snmp sasl jinterface syntax_tools wx
+ SUB_DIRECTORIES = snmp sasl erl_interface jinterface syntax_tools wx
else
ifdef DOC_BOOTSTRAP
- SUB_DIRECTORIES = xmerl edoc erl_docgen
+ SUB_DIRECTORIES = xmerl edoc erl_docgen public_key
else # Not bootstrap build
SUB_DIRECTORIES = $(ERTS_APPLICATIONS) \
$(ERLANG_APPLICATIONS) \
diff --git a/lib/common_test/doc/src/notes.xml b/lib/common_test/doc/src/notes.xml
index 2f53f1c29e..c8e0722a0f 100644
--- a/lib/common_test/doc/src/notes.xml
+++ b/lib/common_test/doc/src/notes.xml
@@ -33,6 +33,66 @@
<file>notes.xml</file>
</header>
+<section><title>Common_Test 1.17</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ A bug caused <c>ct:encrypt_config_file/3</c> and
+ <c>ct:decrypt_config_file/3</c> to fail with
+ <c>badmatch</c> if input parameter <c>KeyOrFile</c> was
+ <c>{key,string()}</c>. This is now corrected.</p>
+ <p>
+ Own Id: OTP-15540</p>
+ </item>
+ <item>
+ <p>
+ The status of a test case which failed with timetrap
+ timeout in <c>end_per_testcase</c> could not be modified
+ by returning <c>{fail,Reason}</c> from a
+ <c>post_end_per_testcase</c> hook function. This is now
+ corrected.</p>
+ <p>
+ Own Id: OTP-15584 Aux Id: ERIERL-282 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ A new variant of the <c>newline</c> option to
+ <c>ct_telnet:cmd/3</c> and <c>ct_telnet:send/3</c> is
+ added, which allows to specify a string to append as
+ newline indicator on a command. By default, the value is
+ "\n", but in some cases it is required to be "\r\n",
+ which this option allows.</p>
+ <p>
+ A faulty regular expression given as parameter to
+ <c>ct_telnet:expect/2,3</c> would earlier crash and look
+ like an internal error in common_test. A better error
+ indication is now given, but the test case will still
+ fail.</p>
+ <p>
+ Own Id: OTP-15229 Aux Id: ERIERL-203 </p>
+ </item>
+ <item>
+ <p>
+ Since the yang RFC allows more than one top element of
+ config data in an <c>edit-config</c> element,
+ <c>ct_netconfc:edit_config/3,4,5</c> can now take a list
+ of XML elements.</p>
+ <p>
+ Own Id: OTP-15298</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Common_Test 1.16.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/common_test/test_server/ts_erl_config.erl b/lib/common_test/test_server/ts_erl_config.erl
index c2852131d6..f3972bea4e 100644
--- a/lib/common_test/test_server/ts_erl_config.erl
+++ b/lib/common_test/test_server/ts_erl_config.erl
@@ -197,28 +197,24 @@ system_include(Root, Vars) ->
" -I" ++ quote(filename:nativename(filename:join([Root, "erts", "emulator", SysDir]))).
erl_interface(Vars,OsType) ->
- {Incl, TargetIncl, {LibPath, MkIncl}} =
+ {Incl, {LibPath, MkIncl}} =
case lib_dir(Vars, erl_interface) of
{error, bad_name} ->
throw({cannot_find_app, erl_interface});
Dir ->
- BaseIncl = filename:join(Dir, "include"),
- case erl_root(Vars) of
- {installed, _Root} ->
- {BaseIncl,
- [],
- {filename:join(Dir, "lib"),
- filename:join([Dir, "src", "eidefs.mk"])}};
- {srctree, _Root, Target} ->
- Obj = case is_debug_build() of
- true -> "obj.debug";
- false -> "obj"
- end,
- {BaseIncl,
- filename:join(BaseIncl, Target),
- {filename:join([Dir, Obj, Target]),
- filename:join([Dir, "src", Target, "eidefs.mk"])}}
- end
+ {filename:join(Dir, "include"),
+ case erl_root(Vars) of
+ {installed, _Root} ->
+ {filename:join(Dir, "lib"),
+ filename:join([Dir, "src", "eidefs.mk"])};
+ {srctree, _Root, Target} ->
+ Obj = case is_debug_build() of
+ true -> "obj.debug";
+ false -> "obj"
+ end,
+ {filename:join([Dir, Obj, Target]),
+ filename:join([Dir, "src", Target, "eidefs.mk"])}
+ end}
end,
Lib = link_library("erl_interface",OsType),
Lib1 = link_library("ei",OsType),
@@ -264,10 +260,6 @@ erl_interface(Vars,OsType) ->
{erl_interface_eilib_drv, quote(filename:join(LibPath, Lib1Drv))},
{erl_interface_threadlib, ThreadLib},
{erl_interface_include, quote(filename:nativename(Incl))},
- {erl_interface_target_include, case TargetIncl of
- [] -> [];
- _ -> "-I" ++ quote(filename:nativename(TargetIncl))
- end},
{erl_interface_mk_include, quote(filename:nativename(MkIncl))}
| Vars].
diff --git a/lib/common_test/vsn.mk b/lib/common_test/vsn.mk
index fd5d4a57aa..23eb8d9656 100644
--- a/lib/common_test/vsn.mk
+++ b/lib/common_test/vsn.mk
@@ -1 +1 @@
-COMMON_TEST_VSN = 1.16.1
+COMMON_TEST_VSN = 1.17
diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml
index 02e6203137..d45dfef8f3 100644
--- a/lib/compiler/doc/src/notes.xml
+++ b/lib/compiler/doc/src/notes.xml
@@ -32,6 +32,38 @@
<p>This document describes the changes made to the Compiler
application.</p>
+<section><title>Compiler 7.3.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>An expression such as <c>(A / B) band 16#ff</c> would
+ crash the compiler.</p>
+ <p>
+ Own Id: OTP-15518 Aux Id: ERL-829 </p>
+ </item>
+ <item>
+ <p>There could be an incorrect warning when the
+ <c>tuple_calls</c> option was given. The generated code
+ would be correct. Here is an example of code that would
+ trigger the warning:</p>
+ <p><c>(list_to_atom("prefix_" ++
+ atom_to_list(suffix))):doit(X)</c>.</p>
+ <p>
+ Own Id: OTP-15552 Aux Id: ERL-838 </p>
+ </item>
+ <item>
+ <p>Optimize (again) Dialyzer's handling of
+ left-associative use of <c>andalso</c> and <c>orelse</c>
+ in guards.</p>
+ <p>
+ Own Id: OTP-15577 Aux Id: ERL-851, PR-2141, PR-1944 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Compiler 7.3.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl
index bcf55f3fda..90c0d3cf16 100644
--- a/lib/compiler/src/beam_ssa_opt.erl
+++ b/lib/compiler/src/beam_ssa_opt.erl
@@ -911,6 +911,11 @@ ssa_opt_float({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) ->
{Linear,Count} = float_opt(Linear0, Count0, Fs),
{St#st{ssa=Linear,cnt=Count}, FuncDb}.
+float_blk_is_in_guard(#b_blk{last=#b_br{fail=F}}, #fs{non_guards=NonGuards}) ->
+ not gb_sets:is_member(F, NonGuards);
+float_blk_is_in_guard(#b_blk{}, #fs{}) ->
+ false.
+
float_non_guards([{L,#b_blk{is=Is}}|Bs]) ->
case Is of
[#b_set{op=landingpad}|_] ->
@@ -920,21 +925,18 @@ float_non_guards([{L,#b_blk{is=Is}}|Bs]) ->
end;
float_non_guards([]) -> [?BADARG_BLOCK].
-float_opt([{L,#b_blk{last=#b_br{fail=F}}=Blk}|Bs0],
- Count0, #fs{non_guards=NonGuards}=Fs) ->
- case gb_sets:is_member(F, NonGuards) of
+float_opt([{L,Blk}|Bs0], Count0, Fs) ->
+ case float_blk_is_in_guard(Blk, Fs) of
true ->
- %% This block is not inside a guard.
- %% We can do the optimization.
- float_opt_1(L, Blk, Bs0, Count0, Fs);
- false ->
%% This block is inside a guard. Don't do
%% any floating point optimizations.
{Bs,Count} = float_opt(Bs0, Count0, Fs),
- {[{L,Blk}|Bs],Count}
+ {[{L,Blk}|Bs],Count};
+ false ->
+ %% This block is not inside a guard.
+ %% We can do the optimization.
+ float_opt_1(L, Blk, Bs0, Count0, Fs)
end;
-float_opt([{L,Blk}|Bs], Count, Fs) ->
- float_opt_1(L, Blk, Bs, Count, Fs);
float_opt([], Count, _Fs) ->
{[],Count}.
@@ -1010,10 +1012,14 @@ float_conv([{L,#b_blk{is=Is0}=Blk0}|Bs0], Fail, Count0) ->
float_maybe_flush(Blk0, #fs{s=cleared,fail=Fail,bs=Blocks}=Fs0, Count0) ->
#b_blk{last=#b_br{bool=#b_var{},succ=Succ}=Br} = Blk0,
- #b_blk{is=Is} = map_get(Succ, Blocks),
+
+ %% If the success block starts with a floating point operation, we can
+ %% defer flushing to that block as long as it isn't a guard.
+ #b_blk{is=Is} = SuccBlk = map_get(Succ, Blocks),
+ SuccIsGuard = float_blk_is_in_guard(SuccBlk, Fs0),
+
case Is of
- [#b_set{anno=#{float_op:=_}}|_] ->
- %% The next operation is also a floating point operation.
+ [#b_set{anno=#{float_op:=_}}|_] when not SuccIsGuard ->
%% No flush needed.
{[],Blk0,Fs0,Count0};
_ ->
diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl
index 296c095be2..ab8caa1a0d 100644
--- a/lib/compiler/src/beam_validator.erl
+++ b/lib/compiler/src/beam_validator.erl
@@ -579,7 +579,11 @@ valfun_1({get_tuple_element,Src,N,Dst}, Vst) ->
Type = get_element_type(Index, Src, Vst),
extract_term(Type, {bif,element}, [Index, Src], Dst, Vst);
valfun_1({jump,{f,Lbl}}, Vst) ->
- kill_state(branch_state(Lbl, Vst));
+ branch(Lbl, Vst,
+ fun(SuccVst) ->
+ %% The next instruction is never executed.
+ kill_state(SuccVst)
+ end);
valfun_1(I, Vst) ->
valfun_2(I, Vst).
@@ -589,14 +593,17 @@ init_try_catch_branch(Tag, Dst, Fail, Vst0) ->
St = St0#st{ct=[[Fail]|Fails]},
Vst = Vst0#vst{current=St},
- complex_test(Fail,
- fun(CatchVst) ->
- #vst{current=#st{ys=Ys}} = CatchVst,
- maps:fold(fun init_catch_handler_1/3, CatchVst, Ys)
- end,
- fun(SuccVst) ->
- SuccVst
- end, Vst).
+ branch(Fail, Vst,
+ fun(CatchVst) ->
+ #vst{current=#st{ys=Ys}} = CatchVst,
+ maps:fold(fun init_catch_handler_1/3, CatchVst, Ys)
+ end,
+ fun(SuccVst) ->
+ %% All potentially-throwing instructions after this
+ %% one will implicitly branch to the fail label;
+ %% see valfun_2/2
+ SuccVst
+ end).
%% Set the initial state at the try/catch label. Assume that Y registers
%% contain terms or try/catch tags.
@@ -607,21 +614,27 @@ init_catch_handler_1(Reg, uninitialized, Vst) ->
init_catch_handler_1(_, _, Vst) ->
Vst.
-%% Update branched state if necessary and try next set of instructions.
-valfun_2(I, #vst{current=#st{ct=[]}}=Vst) ->
- valfun_3(I, Vst);
valfun_2(I, #vst{current=#st{ct=[[Fail]|_]}}=Vst) when is_integer(Fail) ->
- %% Update branched state.
+ %% We have an active try/catch tag and we can jump there from this
+ %% instruction, so we need to update the branched state of the try/catch
+ %% handler.
valfun_3(I, branch_state(Fail, Vst));
+valfun_2(I, #vst{current=#st{ct=[]}}=Vst) ->
+ valfun_3(I, Vst);
valfun_2(_, _) ->
error(ambiguous_catch_try_state).
%% Handle the remaining floating point instructions here.
%% Floating point.
-valfun_3({fconv,Src,{fr,_}=Dst}, Vst0) ->
- assert_term(Src, Vst0),
- Vst = update_type(fun meet/2, number, Src, Vst0),
- set_freg(Dst, Vst);
+valfun_3({fconv,Src,{fr,_}=Dst}, Vst) ->
+ assert_term(Src, Vst),
+
+ %% An exception is raised on error, hence branching to 0.
+ branch(0, Vst,
+ fun(SuccVst0) ->
+ SuccVst = update_type(fun meet/2, number, Src, SuccVst0),
+ set_freg(Dst, SuccVst)
+ end);
valfun_3({bif,fadd,_,[_,_]=Ss,Dst}, Vst) ->
float_op(Ss, Dst, Vst);
valfun_3({bif,fdiv,_,[_,_]=Ss,Dst}, Vst) ->
@@ -682,67 +695,87 @@ valfun_4({call_ext_last,_,_,_}, #vst{current=#st{numy=NumY}}) ->
valfun_4({make_fun2,_,_,_,Live}, Vst) ->
call(make_fun, Live, Vst);
%% Other BIFs
-valfun_4({bif,element,{f,Fail},[Pos,Tuple],Dst}, Vst0) ->
- PosType = get_term_type(Pos, Vst0),
- ElementType = get_element_type(PosType, Tuple, Vst0),
- InferredType = {tuple,[get_tuple_size(PosType)],#{}},
- Vst1 = branch_state(Fail, Vst0),
- Vst2 = update_type(fun meet/2, InferredType, Tuple, Vst1),
- Vst = update_type(fun meet/2, {integer,[]}, Pos, Vst2),
- extract_term(ElementType, {bif,element}, [Pos,Tuple], Dst, Vst);
+valfun_4({bif,element,{f,Fail},[Pos,Src],Dst}, Vst) ->
+ branch(Fail, Vst,
+ fun(SuccVst0) ->
+ PosType = get_term_type(Pos, SuccVst0),
+ TupleType = {tuple,[get_tuple_size(PosType)],#{}},
+
+ SuccVst1 = update_type(fun meet/2, TupleType,
+ Src, SuccVst0),
+ SuccVst = update_type(fun meet/2, {integer,[]},
+ Pos, SuccVst1),
+
+ ElementType = get_element_type(PosType, Src, SuccVst),
+ extract_term(ElementType, {bif,element}, [Pos,Src],
+ Dst, SuccVst)
+ end);
valfun_4({bif,raise,{f,0},Src,_Dst}, Vst) ->
validate_src(Src, Vst),
kill_state(Vst);
valfun_4(raw_raise=I, Vst) ->
call(I, 3, Vst);
-valfun_4({bif,Op,{f,Fail},[Cons]=Ss,Dst}, Vst0)
- when Op =:= hd; Op =:= tl ->
- validate_src(Ss, Vst0),
- Vst = type_test(Fail, cons, Cons, Vst0),
- Type = bif_return_type(Op, Ss, Vst),
- extract_term(Type, {bif,Op}, Ss, Dst, Vst);
-valfun_4({bif,Op,{f,Fail},Ss,Dst}, Vst0) ->
- validate_src(Ss, Vst0),
- Vst1 = branch_state(Fail, Vst0),
-
- %% Infer argument types. Note that we can't type_test in the general case
- %% as the BIF could fail for reasons other than bad argument types.
- ArgTypes = bif_arg_types(Op, Ss),
- Vst = foldl(fun({Arg, T}, Vsti) ->
- update_type(fun meet/2, T, Arg, Vsti)
- end, Vst1, zip(Ss, ArgTypes)),
-
- Type = bif_return_type(Op, Ss, Vst),
- extract_term(Type, {bif,Op}, Ss, Dst, Vst);
+valfun_4({bif,Op,{f,Fail},[Src]=Ss,Dst}, Vst) when Op =:= hd; Op =:= tl ->
+ assert_term(Src, Vst),
+ branch(Fail, Vst,
+ fun(FailVst) ->
+ update_type(fun subtract/2, cons, Src, FailVst)
+ end,
+ fun(SuccVst0) ->
+ SuccVst = update_type(fun meet/2, cons, Src, SuccVst0),
+ extract_term(term, {bif,Op}, Ss, Dst, SuccVst)
+ end);
+valfun_4({bif,Op,{f,Fail},Ss,Dst}, Vst) ->
+ validate_src(Ss, Vst),
+ branch(Fail, Vst,
+ fun(SuccVst0) ->
+ %% Infer argument types. Note that we can't subtract
+ %% types as the BIF could fail for reasons other than
+ %% bad argument types.
+ ArgTypes = bif_arg_types(Op, Ss),
+ SuccVst = foldl(fun({Arg, T}, V) ->
+ update_type(fun meet/2, T, Arg, V)
+ end, SuccVst0, zip(Ss, ArgTypes)),
+ Type = bif_return_type(Op, Ss, SuccVst),
+ extract_term(Type, {bif,Op}, Ss, Dst, SuccVst)
+ end);
valfun_4({gc_bif,Op,{f,Fail},Live,Ss,Dst}, #vst{current=St0}=Vst0) ->
validate_src(Ss, Vst0),
verify_live(Live, Vst0),
verify_y_init(Vst0),
+
+ %% Heap allocations and X registers are killed regardless of whether we
+ %% fail or not, as we may fail after GC.
St = kill_heap_allocation(St0),
- Vst1 = Vst0#vst{current=St},
- Vst2 = branch_state(Fail, Vst1),
+ Vst = prune_x_regs(Live, Vst0#vst{current=St}),
+
+ branch(Fail, Vst,
+ fun(SuccVst0) ->
+ ArgTypes = bif_arg_types(Op, Ss),
+ SuccVst = foldl(fun({Arg, T}, V) ->
+ update_type(fun meet/2, T, Arg, V)
+ end, SuccVst0, zip(Ss, ArgTypes)),
- ArgTypes = bif_arg_types(Op, Ss),
- Vst3 = foldl(fun({Arg, T}, Vsti) ->
- update_type(fun meet/2, T, Arg, Vsti)
- end, Vst2, zip(Ss, ArgTypes)),
+ Type = bif_return_type(Op, Ss, SuccVst),
- Type = bif_return_type(Op, Ss, Vst3),
- Vst = prune_x_regs(Live, Vst3),
- extract_term(Type, {gc_bif,Op}, Ss, Dst, Vst, Vst0);
+ %% We're passing Vst0 as the original because the
+ %% registers were pruned before the branch.
+ extract_term(Type, {gc_bif,Op}, Ss, Dst, SuccVst, Vst0)
+ end);
valfun_4(return, #vst{current=#st{numy=none}}=Vst) ->
assert_durable_term({x,0}, Vst),
kill_state(Vst);
valfun_4(return, #vst{current=#st{numy=NumY}}) ->
error({stack_frame,NumY});
-valfun_4({loop_rec,{f,Fail},Dst}, Vst0) ->
- %% This term may not be part of the root set until
- %% remove_message/0 is executed. If control transfers
- %% to the loop_rec_end/1 instruction, no part of
- %% this term must be stored in a Y register.
- Vst1 = branch_state(Fail, Vst0),
- {Ref, Vst} = new_value(term, loop_rec, [], Vst1),
- mark_fragile(Dst, set_reg_vref(Ref, Dst, Vst));
+valfun_4({loop_rec,{f,Fail},Dst}, Vst) ->
+ %% This term may not be part of the root set until remove_message/0 is
+ %% executed. If control transfers to the loop_rec_end/1 instruction, no
+ %% part of this term must be stored in a Y register.
+ branch(Fail, Vst,
+ fun(SuccVst0) ->
+ {Ref, SuccVst} = new_value(term, loop_rec, [], SuccVst0),
+ mark_fragile(Dst, set_reg_vref(Ref, Dst, SuccVst))
+ end);
valfun_4({wait,_}, Vst) ->
verify_y_init(Vst),
kill_state(Vst);
@@ -769,14 +802,14 @@ valfun_4({set_tuple_element,Src,Tuple,N}, Vst) ->
Es = set_element_type({integer,I}, get_term_type(Src, Vst), Es0),
override_type({tuple, Sz, Es}, Tuple, Vst);
%% Match instructions.
-valfun_4({select_val,Src,{f,Fail},{list,Choices}}, Vst0) ->
- assert_term(Src, Vst0),
+valfun_4({select_val,Src,{f,Fail},{list,Choices}}, Vst) ->
+ assert_term(Src, Vst),
assert_choices(Choices),
- select_val_branches(Fail, Src, Choices, Vst0);
+ validate_select_val(Fail, Choices, Src, Vst);
valfun_4({select_tuple_arity,Tuple,{f,Fail},{list,Choices}}, Vst) ->
assert_type(tuple, Tuple, Vst),
assert_arities(Choices),
- select_arity_branches(Fail, Choices, Tuple, Vst);
+ validate_select_tuple_arity(Fail, Choices, Tuple, Vst);
%% New bit syntax matching instructions.
valfun_4({test,bs_start_match3,{f,Fail},Live,[Src],Dst}, Vst) ->
@@ -785,17 +818,17 @@ valfun_4({test,bs_start_match2,{f,Fail},Live,[Src,Slots],Dst}, Vst) ->
validate_bs_start_match(Fail, Live, bsm_match_state(Slots), Src, Dst, Vst);
valfun_4({test,bs_match_string,{f,Fail},[Ctx,_,_]}, Vst) ->
bsm_validate_context(Ctx, Vst),
- branch_state(Fail, Vst);
+ branch(Fail, Vst, fun(V) -> V end);
valfun_4({test,bs_skip_bits2,{f,Fail},[Ctx,Src,_,_]}, Vst) ->
bsm_validate_context(Ctx, Vst),
assert_term(Src, Vst),
- branch_state(Fail, Vst);
+ branch(Fail, Vst, fun(V) -> V end);
valfun_4({test,bs_test_tail2,{f,Fail},[Ctx,_]}, Vst) ->
bsm_validate_context(Ctx, Vst),
- branch_state(Fail, Vst);
+ branch(Fail, Vst, fun(V) -> V end);
valfun_4({test,bs_test_unit,{f,Fail},[Ctx,_]}, Vst) ->
bsm_validate_context(Ctx, Vst),
- branch_state(Fail, Vst);
+ branch(Fail, Vst, fun(V) -> V end);
valfun_4({test,bs_skip_utf8,{f,Fail},[Ctx,Live,_]}, Vst) ->
validate_bs_skip_utf(Fail, Ctx, Live, Vst);
valfun_4({test,bs_skip_utf16,{f,Fail},[Ctx,Live,_]}, Vst) ->
@@ -830,6 +863,10 @@ valfun_4({bs_set_position, Ctx, Pos}, Vst) ->
Vst;
%% Other test instructions.
+valfun_4({test,has_map_fields,{f,Lbl},Src,{list,List}}, Vst) ->
+ assert_type(map, Src, Vst),
+ assert_unique_map_keys(List),
+ branch(Lbl, Vst, fun(V) -> V end);
valfun_4({test,is_atom,{f,Lbl},[Src]}, Vst) ->
type_test(Lbl, {atom,[]}, Src, Vst);
valfun_4({test,is_binary,{f,Lbl},[Src]}, Vst) ->
@@ -850,70 +887,68 @@ valfun_4({test,is_number,{f,Lbl},[Src]}, Vst) ->
type_test(Lbl, number, Src, Vst);
valfun_4({test,is_list,{f,Lbl},[Src]}, Vst) ->
type_test(Lbl, list, Src, Vst);
+valfun_4({test,is_map,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, map, Src, Vst);
valfun_4({test,is_nil,{f,Lbl},[Src]}, Vst) ->
%% is_nil is an exact check against the 'nil' value, and should not be
%% treated as a simple type test.
assert_term(Src, Vst),
- complex_test(Lbl,
- fun(FailVst) ->
- update_ne_types(Src, nil, FailVst)
- end,
- fun(SuccVst) ->
- update_eq_types(Src, nil, SuccVst)
- end, Vst);
-valfun_4({test,is_map,{f,Lbl},[Src]}, Vst) ->
- case Src of
- {Tag,_} when Tag =:= x; Tag =:= y ->
- type_test(Lbl, map, Src, Vst);
- {literal,Map} when is_map(Map) ->
- Vst;
- _ ->
- assert_term(Src, Vst),
- kill_state(Vst)
- end;
-valfun_4({test,test_arity,{f,Lbl},[Tuple,Sz]}, Vst0) when is_integer(Sz) ->
- assert_type(tuple, Tuple, Vst0),
- Vst = branch_state(Lbl, Vst0),
- update_type(fun meet/2, {tuple,Sz,#{}}, Tuple, Vst);
-valfun_4({test,is_tagged_tuple,{f,Lbl},[Src,Sz,Atom]}, Vst0) ->
- assert_term(Src, Vst0),
- Vst = branch_state(Lbl, Vst0),
- update_type(fun meet/2, {tuple,Sz,#{ {integer,1} => Atom }}, Src, Vst);
-valfun_4({test,has_map_fields,{f,Lbl},Src,{list,List}}, Vst) ->
- assert_type(map, Src, Vst),
- assert_unique_map_keys(List),
- branch_state(Lbl, Vst);
+ branch(Lbl, Vst,
+ fun(FailVst) ->
+ update_ne_types(Src, nil, FailVst)
+ end,
+ fun(SuccVst) ->
+ update_eq_types(Src, nil, SuccVst)
+ end);
+valfun_4({test,test_arity,{f,Lbl},[Tuple,Sz]}, Vst) when is_integer(Sz) ->
+ assert_type(tuple, Tuple, Vst),
+ Type = {tuple, Sz, #{}},
+ type_test(Lbl, Type, Tuple, Vst);
+valfun_4({test,is_tagged_tuple,{f,Lbl},[Src,Sz,Atom]}, Vst) ->
+ assert_term(Src, Vst),
+ Type = {tuple, Sz, #{ {integer,1} => Atom }},
+ type_test(Lbl, Type, Src, Vst);
valfun_4({test,is_eq_exact,{f,Lbl},[Src,Val]=Ss}, Vst) ->
validate_src(Ss, Vst),
- complex_test(Lbl,
- fun(FailVst) ->
- update_ne_types(Src, Val, FailVst)
- end,
- fun(SuccVst) ->
- update_eq_types(Src, Val, SuccVst)
- end, Vst);
+ branch(Lbl, Vst,
+ fun(FailVst) ->
+ update_ne_types(Src, Val, FailVst)
+ end,
+ fun(SuccVst) ->
+ update_eq_types(Src, Val, SuccVst)
+ end);
valfun_4({test,is_ne_exact,{f,Lbl},[Src,Val]=Ss}, Vst) ->
validate_src(Ss, Vst),
- complex_test(Lbl,
- fun(FailVst) ->
- update_eq_types(Src, Val, FailVst)
- end,
- fun(SuccVst) ->
- update_ne_types(Src, Val, SuccVst)
- end, Vst);
+ branch(Lbl, Vst,
+ fun(FailVst) ->
+ update_eq_types(Src, Val, FailVst)
+ end,
+ fun(SuccVst) ->
+ update_ne_types(Src, Val, SuccVst)
+ end);
valfun_4({test,_Op,{f,Lbl},Src}, Vst) ->
+ %% is_pid, is_reference, et cetera.
validate_src(Src, Vst),
- branch_state(Lbl, Vst);
+ branch(Lbl, Vst, fun(V) -> V end);
valfun_4({bs_add,{f,Fail},[A,B,_],Dst}, Vst) ->
assert_term(A, Vst),
assert_term(B, Vst),
- create_term({integer,[]}, bs_add, [A, B], Dst, branch_state(Fail, Vst));
+ branch(Fail, Vst,
+ fun(SuccVst) ->
+ create_term({integer,[]}, bs_add, [A, B], Dst, SuccVst)
+ end);
valfun_4({bs_utf8_size,{f,Fail},A,Dst}, Vst) ->
assert_term(A, Vst),
- create_term({integer,[]}, bs_utf8_size, [A], Dst, branch_state(Fail, Vst));
+ branch(Fail, Vst,
+ fun(SuccVst) ->
+ create_term({integer,[]}, bs_utf8_size, [A], Dst, SuccVst)
+ end);
valfun_4({bs_utf16_size,{f,Fail},A,Dst}, Vst) ->
assert_term(A, Vst),
- create_term({integer,[]}, bs_utf16_size, [A], Dst, branch_state(Fail, Vst));
+ branch(Fail, Vst,
+ fun(SuccVst) ->
+ create_term({integer,[]}, bs_utf16_size, [A], Dst, SuccVst)
+ end);
valfun_4({bs_init2,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) ->
verify_live(Live, Vst0),
verify_y_init(Vst0),
@@ -923,10 +958,12 @@ valfun_4({bs_init2,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) ->
true ->
assert_term(Sz, Vst0)
end,
- Vst1 = heap_alloc(Heap, Vst0),
- Vst2 = branch_state(Fail, Vst1),
- Vst = prune_x_regs(Live, Vst2),
- create_term(binary, bs_init2, [], Dst, Vst, Vst0);
+ Vst = heap_alloc(Heap, Vst0),
+ branch(Fail, Vst,
+ fun(SuccVst0) ->
+ SuccVst = prune_x_regs(Live, SuccVst0),
+ create_term(binary, bs_init2, [], Dst, SuccVst, SuccVst0)
+ end);
valfun_4({bs_init_bits,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) ->
verify_live(Live, Vst0),
verify_y_init(Vst0),
@@ -936,47 +973,71 @@ valfun_4({bs_init_bits,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) ->
true ->
assert_term(Sz, Vst0)
end,
- Vst1 = heap_alloc(Heap, Vst0),
- Vst2 = branch_state(Fail, Vst1),
- Vst = prune_x_regs(Live, Vst2),
- create_term(binary, bs_init_bits, [], Dst, Vst);
+ Vst = heap_alloc(Heap, Vst0),
+ branch(Fail, Vst,
+ fun(SuccVst0) ->
+ SuccVst = prune_x_regs(Live, SuccVst0),
+ create_term(binary, bs_init_bits, [], Dst, SuccVst)
+ end);
valfun_4({bs_append,{f,Fail},Bits,Heap,Live,_Unit,Bin,_Flags,Dst}, Vst0) ->
verify_live(Live, Vst0),
verify_y_init(Vst0),
assert_term(Bits, Vst0),
assert_term(Bin, Vst0),
- Vst1 = heap_alloc(Heap, Vst0),
- Vst2 = branch_state(Fail, Vst1),
- Vst = prune_x_regs(Live, Vst2),
- create_term(binary, bs_append, [Bin], Dst, Vst, Vst0);
-valfun_4({bs_private_append,{f,Fail},Bits,_Unit,Bin,_Flags,Dst}, Vst0) ->
- assert_term(Bits, Vst0),
- assert_term(Bin, Vst0),
- Vst = branch_state(Fail, Vst0),
- create_term(binary, bs_private_append, [Bin], Dst, Vst);
+ Vst = heap_alloc(Heap, Vst0),
+ branch(Fail, Vst,
+ fun(SuccVst0) ->
+ SuccVst = prune_x_regs(Live, SuccVst0),
+ create_term(binary, bs_append, [Bin], Dst, SuccVst, SuccVst0)
+ end);
+valfun_4({bs_private_append,{f,Fail},Bits,_Unit,Bin,_Flags,Dst}, Vst) ->
+ assert_term(Bits, Vst),
+ assert_term(Bin, Vst),
+ branch(Fail, Vst,
+ fun(SuccVst) ->
+ create_term(binary, bs_private_append, [Bin], Dst, SuccVst)
+ end);
valfun_4({bs_put_string,Sz,_}, Vst) when is_integer(Sz) ->
Vst;
valfun_4({bs_put_binary,{f,Fail},Sz,_,_,Src}, Vst) ->
assert_term(Sz, Vst),
assert_term(Src, Vst),
- branch_state(Fail, Vst);
+ branch(Fail, Vst,
+ fun(SuccVst) ->
+ update_type(fun meet/2, binary, Src, SuccVst)
+ end);
valfun_4({bs_put_float,{f,Fail},Sz,_,_,Src}, Vst) ->
assert_term(Sz, Vst),
assert_term(Src, Vst),
- branch_state(Fail, Vst);
+ branch(Fail, Vst,
+ fun(SuccVst) ->
+ update_type(fun meet/2, {float,[]}, Src, SuccVst)
+ end);
valfun_4({bs_put_integer,{f,Fail},Sz,_,_,Src}, Vst) ->
assert_term(Sz, Vst),
assert_term(Src, Vst),
- branch_state(Fail, Vst);
+ branch(Fail, Vst,
+ fun(SuccVst) ->
+ update_type(fun meet/2, {integer,[]}, Src, SuccVst)
+ end);
valfun_4({bs_put_utf8,{f,Fail},_,Src}, Vst) ->
assert_term(Src, Vst),
- branch_state(Fail, Vst);
+ branch(Fail, Vst,
+ fun(SuccVst) ->
+ update_type(fun meet/2, {integer,[]}, Src, SuccVst)
+ end);
valfun_4({bs_put_utf16,{f,Fail},_,Src}, Vst) ->
assert_term(Src, Vst),
- branch_state(Fail, Vst);
+ branch(Fail, Vst,
+ fun(SuccVst) ->
+ update_type(fun meet/2, {integer,[]}, Src, SuccVst)
+ end);
valfun_4({bs_put_utf32,{f,Fail},_,Src}, Vst) ->
assert_term(Src, Vst),
- branch_state(Fail, Vst);
+ branch(Fail, Vst,
+ fun(SuccVst) ->
+ update_type(fun meet/2, {integer,[]}, Src, SuccVst)
+ end);
%% Map instructions.
valfun_4({put_map_assoc=Op,{f,Fail},Src,Dst,Live,{list,List}}, Vst) ->
verify_put_map(Op, Fail, Src, Dst, Live, List, Vst);
@@ -991,15 +1052,15 @@ verify_get_map(Fail, Src, List, Vst0) ->
assert_not_literal(Src), %OTP 22.
assert_type(map, Src, Vst0),
- complex_test(Fail,
- fun(FailVst) ->
- clobber_map_vals(List, Src, FailVst)
- end,
- fun(SuccVst) ->
- Keys = extract_map_keys(List),
- assert_unique_map_keys(Keys),
- extract_map_vals(List, Src, SuccVst, SuccVst)
- end, Vst0).
+ branch(Fail, Vst0,
+ fun(FailVst) ->
+ clobber_map_vals(List, Src, FailVst)
+ end,
+ fun(SuccVst) ->
+ Keys = extract_map_keys(List),
+ assert_unique_map_keys(Keys),
+ extract_map_vals(List, Src, SuccVst, SuccVst)
+ end).
%% get_map_elements may leave its destinations in an inconsistent state when
%% the fail label is taken. Consider the following:
@@ -1033,13 +1094,16 @@ verify_put_map(Op, Fail, Src, Dst, Live, List, Vst0) ->
assert_type(map, Src, Vst0),
verify_live(Live, Vst0),
verify_y_init(Vst0),
- [assert_term(Term, Vst0) || Term <- List],
- Vst1 = heap_alloc(0, Vst0),
- Vst2 = branch_state(Fail, Vst1),
- Vst = prune_x_regs(Live, Vst2),
- Keys = extract_map_keys(List),
- assert_unique_map_keys(Keys),
- create_term(map, Op, [Src], Dst, Vst, Vst0).
+ _ = [assert_term(Term, Vst0) || Term <- List],
+ Vst = heap_alloc(0, Vst0),
+
+ branch(Fail, Vst,
+ fun(SuccVst0) ->
+ SuccVst = prune_x_regs(Live, SuccVst0),
+ Keys = extract_map_keys(List),
+ assert_unique_map_keys(Keys),
+ create_term(map, Op, [Src], Dst, SuccVst, SuccVst0)
+ end).
%%
%% Common code for validating bs_start_match* instructions.
@@ -1052,40 +1116,60 @@ validate_bs_start_match(Fail, Live, Type, Src, Dst, Vst) ->
%% #ms{} can represent either a match context or a term, so we have to mark
%% the source as a term if it fails with a match context as an input. This
%% hack is only needed until we get proper union types.
- complex_test(Fail,
- fun(FailVst) ->
- case get_raw_type(Src, FailVst) of
- #ms{} -> override_type(term, Src, FailVst);
- _ -> FailVst
- end
- end,
- fun(SuccVst0) ->
- SuccVst1 = update_type(fun meet/2, binary, Src, SuccVst0),
- SuccVst = prune_x_regs(Live, SuccVst1),
- extract_term(Type, bs_start_match, [Src], Dst,
- SuccVst, SuccVst0)
- end, Vst).
+ branch(Fail, Vst,
+ fun(FailVst) ->
+ case get_movable_term_type(Src, FailVst) of
+ #ms{} -> override_type(term, Src, FailVst);
+ _ -> FailVst
+ end
+ end,
+ fun(SuccVst0) ->
+ SuccVst1 = update_type(fun meet/2, binary,
+ Src, SuccVst0),
+ SuccVst = prune_x_regs(Live, SuccVst1),
+ extract_term(Type, bs_start_match, [Src], Dst,
+ SuccVst, SuccVst0)
+ end).
%%
%% Common code for validating bs_get* instructions.
%%
-validate_bs_get(Op, Fail, Ctx, Live, Type, Dst, Vst0) ->
- bsm_validate_context(Ctx, Vst0),
- verify_live(Live, Vst0),
- verify_y_init(Vst0),
- Vst1 = prune_x_regs(Live, Vst0),
- Vst = branch_state(Fail, Vst1),
- extract_term(Type, Op, [Ctx], Dst, Vst, Vst0).
+validate_bs_get(Op, Fail, Ctx, Live, Type, Dst, Vst) ->
+ bsm_validate_context(Ctx, Vst),
+ verify_live(Live, Vst),
+ verify_y_init(Vst),
+
+ branch(Fail, Vst,
+ fun(SuccVst0) ->
+ SuccVst = prune_x_regs(Live, SuccVst0),
+ extract_term(Type, Op, [Ctx], Dst, SuccVst, SuccVst0)
+ end).
%%
%% Common code for validating bs_skip_utf* instructions.
%%
-validate_bs_skip_utf(Fail, Ctx, Live, Vst0) ->
- bsm_validate_context(Ctx, Vst0),
- verify_y_init(Vst0),
- verify_live(Live, Vst0),
- Vst = prune_x_regs(Live, Vst0),
- branch_state(Fail, Vst).
+validate_bs_skip_utf(Fail, Ctx, Live, Vst) ->
+ bsm_validate_context(Ctx, Vst),
+ verify_y_init(Vst),
+ verify_live(Live, Vst),
+
+ branch(Fail, Vst,
+ fun(SuccVst) ->
+ prune_x_regs(Live, SuccVst)
+ end).
+
+%%
+%% Common code for is_$type instructions.
+%%
+type_test(Fail, Type, Reg, Vst) ->
+ assert_term(Reg, Vst),
+ branch(Fail, Vst,
+ fun(FailVst) ->
+ update_type(fun subtract/2, Type, Reg, FailVst)
+ end,
+ fun(SuccVst) ->
+ update_type(fun meet/2, Type, Reg, SuccVst)
+ end).
%%
%% Special state handling for setelement/3 and set_tuple_element/3 instructions.
@@ -1156,9 +1240,8 @@ verify_local_args(-1, _Lbl, _CtxIds, _Vst) ->
ok;
verify_local_args(X, Lbl, CtxIds, Vst) ->
Reg = {x, X},
- assert_movable(Reg, Vst),
assert_not_fragile(Reg, Vst),
- case get_raw_type(Reg, Vst) of
+ case get_movable_term_type(Reg, Vst) of
#ms{id=Id}=Type ->
case CtxIds of
#{ Id := Other } ->
@@ -1347,7 +1430,7 @@ assert_arities(_) -> error(bad_tuple_arity_list).
%%%
float_op(Ss, Dst, Vst0) ->
- [assert_freg_set(S, Vst0) || S <- Ss],
+ _ = [assert_freg_set(S, Vst0) || S <- Ss],
assert_fls(cleared, Vst0),
Vst = set_fls(cleared, Vst0),
set_freg(Dst, Vst).
@@ -1424,7 +1507,7 @@ bsm_validate_context(Reg, Vst) ->
ok.
bsm_get_context({Kind,_}=Reg, Vst) when Kind =:= x; Kind =:= y->
- case get_raw_type(Reg, Vst) of
+ case get_movable_term_type(Reg, Vst) of
#ms{}=Ctx -> Ctx;
_ -> error({no_bsm_context,Reg})
end;
@@ -1459,44 +1542,46 @@ bsm_restore(Reg, SavePoint, Vst) ->
_ -> error({illegal_restore,SavePoint,range})
end.
-select_val_branches(Fail, Src, Choices, Vst0) ->
- Vst = svb_1(Choices, Src, Vst0),
- kill_state(branch_state(Fail, Vst)).
-
-svb_1([Val,{f,L}|T], Src, Vst0) ->
- Vst = complex_test(L,
- fun(BranchVst) ->
- update_eq_types(Src, Val, BranchVst)
- end,
- fun(FailVst) ->
- update_ne_types(Src, Val, FailVst)
- end, Vst0),
- svb_1(T, Src, Vst);
-svb_1([], _, Vst) ->
- Vst.
-
-select_arity_branches(Fail, List, Tuple, Vst0) ->
- Type = get_term_type(Tuple, Vst0),
- Vst = sab_1(List, Tuple, Type, Vst0),
- kill_state(branch_state(Fail, Vst)).
-
-sab_1([Sz,{f,L}|T], Tuple, {tuple,[_],Es}=Type0, Vst0) ->
- #vst{current=St0} = Vst0,
- Vst1 = update_type(fun meet/2, {tuple,Sz,Es}, Tuple, Vst0),
- Vst2 = branch_state(L, Vst1),
- Vst = Vst2#vst{current=St0},
-
- sab_1(T, Tuple, Type0, Vst);
-sab_1([Sz,{f,L}|T], Tuple, {tuple,Sz,_Es}=Type, Vst0) ->
- %% The type is already correct. (This test is redundant.)
- Vst = branch_state(L, Vst0),
- sab_1(T, Tuple, Type, Vst);
-sab_1([_,{f,_}|T], Tuple, Type, Vst) ->
- %% We already have an established different exact size for the tuple.
- %% This label can't possibly be reached.
- sab_1(T, Tuple, Type, Vst);
-sab_1([], _, _, #vst{}=Vst) ->
- Vst.
+validate_select_val(_Fail, _Choices, _Src, #vst{current=none}=Vst) ->
+ %% We've already branched on all of Src's possible values, so we know we
+ %% can't reach the fail label or any of the remaining choices.
+ Vst;
+validate_select_val(Fail, [Val,{f,L}|T], Src, Vst0) ->
+ Vst = branch(L, Vst0,
+ fun(BranchVst) ->
+ update_eq_types(Src, Val, BranchVst)
+ end,
+ fun(FailVst) ->
+ update_ne_types(Src, Val, FailVst)
+ end),
+ validate_select_val(Fail, T, Src, Vst);
+validate_select_val(Fail, [], _, Vst) ->
+ branch(Fail, Vst,
+ fun(SuccVst) ->
+ %% The next instruction is never executed.
+ kill_state(SuccVst)
+ end).
+
+validate_select_tuple_arity(_Fail, _Choices, _Src, #vst{current=none}=Vst) ->
+ %% We've already branched on all of Src's possible values, so we know we
+ %% can't reach the fail label or any of the remaining choices.
+ Vst;
+validate_select_tuple_arity(Fail, [Arity,{f,L}|T], Tuple, Vst0) ->
+ Type = {tuple, Arity, #{}},
+ Vst = branch(L, Vst0,
+ fun(BranchVst) ->
+ update_type(fun meet/2, Type, Tuple, BranchVst)
+ end,
+ fun(FailVst) ->
+ update_type(fun subtract/2, Type, Tuple, FailVst)
+ end),
+ validate_select_tuple_arity(Fail, T, Tuple, Vst);
+validate_select_tuple_arity(Fail, [], _, #vst{}=Vst) ->
+ branch(Fail, Vst,
+ fun(SuccVst) ->
+ %% The next instruction is never executed.
+ kill_state(SuccVst)
+ end).
infer_types({Kind,_}=Reg, Vst) when Kind =:= x; Kind =:= y ->
infer_types(get_reg_vref(Reg, Vst), Vst);
@@ -1628,26 +1713,6 @@ resolve_args([Lit | Args], Vst) ->
resolve_args([], _Vst) ->
[].
-%% Helper functions for tests that alter state on both the success and fail
-%% branches, keeping the states from tainting each other.
-complex_test(Fail, FailFun, SuccFun, Vst0) ->
- #vst{current=St0} = Vst0,
- Vst1 = FailFun(Vst0),
- Vst2 = branch_state(Fail, Vst1),
- Vst = Vst2#vst{current=St0},
- SuccFun(Vst).
-
-%% Helper function for simple "is_type" tests.
-type_test(Fail, Type, Reg, Vst) ->
- assert_term(Reg, Vst),
- complex_test(Fail,
- fun(FailVst) ->
- update_type(fun subtract/2, Type, Reg, FailVst)
- end,
- fun(SuccVst) ->
- update_type(fun meet/2, Type, Reg, SuccVst)
- end, Vst).
-
%% Overrides the type of Reg. This is ugly but a necessity for certain
%% destructive operations.
override_type(Type, Reg, Vst) ->
@@ -1655,7 +1720,7 @@ override_type(Type, Reg, Vst) ->
%% This is used when linear code finds out more and more information about a
%% type, so that the type gets more specialized.
-update_type(Merge, Type0, #value_ref{}=Ref, Vst) ->
+update_type(Merge, With, #value_ref{}=Ref, Vst) ->
%% If the old type can't be merged with the new one, the type information
%% is inconsistent and we know that some instructions will never be
%% executed at run-time. For example:
@@ -1664,21 +1729,47 @@ update_type(Merge, Type0, #value_ref{}=Ref, Vst) ->
%% {test,is_tuple,Fail,[Reg]}.
%% {test,test_arity,Fail,[Reg,5]}.
%%
- %% Note that the test_arity instruction can never be reached, so we use the
- %% new type instead of 'none'.
- Type = case Merge(get_raw_type(Ref, Vst), Type0) of
- none -> Type0;
- T -> T
- end,
- set_type(Type, Ref, Vst);
-update_type(Merge, Type, {Kind,_}=Reg, Vst) when Kind =:= x; Kind =:= y ->
- update_type(Merge, Type, get_reg_vref(Reg, Vst), Vst);
-update_type(_Merge, _Type, Literal, Vst) ->
+ %% Note that the test_arity instruction can never be reached, so we need to
+ %% kill the state to avoid raising an error when we encounter it.
+ %%
+ %% Simply returning `kill_state(Vst)` is unsafe however as we might be in
+ %% the middle of an instruction, and altering the rest of the validator
+ %% (eg. prune_x_regs/2) to no-op on dead states is prone to error.
+ %%
+ %% We therefore throw a 'type_conflict' error instead, which causes
+ %% validation to fail unless we're in a context where such errors can be
+ %% handled, such as in a branch handler.
+ Current = get_raw_type(Ref, Vst),
+ case Merge(Current, With) of
+ none -> throw({type_conflict, Current, With});
+ Type -> set_type(Type, Ref, Vst)
+ end;
+update_type(Merge, With, {Kind,_}=Reg, Vst) when Kind =:= x; Kind =:= y ->
+ update_type(Merge, With, get_reg_vref(Reg, Vst), Vst);
+update_type(Merge, With, Literal, Vst) ->
assert_literal(Literal),
- Vst.
+ %% Literals always retain their type, but we still need to bail on type
+ %% conflicts.
+ case Merge(Literal, With) of
+ none -> throw({type_conflict, Literal, With});
+ _Type -> Vst
+ end.
update_ne_types(LHS, RHS, Vst) ->
- update_type(fun subtract/2, get_term_type(RHS, Vst), LHS, Vst).
+ %% While updating types on equality is fairly straightforward, inequality
+ %% is a bit trickier since all we know is that the *value* of LHS differs
+ %% from RHS, so we can't blindly subtract their types.
+ %%
+ %% Consider `number =/= {integer,[]}`; all we know is that LHS isn't equal
+ %% to some *specific integer* of unknown value, and if we were to subtract
+ %% {integer,[]} we would erroneously infer that the new type is {float,[]}.
+ %%
+ %% Therefore, we only subtract when we know that RHS has a specific value.
+ RType = get_term_type(RHS, Vst),
+ case is_literal(RType) of
+ true -> update_type(fun subtract/2, RType, LHS, Vst);
+ false -> Vst
+ end.
update_eq_types(LHS, RHS, Vst0) ->
Infer = infer_types(LHS, Vst0),
@@ -1786,19 +1877,27 @@ assert_term(Src, Vst) ->
ok.
assert_movable(Src, Vst) ->
- _ = get_move_term_type(Src, Vst),
+ _ = get_movable_term_type(Src, Vst),
ok.
-assert_literal(nil) -> ok;
-assert_literal({atom,A}) when is_atom(A) -> ok;
-assert_literal({float,F}) when is_float(F) -> ok;
-assert_literal({integer,I}) when is_integer(I) -> ok;
-assert_literal({literal,_L}) -> ok;
-assert_literal(T) -> error({literal_required,T}).
+assert_literal(Src) ->
+ case is_literal(Src) of
+ true -> ok;
+ false -> error({literal_required,Src})
+ end.
+
+assert_not_literal(Src) ->
+ case is_literal(Src) of
+ true -> error({literal_not_allowed,Src});
+ false -> ok
+ end.
-assert_not_literal({x,_}) -> ok;
-assert_not_literal({y,_}) -> ok;
-assert_not_literal(Literal) -> error({literal_not_allowed,Literal}).
+is_literal(nil) -> true;
+is_literal({atom,A}) when is_atom(A) -> true;
+is_literal({float,F}) when is_float(F) -> true;
+is_literal({integer,I}) when is_integer(I) -> true;
+is_literal({literal,_L}) -> true;
+is_literal(_) -> false.
%% The possible types.
%%
@@ -2050,6 +2149,7 @@ assert_tuple_elements(Limit, Es) ->
%% Subtract Type2 from Type2. Example:
%% subtract(list, nil) -> cons
+subtract(Same, Same) -> none;
subtract(list, nil) -> cons;
subtract(list, cons) -> nil;
subtract(number, {integer,[]}) -> {float,[]};
@@ -2082,11 +2182,10 @@ assert_type(Needed, Actual) ->
get_element_type(Key, Src, Vst) ->
get_element_type_1(Key, get_term_type(Src, Vst)).
-get_element_type_1({integer,Index}=Key, {tuple,Sz,Es}) ->
+get_element_type_1({integer,_}=Key, {tuple,_Sz,Es}) ->
case Es of
#{ Key := Type } -> Type;
- #{} when Index =< Sz -> term;
- #{} -> none
+ #{} -> term
end;
get_element_type_1(_Index, _Type) ->
term.
@@ -2103,7 +2202,7 @@ get_tuple_size({integer,Sz}) -> Sz;
get_tuple_size(_) -> 0.
validate_src(Ss, Vst) when is_list(Ss) ->
- [assert_term(S, Vst) || S <- Ss],
+ _ = [assert_term(S, Vst) || S <- Ss],
ok.
%% get_term_type(Src, ValidatorState) -> Type
@@ -2111,22 +2210,23 @@ validate_src(Ss, Vst) when is_list(Ss) ->
%% a standard Erlang type (no catch/try tags or match contexts).
get_term_type(Src, Vst) ->
- case get_move_term_type(Src, Vst) of
+ case get_movable_term_type(Src, Vst) of
#ms{} -> error({match_context,Src});
Type -> Type
end.
-%% get_move_term_type(Src, ValidatorState) -> Type
+%% get_movable_term_type(Src, ValidatorState) -> Type
%% Get the type of the source Src. The returned type Type will be
%% a standard Erlang type (no catch/try tags). Match contexts are OK.
-get_move_term_type(Src, Vst) ->
+get_movable_term_type(Src, Vst) ->
case get_raw_type(Src, Vst) of
initialized -> error({unassigned,Src});
uninitialized -> error({uninitialized_reg,Src});
{catchtag,_} -> error({catchtag,Src});
{trytag,_} -> error({trytag,Src});
tuple_in_progress -> error({tuple_in_progress,Src});
+ {literal,_}=Lit -> get_literal_type(Lit);
Type -> Type
end.
@@ -2145,7 +2245,8 @@ get_tag_type(Src, _) ->
error({invalid_tag_register,Src}).
%% get_raw_type(Src, ValidatorState) -> Type
-%% Return the type of a register without doing any validity checks.
+%% Return the type of a register without doing any validity checks or
+%% conversions.
get_raw_type({x,X}=Src, #vst{current=#st{xs=Xs}}=Vst) when is_integer(X) ->
check_limit(Src),
case Xs of
@@ -2192,10 +2293,53 @@ glt_1(T) when is_tuple(T) ->
glt_1(L) ->
{literal, L}.
+%%%
+%%% Branch tracking
+%%%
+
+%% Forks the execution flow, with the provided funs returning the new state of
+%% their respective branch; the "fail" fun returns the state where the branch
+%% is taken, and the "success" fun returns the state where it's not.
+%%
+%% If either path is known not to be taken at runtime (eg. due to a type
+%% conflict), it will simply be discarded.
+-spec branch(Lbl :: label(),
+ Original :: #vst{},
+ FailFun :: BranchFun,
+ SuccFun :: BranchFun) -> #vst{} when
+ BranchFun :: fun((#vst{}) -> #vst{}).
+branch(Lbl, Vst0, FailFun, SuccFun) ->
+ #vst{current=St0} = Vst0,
+ try FailFun(Vst0) of
+ Vst1 ->
+ Vst2 = branch_state(Lbl, Vst1),
+ Vst = Vst2#vst{current=St0},
+ try SuccFun(Vst) of
+ V -> V
+ catch
+ {type_conflict, _, _} ->
+ %% The instruction is guaranteed to fail; kill the state.
+ kill_state(Vst)
+ end
+ catch
+ {type_conflict, _, _} ->
+ %% This instruction is guaranteed not to fail, so we run the
+ %% success branch *without* catching type conflicts to avoid hiding
+ %% errors in the validator itself; one of the branches must
+ %% succeed.
+ SuccFun(Vst0)
+ end.
+
+%% A shorthand version of branch/4 for when the state is only altered on
+%% success.
+branch(Fail, Vst, SuccFun) ->
+ branch(Fail, Vst, fun(V) -> V end, SuccFun).
+
+%% Directly branches off the state. This is an "internal" operation that should
+%% be used sparingly.
branch_state(0, #vst{}=Vst) ->
- %% If the instruction fails, the stack may be scanned
- %% looking for a catch tag. Therefore the Y registers
- %% must be initialized at this point.
+ %% If the instruction fails, the stack may be scanned looking for a catch
+ %% tag. Therefore the Y registers must be initialized at this point.
verify_y_init(Vst),
Vst;
branch_state(L, #vst{current=St,branched=B,ref_ctr=Counter0}=Vst) ->
diff --git a/lib/compiler/test/beam_except_SUITE.erl b/lib/compiler/test/beam_except_SUITE.erl
index 9380fe06c8..8e3b373d29 100644
--- a/lib/compiler/test/beam_except_SUITE.erl
+++ b/lib/compiler/test/beam_except_SUITE.erl
@@ -84,9 +84,16 @@ coverage(_) ->
{'EXIT',{function_clause,
[{?MODULE,fc,[y],[File,{line,2}]}|_]}} =
(catch fc(y)),
- {'EXIT',{function_clause,
- [{?MODULE,fc,[[a,b,c]],[File,{line,6}]}|_]}} =
- (catch fc([a,b,c])),
+ case ?MODULE of
+ beam_except_no_opt_SUITE ->
+ %% There will be a different stack fram in
+ %% unoptimized code.
+ ok;
+ _ ->
+ {'EXIT',{function_clause,
+ [{?MODULE,fc,[[a,b,c]],[File,{line,6}]}|_]}} =
+ (catch fc([a,b,c]))
+ end,
{'EXIT',{undef,[{erlang,error,[a,b,c],_}|_]}} =
(catch erlang:error(a, b, c)),
diff --git a/lib/compiler/test/beam_validator_SUITE.erl b/lib/compiler/test/beam_validator_SUITE.erl
index 265da43f9d..8b39fce479 100644
--- a/lib/compiler/test/beam_validator_SUITE.erl
+++ b/lib/compiler/test/beam_validator_SUITE.erl
@@ -34,7 +34,7 @@
undef_label/1,illegal_instruction/1,failing_gc_guard_bif/1,
map_field_lists/1,cover_bin_opt/1,
val_dsetel/1,bad_tuples/1,bad_try_catch_nesting/1,
- receive_stacked/1,aliased_types/1]).
+ receive_stacked/1,aliased_types/1,type_conflict/1]).
-include_lib("common_test/include/ct.hrl").
@@ -63,7 +63,7 @@ groups() ->
undef_label,illegal_instruction,failing_gc_guard_bif,
map_field_lists,cover_bin_opt,val_dsetel,
bad_tuples,bad_try_catch_nesting,
- receive_stacked,aliased_types]}].
+ receive_stacked,aliased_types,type_conflict]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -156,8 +156,8 @@ call_last(Config) when is_list(Config) ->
merge_undefined(Config) when is_list(Config) ->
Errors = do_val(merge_undefined, Config),
[{{t,handle_call,2},
- {{call_ext,1,{extfunc,erlang,exit,1}},
- 10,
+ {{call_ext,2,{extfunc,debug,filter,2}},
+ 22,
{uninitialized_reg,{y,_}}}}] = Errors,
ok.
@@ -630,6 +630,27 @@ aliased_types_3(Bug) ->
hd(List)
end.
+
+%% ERL-867; validation proceeded after a type conflict, causing incorrect types
+%% to be joined.
+
+-record(r, { e1 = e1, e2 = e2 }).
+
+type_conflict(Config) when is_list(Config) ->
+ {e1, e2} = type_conflict_1(#r{}),
+ ok.
+
+type_conflict_1(C) ->
+ Src = id(C#r.e2),
+ TRes = try id(Src) of
+ R -> R
+ catch
+ %% C:R can never match, yet it assumed that the type of 'C' was
+ %% an atom from here on.
+ C:R -> R
+ end,
+ {C#r.e1, TRes}.
+
%%%-------------------------------------------------------------------------
transform_remove(Remove, Module) ->
diff --git a/lib/compiler/test/beam_validator_SUITE_data/merge_undefined.S b/lib/compiler/test/beam_validator_SUITE_data/merge_undefined.S
index 481d55045d..aa344807e4 100644
--- a/lib/compiler/test/beam_validator_SUITE_data/merge_undefined.S
+++ b/lib/compiler/test/beam_validator_SUITE_data/merge_undefined.S
@@ -15,8 +15,9 @@
{select_val,{x,0},{f,1},{list,[{atom,gurka},{f,3},{atom,delete},{f,4}]}}.
{label,3}.
{allocate_heap,2,6,2}.
- %% The Y registers are not initialized here.
{test,is_eq_exact,{f,5},[{x,0},{atom,ok}]}.
+ %% This is unreachable since {x,0} is known not to be 'ok'. We should not
+ %% fail with "uninitialized y registers" on erlang:exit/1
{move,{atom,nisse},{x,0}}.
{call_ext,1,{extfunc,erlang,exit,1}}.
{label,4}.
@@ -29,6 +30,7 @@
{call_ext,2,{extfunc,io,format,2}}.
{test,is_ne_exact,{f,6},[{x,0},{atom,ok}]}.
{label,5}.
+ %% The Y registers are not initialized here.
{move,{atom,logReader},{x,1}}.
{move,{atom,console},{x,0}}.
{call_ext,2,{extfunc,debug,filter,2}}.
diff --git a/lib/compiler/test/beam_validator_SUITE_data/receive_stacked.S b/lib/compiler/test/beam_validator_SUITE_data/receive_stacked.S
index 5b974119c6..a878204d16 100644
--- a/lib/compiler/test/beam_validator_SUITE_data/receive_stacked.S
+++ b/lib/compiler/test/beam_validator_SUITE_data/receive_stacked.S
@@ -240,7 +240,7 @@
{y,0}}.
{'%',{no_bin_opt,{binary_used_in,{test,is_binary,{f,34},[{y,0}]}},
[63,{file,"receive_stacked.erl"}]}}.
- {test,is_binary,{f,34},[{y,0}]}.
+ {test,is_eq_exact,{f,34},[{y,0},{literal,<<0,1,2,3>>}]}.
remove_message.
{move,{integer,42},{x,0}}.
{line,[{location,"receive_stacked.erl",64}]}.
@@ -283,7 +283,7 @@
{y,0}}.
{'%',{no_bin_opt,{[{x,1},{y,0}],{loop_rec_end,{f,38}},not_handled},
[70,{file,"receive_stacked.erl"}]}}.
- {test,is_binary,{f,39},[{x,0}]}.
+ {test,is_eq_exact,{f,39},[{x,0},{literal,<<0,1,2,3>>}]}.
remove_message.
{move,{integer,42},{x,0}}.
{line,[{location,"receive_stacked.erl",71}]}.
diff --git a/lib/compiler/test/float_SUITE.erl b/lib/compiler/test/float_SUITE.erl
index 831e8279aa..0fa8070dc8 100644
--- a/lib/compiler/test/float_SUITE.erl
+++ b/lib/compiler/test/float_SUITE.erl
@@ -21,15 +21,16 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
pending/1,bif_calls/1,math_functions/1,mixed_float_and_int/1,
- subtract_number_type/1]).
+ subtract_number_type/1,float_followed_by_guard/1]).
-include_lib("common_test/include/ct.hrl").
suite() -> [{ct_hooks,[ts_install_cth]}].
-all() ->
+all() ->
[pending, bif_calls, math_functions,
- mixed_float_and_int, subtract_number_type].
+ mixed_float_and_int, subtract_number_type,
+ float_followed_by_guard].
groups() ->
[].
@@ -187,5 +188,21 @@ fact(0, P) -> P;
fact(1, P) -> P;
fact(N, P) -> fact(N-1, P*N).
+float_followed_by_guard(Config) when is_list(Config) ->
+ true = ffbg_1(5, 1),
+ false = ffbg_1(1, 5),
+ ok.
+
+ffbg_1(A, B0) ->
+ %% This is a non-guard block followed by a *guard block* that starts with a
+ %% floating point operation, and the compiler erroneously assumed that it
+ %% was safe to skip fcheckerror because the next block started with a float
+ %% op.
+ B = id(B0) / 1.0,
+ if
+ A - B > 0.0 -> true;
+ A - B =< 0.0 -> false
+ end.
+
id(I) -> I.
diff --git a/lib/compiler/vsn.mk b/lib/compiler/vsn.mk
index efedb414ad..a523627384 100644
--- a/lib/compiler/vsn.mk
+++ b/lib/compiler/vsn.mk
@@ -1 +1 @@
-COMPILER_VSN = 7.3.1
+COMPILER_VSN = 7.3.2
diff --git a/lib/crypto/c_src/atoms.c b/lib/crypto/c_src/atoms.c
index 2e417da7f4..798c26c9bb 100644
--- a/lib/crypto/c_src/atoms.c
+++ b/lib/crypto/c_src/atoms.c
@@ -41,6 +41,18 @@ ERL_NIF_TERM atom_not_enabled;
ERL_NIF_TERM atom_not_supported;
#endif
+ERL_NIF_TERM atom_type;
+ERL_NIF_TERM atom_size;
+ERL_NIF_TERM atom_block_size;
+ERL_NIF_TERM atom_key_length;
+ERL_NIF_TERM atom_iv_length;
+ERL_NIF_TERM atom_mode;
+ERL_NIF_TERM atom_ecb_mode;
+ERL_NIF_TERM atom_cbc_mode;
+ERL_NIF_TERM atom_cfb_mode;
+ERL_NIF_TERM atom_ofb_mode;
+ERL_NIF_TERM atom_stream_cipher;
+
#if defined(HAVE_EC)
ERL_NIF_TERM atom_prime_field;
ERL_NIF_TERM atom_characteristic_two_field;
@@ -140,6 +152,18 @@ int init_atoms(ErlNifEnv *env, const ERL_NIF_TERM fips_mode, const ERL_NIF_TERM
atom_notsup = enif_make_atom(env,"notsup");
atom_digest = enif_make_atom(env,"digest");
+ atom_type = enif_make_atom(env,"type");
+ atom_size = enif_make_atom(env,"size");
+ atom_block_size = enif_make_atom(env,"block_size");
+ atom_key_length = enif_make_atom(env,"key_length");
+ atom_iv_length = enif_make_atom(env,"iv_length");
+ atom_mode = enif_make_atom(env,"mode");
+ atom_ecb_mode = enif_make_atom(env,"ecb_mode");
+ atom_cbc_mode = enif_make_atom(env,"cbc_mode");
+ atom_cfb_mode = enif_make_atom(env,"cfb_mode");
+ atom_ofb_mode = enif_make_atom(env,"ofb_mode");
+ atom_stream_cipher = enif_make_atom(env,"stream_cipher");
+
#if defined(HAVE_EC)
atom_prime_field = enif_make_atom(env,"prime_field");
atom_characteristic_two_field = enif_make_atom(env,"characteristic_two_field");
diff --git a/lib/crypto/c_src/atoms.h b/lib/crypto/c_src/atoms.h
index f15523d865..f8e9211459 100644
--- a/lib/crypto/c_src/atoms.h
+++ b/lib/crypto/c_src/atoms.h
@@ -45,6 +45,18 @@ extern ERL_NIF_TERM atom_not_enabled;
extern ERL_NIF_TERM atom_not_supported;
#endif
+extern ERL_NIF_TERM atom_type;
+extern ERL_NIF_TERM atom_size;
+extern ERL_NIF_TERM atom_block_size;
+extern ERL_NIF_TERM atom_key_length;
+extern ERL_NIF_TERM atom_iv_length;
+extern ERL_NIF_TERM atom_mode;
+extern ERL_NIF_TERM atom_ecb_mode;
+extern ERL_NIF_TERM atom_cbc_mode;
+extern ERL_NIF_TERM atom_cfb_mode;
+extern ERL_NIF_TERM atom_ofb_mode;
+extern ERL_NIF_TERM atom_stream_cipher;
+
#if defined(HAVE_EC)
extern ERL_NIF_TERM atom_prime_field;
extern ERL_NIF_TERM atom_characteristic_two_field;
diff --git a/lib/crypto/c_src/cipher.c b/lib/crypto/c_src/cipher.c
index f8e44b228a..c055a62654 100644
--- a/lib/crypto/c_src/cipher.c
+++ b/lib/crypto/c_src/cipher.c
@@ -67,14 +67,26 @@ static struct cipher_type_t cipher_types[] =
{{"aes_cfb8"}, {&EVP_aes_192_cfb8}, 24, NO_FIPS_CIPHER | AES_CFBx},
{{"aes_cfb8"}, {&EVP_aes_256_cfb8}, 32, NO_FIPS_CIPHER | AES_CFBx},
+ {{"aes_128_cfb8"}, {&EVP_aes_128_cfb8}, 16, NO_FIPS_CIPHER | AES_CFBx},
+ {{"aes_192_cfb8"}, {&EVP_aes_192_cfb8}, 24, NO_FIPS_CIPHER | AES_CFBx},
+ {{"aes_256_cfb8"}, {&EVP_aes_256_cfb8}, 32, NO_FIPS_CIPHER | AES_CFBx},
+
{{"aes_cfb128"}, {&EVP_aes_128_cfb128}, 16, NO_FIPS_CIPHER | AES_CFBx},
{{"aes_cfb128"}, {&EVP_aes_192_cfb128}, 24, NO_FIPS_CIPHER | AES_CFBx},
{{"aes_cfb128"}, {&EVP_aes_256_cfb128}, 32, NO_FIPS_CIPHER | AES_CFBx},
+ {{"aes_128_cfb128"}, {&EVP_aes_128_cfb128}, 16, NO_FIPS_CIPHER | AES_CFBx},
+ {{"aes_192_cfb128"}, {&EVP_aes_192_cfb128}, 24, NO_FIPS_CIPHER | AES_CFBx},
+ {{"aes_256_cfb128"}, {&EVP_aes_256_cfb128}, 32, NO_FIPS_CIPHER | AES_CFBx},
+
{{"aes_ecb"}, {&EVP_aes_128_ecb}, 16, ECB_BUG_0_9_8L},
{{"aes_ecb"}, {&EVP_aes_192_ecb}, 24, ECB_BUG_0_9_8L},
{{"aes_ecb"}, {&EVP_aes_256_ecb}, 32, ECB_BUG_0_9_8L},
+ {{"aes_128_ecb"}, {&EVP_aes_128_ecb}, 16, ECB_BUG_0_9_8L},
+ {{"aes_192_ecb"}, {&EVP_aes_192_ecb}, 24, ECB_BUG_0_9_8L},
+ {{"aes_256_ecb"}, {&EVP_aes_256_ecb}, 32, ECB_BUG_0_9_8L},
+
#if defined(HAVE_EVP_AES_CTR)
{{"aes_128_ctr"}, {&EVP_aes_128_ctr}, 16, 0},
{{"aes_192_ctr"}, {&EVP_aes_192_ctr}, 24, 0},
@@ -207,6 +219,87 @@ int cmp_cipher_types(const void *keyp, const void *elemp) {
}
+ERL_NIF_TERM cipher_info_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type) */
+ const struct cipher_type_t *cipherp;
+ const EVP_CIPHER *cipher;
+ ERL_NIF_TERM ret, ret_mode;
+ unsigned type;
+ unsigned long mode;
+
+ if ((cipherp = get_cipher_type_no_key(argv[0])) == NULL)
+ return enif_make_badarg(env);
+
+ if (FORBIDDEN_IN_FIPS(cipherp))
+ return enif_raise_exception(env, atom_notsup);
+ if ((cipher = cipherp->cipher.p) == NULL)
+ return enif_raise_exception(env, atom_notsup);
+
+ ret = enif_make_new_map(env);
+
+ type = EVP_CIPHER_type(cipher);
+ enif_make_map_put(env, ret, atom_type,
+ type == NID_undef ? atom_undefined : enif_make_int(env, type),
+ &ret);
+
+ enif_make_map_put(env, ret, atom_key_length,
+ enif_make_int(env, EVP_CIPHER_key_length(cipher)), &ret);
+ enif_make_map_put(env, ret, atom_iv_length,
+ enif_make_int(env, EVP_CIPHER_iv_length(cipher)), &ret);
+ enif_make_map_put(env, ret, atom_block_size,
+ enif_make_int(env, EVP_CIPHER_block_size(cipher)), &ret);
+
+ mode = EVP_CIPHER_mode(cipher);
+ switch (mode) {
+ case EVP_CIPH_ECB_MODE:
+ ret_mode = atom_ecb_mode;
+ break;
+
+ case EVP_CIPH_CBC_MODE:
+ ret_mode = atom_cbc_mode;
+ break;
+
+ case EVP_CIPH_CFB_MODE:
+ ret_mode = atom_cfb_mode;
+ break;
+
+ case EVP_CIPH_OFB_MODE:
+ ret_mode = atom_ofb_mode;
+ break;
+
+ case EVP_CIPH_STREAM_CIPHER:
+ ret_mode = atom_stream_cipher;
+ break;
+
+ default:
+ ret_mode = atom_undefined;
+ break;
+ }
+
+ enif_make_map_put(env, ret, atom_mode, ret_mode, &ret);
+
+ return ret;
+}
+
+const struct cipher_type_t* get_cipher_type_no_key(ERL_NIF_TERM type)
+{
+ struct cipher_type_t key;
+
+ key.type.atom = type;
+
+ return bsearch(&key, cipher_types, num_cipher_types, sizeof(cipher_types[0]), cmp_cipher_types_no_key);
+}
+
+int cmp_cipher_types_no_key(const void *keyp, const void *elemp) {
+ const struct cipher_type_t *key = keyp;
+ const struct cipher_type_t *elem = elemp;
+
+ if (key->type.atom < elem->type.atom) return -1;
+ else if (key->type.atom > elem->type.atom) return 1;
+ else /* key->type.atom == elem->type.atom */ return 0;
+}
+
+
ERL_NIF_TERM cipher_types_as_list(ErlNifEnv* env)
{
struct cipher_type_t* p;
diff --git a/lib/crypto/c_src/cipher.h b/lib/crypto/c_src/cipher.h
index 6b43afea99..b0d9d324e1 100644
--- a/lib/crypto/c_src/cipher.h
+++ b/lib/crypto/c_src/cipher.h
@@ -61,12 +61,16 @@ struct evp_cipher_ctx {
EVP_CIPHER_CTX* ctx;
};
+ERL_NIF_TERM cipher_info_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
int init_cipher_ctx(ErlNifEnv *env);
void init_cipher_types(ErlNifEnv* env);
+const struct cipher_type_t* get_cipher_type_no_key(ERL_NIF_TERM type);
const struct cipher_type_t* get_cipher_type(ERL_NIF_TERM type, size_t key_len);
int cmp_cipher_types(const void *keyp, const void *elemp);
+int cmp_cipher_types_no_key(const void *keyp, const void *elemp);
ERL_NIF_TERM cipher_types_as_list(ErlNifEnv* env);
diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c
index 06439c34b2..261590d9a5 100644
--- a/lib/crypto/c_src/crypto.c
+++ b/lib/crypto/c_src/crypto.c
@@ -67,6 +67,7 @@ static ErlNifFunc nif_funcs[] = {
{"info_fips", 0, info_fips, 0},
{"enable_fips_mode", 1, enable_fips_mode, 0},
{"algorithms", 0, algorithms, 0},
+ {"hash_info", 1, hash_info_nif, 0},
{"hash_nif", 2, hash_nif, 0},
{"hash_init_nif", 1, hash_init_nif, 0},
{"hash_update_nif", 2, hash_update_nif, 0},
@@ -78,6 +79,7 @@ static ErlNifFunc nif_funcs[] = {
{"hmac_final_nif", 1, hmac_final_nif, 0},
{"hmac_final_nif", 2, hmac_final_nif, 0},
{"cmac_nif", 3, cmac_nif, 0},
+ {"cipher_info_nif", 1, cipher_info_nif, 0},
{"block_crypt_nif", 5, block_crypt_nif, 0},
{"block_crypt_nif", 4, block_crypt_nif, 0},
{"aes_ige_crypt_nif", 4, aes_ige_crypt_nif, 0},
diff --git a/lib/crypto/c_src/hash.c b/lib/crypto/c_src/hash.c
index 457e9d071a..0a9f64acef 100644
--- a/lib/crypto/c_src/hash.c
+++ b/lib/crypto/c_src/hash.c
@@ -61,6 +61,32 @@ int init_hash_ctx(ErlNifEnv* env) {
#endif
}
+ERL_NIF_TERM hash_info_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type) */
+ struct digest_type_t *digp = NULL;
+ const EVP_MD *md;
+ ERL_NIF_TERM ret;
+
+ ASSERT(argc == 1);
+
+ if ((digp = get_digest_type(argv[0])) == NULL)
+ return enif_make_badarg(env);
+
+ if ((md = digp->md.p) == NULL)
+ return atom_notsup;
+
+ ret = enif_make_new_map(env);
+
+ enif_make_map_put(env, ret, atom_type,
+ enif_make_int(env, EVP_MD_type(md)), &ret);
+ enif_make_map_put(env, ret, atom_size,
+ enif_make_int(env, EVP_MD_size(md)), &ret);
+ enif_make_map_put(env, ret, atom_block_size,
+ enif_make_int(env, EVP_MD_block_size(md)), &ret);
+
+ return ret;
+}
+
ERL_NIF_TERM hash_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{/* (Type, Data) */
struct digest_type_t *digp = NULL;
diff --git a/lib/crypto/c_src/hash.h b/lib/crypto/c_src/hash.h
index 8bae07f39a..92a25cedb7 100644
--- a/lib/crypto/c_src/hash.h
+++ b/lib/crypto/c_src/hash.h
@@ -25,6 +25,7 @@
int init_hash_ctx(ErlNifEnv *env);
+ERL_NIF_TERM hash_info_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM hash_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM hash_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM hash_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
diff --git a/lib/crypto/c_src/openssl_config.h b/lib/crypto/c_src/openssl_config.h
index 45144a0c25..1c138e3bd1 100644
--- a/lib/crypto/c_src/openssl_config.h
+++ b/lib/crypto/c_src/openssl_config.h
@@ -203,12 +203,17 @@
#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
# ifndef HAS_LIBRESSL
-# define HAVE_CHACHA20
# define HAVE_CHACHA20_POLY1305
# define HAVE_RSA_OAEP_MD
# endif
#endif
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(1,1,0,'d')
+# ifndef HAS_LIBRESSL
+# define HAVE_CHACHA20
+# endif
+#endif
+
// OPENSSL_VERSION_NUMBER >= 1.1.1-pre8
#if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1)-7)
# ifndef HAS_LIBRESSL
diff --git a/lib/crypto/c_src/poly1305.c b/lib/crypto/c_src/poly1305.c
index db3433dce3..76579c0a29 100644
--- a/lib/crypto/c_src/poly1305.c
+++ b/lib/crypto/c_src/poly1305.c
@@ -85,6 +85,6 @@ ERL_NIF_TERM poly1305_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
return ret;
#else
- return atom_notsup;
+ return enif_raise_exception(env, atom_notsup);
#endif
}
diff --git a/lib/crypto/doc/src/notes.xml b/lib/crypto/doc/src/notes.xml
index 0a3f68ade2..c0b302734e 100644
--- a/lib/crypto/doc/src/notes.xml
+++ b/lib/crypto/doc/src/notes.xml
@@ -31,6 +31,38 @@
</header>
<p>This document describes the changes made to the Crypto application.</p>
+<section><title>Crypto 4.4.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixes a bug that caused <c>crypto:sign</c> and
+ <c>crypto:verify</c> to return the error message
+ <c>badarg</c> instead of <c>notsup</c> in one case. That
+ case was when signing or verifying with eddsa keys (that
+ is, ed15519 or ed448), but only when FIPS was supported
+ and enabled.</p>
+ <p>
+ Own Id: OTP-15634</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Added a crypto benchmark test suite.</p>
+ <p>
+ Own Id: OTP-15447</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Crypto 4.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl
index fe8390c5b8..97a4a7a3f0 100644
--- a/lib/crypto/src/crypto.erl
+++ b/lib/crypto/src/crypto.erl
@@ -24,6 +24,7 @@
-export([start/0, stop/0, info_lib/0, info_fips/0, supports/0, enable_fips_mode/1,
version/0, bytes_to_integer/1]).
+-export([cipher_info/1, hash_info/1]).
-export([hash/2, hash_init/1, hash_update/2, hash_final/1]).
-export([sign/4, sign/5, verify/5, verify/6]).
-export([generate_key/2, generate_key/3, compute_key/4]).
@@ -403,6 +404,11 @@ enable_fips_mode(_) -> ?nif_stub.
-define(HASH_HASH_ALGORITHM, sha1() | sha2() | sha3() | blake2() | ripemd160 | compatibility_only_hash() ).
+-spec hash_info(Type) -> map() when Type :: ?HASH_HASH_ALGORITHM.
+
+hash_info(Type) ->
+ notsup_to_error(hash_info_nif(Type)).
+
-spec hash(Type, Data) -> Digest when Type :: ?HASH_HASH_ALGORITHM,
Data :: iodata(),
Digest :: binary().
@@ -531,6 +537,12 @@ poly1305(Key, Data) ->
%%%
%%%================================================================
+-spec cipher_info(Type) -> map() when Type :: block_cipher_with_iv()
+ | aead_cipher()
+ | block_cipher_without_iv().
+cipher_info(Type) ->
+ cipher_info_nif(Type).
+
%%%---- Block ciphers
%%%----------------------------------------------------------------
@@ -1726,6 +1738,7 @@ hash_update(State0, Data, _, MaxBytes) ->
State = notsup_to_error(hash_update_nif(State0, Increment)),
hash_update(State, Rest, erlang:byte_size(Rest), MaxBytes).
+hash_info_nif(_Hash) -> ?nif_stub.
hash_nif(_Hash, _Data) -> ?nif_stub.
hash_init_nif(_Hash) -> ?nif_stub.
hash_update_nif(_State, _Data) -> ?nif_stub.
@@ -1770,6 +1783,8 @@ poly1305_nif(_Key, _Data) -> ?nif_stub.
%% CIPHERS --------------------------------------------------------------------
+cipher_info_nif(_Type) -> ?nif_stub.
+
block_crypt_nif(_Type, _Key, _Ivec, _Text, _IsEncrypt) -> ?nif_stub.
block_crypt_nif(_Type, _Key, _Text, _IsEncrypt) -> ?nif_stub.
diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl
index ab6d88deb2..7257f4fb9f 100644
--- a/lib/crypto/test/crypto_SUITE.erl
+++ b/lib/crypto/test/crypto_SUITE.erl
@@ -40,7 +40,9 @@ all() ->
rand_uniform,
rand_threads,
rand_plugin,
- rand_plugin_s
+ rand_plugin_s,
+ cipher_info,
+ hash_info
].
groups() ->
@@ -666,6 +668,23 @@ rand_plugin_s(Config) when is_list(Config) ->
rand_plugin_aux(explicit_state).
%%--------------------------------------------------------------------
+cipher_info() ->
+ [{doc, "crypto cipher_info testing"}].
+cipher_info(Config) when is_list(Config) ->
+ #{type := _,key_length := _,iv_length := _,
+ block_size := _,mode := _} = crypto:cipher_info(aes_128_cbc),
+ {'EXIT',_} = (catch crypto:cipher_info(not_a_cipher)),
+ ok.
+
+%%--------------------------------------------------------------------
+hash_info() ->
+ [{doc, "crypto hash_info testing"}].
+hash_info(Config) when is_list(Config) ->
+ #{type := _,size := _,block_size := _} = crypto:hash_info(sha256),
+ {'EXIT',_} = (catch crypto:hash_info(not_a_hash)),
+ ok.
+
+%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
hash(_, [], []) ->
diff --git a/lib/crypto/vsn.mk b/lib/crypto/vsn.mk
index 6a91244715..deba17fb66 100644
--- a/lib/crypto/vsn.mk
+++ b/lib/crypto/vsn.mk
@@ -1 +1 @@
-CRYPTO_VSN = 4.4
+CRYPTO_VSN = 4.4.1
diff --git a/lib/debugger/doc/src/Makefile b/lib/debugger/doc/src/Makefile
index 56d6085e9c..49b5a4be57 100644
--- a/lib/debugger/doc/src/Makefile
+++ b/lib/debugger/doc/src/Makefile
@@ -114,8 +114,7 @@ include $(ERL_TOP)/make/otp_release_targets.mk
release_docs_spec: docs
$(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
$(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- ($(CP) -rf $(HTMLDIR) "$(RELSYSDIR)/doc")
+ $(INSTALL_DIR_DATA) $(HTMLDIR) "$(RELSYSDIR)/doc/html"
$(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
$(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
$(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
diff --git a/lib/dialyzer/doc/src/notes.xml b/lib/dialyzer/doc/src/notes.xml
index 3cf776e566..bc422c43a0 100644
--- a/lib/dialyzer/doc/src/notes.xml
+++ b/lib/dialyzer/doc/src/notes.xml
@@ -32,6 +32,36 @@
<p>This document describes the changes made to the Dialyzer
application.</p>
+<section><title>Dialyzer 3.3.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p> Fix a bug that caused Dialyzer to crash when
+ analyzing a contract with a module name differing from
+ the analyzed module's name. The bug was introduced in
+ Erlang/OTP 18. </p>
+ <p>
+ Own Id: OTP-15562 Aux Id: ERL-845 </p>
+ </item>
+ <item>
+ <p> Fix a bug in the handling of the <c>Key</c> argument
+ of <c>lists:{keysearch, keyfind, keymember}</c>. </p>
+ <p>
+ Own Id: OTP-15570</p>
+ </item>
+ <item>
+ <p>Optimize (again) Dialyzer's handling of
+ left-associative use of <c>andalso</c> and <c>orelse</c>
+ in guards.</p>
+ <p>
+ Own Id: OTP-15577 Aux Id: ERL-851, PR-2141, PR-1944 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Dialyzer 3.3.1</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/dialyzer/vsn.mk b/lib/dialyzer/vsn.mk
index 98ab533a58..7221993963 100644
--- a/lib/dialyzer/vsn.mk
+++ b/lib/dialyzer/vsn.mk
@@ -1 +1 @@
-DIALYZER_VSN = 3.3.1
+DIALYZER_VSN = 3.3.2
diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml
index 0a0194af2d..85522c99b2 100644
--- a/lib/diameter/doc/src/diameter.xml
+++ b/lib/diameter/doc/src/diameter.xml
@@ -1,7 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE erlref SYSTEM "erlref.dtd" [
- <!ENTITY spawn_opt
+ <!ENTITY spawn_opt2
'<seealso marker="erts:erlang#spawn_opt-2">erlang:spawn_opt/2</seealso>'>
+ <!ENTITY spawn_opt5
+ '<seealso marker="erts:erlang#spawn_opt-5">erlang:spawn_opt/5</seealso>'>
<!ENTITY nodes
'<seealso marker="erts:erlang#nodes-0">erlang:nodes/0</seealso>'>
<!ENTITY make_ref
@@ -21,7 +23,7 @@
<copyright>
<year>2011</year>
-<year>2017</year>
+<year>2019</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -1384,12 +1386,22 @@ the same peer.</p>
</item>
<tag>
-<marker id="spawn_opt"/><c>{spawn_opt, [term()]}</c></tag>
+<marker id="spawn_opt"/><c>{spawn_opt, [term()] | {M,F,A}}</c></tag>
<item>
<p>
-Options passed to &spawn_opt; when spawning a process for an
-incoming Diameter request.
-Options <c>monitor</c> and <c>link</c> are ignored.</p>
+An options list passed to &spawn_opt2; to spawn a handler process for an
+incoming Diameter request on the local node, or an MFA that returns
+the pid of a handler process.</p>
+
+<p>
+Options <c>monitor</c> and <c>link</c> are ignored in the list-valued
+case.
+An MFA is applied with an additional term prepended to its argument
+list, and should return either the pid of the handler process that
+invokes <c>diameter_traffic:request/1</c> on the term in order to
+process the request, or the atom <c>discard</c>.
+The handler process need not be local, but diameter must be started on
+the remote node.</p>
<p>
Defaults to the empty list.</p>
diff --git a/lib/diameter/doc/src/diameter_transport.xml b/lib/diameter/doc/src/diameter_transport.xml
index 67fd54bc56..0a8ef321c6 100644
--- a/lib/diameter/doc/src/diameter_transport.xml
+++ b/lib/diameter/doc/src/diameter_transport.xml
@@ -14,7 +14,8 @@
<erlref>
<header>
<copyright>
-<year>2011</year><year>2016</year>
+<year>2011</year>
+<year>2019</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -174,10 +175,13 @@ its parent.</p>
<taglist>
-<tag><c>{diameter, {send, &message;}}</c></tag>
+<tag><c>{diameter, {send, &message; | false}}</c></tag>
<item>
<p>
-An outbound Diameter message.</p>
+An outbound Diameter message.
+The atom <c>false</c> can only be received when request
+acknowledgements have been requests: see the <c>ack</c> message
+below.</p>
</item>
<tag><c>{diameter, {close, Pid}}</c></tag>
@@ -246,6 +250,27 @@ A <c>LocalAddr</c> list has the same semantics as one returned from
&start;.</p>
</item>
+<tag><c>{diameter, ack}</c></tag>
+<item>
+<p>
+Request acknowledgements of unanswered requests.
+A transport process should send this once before passing incoming
+Diameter messages into diameter.
+As a result, every Diameter request passed into diameter with a
+<c>recv</c> message (below) will be answered with a
+<c>send</c> message (above), either a &message; for the transport
+process to send or the atom <c>false</c> if the request has been
+discarded or otherwise not answered.</p>
+
+<p>
+This is to allow a transport process to keep count of the number
+of incoming request messages that have not yet been answered or
+discarded, to allow it to regulate the amount of incoming traffic.
+Both diameter_tcp and diameter_sctp request acknowledgements when a
+<c>message_cb</c> is configured, turning send/recv message into
+callbacks that can be used to regulate traffic.</p>
+</item>
+
<tag><c>{diameter, {recv, &message;}}</c></tag>
<item>
<p>
diff --git a/lib/diameter/doc/src/notes.xml b/lib/diameter/doc/src/notes.xml
index cc92bd99f0..5777225ae7 100644
--- a/lib/diameter/doc/src/notes.xml
+++ b/lib/diameter/doc/src/notes.xml
@@ -43,6 +43,41 @@ first.</p>
<!-- ===================================================================== -->
+<section><title>diameter 2.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix failure of incoming answer message with faulty
+ Experimental-Result-Code. Failure to decode the AVP
+ resulted in an uncaught exception, with no no
+ handle_answer/error callback as a consequence.</p>
+ <p>
+ Own Id: OTP-15569 Aux Id: ERIERL-302 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add spawn_opt MFA configuration to allow a callback to
+ spawn a handler process for an incoming Diameter request
+ on an an arbitrary node. Module diameter_dist provides a
+ route_session/2 that can be used to distribute requests
+ based on Session-Id, although this module is currently
+ only documented in the module itself and may change.</p>
+ <p>
+ Own Id: OTP-15398</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>diameter 2.1.6</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/diameter/src/base/diameter.erl b/lib/diameter/src/base/diameter.erl
index b90b794611..7f172e1fa1 100644
--- a/lib/diameter/src/base/diameter.erl
+++ b/lib/diameter/src/base/diameter.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2019. 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.
@@ -365,7 +365,7 @@ call(SvcName, App, Message) ->
| {connect_timer, 'Unsigned32'()}
| {watchdog_timer, 'Unsigned32'() | {module(), atom(), list()}}
| {watchdog_config, [{okay|suspect, non_neg_integer()}]}
- | {spawn_opt, list()}.
+ | {spawn_opt, list() | mfa()}.
%% Options passed to start_service/2
diff --git a/lib/diameter/src/base/diameter_callback.erl b/lib/diameter/src/base/diameter_callback.erl
index d04a416bef..3bcf550cd8 100644
--- a/lib/diameter/src/base/diameter_callback.erl
+++ b/lib/diameter/src/base/diameter_callback.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2019. 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.
@@ -70,7 +70,7 @@
-module(diameter_callback).
-%% Default callbacks when no aleternate is specified.
+%% Default callbacks when no alternate is specified.
-export([peer_up/3,
peer_down/3,
pick_peer/4,
diff --git a/lib/diameter/src/base/diameter_dist.erl b/lib/diameter/src/base/diameter_dist.erl
new file mode 100644
index 0000000000..5c29ea95a4
--- /dev/null
+++ b/lib/diameter/src/base/diameter_dist.erl
@@ -0,0 +1,525 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. 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(diameter_dist).
+
+-behaviour(gen_server).
+
+%%
+%% Implements callbacks that can be configured as a spawn_opt
+%% transport configuration, to be able to distribute incoming Diameter
+%% requests to handler processes (local or remote) in various ways.
+%%
+
+%% spawn_opt callbacks
+-export([spawn_local/2,
+ spawn_local/1,
+ route_session/2,
+ route_session/1]).
+
+%% signal availability for handling incoming requests to route_sesssion/2
+-export([attach/1,
+ detach/1]).
+
+%% consistent hashing
+-export([hash/3, %% for use as default MFA in route_session/2 options map
+ hash/2]). %% arbitrary key/values
+
+-include_lib("diameter/include/diameter.hrl").
+
+%% server start
+-export([start_link/0]).
+
+%% gen_server callbacks
+-export([init/1,
+ handle_info/2,
+ handle_cast/2,
+ handle_call/3,
+ code_change/3,
+ terminate/2]).
+
+-type request() :: tuple(). %% callback argument from diameter_traffic
+
+-define(SERVER, ?MODULE). %% server monitoring node connections
+
+%% Maps a node name binary to the corresponding atom. Used by
+%% route_session/2 to map the optional value of a Session-Id to
+%% node().
+-define(NODE_TABLE, diameter_dist_node).
+
+%% Maps a diameter:service_name() to a node() that has called attach/1
+%% to declare its willingness to handle incoming requests for the
+%% service. Use by route_session/2 in case the optional value mapping
+%% has failed.
+-define(SERVICE_TABLE, diameter_dist_service).
+
+-define(B(A), atom_to_binary(A, utf8)).
+-define(ORCOND(List), list_to_tuple(['orelse', false | List])).
+-define(HASH(T), erlang:phash2(T, 16#100000000)).
+
+%% spawn_local/2
+%%
+%% Callback that is equivalent to an options list. That is, the
+%% following are equivalent when passed as options to
+%% diameter:add_transport/2.
+%%
+%% {spawn_opt, Opts}
+%% {spawn_opt, {diameter_dist, spawn_local, [Opts]}}
+
+-spec spawn_local(ReqT :: request(), Opts :: list())
+ -> pid().
+
+spawn_local(ReqT, Opts) ->
+ spawn_opt(diameter_traffic, request, [ReqT], Opts).
+
+%% spawn_local/1
+
+spawn_local(ReqT) ->
+ spawn_local(ReqT, []).
+
+%% route_session/2
+%%
+%% Callback that maps the Session-Id of an incoming request to a
+%% handler node.
+%%
+%% With an options list, maps an id whose optional value is the name
+%% of a connected node to the same node, to handle the case that the
+%% session id has been returned from diameter:session_id/1; otherwise
+%% to a node that has called diameter_dist:attach/1 using the
+%% consistent hashing provided by hash/3, or to the local node() if a
+%% session id could not be extracted or there are no attached nodes. A
+%% handler process is spawned on the selected node using
+%% erlang:spawn_opt/4.
+%%
+%% Different behaviour can be configured by supplying an options map
+%% of the following form:
+%%
+%% #{search => non_neg_integer(),
+%% id => [binary()],
+%% default => discard | local | mfa(),
+%% dispatch => list() | mfa()}
+%%
+%% The search member limits the number of AVPs that are examined in
+%% the message (from the front), to avoid searching entire message in
+%% case it's known that peers follow RFC 6733's recommendation that
+%% Session-Id be placed at the head of a message. The default is to
+%% search the entire message.
+%%
+%% The id member restricts the optional value mapping to session ids
+%% whose DiamterIdentity is one of those specified. Set this to the
+%% list of Diameter identities advertised by the service in question
+%% (typically one) to ensure that only locally generated session ids
+%% are mapped; or to the empty list to disable the mapping.
+%%
+%% The default member determines where to handle a message whose
+%% Session-Id isn't found or whose optional value isn't mapped to the
+%% name of a connected node. The atom local says the local node, an
+%% MFA is invoked on Session-Id | false, the name of the diameter
+%% service, and the message binary, and should return either a node()
+%% or false to discard the message. Defaults to {diameter_dist, hash, []}.
+%%
+%% The dispatch member determines how the pid() of the request handler
+%% process is retrieved. An MFA is applied to a previously selected
+%% node(), and the module, function, and arguments list to apply in
+%% the handler process to handle the request, the MFA being supplied
+%% by diameter, and returns pid() | discard. A list is equivalent to
+%% {erlang, spawn_opt, []}. Defaults to [].
+%%
+%% This can be used with search = 0 to route on something other than
+%% Session-Id, but this is probably no simpler than just implementing
+%% an own spawn_opt callback. (Except with the default dispatch possibly.)
+%%
+%% Note that if the peer is also implemented with OTP diameter and
+%% generating session ids with diameter:session_id/1 then
+%% route_session/2 can map an optional value to a local node that
+%% happens to have the same name as one of the peer's nodes. This
+%% could lead to an uneven distribution; for example, if the peer
+%% nodes are a subset of the local nodes. In practice, it's typically
+%% known if it's peers or the local node originating sessions; if the
+%% former then setting id = [] disables the optional value mapping, if
+%% the latter then setting default = local disables the hashing.
+-spec route_session(ReqT :: request(), Opts)
+ -> discard
+ | pid()
+ when Opts :: pos_integer() %% aka #{search => N}
+ | list() %% aka #{dispatch => Opts}
+ | #{search => non_neg_integer(), %% limit number of examined AVPs
+ id => [binary()], %% restrict optional value map on DiamIdent
+ default => local %% handle locally
+ | discard
+ | mfa(), %% return node() | false
+ dispatch => list() %% spawn options
+ | mfa()}. %% (Node, M, F, A) -> pid() | discard
+
+route_session(ReqT, Opts) ->
+ {_, Bin} = Info = diameter_traffic:request_info(ReqT),
+ Sid = session_id(avps(Bin), search(Opts)),
+ Node = default(node_of_session_id(Sid, Opts), Sid, Opts, Info),
+ dispatch(Node, ReqT, dispatch(Opts)).
+
+%% avps/1
+
+avps(<<_:20/binary, Bin/binary>>) ->
+ Bin;
+
+avps(_) ->
+ false.
+
+%% dispatch/3
+
+dispatch(false, _, _) ->
+ discard;
+
+dispatch(Node, ReqT, {M,F,A}) ->
+ apply(M, F, [Node, diameter_traffic, request, [ReqT] | A]);
+
+dispatch(Node, ReqT, Opts) ->
+ spawn_opt(Node, diameter_traffic, request, [ReqT], Opts).
+
+%% route_session/1
+
+route_session(ReqT) ->
+ route_session(ReqT, []).
+
+%% node_of_session_id/2
+%%
+%% Return the node name encoded as optional value in a Session-Id,
+%% assuming the id has been created with diameter:session_id/0. Lookup
+%% the node name to ensure we don't convert arbitrary binaries to
+%% atom.
+
+node_of_session_id([Id, _, _, Bin], #{id := Ids}) ->
+ lists:member(Id, Ids) andalso nodemap(Bin);
+
+node_of_session_id([_, _, _, Bin], _) ->
+ nodemap(Bin);
+
+node_of_session_id(_, _) ->
+ false.
+
+%% nodemap/1
+
+nodemap(Bin) ->
+ try
+ ets:lookup_element(?NODE_TABLE, Bin, 2)
+ catch
+ error: badarg -> false
+ end.
+
+%% session_id/2
+
+session_id(_, 0) -> %% give up
+ false;
+
+%% Session-Id = Command Code 263, V-bit = 0.
+session_id(<<263:32, 0:1, _:7, Len:24, _/binary>> = Bin, _) ->
+ case Bin of
+ <<Avp:Len/binary, _/binary>> ->
+ <<_:8/binary, Sid/binary>> = Avp,
+ split(Sid);
+ _ ->
+ false
+ end;
+
+%% Jump to the next AVP. This is potentially costly for a message with
+%% many AVPs and no Session-Id, which an attacker is prone to send.
+%% 8.8 or RFC 6733 says that Session-Id SHOULD (but not MUST) appear
+%% immediately following the Diameter Header, so there is no
+%% guarantee.
+session_id(<<_:40, Len:24, _/binary>> = Bin, N) ->
+ Pad = (4 - (Len rem 4)) rem 4,
+ case Bin of
+ <<_:Len/binary, _:Pad/binary, Rest/binary>> ->
+ session_id(Rest, if N == infinity -> N; true -> N-1 end);
+ _ ->
+ false
+ end;
+
+session_id(_, _) ->
+ false.
+
+%% split/1
+%%
+%% Split a Session-Id at no more than three semicolons: the optional
+%% value (if any) follows the third. binary:split/2 does better than
+%% matching character by character, especially when the pattern is
+%% compiled.
+
+split(Bin) ->
+ split(3, Bin, pattern()).
+
+%% split/3
+
+split(0, Bin, _) ->
+ [Bin];
+
+split(N, Bin, Pattern) ->
+ [H|T] = binary:split(Bin, Pattern),
+ [H | case T of
+ [] ->
+ T;
+ [Rest] ->
+ split(N-1, Rest, Pattern)
+ end].
+
+%% pattern/0
+%%
+%% Since this is being called in a watchdog process, compile the
+%% pattern once and maintain it in the process dictionary.
+
+pattern() ->
+ case get(?MODULE) of
+ undefined ->
+ CP = binary:compile_pattern(<<$;>>), %% tuple
+ put(?MODULE, CP),
+ CP;
+ CP ->
+ CP
+ end.
+
+%% dispatch/1
+
+dispatch(#{} = Opts) ->
+ maps:get(dispatch, Opts, []);
+
+dispatch(Opts)
+ when is_list(Opts) ->
+ Opts;
+
+dispatch(_) ->
+ [].
+
+%% search/1
+%%
+%% Bound number of AVPs examined when looking for Session-Id.
+
+search(#{search := N})
+ when is_integer(N), 0 =< N ->
+ N;
+
+search(N)
+ when is_integer(N), 0 =< N ->
+ N;
+
+search(_) ->
+ infinity.
+
+%% default/3
+%%
+%% Choose a node when Session-Id lookup has failed.
+
+default(false, _, #{default := discard}, _) ->
+ false;
+
+default(false, _, #{default := local}, _) ->
+ node();
+
+default(false, Sid, #{default := {M,F,A}}, Info) ->
+ {ServiceName, Bin} = Info,
+ apply(M, F, [Sid, ServiceName, Bin | A]); %% node() | false
+
+default(false, Sid, _, Info) -> %% aka {?MODULE, hash, []}
+ {ServiceName, Bin} = Info,
+ hash(Sid, ServiceName, Bin);
+
+default(Node, _, _, _) ->
+ Node.
+
+%% ===========================================================================
+
+%% hash/3
+%%
+%% Consistent hashing of Session-Id to an attached node, or the local
+%% node if Session-Id = false or no attached nodes.
+
+hash(Sid, ServiceName, _) ->
+ case false /= Sid andalso attached(ServiceName) of
+ [_|_] = Nodes ->
+ hash(Sid, Nodes);
+ _ ->
+ node()
+ end.
+
+%% hash/2
+%%
+%% Consistent hashing on arbitrary key/values. Returns false if the
+%% list is empty.
+
+%% No key or no values.
+hash(_, []) ->
+ false;
+
+%% Not much choice.
+hash(_, [Value]) ->
+ Value;
+
+%% Hash on a circle and choose the closest predecessor.
+hash(Key, Values) ->
+ Hash = ?HASH(Key),
+ tl(lists:foldl(fun(V,A) ->
+ choose(Hash, [?HASH({Key, V}) | V], A)
+ end,
+ false, %% < list()
+ Values)).
+
+%% choose/3
+
+choose(Hash, [Hash1 | _] = T, [Hash2 | _])
+ when Hash1 =< Hash, Hash < Hash2 ->
+ T;
+
+choose(Hash, [Hash1 | _], [Hash2 | _] = T)
+ when Hash2 =< Hash, Hash < Hash1 ->
+ T;
+
+choose(_, T1, T2) ->
+ max(T1, T2).
+
+%% ===========================================================================
+
+%% attach/1
+%%
+%% Register the local node as a handler of incoming requests for the
+%% specified services when using the route_session/2 spawn_opt
+%% callback.
+
+attach(ServiceNames) ->
+ abcast({attach, node(), ServiceNames}).
+
+%% detach/1
+%%
+%% Deregister the local node as a handler of incoming requests.
+
+detach(ServiceNames) ->
+ abcast({detach, node(), ServiceNames}).
+
+%% abcast/1
+
+abcast(T) ->
+ gen_server:abcast([node() | nodes()], ?SERVER, T),
+ ok.
+
+%% attached/1
+
+attached(ServiceName) ->
+ try
+ ets:lookup_element(?SERVICE_TABLE, ServiceName, 2)
+ catch
+ error: badarg -> []
+ end.
+
+%% cast/2
+
+cast(Node, T) ->
+ gen_server:cast({?SERVER, Node}, T).
+
+%% attach/2
+
+attach(Node, S) ->
+ case sets:to_list(S) of
+ [] ->
+ ok;
+ Services ->
+ cast(Node, {attach, node(), Services})
+ end.
+
+%% ===========================================================================
+
+start_link() ->
+ gen_server:start_link({local, ?SERVER}, ?MODULE, _Args = [], _Opts = []).
+
+%% init/1
+%%
+%% Maintain [node() | nodes()] in a table that maps from binary-valued
+%% names, so we can lookup the corresponding atoms rather than convert
+%% binaries that aren't necessarily node names.
+
+init([]) ->
+ ets:new(?NODE_TABLE, [set, named_table]),
+ ets:new(?SERVICE_TABLE, [bag, named_table]),
+ ok = net_kernel:monitor_nodes(true, [{node_type, all}, nodedown_reason]),
+ ets:insert(?NODE_TABLE, [{?B(N), N} || N <- [node() | nodes()]]),
+ abcast({attach, node()}),
+ {ok, sets:new()}.
+
+%% handle_call/3
+
+handle_call(_, _From, S) ->
+ {reply, nok, S}.
+
+%% handle_cast/2
+
+%% Remote node is asking which services the local node wants to handle.
+handle_cast({attach, Node}, S)
+ when Node /= node() ->
+ attach(Node, S),
+ {noreply, S};
+
+%% Node wants to handle incoming requests ...
+handle_cast({attach, Node, ServiceNames}, S) ->
+ ets:insert(?SERVICE_TABLE, [{N, Node} || N <- ServiceNames]),
+ {noreply, case node() of
+ Node ->
+ sets:union(S, sets:from_list(ServiceNames));
+ _ ->
+ S
+ end};
+
+%% ... or not.
+handle_cast({detach, Node, ServiceNames}, S) ->
+ ets:select_delete(?SERVICE_TABLE, [{{'$1', Node},
+ [?ORCOND([{'==', '$1', {const, N}}
+ || N <- ServiceNames])],
+ [true]}]),
+ {noreply, case node() of
+ Node ->
+ sets:subtract(S, sets:from_list(ServiceNames));
+ _ ->
+ S
+ end};
+
+handle_cast(_, S) ->
+ {noreply, S}.
+
+%% handle_info/2
+
+handle_info({nodeup, Node, _}, S) ->
+ ets:insert(?NODE_TABLE, {?B(Node), Node}),
+ cast(Node, {attach, node()}), %% ask which services remote node handles
+ attach(Node, S), %% say which service local node handles
+ {noreply, S};
+
+handle_info({nodedown, Node, _}, S) ->
+ ets:delete(?NODE_TABLE, ?B(Node)),
+ ets:select_delete(?SERVICE_TABLE, [{{'_', Node}, [], [true]}]),
+ {noreply, S};
+
+handle_info(_, S) ->
+ {noreply, S}.
+
+%% terminate/2
+
+terminate(_, _) ->
+ ok.
+
+%% code_change/3
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
diff --git a/lib/diameter/src/base/diameter_misc_sup.erl b/lib/diameter/src/base/diameter_misc_sup.erl
index 343688be23..fec5a41b5c 100644
--- a/lib/diameter/src/base/diameter_misc_sup.erl
+++ b/lib/diameter/src/base/diameter_misc_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2019. 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.
@@ -35,6 +35,7 @@
diameter_stats, %% statistics counter management
diameter_reg, %% service/property publishing
diameter_peer, %% remote peer manager
+ diameter_dist, %% request distribution
diameter_config]). %% configuration/restart
%% start_link/0
diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl
index 2d3e4a2ac9..8423e30269 100644
--- a/lib/diameter/src/base/diameter_traffic.erl
+++ b/lib/diameter/src/base/diameter_traffic.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2019. 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.
@@ -42,8 +42,12 @@
peer_up/1,
peer_down/1]).
+%% towards diameter_dist
+-export([request_info/1]).
+
%% internal
-export([send/1, %% send from remote node
+ request/1, %% process request in handler process
init/1]). %% monitor process start
-include_lib("diameter/include/diameter.hrl").
@@ -232,7 +236,7 @@ incr_rc(Dir, Pkt, TPid, MsgDict, AppDict, Dict0) ->
-spec receive_message(pid(), Route, #diameter_packet{}, module(), RecvData)
-> pid() %% request handler
| boolean() %% answer, known request or not
- | discard %% request discarded by MFA
+ | discard %% request discarded
when Route :: {Handler, RequestRef, TPid}
| Ack,
RecvData :: {[SpawnOpt], #recvdata{}},
@@ -252,7 +256,8 @@ receive_message(TPid, Route, Pkt, Dict0, RecvData) ->
recv(true, Ack, TPid, Pkt, Dict0, T)
when is_boolean(Ack) ->
{Opts, RecvData} = T,
- spawn_request(Ack, TPid, Pkt, Dict0, RecvData, Opts);
+ AppT = find_app(TPid, Pkt, RecvData),
+ ack(Ack, TPid, spawn_request(AppT, Opts, Ack, TPid, Pkt, Dict0, RecvData));
%% ... answer to known request ...
recv(false, {Pid, Ref, TPid}, _, Pkt, Dict0, _) ->
@@ -274,58 +279,73 @@ recv(false, false, TPid, Pkt, _, _) ->
incr(TPid, {{unknown, 0}, recv, discarded}),
false.
-%% spawn_request/6
+%% spawn_request/7
+
+spawn_request(false, _, _, _, _, _, _) -> %% no transport
+ discard;
-%% An MFA should return a pid() or the atom 'discard'. The latter
-%% results in an acknowledgment back to the transport process when
-%% appropriate, to ensure that send/recv callbacks can count
-%% outstanding requests. Acknowledgement is implicit if the
+%% An MFA should return the pid() of a process in which the argument
+%% fun in applied, or the atom 'discard' if the fun is not applied.
+%% The latter results in an acknowledgment back to the transport
+%% process when appropriate, to ensure that send/recv callbacks can
+%% count outstanding requests. Acknowledgement is implicit if the
%% handler process dies (in a handle_request callback for example).
-spawn_request(Ack, TPid, Pkt, Dict0, RecvData, {M,F,A}) ->
- ReqF = fun() ->
- ack(Ack, TPid, recv_request(Ack, TPid, Pkt, Dict0, RecvData))
- end,
- ack(Ack, TPid, apply(M, F, [ReqF | A]));
+spawn_request(AppT, {M,F,A}, Ack, TPid, Pkt, Dict0, RecvData) ->
+ %% Term to pass to request/1 in an appropriate process. Module
+ %% diameter_dist implements callbacks.
+ ReqT = {Pkt, AppT, Ack, TPid, Dict0, RecvData},
+ apply(M, F, [ReqT | A]);
%% A spawned process acks implicitly when it dies, so there's no need
%% to handle 'discard'.
-spawn_request(Ack, TPid, Pkt, Dict0, RecvData, Opts) ->
+spawn_request(AppT, Opts, Ack, TPid, Pkt, Dict0, RecvData) ->
spawn_opt(fun() ->
- recv_request(Ack, TPid, Pkt, Dict0, RecvData)
+ recv_request(Ack, TPid, Pkt, Dict0, RecvData, AppT)
end,
Opts).
+%% request_info/1
+%%
+%% Limited request information for diameter_dist.
+
+request_info({Pkt, _AppT, _Ack, _TPid, _Dict0, RecvData} = _ReqT) ->
+ {RecvData#recvdata.service_name, Pkt#diameter_packet.bin}.
+
+%% request/1
+%%
+%% Called from a handler process chosen by a transport spawn_opt MFA
+%% to process an incoming request.
+
+request({Pkt, AppT, Ack, TPid, Dict0, RecvData} = _ReqT) ->
+ ack(Ack, TPid, recv_request(Ack, TPid, Pkt, Dict0, RecvData, AppT)).
+
%% ack/3
ack(Ack, TPid, RC) ->
- RC == discard andalso Ack andalso (TPid ! {send, false}),
+ RC == discard
+ andalso Ack
+ andalso (TPid ! {send, false}),
RC.
%% ---------------------------------------------------------------------------
-%% recv_request/5
+%% recv_request/6
%% ---------------------------------------------------------------------------
-spec recv_request(Ack :: boolean(),
TPid :: pid(),
#diameter_packet{},
Dict0 :: module(),
- #recvdata{})
+ #recvdata{},
+ AppT :: {#diameter_app{}, #diameter_caps{}}
+ | #diameter_caps{}) %% no suitable app
-> ok %% answer was sent
- | discard %% or not
- | false. %% no transport
-
-recv_request(Ack,
- TPid,
- #diameter_packet{header = #diameter_header{application_id = Id}}
- = Pkt,
- Dict0,
- #recvdata{peerT = PeerT,
- apps = Apps,
- counters = Count}
- = RecvData) ->
+ | discard. %% or not
+
+recv_request(Ack, TPid, Pkt, Dict0, RecvData, AppT) ->
Ack andalso (TPid ! {handler, self()}),
- case diameter_service:find_incoming_app(PeerT, TPid, Id, Apps) of
+ case AppT of
{#diameter_app{id = Aid, dictionary = AppDict} = App, Caps} ->
+ Count = RecvData#recvdata.counters,
Count andalso incr(recv, Pkt, TPid, AppDict),
DecPkt = decode(Aid, AppDict, RecvData, Pkt),
Count andalso incr_error(recv, DecPkt, TPid, AppDict),
@@ -349,11 +369,20 @@ recv_request(Ack,
Dict0,
RecvData,
DecPkt,
- [[]]);
- false = No -> %% transport has gone down
- No
+ [[]])
end.
+%% find_app/3
+%%
+%% Lookup the application of a received Diameter request on the node
+%% on which it's received.
+
+find_app(TPid,
+ #diameter_packet{header = #diameter_header{application_id = Id}},
+ #recvdata{peerT = PeerT,
+ apps = Apps}) ->
+ diameter_service:find_incoming_app(PeerT, TPid, Id, Apps).
+
%% decode/4
decode(Id, Dict, #recvdata{codec = Opts}, Pkt) ->
diff --git a/lib/diameter/src/diameter.appup.src b/lib/diameter/src/diameter.appup.src
index 4e6b983bac..52263633fb 100644
--- a/lib/diameter/src/diameter.appup.src
+++ b/lib/diameter/src/diameter.appup.src
@@ -60,7 +60,8 @@
{"2.1.3", [{restart_application, diameter}]}, %% 20.2
{"2.1.4", [{restart_application, diameter}]}, %% 20.3
{"2.1.4.1", [{restart_application, diameter}]}, %% 20.3.8.19
- {"2.1.5", [{update, diameter_peer_fsm}]} %% 21.0
+ {"2.1.5", [{restart_application, diameter}]}, %% 21.0
+ {"2.1.6", [{restart_application, diameter}]} %% 21.1
],
[
{"0.9", [{restart_application, diameter}]},
@@ -102,6 +103,7 @@
{"2.1.3", [{restart_application, diameter}]},
{"2.1.4", [{restart_application, diameter}]},
{"2.1.4.1", [{restart_application, diameter}]},
- {"2.1.5", [{update, diameter_peer_fsm}]}
+ {"2.1.5", [{restart_application, diameter}]},
+ {"2.1.6", [{restart_application, diameter}]}
]
}.
diff --git a/lib/diameter/src/modules.mk b/lib/diameter/src/modules.mk
index bb86de016a..d16292bb88 100644
--- a/lib/diameter/src/modules.mk
+++ b/lib/diameter/src/modules.mk
@@ -1,7 +1,7 @@
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2010-2017. All Rights Reserved.
+# Copyright Ericsson AB 2010-2019. 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.
@@ -40,6 +40,7 @@ RT_MODULES = \
base/diameter_config \
base/diameter_config_sup \
base/diameter_codec \
+ base/diameter_dist \
base/diameter_gen \
base/diameter_lib \
base/diameter_misc_sup \
diff --git a/lib/diameter/test/diameter_dist_SUITE.erl b/lib/diameter/test/diameter_dist_SUITE.erl
new file mode 100644
index 0000000000..b2e4c35b9a
--- /dev/null
+++ b/lib/diameter/test/diameter_dist_SUITE.erl
@@ -0,0 +1,332 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. 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%
+%%
+
+%%
+%% Tests of traffic between two Diameter nodes, the server being
+%% spread across three Erlang nodes.
+%%
+
+-module(diameter_dist_SUITE).
+
+-export([suite/0,
+ all/0]).
+
+%% testcases
+-export([enslave/1, enslave/0,
+ ping/1,
+ start/1,
+ connect/1,
+ send/1,
+ stop/1, stop/0]).
+
+%% diameter callbacks
+-export([peer_up/3,
+ peer_down/3,
+ pick_peer/4,
+ prepare_request/3,
+ prepare_retransmit/3,
+ handle_answer/4,
+ handle_error/4,
+ handle_request/3]).
+
+-export([call/1]).
+
+-include("diameter.hrl").
+-include("diameter_gen_base_rfc6733.hrl").
+
+%% ===========================================================================
+
+-define(util, diameter_util).
+
+-define(CLIENT, 'CLIENT').
+-define(SERVER, 'SERVER').
+-define(REALM, "erlang.org").
+-define(DICT, diameter_gen_base_rfc6733).
+-define(ADDR, {127,0,0,1}).
+
+%% Config for diameter:start_service/2.
+-define(SERVICE(Host),
+ [{'Origin-Host', Host ++ [$.|?REALM]},
+ {'Origin-Realm', ?REALM},
+ {'Host-IP-Address', [?ADDR]},
+ {'Vendor-Id', 12345},
+ {'Product-Name', "OTP/diameter"},
+ {'Auth-Application-Id', [?DICT:id()]},
+ {'Origin-State-Id', origin()},
+ {spawn_opt, {diameter_dist, route_session, [#{id => []}]}},
+ {sequence, fun sequence/0},
+ {string_decode, false},
+ {application, [{dictionary, ?DICT},
+ {module, ?MODULE},
+ {request_errors, callback},
+ {answer_errors, callback}]}]).
+
+-define(SUCCESS, 2001).
+-define(BUSY, 3004).
+-define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT').
+-define(MOVED, ?'DIAMETER_BASE_TERMINATION-CAUSE_USER_MOVED').
+-define(TIMEOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_SESSION_TIMEOUT').
+
+-define(L, atom_to_list).
+-define(A, list_to_atom).
+
+%% The order here is significant and causes the server to listen
+%% before the clients connect. The server listens on the first node,
+%% and distributes requests to the other two.
+-define(NODES, [{server0, ?SERVER},
+ {server1, ?SERVER},
+ {server2, ?SERVER},
+ {client, ?CLIENT}]).
+
+%% Options to ct_slave:start/2.
+-define(TIMEOUTS, [{T, 15000} || T <- [boot_timeout,
+ init_timeout,
+ start_timeout]]).
+
+%% ===========================================================================
+
+suite() ->
+ [{timetrap, {seconds, 60}}].
+
+all() ->
+ [enslave,
+ ping,
+ start,
+ connect,
+ send,
+ stop].
+
+%% ===========================================================================
+%% start/stop testcases
+
+%% enslave/1
+%%
+%% Start four slave nodes, three to implement a Diameter server,
+%% one to implement a client.
+
+enslave() ->
+ [{timetrap, {seconds, 30*length(?NODES)}}].
+
+enslave(Config) ->
+ Here = filename:dirname(code:which(?MODULE)),
+ Ebin = filename:join([Here, "..", "ebin"]),
+ Dirs = [Here, Ebin],
+ Nodes = [{N,S} || {M,S} <- ?NODES, N <- [slave(M, Dirs)]],
+ ?util:write_priv(Config, nodes, [{N,S} || {{N,ok},S} <- Nodes]),
+ [] = [{T,S} || {{_,E} = T, S} <- Nodes, E /= ok].
+
+slave(Name, Dirs) ->
+ add_pathsa(Dirs, ct_slave:start(Name, ?TIMEOUTS)).
+
+add_pathsa(Dirs, {ok, Node}) ->
+ {Node, rpc:call(Node, code, add_pathsa, [Dirs])};
+add_pathsa(_, No) ->
+ {No, error}.
+
+%% ping/1
+%%
+%% Ensure the server nodes are connected so that diameter_dist can attach.
+
+ping({S, Nodes}) ->
+ ?SERVER = S,
+ [N || {N,_} <- Nodes,
+ node() /= N,
+ pang <- [net_adm:ping(N)]];
+
+ping(Config) ->
+ Nodes = lists:droplast(?util:read_priv(Config, nodes)),
+ [] = [{N,RC} || {N,S} <- Nodes,
+ RC <- [rpc:call(N, ?MODULE, ping, [{S,Nodes}])],
+ RC /= []].
+
+%% start/1
+%%
+%% Start diameter services.
+
+start(SvcName)
+ when is_atom(SvcName) ->
+ ok = diameter:start(),
+ ok = diameter:start_service(SvcName, ?SERVICE((?L(SvcName))));
+
+start(Config) ->
+ Nodes = ?util:read_priv(Config, nodes),
+ [] = [{N,RC} || {N,S} <- Nodes,
+ RC <- [rpc:call(N, ?MODULE, start, [S])],
+ RC /= ok].
+
+sequence() ->
+ sequence(sname()).
+
+sequence(client) ->
+ {0,32};
+sequence(Server) ->
+ "server" ++ N = ?L(Server),
+ {list_to_integer(N), 30}.
+
+origin() ->
+ origin(sname()).
+
+origin(client) ->
+ 99;
+origin(Server) ->
+ "server" ++ N = ?L(Server),
+ list_to_integer(N).
+
+%% connect/1
+%%
+%% Establish one connection from the client, terminated on the first
+%% server node, the others handling requests.
+
+connect({?SERVER, Config, [{Node, _} | _]}) ->
+ if Node == node() -> %% server0
+ ?util:write_priv(Config, lref, {Node, ?util:listen(?SERVER, tcp)});
+ true ->
+ diameter_dist:attach([?SERVER])
+ end,
+ ok;
+
+connect({?CLIENT, Config, _}) ->
+ ?util:connect(?CLIENT, tcp, ?util:read_priv(Config, lref)),
+ ok;
+
+connect(Config) ->
+ Nodes = ?util:read_priv(Config, nodes),
+ [] = [{N,RC} || {N,S} <- Nodes,
+ RC <- [rpc:call(N, ?MODULE, connect, [{S, Config, Nodes}])],
+ RC /= ok].
+
+%% stop/1
+%%
+%% Stop the slave nodes.
+
+stop() ->
+ [{timetrap, {seconds, 30*length(?NODES)}}].
+
+stop(_Config) ->
+ [] = [{N,E} || {N,_} <- ?NODES,
+ {error, _, _} = E <- [ct_slave:stop(N)]].
+
+%% ===========================================================================
+%% traffic testcases
+
+%% send/1
+%%
+%% Send 100 requests and ensure the node name sent as User-Name isn't
+%% the node terminating transport.
+
+send(Config) ->
+ send(Config, 100, dict:new()).
+
+%% send/2
+
+send(Config, 0, Dict) ->
+ [{Server0, _} | _] = ?util:read_priv(Config, nodes) ,
+ Node = atom_to_binary(Server0, utf8),
+ {false, _} = {dict:is_key(Node, Dict), dict:to_list(Dict)};
+
+send(Config, N, Dict) ->
+ #diameter_base_STA{'Result-Code' = ?SUCCESS,
+ 'User-Name' = [ServerNode]}
+ = send(Config, str(?LOGOUT)),
+ true = is_binary(ServerNode),
+ send(Config, N-1, dict:update_counter(ServerNode, 1, Dict)).
+
+%% ===========================================================================
+
+str(Cause) ->
+ #diameter_base_STR{'Destination-Realm' = ?REALM,
+ 'Auth-Application-Id' = ?DICT:id(),
+ 'Termination-Cause' = Cause}.
+
+%% send/2
+
+send(Config, Req) ->
+ {Node, _} = lists:last(?util:read_priv(Config, nodes)),
+ rpc:call(Node, ?MODULE, call, [Req]).
+
+%% call/1
+
+call(Req) ->
+ diameter:call(?CLIENT, ?DICT, Req, []).
+
+%% sname/0
+
+sname() ->
+ ?A(hd(string:tokens(?L(node()), "@"))).
+
+%% ===========================================================================
+%% diameter callbacks
+
+%% peer_up/3
+
+peer_up(_SvcName, _Peer, State) ->
+ State.
+
+%% peer_down/3
+
+peer_down(_SvcName, _Peer, State) ->
+ State.
+
+%% pick_peer/4
+
+pick_peer([Peer], [], ?CLIENT, _State) ->
+ {ok, Peer}.
+
+%% prepare_request/3
+
+prepare_request(Pkt, ?CLIENT, {_Ref, Caps}) ->
+ #diameter_packet{msg = Req}
+ = Pkt,
+ #diameter_caps{origin_host = {OH, _},
+ origin_realm = {OR, _}}
+ = Caps,
+ {send, Req#diameter_base_STR{'Origin-Host' = OH,
+ 'Origin-Realm' = OR,
+ 'Session-Id' = diameter:session_id(OH)}}.
+
+%% prepare_retransmit/3
+
+prepare_retransmit(_, ?CLIENT, _) ->
+ discard.
+
+%% handle_answer/5
+
+handle_answer(Pkt, _Req, ?CLIENT, _Peer) ->
+ #diameter_packet{msg = Rec, errors = []} = Pkt,
+ Rec.
+
+%% handle_error/5
+
+handle_error(Reason, _Req, ?CLIENT, _Peer) ->
+ {error, Reason}.
+
+%% handle_request/3
+
+handle_request(Pkt, ?SERVER, {_, Caps}) ->
+ #diameter_packet{msg = #diameter_base_STR{'Session-Id' = SId}}
+ = Pkt,
+ #diameter_caps{origin_host = {OH, _},
+ origin_realm = {OR, _}}
+ = Caps,
+ {reply, #diameter_base_STA{'Result-Code' = ?SUCCESS,
+ 'Session-Id' = SId,
+ 'Origin-Host' = OH,
+ 'Origin-Realm' = OR,
+ 'User-Name' = [atom_to_binary(node(), utf8)]}}.
diff --git a/lib/diameter/test/diameter_distribution_SUITE.erl b/lib/diameter/test/diameter_distribution_SUITE.erl
index 5146f68ff1..5fe02284ae 100644
--- a/lib/diameter/test/diameter_distribution_SUITE.erl
+++ b/lib/diameter/test/diameter_distribution_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2019. 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.
@@ -76,6 +76,7 @@
{share_peers, peers()},
{use_shared_peers, peers()},
{restrict_connections, false},
+ {spawn_opt, {diameter_dist, spawn_local, []}},
{sequence, fun sequence/0},
{application, [{dictionary, ?DICT},
{module, ?MODULE},
@@ -125,7 +126,7 @@ all() ->
%% enslave/1
%%
%% Start four slave nodes, one to implement a Diameter server,
-%% two three to implement a client.
+%% three to implement a client.
enslave() ->
[{timetrap, {seconds, 30*length(?NODES)}}].
@@ -331,6 +332,8 @@ prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, {_, client0}) ->
'Origin-Realm' = OR,
'Session-Id' = diameter:session_id(OH)}}.
+%% prepare_retransmit/4
+
prepare_retransmit(Pkt, ?CLIENT, _, {_, client0}) ->
#diameter_packet{msg = #diameter_base_STR{'Termination-Cause' = ?MOVED}}
= Pkt, %% assert
diff --git a/lib/diameter/test/diameter_pool_SUITE.erl b/lib/diameter/test/diameter_pool_SUITE.erl
index 97c16940ff..a36a4fa17a 100644
--- a/lib/diameter/test/diameter_pool_SUITE.erl
+++ b/lib/diameter/test/diameter_pool_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2019. 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.
@@ -51,6 +51,7 @@
{'Auth-Application-Id', [0]}, %% common
{'Acct-Application-Id', [3]}, %% accounting
{restrict_connections, false},
+ {spawn_opt, {diameter_dist, route_session, []}},
{application, [{alias, common},
{dictionary, diameter_gen_base_rfc6733},
{module, diameter_callback}]},
diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl
index 434aef01dd..47b00c25a2 100644
--- a/lib/diameter/test/diameter_traffic_SUITE.erl
+++ b/lib/diameter/test/diameter_traffic_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2019. 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.
@@ -539,8 +539,7 @@ add_transports(Config) ->
++ [{unordered, unordered()} || T == sctp],
[{capabilities_cb, fun capx/2},
{pool_size, 8}
- | server_apps()]
- ++ [{spawn_opt, {erlang, spawn, []}} || CS]),
+ | server_apps()]),
Cs = [?util:connect(CN,
[T, {sender, CS} | client_opts(T)],
LRef,
diff --git a/lib/diameter/test/modules.mk b/lib/diameter/test/modules.mk
index 0c73adca12..90b0a25d5f 100644
--- a/lib/diameter/test/modules.mk
+++ b/lib/diameter/test/modules.mk
@@ -1,7 +1,7 @@
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2010-2017. All Rights Reserved.
+# Copyright Ericsson AB 2010-2019. 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.
@@ -31,6 +31,7 @@ MODULES = \
diameter_codec_test \
diameter_config_SUITE \
diameter_compiler_SUITE \
+ diameter_dist_SUITE \
diameter_distribution_SUITE \
diameter_dpr_SUITE \
diameter_event_SUITE \
diff --git a/lib/diameter/vsn.mk b/lib/diameter/vsn.mk
index 8c75c9e55e..a900e8f28e 100644
--- a/lib/diameter/vsn.mk
+++ b/lib/diameter/vsn.mk
@@ -17,5 +17,5 @@
# %CopyrightEnd%
APPLICATION = diameter
-DIAMETER_VSN = 2.1.6
+DIAMETER_VSN = 2.2
APP_VSN = $(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN)
diff --git a/lib/edoc/doc/src/notes.xml b/lib/edoc/doc/src/notes.xml
index e818887eb8..145856bcaa 100644
--- a/lib/edoc/doc/src/notes.xml
+++ b/lib/edoc/doc/src/notes.xml
@@ -32,6 +32,22 @@
<p>This document describes the changes made to the EDoc
application.</p>
+<section><title>Edoc 0.10</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p> Print a helpful message explaining that adding
+ <c>{preprocess, true}</c> can help if reading a source
+ file fails. </p>
+ <p>
+ Own Id: OTP-15605 Aux Id: ERL-841 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Edoc 0.9.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/edoc/vsn.mk b/lib/edoc/vsn.mk
index 0b3636f030..b6e1422623 100644
--- a/lib/edoc/vsn.mk
+++ b/lib/edoc/vsn.mk
@@ -1 +1 @@
-EDOC_VSN = 0.9.4
+EDOC_VSN = 0.10
diff --git a/lib/erl_docgen/doc/src/notes.xml b/lib/erl_docgen/doc/src/notes.xml
index 97c842a324..54f0a36b27 100644
--- a/lib/erl_docgen/doc/src/notes.xml
+++ b/lib/erl_docgen/doc/src/notes.xml
@@ -31,7 +31,31 @@
</header>
<p>This document describes the changes made to the <em>erl_docgen</em> application.</p>
- <section><title>Erl_Docgen 0.8.1</title>
+ <section><title>Erl_Docgen 0.9</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The HTML reference documentation shows the OTP version
+ where modules and functions were first introduced.
+ Versions older than R13B04 are not shown.</p>
+ <p>
+ Own Id: OTP-15460</p>
+ </item>
+ <item>
+ <p>
+ Make html documentation of C functions with many
+ arguments more readable.</p>
+ <p>
+ Own Id: OTP-15637 Aux Id: PR-2160 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erl_Docgen 0.8.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/erl_docgen/vsn.mk b/lib/erl_docgen/vsn.mk
index 3b2f6db6a1..6321f229dd 100644
--- a/lib/erl_docgen/vsn.mk
+++ b/lib/erl_docgen/vsn.mk
@@ -1 +1 @@
-ERL_DOCGEN_VSN = 0.8.1
+ERL_DOCGEN_VSN = 0.9
diff --git a/lib/erl_interface/configure.in b/lib/erl_interface/configure.in
index 53c2f7069c..f0e9b2eb3f 100644
--- a/lib/erl_interface/configure.in
+++ b/lib/erl_interface/configure.in
@@ -61,7 +61,7 @@ fi
TARGET=$host
AC_SUBST(TARGET)
-AC_CONFIG_HEADER([src/$host/config.h:config.h.in include/$host/ei_config.h:include/ei_config.h.in])
+AC_CONFIG_HEADER([src/$host/config.h:config.h.in])
dnl ----------------------------------------------------------------------
dnl Optional features
@@ -184,14 +184,6 @@ AC_TRY_COMPILE([#include <sys/types.h>
AC_DEFINE(HAVE_SOCKLEN_T, [], [Define if you have the `socklen_t' type])],
[AC_MSG_RESULT(no)])
-AC_MSG_CHECKING([for deprecated attribute])
-AC_TRY_COMPILE([],
-[void my_function(void) __attribute__((deprecated));],
-[AC_MSG_RESULT(yes)
- AC_DEFINE(HAVE_DEPRECATED_ATTRIBUTE, [], [Define to 1 if you have the `deprecated' attribute])],
-[AC_MSG_RESULT(no)])
-
-
# Checks for library functions.
AC_FUNC_ALLOCA
dnl AC_FUNC_FORK
diff --git a/lib/erl_interface/doc/src/ei.xml b/lib/erl_interface/doc/src/ei.xml
index 2bdb390644..f081ca926a 100644
--- a/lib/erl_interface/doc/src/ei.xml
+++ b/lib/erl_interface/doc/src/ei.xml
@@ -736,7 +736,7 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_init(void)</nametext></name>
+ <name since="OTP 21.3"><ret>int</ret><nametext>ei_init(void)</nametext></name>
<fsummary>Initialize the ei library.</fsummary>
<desc>
<p>Initialize the <c>ei</c> library. This function should be called once
diff --git a/lib/erl_interface/doc/src/ei_connect.xml b/lib/erl_interface/doc/src/ei_connect.xml
index df40973270..795f1249b3 100644
--- a/lib/erl_interface/doc/src/ei_connect.xml
+++ b/lib/erl_interface/doc/src/ei_connect.xml
@@ -412,7 +412,7 @@ typedef struct {
</func>
<func>
- <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_close_connection(int fd)</nametext></name>
+ <name since="OTP 21.3"><ret>int</ret><nametext>ei_close_connection(int fd)</nametext></name>
<fsummary>Close a connection.</fsummary>
<desc>
<p>Closes a previously opened connection or listen socket.</p>
@@ -472,9 +472,9 @@ fd = ei_xconnect(&ec, &addr, ALIVE);
<func>
<name since=""><ret>int</ret><nametext>ei_connect_init(ei_cnode* ec, const char* this_node_name, const char *cookie, short creation)</nametext></name>
- <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_connect_init_ussi(ei_cnode* ec, const char* this_node_name, const char *cookie, short creation, ei_socket_callbacks *cbs, int cbs_sz, void *setup_context)</nametext></name>
+ <name since="OTP 21.3"><ret>int</ret><nametext>ei_connect_init_ussi(ei_cnode* ec, const char* this_node_name, const char *cookie, short creation, ei_socket_callbacks *cbs, int cbs_sz, void *setup_context)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_connect_xinit(ei_cnode* ec, const char *thishostname, const char *thisalivename, const char *thisnodename, Erl_IpAddr thisipaddr, const char *cookie, short creation)</nametext></name>
- <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_connect_xinit_ussi(ei_cnode* ec, const char *thishostname, const char *thisalivename, const char *thisnodename, Erl_IpAddr thisipaddr, const char *cookie, short creation, ei_socket_callbacks *cbs, int cbs_sz, void *setup_context)</nametext></name>
+ <name since="OTP 21.3"><ret>int</ret><nametext>ei_connect_xinit_ussi(ei_cnode* ec, const char *thishostname, const char *thisalivename, const char *thisnodename, Erl_IpAddr thisipaddr, const char *cookie, short creation, ei_socket_callbacks *cbs, int cbs_sz, void *setup_context)</nametext></name>
<fsummary>Initialize for a connection.</fsummary>
<desc>
<p>Initializes the <c>ec</c> structure, to
@@ -595,8 +595,8 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
</func>
<func>
- <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_listen(ei_cnode *ec, int *port, int backlog)</nametext></name>
- <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_xlisten(ei_cnode *ec, Erl_IpAddr adr, int *port, int backlog)</nametext></name>
+ <name since="OTP 21.3"><ret>int</ret><nametext>ei_listen(ei_cnode *ec, int *port, int backlog)</nametext></name>
+ <name since="OTP 21.3"><ret>int</ret><nametext>ei_xlisten(ei_cnode *ec, Erl_IpAddr adr, int *port, int backlog)</nametext></name>
<fsummary>Create a listen socket.</fsummary>
<desc>
<p>Used by a server process to setup a listen socket which
diff --git a/lib/erl_interface/doc/src/notes.xml b/lib/erl_interface/doc/src/notes.xml
index 07ddd82718..b686cfbf33 100644
--- a/lib/erl_interface/doc/src/notes.xml
+++ b/lib/erl_interface/doc/src/notes.xml
@@ -31,6 +31,23 @@
</header>
<p>This document describes the changes made to the Erl_interface application.</p>
+<section><title>Erl_Interface 3.11</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Support for plugin of a <seealso
+ marker="ei_connect#ussi">user supplied socket
+ implementation</seealso> has been added.</p>
+ <p>
+ Own Id: OTP-15442 Aux Id: ERIERL-258 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erl_Interface 3.10.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/erl_interface/include/ei.h b/lib/erl_interface/include/ei.h
index 10a6e5bf50..aa2a49098f 100644
--- a/lib/erl_interface/include/ei.h
+++ b/lib/erl_interface/include/ei.h
@@ -47,21 +47,26 @@ typedef LONG_PTR ssize_t; /* Sigh... */
# include <netdb.h>
#endif
-#ifndef EI_INCLUDED_CONFIG_H__
-#include "ei_config.h"
+#ifdef __has_attribute
+#if __has_attribute(deprecated)
+#define EI_HAVE_DEPRECATED_ATTR__ 1
+#else
+#undef EI_HAVE_DEPRECATED_ATTR__
+#endif
#endif
-#if defined(HAVE_DEPRECATED_ATTRIBUTE) && !defined(EI_NO_DEPR_WARN)
-#define EI_HAVE_DEPRECATED_ATTR__ 1
+#ifdef EI_NO_DEPR_WARN
+#undef EI_HAVE_DEPRECATED_ATTR__
+#endif
+
+#ifdef EI_HAVE_DEPRECATED_ATTR__
#define EI_DEPRECATED_ATTR_NAME deprecated
#define EI_DEPRECATED_ATTR __attribute__((EI_DEPRECATED_ATTR_NAME))
#else
-#undef EI_HAVE_DEPRECATED_ATTR__
#define EI_DEPRECATED_ATTR_NAME
#define EI_DEPRECATED_ATTR
#endif
-
/* -------------------------------------------------------------------- */
/* Defines part of API */
/* -------------------------------------------------------------------- */
diff --git a/lib/erl_interface/include/ei_config.h.in b/lib/erl_interface/include/ei_config.h.in
deleted file mode 100644
index dfcf676bed..0000000000
--- a/lib/erl_interface/include/ei_config.h.in
+++ /dev/null
@@ -1,3 +0,0 @@
-
-/* Define to 1 if you have the `deprecated' attribute */
-#undef HAVE_DEPRECATED_ATTRIBUTE
diff --git a/lib/erl_interface/src/Makefile b/lib/erl_interface/src/Makefile
index 800557fbfe..00c49f1622 100644
--- a/lib/erl_interface/src/Makefile
+++ b/lib/erl_interface/src/Makefile
@@ -27,7 +27,9 @@ include $(ERL_TOP)/make/output.mk
include $(ERL_TOP)/make/target.mk
debug opt shared purify quantify purecov gcov:
+ifndef TERTIARY_BOOTSTRAP
$(make_verbose)$(MAKE) -f $(TARGET)/Makefile TYPE=$@
+endif
clean depend docs release release_docs tests release_tests check xmllint:
$(make_verbose)$(MAKE) -f $(TARGET)/Makefile $@
diff --git a/lib/erl_interface/src/Makefile.in b/lib/erl_interface/src/Makefile.in
index 1b19a85f1e..b2600f0fab 100644
--- a/lib/erl_interface/src/Makefile.in
+++ b/lib/erl_interface/src/Makefile.in
@@ -118,7 +118,7 @@ else
AR_FLAGS=rcv
endif
-INCFLAGS = -I. -I../include -I../include/$(TARGET) -Iconnect -Iencode -Idecode -Imisc -Iepmd \
+INCFLAGS = -I. -I../include -Iconnect -Iencode -Idecode -Imisc -Iepmd \
-Iregistry -I$(TARGET)
ifeq ($(USING_VC),yes)
@@ -322,8 +322,7 @@ HEADERS = \
../include/ei.h \
../include/ei_connect.h \
../include/eicode.h \
- ../include/erl_interface.h \
- ../include/$(TARGET)/ei_config.h
+ ../include/erl_interface.h
EISOURCES = \
$(CONNECTSRC) \
diff --git a/lib/erl_interface/src/misc/eidef.h b/lib/erl_interface/src/misc/eidef.h
index 083814c6e9..f38824d826 100644
--- a/lib/erl_interface/src/misc/eidef.h
+++ b/lib/erl_interface/src/misc/eidef.h
@@ -25,7 +25,6 @@
/* Common definitions used in ei user interface */
-#define EI_INCLUDED_CONFIG_H__
#include "config.h" /* Central include of config.h */
/* vxWorks.h needs to be before stddef.h */
diff --git a/lib/erl_interface/test/all_SUITE_data/Makefile.src b/lib/erl_interface/test/all_SUITE_data/Makefile.src
index 57e522fd3e..4f27b097c8 100644
--- a/lib/erl_interface/test/all_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/all_SUITE_data/Makefile.src
@@ -21,8 +21,8 @@ include @erl_interface_mk_include@
CC0 = @CC@
CC = .@DS@gccifier@exe@ -CC"$(CC0)"
-CFLAGS0 = @CFLAGS@ -I@erl_interface_include@ @erl_interface_target_include@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@
+CFLAGS0 = @CFLAGS@ -I@erl_interface_include@
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@
EI_COMMON_OBJS = runner@obj@ ei_runner@obj@
ALL_OBJS = gccifier@exe@ $(EI_COMMON_OBJS)
diff --git a/lib/erl_interface/test/ei_accept_SUITE_data/Makefile.src b/lib/erl_interface/test/ei_accept_SUITE_data/Makefile.src
index 76dc84221f..10ef437f8b 100644
--- a/lib/erl_interface/test/ei_accept_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/ei_accept_SUITE_data/Makefile.src
@@ -27,7 +27,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/ei_runner@obj@ \
$(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
EI_ACCEPT_OBJS = ei_accept_test@obj@ ei_accept_test_decl@obj@
EIACCNODE_OBJS = eiaccnode@obj@
diff --git a/lib/erl_interface/test/ei_connect_SUITE_data/Makefile.src b/lib/erl_interface/test/ei_connect_SUITE_data/Makefile.src
index d1694e607d..c2d8261dd8 100644
--- a/lib/erl_interface/test/ei_connect_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/ei_connect_SUITE_data/Makefile.src
@@ -28,7 +28,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/ei_runner@obj@ \
$(LIBERL) $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
EI_CONNECT_OBJS = ei_connect_test@obj@ ei_connect_test_decl@obj@
EINODE_OBJS = einode@obj@
diff --git a/lib/erl_interface/test/ei_decode_SUITE_data/Makefile.src b/lib/erl_interface/test/ei_decode_SUITE_data/Makefile.src
index 76591d893c..e678914a40 100644
--- a/lib/erl_interface/test/ei_decode_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/ei_decode_SUITE_data/Makefile.src
@@ -27,7 +27,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/ei_runner@obj@ \
$(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
EI_DECODE_OBJS = ei_decode_test@obj@ ei_decode_test_decl@obj@
all: ei_decode_test@exe@
diff --git a/lib/erl_interface/test/ei_decode_encode_SUITE_data/Makefile.src b/lib/erl_interface/test/ei_decode_encode_SUITE_data/Makefile.src
index 3f5fa4f295..853fe9ddeb 100644
--- a/lib/erl_interface/test/ei_decode_encode_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/ei_decode_encode_SUITE_data/Makefile.src
@@ -27,7 +27,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/ei_runner@obj@ \
$(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
EI_DECODE_ENCODE_OBJS = ei_decode_encode_test@obj@ ei_decode_encode_test_decl@obj@
all: ei_decode_encode_test@exe@
diff --git a/lib/erl_interface/test/ei_encode_SUITE_data/Makefile.src b/lib/erl_interface/test/ei_encode_SUITE_data/Makefile.src
index 489382d85e..3b2cab7af4 100644
--- a/lib/erl_interface/test/ei_encode_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/ei_encode_SUITE_data/Makefile.src
@@ -27,7 +27,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/ei_runner@obj@ \
$(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
EI_ENCODE_OBJS = ei_encode_test@obj@ ei_encode_test_decl@obj@
all: ei_encode_test@exe@
diff --git a/lib/erl_interface/test/ei_format_SUITE_data/Makefile.src b/lib/erl_interface/test/ei_format_SUITE_data/Makefile.src
index 9e5a271db6..b89dcae45a 100644
--- a/lib/erl_interface/test/ei_format_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/ei_format_SUITE_data/Makefile.src
@@ -27,7 +27,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/ei_runner@obj@ \
$(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
EI_FORMAT_OBJS = ei_format_test@obj@ ei_format_test_decl@obj@
all: ei_format_test@exe@
diff --git a/lib/erl_interface/test/ei_print_SUITE_data/Makefile.src b/lib/erl_interface/test/ei_print_SUITE_data/Makefile.src
index 354011f1a5..150c11b99c 100644
--- a/lib/erl_interface/test/ei_print_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/ei_print_SUITE_data/Makefile.src
@@ -27,7 +27,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/ei_runner@obj@ \
$(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
EI_PRINT_OBJS = ei_print_test@obj@ ei_print_test_decl@obj@
all: ei_print_test@exe@
diff --git a/lib/erl_interface/test/ei_tmo_SUITE_data/Makefile.src b/lib/erl_interface/test/ei_tmo_SUITE_data/Makefile.src
index 76a9c6a606..b4ee361939 100644
--- a/lib/erl_interface/test/ei_tmo_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/ei_tmo_SUITE_data/Makefile.src
@@ -27,7 +27,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/ei_runner@obj@ \
$(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
EI_TMO_OBJS = ei_tmo_test@obj@ ei_tmo_test_decl@obj@
all: ei_tmo_test@exe@
diff --git a/lib/erl_interface/test/erl_connect_SUITE_data/Makefile.src b/lib/erl_interface/test/erl_connect_SUITE_data/Makefile.src
index 19b076794a..ff4c382c97 100644
--- a/lib/erl_interface/test/erl_connect_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/erl_connect_SUITE_data/Makefile.src
@@ -28,7 +28,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/runner@obj@ \
$(LIBERL) $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
OBJS = erl_connect_test@obj@ erl_connect_test_decl@obj@
all: erl_connect_test@exe@
diff --git a/lib/erl_interface/test/erl_eterm_SUITE_data/Makefile.src b/lib/erl_interface/test/erl_eterm_SUITE_data/Makefile.src
index cd4de56d7c..4b1ddf77b6 100644
--- a/lib/erl_interface/test/erl_eterm_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/erl_eterm_SUITE_data/Makefile.src
@@ -28,7 +28,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/runner@obj@ \
$(LIBERL) $(LIBEI) @erl_interface_sock_libs@ @LIBS@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
ETERM_OBJS = eterm_test@obj@ eterm_test_decl@obj@
CNODE_OBJS = cnode@obj@
PRINT_OBJS = print_term@obj@
diff --git a/lib/erl_interface/test/erl_ext_SUITE_data/Makefile.src b/lib/erl_interface/test/erl_ext_SUITE_data/Makefile.src
index 50b60637cd..fe8caebbd6 100644
--- a/lib/erl_interface/test/erl_ext_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/erl_ext_SUITE_data/Makefile.src
@@ -28,7 +28,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/runner@obj@ \
$(LIBERL) $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
EXT_OBJS = ext_test@obj@ ext_test_decl@obj@
all: ext_test@exe@
diff --git a/lib/erl_interface/test/erl_format_SUITE_data/Makefile.src b/lib/erl_interface/test/erl_format_SUITE_data/Makefile.src
index 7d51cd6007..2ba59ab651 100644
--- a/lib/erl_interface/test/erl_format_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/erl_format_SUITE_data/Makefile.src
@@ -28,7 +28,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/runner@obj@ \
$(LIBERL) $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
FORMAT_OBJS = format_test@obj@ format_test_decl@obj@
all: format_test@exe@
diff --git a/lib/erl_interface/test/erl_global_SUITE_data/Makefile.src b/lib/erl_interface/test/erl_global_SUITE_data/Makefile.src
index 9f2a8619ac..1c1530d1b6 100644
--- a/lib/erl_interface/test/erl_global_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/erl_global_SUITE_data/Makefile.src
@@ -28,7 +28,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/runner@obj@ \
$(LIBERL) $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
OBJS = erl_global_test@obj@ erl_global_test_decl@obj@
all: erl_global_test@exe@
diff --git a/lib/erl_interface/test/erl_match_SUITE_data/Makefile.src b/lib/erl_interface/test/erl_match_SUITE_data/Makefile.src
index 56ed1df203..156214a269 100644
--- a/lib/erl_interface/test/erl_match_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/erl_match_SUITE_data/Makefile.src
@@ -28,7 +28,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/runner@obj@ \
$(LIBERL) $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
MATCH_OBJS = match_test@obj@ match_test_decl@obj@
all: match_test@exe@
diff --git a/lib/erl_interface/test/port_call_SUITE_data/Makefile.src b/lib/erl_interface/test/port_call_SUITE_data/Makefile.src
index e09e0fe175..0f97ce9f70 100644
--- a/lib/erl_interface/test/port_call_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/port_call_SUITE_data/Makefile.src
@@ -27,7 +27,7 @@ LIBERL = @erl_interface_lib_drv@
LIBEI = @erl_interface_eilib_drv@
SHLIB_EXTRA_LDLIBS = $(LIBERL) $(LIBEI) @erl_interface_threadlib@
-SHLIB_EXTRA_CFLAGS = -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
+SHLIB_EXTRA_CFLAGS = -I@erl_interface_include@ -I../all_SUITE_data
all: port_call_drv@dll@
diff --git a/lib/erl_interface/vsn.mk b/lib/erl_interface/vsn.mk
index 06ef907d6c..0ed5c07bca 100644
--- a/lib/erl_interface/vsn.mk
+++ b/lib/erl_interface/vsn.mk
@@ -1,2 +1,2 @@
-EI_VSN = 3.10.4
+EI_VSN = 3.11
ERL_INTERFACE_VSN = $(EI_VSN)
diff --git a/lib/ftp/doc/src/notes.xml b/lib/ftp/doc/src/notes.xml
index 01c1f88cf1..61da079900 100644
--- a/lib/ftp/doc/src/notes.xml
+++ b/lib/ftp/doc/src/notes.xml
@@ -33,7 +33,23 @@
<file>notes.xml</file>
</header>
- <section><title>Ftp 1.0.1</title>
+ <section><title>Ftp 1.0.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed timing related bug that could make ftp functions
+ behave badly.</p>
+ <p>
+ Own Id: OTP-15659 Aux Id: ERIERL-316 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ftp 1.0.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/ftp/src/ftp.erl b/lib/ftp/src/ftp.erl
index 40f6b53fa3..18cd8c7524 100644
--- a/lib/ftp/src/ftp.erl
+++ b/lib/ftp/src/ftp.erl
@@ -1065,9 +1065,9 @@ handle_call({_, {open, ip_comm, Opts, {CtrlOpts, DataPassOpts, DataActOpts}}}, F
end
end;
-handle_call({_, {open, tls_upgrade, TLSOptions}}, From, State) ->
- _ = send_ctrl_message(State, mk_cmd("AUTH TLS", [])),
- activate_ctrl_connection(State),
+handle_call({_, {open, tls_upgrade, TLSOptions}}, From, State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("AUTH TLS", [])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{client = From, caller = open, tls_options = TLSOptions}};
handle_call({_, {user, User, Password}}, From,
@@ -1081,17 +1081,17 @@ handle_call({_, {user, User, Password, Acc}}, From,
handle_call({_, {account, Acc}}, From, State)->
handle_user_account(Acc, State#state{client = From});
-handle_call({_, pwd}, From, #state{chunk = false} = State) ->
- _ = send_ctrl_message(State, mk_cmd("PWD", [])),
- activate_ctrl_connection(State),
+handle_call({_, pwd}, From, #state{chunk = false} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("PWD", [])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{client = From, caller = pwd}};
handle_call({_, lpwd}, From, #state{ldir = LDir} = State) ->
{reply, {ok, LDir}, State#state{client = From}};
-handle_call({_, {cd, Dir}}, From, #state{chunk = false} = State) ->
- _ = send_ctrl_message(State, mk_cmd("CWD ~s", [Dir])),
- activate_ctrl_connection(State),
+handle_call({_, {cd, Dir}}, From, #state{chunk = false} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("CWD ~s", [Dir])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{client = From, caller = cd}};
handle_call({_,{lcd, Dir}}, _From, #state{ldir = LDir0} = State) ->
@@ -1108,41 +1108,41 @@ handle_call({_, {dir, Len, Dir}}, {_Pid, _} = From,
setup_data_connection(State#state{caller = {dir, Dir, Len},
client = From});
handle_call({_, {rename, CurrFile, NewFile}}, From,
- #state{chunk = false} = State) ->
- _ = send_ctrl_message(State, mk_cmd("RNFR ~s", [CurrFile])),
- activate_ctrl_connection(State),
+ #state{chunk = false} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("RNFR ~s", [CurrFile])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {rename, NewFile}, client = From}};
handle_call({_, {delete, File}}, {_Pid, _} = From,
- #state{chunk = false} = State) ->
- _ = send_ctrl_message(State, mk_cmd("DELE ~s", [File])),
- activate_ctrl_connection(State),
+ #state{chunk = false} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("DELE ~s", [File])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{client = From}};
-handle_call({_, {mkdir, Dir}}, From, #state{chunk = false} = State) ->
- _ = send_ctrl_message(State, mk_cmd("MKD ~s", [Dir])),
- activate_ctrl_connection(State),
+handle_call({_, {mkdir, Dir}}, From, #state{chunk = false} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("MKD ~s", [Dir])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{client = From}};
-handle_call({_,{rmdir, Dir}}, From, #state{chunk = false} = State) ->
- _ = send_ctrl_message(State, mk_cmd("RMD ~s", [Dir])),
- activate_ctrl_connection(State),
+handle_call({_,{rmdir, Dir}}, From, #state{chunk = false} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("RMD ~s", [Dir])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{client = From}};
-handle_call({_,{type, Type}}, From, #state{chunk = false} = State) ->
+handle_call({_,{type, Type}}, From, #state{chunk = false} = State0) ->
case Type of
ascii ->
- _ = send_ctrl_message(State, mk_cmd("TYPE A", [])),
- activate_ctrl_connection(State),
+ _ = send_ctrl_message(State0, mk_cmd("TYPE A", [])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = type, type = ascii,
client = From}};
binary ->
- _ = send_ctrl_message(State, mk_cmd("TYPE I", [])),
- activate_ctrl_connection(State),
+ _ = send_ctrl_message(State0, mk_cmd("TYPE I", [])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = type, type = binary,
client = From}};
_ ->
- {reply, {error, etype}, State}
+ {reply, {error, etype}, State0}
end;
handle_call({_,{recv, RemoteFile, LocalFile}}, From,
@@ -1181,8 +1181,8 @@ handle_call({_, recv_chunk}, _From, #state{chunk = true,
} = State0) ->
%% The ftp:recv_chunk call was the last event we waited for, finnish and clean up
?DBG("recv_chunk_closing ftp:recv_chunk, last event",[]),
- activate_ctrl_connection(State0),
- {reply, ok, State0#state{caller = undefined,
+ State = activate_ctrl_connection(State0),
+ {reply, ok, State#state{caller = undefined,
chunk = false,
client = undefined}};
@@ -1238,18 +1238,18 @@ handle_call({_, {transfer_chunk, Bin}}, _, #state{chunk = true} = State) ->
handle_call({_, {transfer_chunk, _}}, _, #state{chunk = false} = State) ->
{reply, {error, echunk}, State};
-handle_call({_, chunk_end}, From, #state{chunk = true} = State) ->
- close_data_connection(State),
- activate_ctrl_connection(State),
+handle_call({_, chunk_end}, From, #state{chunk = true} = State0) ->
+ close_data_connection(State0),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{client = From, dsock = undefined,
caller = end_chunk_transfer, chunk = false}};
handle_call({_, chunk_end}, _, #state{chunk = false} = State) ->
{reply, {error, echunk}, State};
-handle_call({_, {quote, Cmd}}, From, #state{chunk = false} = State) ->
- _ = send_ctrl_message(State, mk_cmd(Cmd, [])),
- activate_ctrl_connection(State),
+handle_call({_, {quote, Cmd}}, From, #state{chunk = false} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd(Cmd, [])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{client = From, caller = quote}};
handle_call({_, _Req}, _From, #state{csock = CSock} = State)
@@ -1325,38 +1325,38 @@ handle_info({Trpt, Socket, Data}, #state{dsock = {Trpt,Socket}} = State0) when T
Data/binary>>}};
handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket},
- caller = {recv_file, Fd}} = State)
+ caller = {recv_file, Fd}} = State0)
when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
file_close(Fd),
- progress_report({transfer_size, 0}, State),
- activate_ctrl_connection(State),
+ progress_report({transfer_size, 0}, State0),
+ State = activate_ctrl_connection(State0),
?DBG("Data channel close",[]),
{noreply, State#state{dsock = undefined, data = <<>>}};
handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket},
client = Client,
- caller = recv_chunk} = State)
+ caller = recv_chunk} = State0)
when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
?DBG("Data channel close recv_chunk",[]),
- activate_ctrl_connection(State),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{dsock = undefined,
caller = #recv_chunk_closing{dconn_closed = true,
client_called_us = Client =/= undefined}
}};
handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, caller = recv_bin,
- data = Data} = State)
+ data = Data} = State0)
when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
?DBG("Data channel close",[]),
- activate_ctrl_connection(State),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{dsock = undefined, data = <<>>,
caller = {recv_bin, Data}}};
handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, data = Data,
- caller = {handle_dir_result, Dir}}
- = State) when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
+ caller = {handle_dir_result, Dir}}
+ = State0) when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
?DBG("Data channel close",[]),
- activate_ctrl_connection(State),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{dsock = undefined,
caller = {handle_dir_result, Dir, Data},
% data = <<?CR,?LF>>}};
@@ -1377,7 +1377,7 @@ handle_info({Transport, Socket, Data}, #state{csock = {Transport, Socket},
client = From,
ctrl_data = {CtrlData, AccLines,
LineStatus}}
- = State) ->
+ = State0) ->
?DBG('--ctrl ~p ----> ~s~p~n',[Socket,<<CtrlData/binary, Data/binary>>,State]),
case ftp_response:parse_lines(<<CtrlData/binary, Data/binary>>,
AccLines, LineStatus) of
@@ -1387,21 +1387,21 @@ handle_info({Transport, Socket, Data}, #state{csock = {Transport, Socket},
case Caller of
quote ->
gen_server:reply(From, string:tokens(Lines, [?CR, ?LF])),
- {noreply, State#state{client = undefined,
- caller = undefined,
- latest_ctrl_response = Lines,
- ctrl_data = {NextMsgData, [],
- start}}};
+ {noreply, State0#state{client = undefined,
+ caller = undefined,
+ latest_ctrl_response = Lines,
+ ctrl_data = {NextMsgData, [],
+ start}}};
_ ->
?DBG(' ...handle_ctrl_result(~p,...) ctrl_data=~p~n',[CtrlResult,{NextMsgData, [], start}]),
handle_ctrl_result(CtrlResult,
- State#state{latest_ctrl_response = Lines,
- ctrl_data =
- {NextMsgData, [], start}})
+ State0#state{latest_ctrl_response = Lines,
+ ctrl_data =
+ {NextMsgData, [], start}})
end;
{continue, NewCtrlData} ->
?DBG(' ...Continue... ctrl_data=~p~n',[NewCtrlData]),
- activate_ctrl_connection(State),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{ctrl_data = NewCtrlData}}
end;
@@ -1567,19 +1567,19 @@ start_link(Opts, GenServerOptions) ->
%%% Help functions to handle_call and/or handle_ctrl_result
%%--------------------------------------------------------------------------
%% User handling
-handle_user(User, Password, Acc, State) ->
- _ = send_ctrl_message(State, mk_cmd("USER ~s", [User])),
- activate_ctrl_connection(State),
+handle_user(User, Password, Acc, State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("USER ~s", [User])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {handle_user, Password, Acc}}}.
-handle_user_passwd(Password, Acc, State) ->
- _ = send_ctrl_message(State, mk_cmd("PASS ~s", [Password])),
- activate_ctrl_connection(State),
+handle_user_passwd(Password, Acc, State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("PASS ~s", [Password])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {handle_user_passwd, Acc}}}.
-handle_user_account(Acc, State) ->
- _ = send_ctrl_message(State, mk_cmd("ACCT ~s", [Acc])),
- activate_ctrl_connection(State),
+handle_user_account(Acc, State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("ACCT ~s", [Acc])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = handle_user_account}}.
@@ -1594,9 +1594,9 @@ handle_ctrl_result({tls_upgrade, _}, #state{csock = {tcp, Socket},
?DBG('<--ctrl ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State0]),
case ssl:connect(Socket, TLSOptions, Timeout) of
{ok, TLSSocket} ->
- State = State0#state{csock = {ssl,TLSSocket}},
- _ = send_ctrl_message(State, mk_cmd("PBSZ 0", [])),
- activate_ctrl_connection(State),
+ State1 = State0#state{csock = {ssl,TLSSocket}},
+ _ = send_ctrl_message(State1, mk_cmd("PBSZ 0", [])),
+ State = activate_ctrl_connection(State1),
{noreply, State#state{tls_upgrading_data_connection = {true, pbsz}} };
{error, _} = Error ->
gen_server:reply(From, {Error, self()}),
@@ -1605,9 +1605,9 @@ handle_ctrl_result({tls_upgrade, _}, #state{csock = {tcp, Socket},
tls_upgrading_data_connection = false}}
end;
-handle_ctrl_result({pos_compl, _}, #state{tls_upgrading_data_connection = {true, pbsz}} = State) ->
- _ = send_ctrl_message(State, mk_cmd("PROT P", [])),
- activate_ctrl_connection(State),
+handle_ctrl_result({pos_compl, _}, #state{tls_upgrading_data_connection = {true, pbsz}} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("PROT P", [])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{tls_upgrading_data_connection = {true, prot}}};
handle_ctrl_result({pos_compl, _}, #state{tls_upgrading_data_connection = {true, prot},
@@ -1801,10 +1801,10 @@ handle_ctrl_result({pos_compl, _}, #state{caller = {handle_dir_result, Dir,
handle_ctrl_result({pos_compl, Lines},
#state{caller = {handle_dir_data, Dir, DirData}} =
- State) ->
+ State0) ->
OldDir = pwd_result(Lines),
- _ = send_ctrl_message(State, mk_cmd("CWD ~s", [Dir])),
- activate_ctrl_connection(State),
+ _ = send_ctrl_message(State0, mk_cmd("CWD ~s", [Dir])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {handle_dir_data_second_phase, OldDir,
DirData}}};
handle_ctrl_result({Status, _},
@@ -1818,9 +1818,9 @@ handle_ctrl_result(S={_Status, _},
handle_ctrl_result({pos_compl, _},
#state{caller = {handle_dir_data_second_phase, OldDir,
- DirData}} = State) ->
- _ = send_ctrl_message(State, mk_cmd("CWD ~s", [OldDir])),
- activate_ctrl_connection(State),
+ DirData}} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("CWD ~s", [OldDir])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {handle_dir_data_third_phase, DirData}}};
handle_ctrl_result({Status, _},
#state{caller = {handle_dir_data_second_phase, _, _}}
@@ -1840,9 +1840,9 @@ handle_ctrl_result(Status={epath, _}, #state{caller = {dir,_}} = State) ->
%%--------------------------------------------------------------------------
%% File renaming
handle_ctrl_result({pos_interm, _}, #state{caller = {rename, NewFile}}
- = State) ->
- _ = send_ctrl_message(State, mk_cmd("RNTO ~s", [NewFile])),
- activate_ctrl_connection(State),
+ = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("RNTO ~s", [NewFile])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = rename_second_phase}};
handle_ctrl_result({Status, _},
@@ -1916,10 +1916,10 @@ handle_ctrl_result({pos_compl, _}, #state{client = From,
%% The pos_compl was the last event we waited for, finnish and clean up
?DBG("recv_chunk_closing pos_compl, last event",[]),
gen_server:reply(From, ok),
- activate_ctrl_connection(State0),
- {noreply, State0#state{caller = undefined,
- chunk = false,
- client = undefined}};
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = undefined,
+ chunk = false,
+ client = undefined}};
handle_ctrl_result({pos_compl, _}, #state{caller = #recv_chunk_closing{}=R}
= State0) ->
@@ -2013,71 +2013,71 @@ ctrl_result_response(_, #state{client = From} = State, ErrorMsg) ->
{noreply, State#state{client = undefined, caller = undefined}}.
%%--------------------------------------------------------------------------
-handle_caller(#state{caller = {dir, Dir, Len}} = State) ->
+handle_caller(#state{caller = {dir, Dir, Len}} = State0) ->
Cmd = case Len of
short -> "NLST";
long -> "LIST"
end,
_ = case Dir of
"" ->
- send_ctrl_message(State, mk_cmd(Cmd, ""));
+ send_ctrl_message(State0, mk_cmd(Cmd, ""));
_ ->
- send_ctrl_message(State, mk_cmd(Cmd ++ " ~s", [Dir]))
+ send_ctrl_message(State0, mk_cmd(Cmd ++ " ~s", [Dir]))
end,
- activate_ctrl_connection(State),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {dir, Dir}}};
-handle_caller(#state{caller = {recv_bin, RemoteFile}} = State) ->
- _ = send_ctrl_message(State, mk_cmd("RETR ~s", [RemoteFile])),
- activate_ctrl_connection(State),
+handle_caller(#state{caller = {recv_bin, RemoteFile}} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("RETR ~s", [RemoteFile])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = recv_bin}};
handle_caller(#state{caller = {start_chunk_transfer, Cmd, RemoteFile}} =
- State) ->
- _ = send_ctrl_message(State, mk_cmd("~s ~s", [Cmd, RemoteFile])),
- activate_ctrl_connection(State),
+ State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("~s ~s", [Cmd, RemoteFile])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = start_chunk_transfer}};
-handle_caller(#state{caller = {recv_file, RemoteFile, Fd}} = State) ->
- _ = send_ctrl_message(State, mk_cmd("RETR ~s", [RemoteFile])),
- activate_ctrl_connection(State),
+handle_caller(#state{caller = {recv_file, RemoteFile, Fd}} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("RETR ~s", [RemoteFile])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {recv_file, Fd}}};
handle_caller(#state{caller = {transfer_file, {Cmd, LocalFile, RemoteFile}},
- ldir = LocalDir, client = From} = State) ->
+ ldir = LocalDir, client = From} = State0) ->
case file_open(filename:absname(LocalFile, LocalDir), read) of
{ok, Fd} ->
- _ = send_ctrl_message(State, mk_cmd("~s ~s", [Cmd, RemoteFile])),
- activate_ctrl_connection(State),
+ _ = send_ctrl_message(State0, mk_cmd("~s ~s", [Cmd, RemoteFile])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {transfer_file, Fd}}};
{error, _} ->
gen_server:reply(From, {error, epath}),
- {noreply, State#state{client = undefined, caller = undefined,
- dsock = undefined}}
+ {noreply, State0#state{client = undefined, caller = undefined,
+ dsock = undefined}}
end;
handle_caller(#state{caller = {transfer_data, {Cmd, Bin, RemoteFile}}} =
- State) ->
- _ = send_ctrl_message(State, mk_cmd("~s ~s", [Cmd, RemoteFile])),
- activate_ctrl_connection(State),
+ State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("~s ~s", [Cmd, RemoteFile])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {transfer_data, Bin}}}.
%% ----------- FTP SERVER COMMUNICATION -------------------------
%% Connect to FTP server at Host (default is TCP port 21)
%% in order to establish a control connection.
-setup_ctrl_connection(Host, Port, Timeout, #state{sockopts_ctrl = SockOpts} = State) ->
+setup_ctrl_connection(Host, Port, Timeout, #state{sockopts_ctrl = SockOpts} = State0) ->
MsTime = erlang:monotonic_time(),
- case connect(Host, Port, SockOpts, Timeout, State) of
+ case connect(Host, Port, SockOpts, Timeout, State0) of
{ok, IpFam, CSock} ->
- NewState = State#state{csock = {tcp, CSock}, ipfamily = IpFam},
- activate_ctrl_connection(NewState),
+ State1 = State0#state{csock = {tcp, CSock}, ipfamily = IpFam},
+ State = activate_ctrl_connection(State1),
case Timeout - millisec_passed(MsTime) of
Timeout2 when (Timeout2 >= 0) ->
- {ok, NewState#state{caller = open}, Timeout2};
+ {ok, State#state{caller = open}, Timeout2};
_ ->
%% Oups: Simulate timeout
- {ok, NewState#state{caller = open}, 0}
+ {ok, State#state{caller = open}, 0}
end;
Error ->
Error
@@ -2087,7 +2087,7 @@ setup_data_connection(#state{mode = active,
caller = Caller,
csock = CSock,
sockopts_data_active = SockOpts,
- ftp_extension = FtpExt} = State) ->
+ ftp_extension = FtpExt} = State0) ->
case (catch sockname(CSock)) of
{ok, {{_, _, _, _, _, _, _, _} = IP0, _}} ->
IP = proplists:get_value(ip, SockOpts, IP0),
@@ -2098,8 +2098,8 @@ setup_data_connection(#state{mode = active,
{ok, {_, Port}} = sockname({tcp,LSock}),
IpAddress = inet_parse:ntoa(IP),
Cmd = mk_cmd("EPRT |2|~s|~p|", [IpAddress, Port]),
- _ = send_ctrl_message(State, Cmd),
- activate_ctrl_connection(State),
+ _ = send_ctrl_message(State0, Cmd),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {setup_data_connection,
{LSock, Caller}}}};
{ok, {{_,_,_,_} = IP0, _}} ->
@@ -2112,37 +2112,37 @@ setup_data_connection(#state{mode = active,
false ->
{IP1, IP2, IP3, IP4} = IP,
{Port1, Port2} = {Port div 256, Port rem 256},
- send_ctrl_message(State,
+ send_ctrl_message(State0,
mk_cmd("PORT ~w,~w,~w,~w,~w,~w",
[IP1, IP2, IP3, IP4, Port1, Port2]));
true ->
IpAddress = inet_parse:ntoa(IP),
Cmd = mk_cmd("EPRT |1|~s|~p|", [IpAddress, Port]),
- send_ctrl_message(State, Cmd)
+ send_ctrl_message(State0, Cmd)
end,
- activate_ctrl_connection(State),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {setup_data_connection,
{LSock, Caller}}}}
end;
setup_data_connection(#state{mode = passive, ipfamily = inet6,
- caller = Caller} = State) ->
- _ = send_ctrl_message(State, mk_cmd("EPSV", [])),
- activate_ctrl_connection(State),
+ caller = Caller} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("EPSV", [])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {setup_data_connection, Caller}}};
setup_data_connection(#state{mode = passive, ipfamily = inet,
caller = Caller,
- ftp_extension = false} = State) ->
- _ = send_ctrl_message(State, mk_cmd("PASV", [])),
- activate_ctrl_connection(State),
+ ftp_extension = false} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("PASV", [])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {setup_data_connection, Caller}}};
setup_data_connection(#state{mode = passive, ipfamily = inet,
caller = Caller,
- ftp_extension = true} = State) ->
- _ = send_ctrl_message(State, mk_cmd("EPSV", [])),
- activate_ctrl_connection(State),
+ ftp_extension = true} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("EPSV", [])),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {setup_data_connection, Caller}}}.
connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet = IpFam}) ->
@@ -2248,14 +2248,15 @@ send_message({tcp, Socket}, Message) ->
send_message({ssl, Socket}, Message) ->
ssl:send(Socket, Message).
-activate_ctrl_connection(#state{csock = CSock, ctrl_data = {<<>>, _, _}}) ->
- activate_connection(CSock);
-activate_ctrl_connection(#state{csock = CSock}) ->
+activate_ctrl_connection(#state{csock = CSock, ctrl_data = {<<>>, _, _}} = State) ->
+ activate_connection(CSock),
+ State;
+activate_ctrl_connection(#state{csock = CSock} = State0) ->
activate_connection(CSock),
%% We have already received at least part of the next control message,
%% that has been saved in ctrl_data, process this first.
- self() ! {socket_type(CSock), unwrap_socket(CSock), <<>>},
- ok.
+ {noreply, State} = handle_info({socket_type(CSock), unwrap_socket(CSock), <<>>}, State0),
+ State.
activate_data_connection(#state{dsock = DSock} = State) ->
activate_connection(DSock),
@@ -2290,22 +2291,22 @@ close_connection({ssl, Socket}) -> ignore_return_value( ssl:close(Socket) ).
%% ------------ FILE HANDLING ----------------------------------------
send_file(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State, Fd) ->
{noreply, State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, send_file, Fd}}};
-send_file(State, Fd) ->
+send_file(State0, Fd) ->
case file_read(Fd) of
{ok, N, Bin} when N > 0 ->
- send_data_message(State, Bin),
- progress_report({binary, Bin}, State),
- send_file(State, Fd);
+ send_data_message(State0, Bin),
+ progress_report({binary, Bin}, State0),
+ send_file(State0, Fd);
{ok, _, _} ->
file_close(Fd),
- close_data_connection(State),
- progress_report({transfer_size, 0}, State),
- activate_ctrl_connection(State),
+ close_data_connection(State0),
+ progress_report({transfer_size, 0}, State0),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = transfer_file_second_phase,
dsock = undefined}};
{error, Reason} ->
- gen_server:reply(State#state.client, {error, Reason}),
- {stop, normal, State#state{client = undefined}}
+ gen_server:reply(State0#state.client, {error, Reason}),
+ {stop, normal, State0#state{client = undefined}}
end.
file_open(File, Option) ->
@@ -2347,10 +2348,10 @@ cast(GenServer, Msg) ->
send_bin(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State, Bin) ->
State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, send_bin, Bin}};
-send_bin(State, Bin) ->
- send_data_message(State, Bin),
- close_data_connection(State),
- activate_ctrl_connection(State),
+send_bin(State0, Bin) ->
+ send_data_message(State0, Bin),
+ close_data_connection(State0),
+ State = activate_ctrl_connection(State0),
{noreply, State#state{caller = transfer_data_second_phase,
dsock = undefined}}.
diff --git a/lib/ftp/test/ftp_SUITE.erl b/lib/ftp/test/ftp_SUITE.erl
index 7c87d5cbdb..0b070ee8cb 100644
--- a/lib/ftp/test/ftp_SUITE.erl
+++ b/lib/ftp/test/ftp_SUITE.erl
@@ -96,6 +96,7 @@ ftp_tests()->
recv_chunk,
recv_chunk_twice,
recv_chunk_three_times,
+ recv_chunk_delay,
type,
quote,
error_elogin,
@@ -669,9 +670,9 @@ recv_chunk(Config0) ->
Contents = list_to_binary( lists:duplicate(1000, lists:seq(0,255)) ),
Config = set_state([reset, {mkfile,File,Contents}], Config0),
Pid = proplists:get_value(ftp, Config),
- {{error, "ftp:recv_chunk_start/2 not called"},_} = recv_chunk(Pid, <<>>),
+ {error, "ftp:recv_chunk_start/2 not called"} = do_recv_chunk(Pid),
ok = ftp:recv_chunk_start(Pid, id2ftp(File,Config)),
- {ok, ReceivedContents, _Ncunks} = recv_chunk(Pid, <<>>),
+ {ok, ReceivedContents} = do_recv_chunk(Pid),
find_diff(ReceivedContents, Contents).
recv_chunk_twice() ->
@@ -683,11 +684,11 @@ recv_chunk_twice(Config0) ->
Contents2 = crypto:strong_rand_bytes(1200),
Config = set_state([reset, {mkfile,File1,Contents1}, {mkfile,File2,Contents2}], Config0),
Pid = proplists:get_value(ftp, Config),
- {{error, "ftp:recv_chunk_start/2 not called"},_} = recv_chunk(Pid, <<>>),
+ {error, "ftp:recv_chunk_start/2 not called"} = do_recv_chunk(Pid),
ok = ftp:recv_chunk_start(Pid, id2ftp(File1,Config)),
- {ok, ReceivedContents1, _Ncunks1} = recv_chunk(Pid, <<>>),
+ {ok, ReceivedContents1} = do_recv_chunk(Pid),
ok = ftp:recv_chunk_start(Pid, id2ftp(File2,Config)),
- {ok, ReceivedContents2, _Ncunks2} = recv_chunk(Pid, <<>>),
+ {ok, ReceivedContents2} = do_recv_chunk(Pid),
find_diff(ReceivedContents1, Contents1),
find_diff(ReceivedContents2, Contents2).
@@ -704,46 +705,56 @@ recv_chunk_three_times(Config0) ->
Config = set_state([reset, {mkfile,File1,Contents1}, {mkfile,File2,Contents2}, {mkfile,File3,Contents3}], Config0),
Pid = proplists:get_value(ftp, Config),
- {{error, "ftp:recv_chunk_start/2 not called"},_} = recv_chunk(Pid, <<>>),
+ {error, "ftp:recv_chunk_start/2 not called"} = do_recv_chunk(Pid),
+ ok = ftp:recv_chunk_start(Pid, id2ftp(File3,Config)),
+ {ok, ReceivedContents3} = do_recv_chunk(Pid),
+
ok = ftp:recv_chunk_start(Pid, id2ftp(File1,Config)),
- {ok, ReceivedContents1, Nchunks1} = recv_chunk(Pid, <<>>),
+ {ok, ReceivedContents1} = do_recv_chunk(Pid),
ok = ftp:recv_chunk_start(Pid, id2ftp(File2,Config)),
- {ok, ReceivedContents2, _Nchunks2} = recv_chunk(Pid, <<>>),
-
- ok = ftp:recv_chunk_start(Pid, id2ftp(File3,Config)),
- {ok, ReceivedContents3, _Nchunks3} = recv_chunk(Pid, <<>>, 10000, 0, Nchunks1),
+ {ok, ReceivedContents2} = do_recv_chunk(Pid),
find_diff(ReceivedContents1, Contents1),
find_diff(ReceivedContents2, Contents2),
find_diff(ReceivedContents3, Contents3).
-
+do_recv_chunk(Pid) ->
+ recv_chunk(Pid, <<>>).
recv_chunk(Pid, Acc) ->
- recv_chunk(Pid, Acc, 0, 0, undefined).
-
-
-
-%% ExpectNchunks :: integer() | undefined
-recv_chunk(Pid, Acc, DelayMilliSec, N, ExpectNchunks) when N+1 < ExpectNchunks ->
- %% for all I in integer(), I < undefined
- recv_chunk1(Pid, Acc, DelayMilliSec, N, ExpectNchunks);
-
-recv_chunk(Pid, Acc, DelayMilliSec, N, ExpectNchunks) ->
- %% N >= ExpectNchunks-1
- timer:sleep(DelayMilliSec),
- recv_chunk1(Pid, Acc, DelayMilliSec, N, ExpectNchunks).
+ case ftp:recv_chunk(Pid) of
+ ok ->
+ {ok, Acc};
+ {ok, Bin} ->
+ recv_chunk(Pid, <<Acc/binary, Bin/binary>>);
+ Error ->
+ Error
+ end.
+recv_chunk_delay(Config0) when is_list(Config0) ->
+ File1 = "big_file1.txt",
+ Contents = list_to_binary(lists:duplicate(1000, lists:seq(0,255))),
+ Config = set_state([reset, {mkfile,File1,Contents}], Config0),
+ Pid = proplists:get_value(ftp, Config),
+ ok = ftp:recv_chunk_start(Pid, id2ftp(File1,Config)),
+ {ok, ReceivedContents} = delay_recv_chunk(Pid),
+ find_diff(ReceivedContents, Contents).
-recv_chunk1(Pid, Acc, DelayMilliSec, N, ExpectNchunks) ->
- ct:log("Call ftp:recv_chunk",[]),
+delay_recv_chunk(Pid) ->
+ delay_recv_chunk(Pid, <<>>).
+delay_recv_chunk(Pid, Acc) ->
+ ct:pal("Recived size ~p", [byte_size(Acc)]),
case ftp:recv_chunk(Pid) of
- ok -> {ok, Acc, N};
- {ok, Bin} -> recv_chunk(Pid, <<Acc/binary, Bin/binary>>, DelayMilliSec, N+1, ExpectNchunks);
- Error -> {Error, N}
- end.
+ ok ->
+ {ok, Acc};
+ {ok, Bin} ->
+ ct:sleep(100),
+ delay_recv_chunk(Pid, <<Acc/binary, Bin/binary>>);
+ Error ->
+ Error
+ end.
%%-------------------------------------------------------------------------
type() ->
diff --git a/lib/ftp/vsn.mk b/lib/ftp/vsn.mk
index d5d6c45b28..9f14658099 100644
--- a/lib/ftp/vsn.mk
+++ b/lib/ftp/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = ftp
-FTP_VSN = 1.0.1
+FTP_VSN = 1.0.2
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(FTP_VSN)$(PRE_VSN)"
diff --git a/lib/hipe/doc/src/notes.xml b/lib/hipe/doc/src/notes.xml
index e9cdf42018..9a803cb9df 100644
--- a/lib/hipe/doc/src/notes.xml
+++ b/lib/hipe/doc/src/notes.xml
@@ -31,6 +31,21 @@
</header>
<p>This document describes the changes made to HiPE.</p>
+<section><title>Hipe 3.18.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p> Fix a bug in the handling of the <c>Key</c> argument
+ of <c>lists:{keysearch, keyfind, keymember}</c>. </p>
+ <p>
+ Own Id: OTP-15570</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Hipe 3.18.2</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/hipe/vsn.mk b/lib/hipe/vsn.mk
index 12d621bf01..39565d721f 100644
--- a/lib/hipe/vsn.mk
+++ b/lib/hipe/vsn.mk
@@ -1 +1 @@
-HIPE_VSN = 3.18.2
+HIPE_VSN = 3.18.3
diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml
index 31dae6317e..91dd9cd6ed 100644
--- a/lib/inets/doc/src/notes.xml
+++ b/lib/inets/doc/src/notes.xml
@@ -33,7 +33,43 @@
<file>notes.xml</file>
</header>
- <section><title>Inets 7.0.5</title>
+ <section><title>Inets 7.0.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix the internal handling of the option
+ erl_script_timeout in httpd. When httpd was started with
+ explicit erl_script_timeout, the value of the option was
+ converted to milliseconds before storage. Subsequent
+ calls to httpd:info/1 returned the input value multiplied
+ by 1000.</p>
+ <p>
+ This change fixes the handing of erl_script_timeout by
+ storing the timeout in seconds and converting to
+ milliseconds before usage.</p>
+ <p>
+ Own Id: OTP-15669 Aux Id: ERIERL-321 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Enhance documentation</p>
+ <p>
+ Own Id: OTP-15508 Aux Id: ERL-816 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Inets 7.0.5</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/inets/src/http_server/mod_esi.erl b/lib/inets/src/http_server/mod_esi.erl
index 443b7ee564..f495f12f03 100644
--- a/lib/inets/src/http_server/mod_esi.erl
+++ b/lib/inets/src/http_server/mod_esi.erl
@@ -33,7 +33,7 @@
-include("httpd_internal.hrl").
-define(VMODULE,"ESI").
--define(DEFAULT_ERL_TIMEOUT,15000).
+-define(DEFAULT_ERL_TIMEOUT,15).
%%%=========================================================================
@@ -174,7 +174,7 @@ store({erl_script_alias, Value}, _) ->
{error, {wrong_type, {erl_script_alias, Value}}};
store({erl_script_timeout, TimeoutSec}, _)
when is_integer(TimeoutSec) andalso (TimeoutSec >= 0) ->
- {ok, {erl_script_timeout, TimeoutSec * 1000}};
+ {ok, {erl_script_timeout, TimeoutSec}};
store({erl_script_timeout, Value}, _) ->
{error, {wrong_type, {erl_script_timeout, Value}}};
store({erl_script_nocache, Value} = Conf, _)
@@ -500,7 +500,7 @@ kill_esi_delivery_process(Pid) ->
erl_script_timeout(Db) ->
- httpd_util:lookup(Db, erl_script_timeout, ?DEFAULT_ERL_TIMEOUT).
+ httpd_util:lookup(Db, erl_script_timeout, ?DEFAULT_ERL_TIMEOUT * 1000).
script_elements(FuncAndInput, Input) ->
case input_type(FuncAndInput) of
diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl
index 5b6740fba3..fcb9ad7905 100644
--- a/lib/inets/test/httpd_SUITE.erl
+++ b/lib/inets/test/httpd_SUITE.erl
@@ -78,7 +78,8 @@ all() ->
{group, http_rel_path_script_alias},
{group, http_not_sup},
{group, https_not_sup},
- mime_types_format
+ mime_types_format,
+ erl_script_timeout_option
].
groups() ->
@@ -1777,6 +1778,18 @@ mime_types_format(Config) when is_list(Config) ->
{"hqx","application/mac-binhex40"}]} = httpd_conf:load_mime_types(MimeTypes).
+erl_script_timeout_option(Config) when is_list(Config) ->
+ inets:start(),
+ {ok, Pid} = inets:start(httpd, [{erl_script_timeout, 215},
+ {server_name, "test"},
+ {port,0},
+ {server_root, "."},
+ {document_root, "."}]),
+ Info = httpd:info(Pid),
+ 215 = proplists:get_value(erl_script_timeout, Info),
+ inets:stop().
+
+
%%--------------------------------------------------------------------
%% Internal functions -----------------------------------
%%--------------------------------------------------------------------
diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk
index 921161dce1..b7ddf39ebd 100644
--- a/lib/inets/vsn.mk
+++ b/lib/inets/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = inets
-INETS_VSN = 7.0.5
+INETS_VSN = 7.0.6
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)"
diff --git a/lib/jinterface/doc/src/Makefile b/lib/jinterface/doc/src/Makefile
index d80bb38ec1..f5cba9d074 100644
--- a/lib/jinterface/doc/src/Makefile
+++ b/lib/jinterface/doc/src/Makefile
@@ -137,6 +137,7 @@ clean clean_docs:
jdoc:$(JAVA_SRC_FILES)
(cd ../../java_src;$(JAVADOC) -sourcepath . -d $(JAVADOC_DEST) \
-windowtitle $(JAVADOC_TITLE) $(JAVADOC_PKGS))
+ touch jdoc
man:
@@ -152,15 +153,8 @@ include $(ERL_TOP)/make/otp_release_targets.mk
release_docs_spec: docs
$(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
$(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html/java/$(JAVA_PKG_PATH)"
$(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- ($(CP) -rf ../html "$(RELSYSDIR)/doc")
-
-# $(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTML_FILES) \
-# "$(RELSYSDIR)/doc/html"
-# $(INSTALL_DATA) $(JAVA_EXTRA_FILES) "$(RELSYSDIR)/doc/html/java"
-# $(INSTALL_DATA) $(TOP_HTML_FILES) "$(RELSYSDIR)/doc"
+ $(INSTALL_DIR_DATA) $(HTMLDIR) "$(RELSYSDIR)/doc/html"
release_spec:
diff --git a/lib/kernel/doc/src/application.xml b/lib/kernel/doc/src/application.xml
index 5170502581..83a83ebad2 100644
--- a/lib/kernel/doc/src/application.xml
+++ b/lib/kernel/doc/src/application.xml
@@ -238,8 +238,8 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="set_env" arity="1" since="OTP @OTP-15642@"/>
- <name name="set_env" arity="2" since="OTP @OTP-15642@"/>
+ <name name="set_env" arity="1" since="OTP 21.3"/>
+ <name name="set_env" arity="2" since="OTP 21.3"/>
<fsummary>Sets the configuration parameters of multiple applications.</fsummary>
<desc>
<p>Sets the configuration <c><anno>Config</anno></c> for multiple
diff --git a/lib/kernel/doc/src/kernel_app.xml b/lib/kernel/doc/src/kernel_app.xml
index 15dbdb47dc..7f9609d5c1 100644
--- a/lib/kernel/doc/src/kernel_app.xml
+++ b/lib/kernel/doc/src/kernel_app.xml
@@ -369,6 +369,14 @@ MaxT = TickTime + TickTime / 4</code>
performed. This option ensures that <c>global</c> is
synchronized.</p>
</item>
+ <tag><c>start_distribution = true | false</c></tag>
+ <item>
+ <p>Starts all distribution services, such as <c>rpc</c>,
+ <c>global</c>, and <c>net_kernel</c> if the parameter is
+ <c>true</c>. This parameter is to be set to <c>false</c>
+ for systems who want to disable all distribution functionality.</p>
+ <p>Defaults to <c>true</c>.</p>
+ </item>
<tag><c>start_dist_ac = true | false</c></tag>
<item>
<p>Starts the <c>dist_ac</c> server if the parameter is
@@ -510,11 +518,13 @@ MaxT = TickTime + TickTime / 4</code>
parameters for Logger are not set.</p>
<taglist>
<tag><c>error_logger</c></tag>
- <item>Replaced by setting the type of the default
- <seealso marker="logger_std_h#type"><c>logger_std_h</c></seealso>
- to the same value. Example:
+ <item>Replaced by setting the <seealso
+ marker="logger_std_h#type"><c>type</c></seealso>, and possibly
+ <seealso marker="logger_std_h#file"><c>file</c></seealso> and
+ <seealso marker="logger_std_h#modes"><c>modes</c></seealso>
+ parameters of the default <c>logger_std_h</c> handler. Example:
<code type="none">
-erl -kernel logger '[{handler,default,logger_std_h,#{config=>#{type=>{file,"/tmp/erlang.log"}}}}]'
+erl -kernel logger '[{handler,default,logger_std_h,#{config=>#{file=>"/tmp/erlang.log"}}}]'
</code>
</item>
<tag><c>error_logger_format_depth</c></tag>
diff --git a/lib/kernel/doc/src/logger.xml b/lib/kernel/doc/src/logger.xml
index e6448e144e..2a060ce1f9 100644
--- a/lib/kernel/doc/src/logger.xml
+++ b/lib/kernel/doc/src/logger.xml
@@ -66,7 +66,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
[{kernel,
[{logger,
[{handler, default, logger_std_h,
- #{config => #{type => {file, "path/to/file.log"}}}}]}]}].
+ #{config => #{file => "path/to/file.log"}}}]}]}].
</code>
<p>
For more information about:
@@ -1119,7 +1119,7 @@ logger:set_proxy_config(maps:merge(Old, Config)).
</func>
<func>
- <name name="timestamp" arity="0" since="OTP @OTP-15625@"/>
+ <name name="timestamp" arity="0" since="OTP 21.3"/>
<fsummary>Return a timestamp to insert in meta data for a log
event.</fsummary>
<desc>
@@ -1351,5 +1351,3 @@ logger:set_proxy_config(maps:merge(Old, Config)).
</p>
</section>
</erlref>
-
-
diff --git a/lib/kernel/doc/src/logger_chapter.xml b/lib/kernel/doc/src/logger_chapter.xml
index 03b9edcf8f..8458ffa042 100644
--- a/lib/kernel/doc/src/logger_chapter.xml
+++ b/lib/kernel/doc/src/logger_chapter.xml
@@ -801,7 +801,7 @@ logger:debug(#{got => connection_request, id => Id, state => State},
[{kernel,
[{logger,
[{handler, default, logger_std_h, % {handler, HandlerId, Module,
- #{config => #{type => {file,"log/erlang.log"}}}} % Config}
+ #{config => #{file => "log/erlang.log"}}} % Config}
]}]}].
</code>
<p>Modify the default handler to print each log event as a
@@ -831,10 +831,10 @@ logger:debug(#{got => connection_request, id => Id, state => State},
[{logger,
[{handler, default, logger_std_h,
#{level => error,
- config => #{type => {file, "log/erlang.log"}}}},
+ config => #{file => "log/erlang.log"}}},
{handler, info, logger_std_h,
#{level => debug,
- config => #{type => {file, "log/debug.log"}}}}
+ config => #{file => "log/debug.log"}}}
]}]}].
</code>
</section>
@@ -1004,10 +1004,10 @@ ok</pre>
<p>Then, add a new handler which prints to file. You can use the
handler
module <seealso marker="logger_std_h"><c>logger_std_h</c></seealso>,
- and specify type <c>{file,File}</c>.:</p>
+ and configure it to log to file:</p>
<pre>
-4> <input>Config = #{config => #{type => {file,"./info.log"}}, level => info}.</input>
-#{config => #{type => {file,"./info.log"}},level => info}
+4> <input>Config = #{config => #{file => "./info.log"}, level => info}.</input>
+#{config => #{file => "./info.log"},level => info}
5> <input>logger:add_handler(myhandler, logger_std_h, Config).</input>
ok</pre>
<p>Since <c>filter_default</c> defaults to <c>log</c>, this
@@ -1246,7 +1246,7 @@ do_log(Fd, LogEvent, #{formatter := {FModule, FConfig}}) ->
<p>A configuration example:</p>
<code type="none">
logger:add_handler(my_standard_h, logger_std_h,
- #{config => #{type => {file,"./system_info.log"},
+ #{config => #{file => "./system_info.log",
sync_mode_qlen => 100,
drop_mode_qlen => 1000,
flush_qlen => 2000}}).
diff --git a/lib/kernel/doc/src/logger_std_h.xml b/lib/kernel/doc/src/logger_std_h.xml
index fcd180abd6..5ed1a2f210 100644
--- a/lib/kernel/doc/src/logger_std_h.xml
+++ b/lib/kernel/doc/src/logger_std_h.xml
@@ -55,30 +55,105 @@
is stored in a sub map with the key <c>config</c>, and can contain the
following parameters:</p>
<taglist>
- <tag><marker id="type"/><c>type</c></tag>
+ <tag><marker id="type"/><c>type = standard_io | standard_error | file</c></tag>
<item>
- <p>This has the value <c>standard_io</c>, <c>standard_error</c>,
- <c>{file,LogFileName}</c>, or <c>{file,LogFileName,LogFileOpts}</c>.</p>
- <p>If <c>LogFileOpts</c> is specified, it replaces the default
- list of options used when opening the log file. The default
- list is <c>[raw,append,delayed_write]</c>. One reason to do
- so can be to change <c>append</c> to, for
- example, <c>write</c>, ensuring that the old log is
- truncated when a node is restarted. See the reference manual
- for <seealso marker="file#open-2"><c>file:open/2</c></seealso>
- for more information about file options.</p>
+ <p>Specifies the log destination.</p>
+ <p>The value is set when the handler is added, and it can not
+ be changed in runtime.</p>
+ <p>Defaults to <c>standard_io</c>, unless
+ parameter <seealso marker="#file"><c>file</c></seealso> is
+ given, in which case it defaults to <c>file</c>.</p>
+ </item>
+ <tag><marker id="file"/><c>file = </c><seealso marker="file#type-filename"><c>file:filename()</c></seealso></tag>
+ <item>
+ <p>This specifies the name of the log file when the handler is
+ of type <c>file</c>.</p>
+ <p>The value is set when the handler is added, and it can not
+ be changed in runtime.</p>
+ <p>Defaults to the same name as the handler identity, in the
+ current directory.</p>
+ </item>
+ <tag><marker id="modes"/><c>modes = [</c><seealso marker="file#type-mode"><c>file:mode()</c></seealso><c>]</c></tag>
+ <item>
+ <p>This specifies the file modes to use when opening the log
+ file,
+ see <seealso marker="file#open-2"><c>file:open/2</c></seealso>.
+ If <c>modes</c> are not specified, the default list used
+ is <c>[raw,append,delayed_write]</c>. If <c>modes</c> are
+ specified, the list replaces the default modes list with the
+ following adjustments:</p>
+ <list>
+ <item>
+ If <c>raw</c> is not found in the list, it is added.
+ </item>
+ <item>
+ If none of <c>write</c>, <c>append</c> or <c>exclusive</c> is
+ found in the list, <c>append</c> is added.</item>
+ <item>If none of <c>delayed_write</c>
+ or <c>{delayed_write,Size,Delay}</c> is found in the
+ list, <c>delayed_write</c> is added.</item>
+ </list>
<p>Log files are always UTF-8 encoded. The encoding can not be
- changed by setting the option <c>{encoding,Encoding}</c>
- in <c>LogFileOpts</c>.</p>
- <p>Notice that the standard handler does not have support for
- circular logging. Use the disk_log handler,
- <seealso marker="logger_disk_log_h"><c>logger_disk_log_h</c></seealso>,
- for this.</p>
+ changed by setting the mode <c>{encoding,Encoding}</c>.</p>
<p>The value is set when the handler is added, and it can not
be changed in runtime.</p>
- <p>Defaults to <c>standard_io</c>.</p>
+ <p>Defaults to <c>[raw,append,delayed_write]</c>.</p>
+ </item>
+ <tag><marker id="max_no_bytes"/><c>max_no_bytes = pos_integer() | infinity</c></tag>
+ <item>
+ <p>This parameter specifies if the log file should be rotated
+ or not. The value <c>infinity</c> means the log file will
+ grow indefinitely, while an integer value specifies at which
+ file size (bytes) the file is rotated.</p>
+ <p>Defaults to <c>infinity</c>.</p>
+ </item>
+ <tag><marker id="max_no_files"/><c>max_no_files = non_neg_integer()</c></tag>
+ <item>
+ <p>This parameter specifies the number of rotated log file
+ archives to keep. This has meaning only
+ if <seealso marker="#max_no_bytes"><c>max_no_bytes</c></seealso>
+ is set to an integer value.</p>
+ <p>The log archives are
+ named <c>FileName.0</c>, <c>FileName.1</c>,
+ ... <c>FileName.N</c>, where <c>FileName</c> is the name of
+ the current log file. <c>FileName.0</c> is the newest of the
+ archives. The maximum value for <c>N</c> is the value
+ of <c>max_no_files</c> minus 1.</p>
+ <p>Notice that setting this value to <c>0</c> does not turn of
+ rotation. It only specifies that no archives are kept.</p>
+ <p>Defaults to <c>0</c>.</p>
+ </item>
+ <tag><marker id="compress_on_rotate"/><c>compress_on_rotate = boolean()</c></tag>
+ <item>
+ <p>This parameter specifies if the rotated log file archives
+ shall be compressed or not. If set to <c>true</c>, all
+ archives are compressed with <c>gzip</c>, and renamed
+ to <c>FileName.N.gz</c></p>
+ <p><c>compress_on_rotate</c> has no meaning if <seealso
+ marker="#max_no_bytes"><c>max_no_bytes</c></seealso> has the
+ value <c>infinity</c>.</p>
+ <p>Defaults to <c>false</c>.</p>
+ </item>
+ <tag><marker id="file_check"/><c>file_check = non_neg_integer()</c></tag>
+ <item>
+ <p>When <c>logger_std_h</c> logs to a file, it reads the file
+ information of the log file prior to each write
+ operation. This is to make sure the file still exists and
+ has the same inode as when it was opened. This implies some
+ performance loss, but ensures that no log events are lost in
+ the case when the file has been removed or renamed by an
+ external actor.</p>
+ <p>In order to allow minimizing the performance loss, the
+ <c>file_check</c> parameter can be set to a positive integer
+ value, <c>N</c>. The handler will then skip reading the file
+ information prior to writing, as long as no more
+ than <c>N</c> milliseconds have passed since it was last
+ read.</p>
+ <p>Notice that the risk of loosing log events grows when
+ the <c>file_check</c> value grows.</p>
+ <p>Defaults to 0.</p>
</item>
- <tag><c>filesync_repeat_interval</c></tag>
+ <tag><c>filesync_repeat_interval = pos_integer() | no_repeat</c></tag>
<item>
<p>This value, in milliseconds, specifies how often the handler does
a file sync operation to write buffered data to disk. The handler attempts
@@ -97,12 +172,13 @@
standard handler and the disk_log handler, and are documented in the
<seealso marker="logger_chapter#overload_protection"><c>User's Guide</c>
</seealso>.</p>
- <p>Notice that if changing the configuration of the handler in runtime,
- the <c>type</c> parameter must not be modified.</p>
+ <p>Notice that if changing the configuration of the handler in
+ runtime, the <c>type</c>, <c>file</c>, or <c>modes</c> parameters
+ must not be modified.</p>
<p>Example of adding a standard handler:</p>
<code type="none">
logger:add_handler(my_standard_h, logger_std_h,
- #{config => #{type => {file,"./system_info.log"},
+ #{config => #{file => "./system_info.log",
filesync_repeat_interval => 1000}}).
</code>
<p>To set the default handler, that starts initially with
@@ -110,7 +186,7 @@ logger:add_handler(my_standard_h, logger_std_h,
change the Kernel default logger configuration. Example:</p>
<code type="none">
erl -kernel logger '[{handler,default,logger_std_h,
- #{config => #{type => {file,"./log.log"}}}}]'
+ #{config => #{file => "./log.log"}}}]'
</code>
<p>An example of how to replace the standard handler with a disk_log handler
at startup is found in the
diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml
index 021ecfa40d..0c187eb19f 100644
--- a/lib/kernel/doc/src/notes.xml
+++ b/lib/kernel/doc/src/notes.xml
@@ -31,6 +31,204 @@
</header>
<p>This document describes the changes made to the Kernel application.</p>
+<section><title>Kernel 6.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ If for example the <c>/etc/hosts</c> did not come into
+ existence until after the kernel application had started,
+ its content was never read. This bug has now been
+ corrected.</p>
+ <p>
+ Own Id: OTP-14702 Aux Id: PR-2066 </p>
+ </item>
+ <item>
+ <p>
+ Fix bug where doing <c>seq_trace:reset_trace()</c> while
+ another process was doing a garbage collection could
+ cause the run-time system to segfault.</p>
+ <p>
+ Own Id: OTP-15490</p>
+ </item>
+ <item>
+ <p>
+ Fix <c>erl_epmd:port_please</c> spec to include
+ <c>atom()</c> and <c>string()</c>.</p>
+ <p>
+ Own Id: OTP-15557 Aux Id: PR-2117 </p>
+ </item>
+ <item>
+ <p>
+ The Logger handler logger_std_h now keeps track of the
+ inode of its log file in order to re-open the file if the
+ inode changes. This may happen, for instance, if the log
+ file is opened and saved by an editor.</p>
+ <p>
+ Own Id: OTP-15578 Aux Id: ERL-850 </p>
+ </item>
+ <item>
+ <p>
+ When user specific file modes are given to the logger
+ handler <c>logger_std_h</c>, they were earlier accepted
+ without any control. This is now changes, so Logger will
+ adjust the file modes as follows:</p>
+ <p>
+ - If <c>raw</c> is not found in the list, it is
+ added.<br/> - If none of <c>write</c>, <c>append</c> or
+ <c>exclusive</c> are found in the list, <c>append</c> is
+ added.<br/> - If none of <c>delayed_write</c> or
+ <c>{delayed_write,Size,Delay}</c> are found in the list,
+ <c>delayed_write</c> is added.</p>
+ <p>
+ Own Id: OTP-15602</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The standard logger handler, <c>logger_std_h</c>, now has
+ a new internal feature for log rotation. The rotation
+ scheme is as follows:</p>
+ <p>
+ The log file to which the handler currently writes always
+ has the same name, i.e. the name which is configured for
+ the handler. The archived files have the same name, but
+ with extension ".N", where N is an integer. The newest
+ archive has extension ".0", and the oldest archive has
+ the highest number. </p>
+ <p>
+ The size at which the log file is rotated, and the number
+ of archive files that are kept, is specified with the
+ handler specific configuration parameters
+ <c>max_no_bytes</c> and <c>max_no_files</c> respectively. </p>
+ <p>
+ Archives can be compressed, in which case they get a
+ ".gz" file extension after the integer. Compression is
+ specified with the handler specific configuration
+ parameter <c>compress_on_rotate</c>.</p>
+ <p>
+ Own Id: OTP-15479</p>
+ </item>
+ <item>
+ <p>
+ The new functions <c>logger:i/0</c> and <c>logger:i/1</c>
+ are added. These provide the same information as
+ <c>logger:get_config/0</c> and other
+ <c>logger:get_*_config</c> functions, but the information
+ is more logically sorted and more readable.</p>
+ <p>
+ Own Id: OTP-15600</p>
+ </item>
+ <item>
+ <p>
+ Logger is now protected against overload due to massive
+ amounts of log events from the emulator or from remote
+ nodes.</p>
+ <p>
+ Own Id: OTP-15601</p>
+ </item>
+ <item>
+ <p>
+ Logger now uses os:system_time/1 instead of
+ erlang:system_time/1 to generate log event timestamps.</p>
+ <p>
+ Own Id: OTP-15625</p>
+ </item>
+ <item>
+ <p>
+ Add functions <c>application:set_env/1,2</c> and
+ <c>application:set_env/2</c>. These take a list of
+ application configuration parameters, and the behaviour
+ is equivalent to calling <c>application:set_env/4</c>
+ individually for each application/key combination, except
+ it is more efficient.</p>
+ <p>
+ <c>set_env/1,2</c> warns about duplicated applications or
+ keys. The warning is also emitted during boot, if
+ applications or keys are duplicated within one
+ configuration file, e.g. sys.config.</p>
+ <p>
+ Own Id: OTP-15642 Aux Id: PR-2164 </p>
+ </item>
+ <item>
+ <p>
+ Handler specific configuration parameters for the
+ standard handler <c>logger_std_h</c> are changed to be
+ more intuitive and more similar to the disk_log handler.</p>
+ <p>
+ Earlier there was only one parameter, <c>type</c>, which
+ could have the values <c>standard_io</c>,
+ <c>standard_error</c>, <c>{file,FileName}</c> or
+ <c>{file,FileName,Modes}</c>.</p>
+ <p>
+ This is now changed, so the following parameters are
+ allowed:</p>
+ <p>
+ <c>type = standard_io | standard_error | file</c><br/>
+ <c>file = file:filename()</c><br/> <c>modes =
+ [file:mode()]</c></p>
+ <p>
+ All parameters are optional. <c>type</c> defaults to
+ <c>standard_io</c>, unless a file name is given, in which
+ case it defaults to <c>file</c>. If <c>type</c> is set to
+ <c>file</c>, the file name defaults to the same as the
+ handler id.</p>
+ <p>
+ The potential incompatibility is that
+ <c>logger:get_config/0</c> and
+ <c>logger:get_handler_config/1</c> now returns the new
+ parameters, even if the configuration was set with the
+ old variant, e.g. <c>#{type=>{file,FileName}}</c>.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15662</p>
+ </item>
+ <item>
+ <p>
+ The new configuration parameter <c>file_check</c> is
+ added to the Logger handler <c>logger_std_h</c>. This
+ parameter specifies how long (in milliseconds) the
+ handler may wait before checking if the log file still
+ exists and the inode is the same as when it was opened.</p>
+ <p>
+ The default value is 0, which means that this check is
+ done prior to each write operation. Setting a higher
+ number may improve performance, but adds the risk of
+ loosing log events.</p>
+ <p>
+ Own Id: OTP-15663</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Kernel 6.2.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Setting the <c>recbuf</c> size of an inet socket the
+ <c>buffer</c> is also automatically increased. Fix a bug
+ where the auto adjustment of inet buffer size would be
+ triggered even if an explicit inet buffer size had
+ already been set.</p>
+ <p>
+ Own Id: OTP-15651 Aux Id: ERIERL-304 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Kernel 6.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/kernel/src/kernel.app.src b/lib/kernel/src/kernel.app.src
index 4b48f6cd1d..8fe6bdd1ca 100644
--- a/lib/kernel/src/kernel.app.src
+++ b/lib/kernel/src/kernel.app.src
@@ -147,6 +147,6 @@
{logger_sasl_compatible, false}
]},
{mod, {kernel, []}},
- {runtime_dependencies, ["erts-10.1", "stdlib-3.5", "sasl-3.0"]}
+ {runtime_dependencies, ["erts-10.2.5", "stdlib-3.5", "sasl-3.0"]}
]
}.
diff --git a/lib/kernel/src/kernel.appup.src b/lib/kernel/src/kernel.appup.src
index ccf0a82ced..8fa3f5c588 100644
--- a/lib/kernel/src/kernel.appup.src
+++ b/lib/kernel/src/kernel.appup.src
@@ -40,7 +40,10 @@
{<<"^6\\.0\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^6\\.1$">>,[restart_new_emulator]},
{<<"^6\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^6\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
+ {<<"^6\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^6\\.2$">>,[restart_new_emulator]},
+ {<<"^6\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^6\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
[{<<"^5\\.3$">>,[restart_new_emulator]},
{<<"^5\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^5\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
@@ -54,4 +57,7 @@
{<<"^6\\.0\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^6\\.1$">>,[restart_new_emulator]},
{<<"^6\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^6\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
+ {<<"^6\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^6\\.2$">>,[restart_new_emulator]},
+ {<<"^6\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^6\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
diff --git a/lib/kernel/src/kernel.erl b/lib/kernel/src/kernel.erl
index c68d04e279..111d103df2 100644
--- a/lib/kernel/src/kernel.erl
+++ b/lib/kernel/src/kernel.erl
@@ -145,26 +145,11 @@ init([]) ->
{ok, {SupFlags,
[Code, File, StdError, User, LoggerSup, Config, RefC, SafeSup]}};
_ ->
- Rpc = #{id => rex,
- start => {rpc, start_link, []},
- restart => permanent,
- shutdown => 2000,
- type => worker,
- modules => [rpc]},
-
- Global = #{id => global_name_server,
- start => {global, start_link, []},
- restart => permanent,
- shutdown => 2000,
- type => worker,
- modules => [global]},
-
- GlGroup = #{id => global_group,
- start => {global_group,start_link,[]},
- restart => permanent,
- shutdown => 2000,
- type => worker,
- modules => [global_group]},
+ DistChildren =
+ case application:get_env(kernel, start_distribution) of
+ {ok, false} -> [];
+ _ -> start_distribution()
+ end,
InetDb = #{id => inet_db,
start => {inet_db, start_link, []},
@@ -173,13 +158,6 @@ init([]) ->
type => worker,
modules => [inet_db]},
- NetSup = #{id => net_sup,
- start => {erl_distribution, start_link, []},
- restart => permanent,
- shutdown => infinity,
- type => supervisor,
- modules => [erl_distribution]},
-
SigSrv = #{id => erl_signal_server,
start => {gen_event, start_link, [{local, erl_signal_server}]},
restart => permanent,
@@ -187,14 +165,11 @@ init([]) ->
type => worker,
modules => dynamic},
- DistAC = start_dist_ac(),
-
Timer = start_timer(),
{ok, {SupFlags,
- [Code, Rpc, Global, InetDb | DistAC] ++
- [NetSup, GlGroup, File, SigSrv,
- StdError, User, Config, RefC, SafeSup, LoggerSup] ++ Timer}}
+ [Code, InetDb | DistChildren] ++
+ [File, SigSrv, StdError, User, Config, RefC, SafeSup, LoggerSup] ++ Timer}}
end;
init(safe) ->
SupFlags = #{strategy => one_for_one,
@@ -213,6 +188,39 @@ init(safe) ->
{ok, {SupFlags, Boot ++ DiskLog ++ Pg2}}.
+start_distribution() ->
+ Rpc = #{id => rex,
+ start => {rpc, start_link, []},
+ restart => permanent,
+ shutdown => 2000,
+ type => worker,
+ modules => [rpc]},
+
+ Global = #{id => global_name_server,
+ start => {global, start_link, []},
+ restart => permanent,
+ shutdown => 2000,
+ type => worker,
+ modules => [global]},
+
+ DistAC = start_dist_ac(),
+
+ NetSup = #{id => net_sup,
+ start => {erl_distribution, start_link, []},
+ restart => permanent,
+ shutdown => infinity,
+ type => supervisor,
+ modules => [erl_distribution]},
+
+ GlGroup = #{id => global_group,
+ start => {global_group,start_link,[]},
+ restart => permanent,
+ shutdown => 2000,
+ type => worker,
+ modules => [global_group]},
+
+ [Rpc, Global | DistAC] ++ [NetSup, GlGroup].
+
start_dist_ac() ->
Spec = [#{id => dist_ac,
start => {dist_ac,start_link,[]},
diff --git a/lib/kernel/src/logger_formatter.erl b/lib/kernel/src/logger_formatter.erl
index ded89bac9f..8696adbd72 100644
--- a/lib/kernel/src/logger_formatter.erl
+++ b/lib/kernel/src/logger_formatter.erl
@@ -64,7 +64,7 @@ format(#{level:=Level,msg:=Msg0,meta:=Meta},Config0)
Config;
Size0 ->
Size =
- case Size0 - string:length([B,A]) of
+ case Size0 - io_lib:chars_length([B,A]) of
S when S>=0 -> S;
_ -> 0
end,
@@ -75,7 +75,11 @@ format(#{level:=Level,msg:=Msg0,meta:=Meta},Config0)
true ->
%% Trim leading and trailing whitespaces, and replace
%% newlines with ", "
- re:replace(string:trim(MsgStr0),",?\r?\n\s*",", ",
+ T = lists:reverse(
+ trim(
+ lists:reverse(
+ trim(MsgStr0,false)),true)),
+ re:replace(T,",?\r?\n\s*",", ",
[{return,list},global,unicode]);
_false ->
MsgStr0
@@ -83,7 +87,26 @@ format(#{level:=Level,msg:=Msg0,meta:=Meta},Config0)
true ->
""
end,
- truncate([B,MsgStr,A],maps:get(max_size,Config)).
+ truncate(B,MsgStr,A,maps:get(max_size,Config)).
+
+trim([H|T],Rev) when H==$\s; H==$\r; H==$\n ->
+ trim(T,Rev);
+trim([H|T],false) when is_list(H) ->
+ case trim(H,false) of
+ [] ->
+ trim(T,false);
+ TrimmedH ->
+ [TrimmedH|T]
+ end;
+trim([H|T],true) when is_list(H) ->
+ case trim(lists:reverse(H),true) of
+ [] ->
+ trim(T,true);
+ TrimmedH ->
+ [lists:reverse(TrimmedH)|T]
+ end;
+trim(String,_) ->
+ String.
do_format(Level,Data,[level|Format],Config) ->
[to_string(level,Level,Config)|do_format(Level,Data,Format,Config)];
@@ -239,21 +262,47 @@ chardata_to_list(Chardata) ->
throw(Error)
end.
-truncate(String,unlimited) ->
- String;
-truncate(String,Size) ->
- Length = string:length(String),
+truncate(B,Msg,A,unlimited) ->
+ [B,Msg,A];
+truncate(B,Msg,A,Size) ->
+ String = [B,Msg,A],
+ Length = io_lib:chars_length(String),
if Length>Size ->
- case lists:reverse(lists:flatten(String)) of
- [$\n|_] ->
- string:slice(String,0,Size-4)++"...\n";
+ {Last,FlatString} =
+ case A of
+ [] ->
+ case Msg of
+ [] ->
+ {get_last(B),lists:flatten(B)};
+ _ ->
+ {get_last(Msg),lists:flatten([B,Msg])}
+ end;
+ _ ->
+ {get_last(A),lists:flatten(String)}
+ end,
+ case Last of
+ $\n->
+ lists:sublist(FlatString,1,Size-4)++"...\n";
_ ->
- string:slice(String,0,Size-3)++"..."
+ lists:sublist(FlatString,1,Size-3)++"..."
end;
true ->
String
end.
+get_last(L) ->
+ get_first(lists:reverse(L)).
+
+get_first([]) ->
+ error;
+get_first([C|_]) when is_integer(C) ->
+ C;
+get_first([L|Rest]) when is_list(L) ->
+ case get_last(L) of
+ error -> get_first(Rest);
+ First -> First
+ end.
+
%% SysTime is the system time in microseconds
format_time(SysTime,#{time_offset:=Offset,time_designator:=Des})
when is_integer(SysTime) ->
diff --git a/lib/kernel/src/logger_h_common.erl b/lib/kernel/src/logger_h_common.erl
index 4b5e0a7dd0..16946ff97c 100644
--- a/lib/kernel/src/logger_h_common.erl
+++ b/lib/kernel/src/logger_h_common.erl
@@ -142,8 +142,9 @@ changing_config(SetOrUpdate,
maps:with(?OLP_KEYS,NewHConfig0)),
case logger_olp:set_opts(Olp,NewOlpOpts) of
ok ->
- maybe_set_repeated_filesync(Olp,OldCommonConfig,
- NewCommonConfig),
+ logger_olp:cast(Olp, {config_changed,
+ NewCommonConfig,
+ NewHandlerConfig}),
ReadOnly = maps:with(?READ_ONLY_KEYS,OldHConfig),
NewHConfig =
maps:merge(
@@ -281,11 +282,24 @@ handle_cast(repeated_filesync,
State#{handler_state => HS, last_op => sync}
end,
{noreply,set_repeated_filesync(State1)};
-
-handle_cast({set_repeated_filesync,FSyncInt},State) ->
- State1 = State#{filesync_repeat_interval=>FSyncInt},
- State2 = set_repeated_filesync(cancel_repeated_filesync(State1)),
- {noreply, State2}.
+handle_cast({config_changed, CommonConfig, HConfig},
+ State = #{id := Name,
+ module := Module,
+ handler_state := HandlerState,
+ filesync_repeat_interval := OldFSyncInt}) ->
+ State1 =
+ case maps:get(filesync_repeat_interval,CommonConfig) of
+ OldFSyncInt ->
+ State;
+ FSyncInt ->
+ set_repeated_filesync(
+ cancel_repeated_filesync(
+ State#{filesync_repeat_interval=>FSyncInt}))
+ end,
+ HS = try Module:config_changed(Name, HConfig, HandlerState)
+ catch error:undef -> HandlerState
+ end,
+ {noreply, State1#{handler_state => HS}}.
handle_info(Info, #{id := Name, module := Module,
handler_state := HandlerState} = State) ->
@@ -447,10 +461,3 @@ cancel_repeated_filesync(State) ->
end.
error_notify(Term) ->
?internal_log(error, Term).
-
-maybe_set_repeated_filesync(_Olp,
- #{filesync_repeat_interval:=FSyncInt},
- #{filesync_repeat_interval:=FSyncInt}) ->
- ok;
-maybe_set_repeated_filesync(Olp,_,#{filesync_repeat_interval:=FSyncInt}) ->
- logger_olp:cast(Olp,{set_repeated_filesync,FSyncInt}).
diff --git a/lib/kernel/src/logger_olp.erl b/lib/kernel/src/logger_olp.erl
index 009280a9c9..8365383fe2 100644
--- a/lib/kernel/src/logger_olp.erl
+++ b/lib/kernel/src/logger_olp.erl
@@ -515,10 +515,11 @@ check_load(State = #{id:=_Name, mode_ref := ModeRef, mode := Mode,
end,
State1 = ?update_other(drops,DROPS,_NewDrops,State),
State2 = ?update_max_qlen(QLen,State1),
- State3 = maybe_notify_mode_change(Mode1,State2),
+ State3 = ?update_max_mem(Mem,State2),
+ State4 = maybe_notify_mode_change(Mode1,State3),
{Mode1, QLen, Mem,
?update_other(flushes,FLUSHES,_NewFlushes,
- State3#{last_qlen => QLen})}.
+ State4#{last_qlen => QLen})}.
limit_burst(#{burst_limit_enable := false}=State) ->
{true,State};
diff --git a/lib/kernel/src/logger_olp.hrl b/lib/kernel/src/logger_olp.hrl
index 9b4f5ebf27..d68b5c048d 100644
--- a/lib/kernel/src/logger_olp.hrl
+++ b/lib/kernel/src/logger_olp.hrl
@@ -114,12 +114,16 @@
flushes => 0, flushed => 0, drops => 0,
burst_drops => 0, casts => 0, calls => 0,
writes => 0, max_qlen => 0, max_time => 0,
- freq => {TIME,0,0}} end).
+ max_mem => 0, freq => {TIME,0,0}} end).
-define(update_max_qlen(QLEN, STATE),
begin #{max_qlen := QLEN0} = STATE,
STATE#{max_qlen => ?max(QLEN0,QLEN)} end).
+ -define(update_max_mem(MEM, STATE),
+ begin #{max_mem := MEM0} = STATE,
+ STATE#{max_mem => ?max(MEM0,MEM)} end).
+
-define(update_calls_or_casts(CALL_OR_CAST, INC, STATE),
case CALL_OR_CAST of
cast ->
@@ -154,6 +158,7 @@
-else. % DEFAULT!
-define(merge_with_stats(STATE), STATE).
-define(update_max_qlen(_QLEN, STATE), STATE).
+ -define(update_max_mem(_MEM, STATE), STATE).
-define(update_calls_or_casts(_CALL_OR_CAST, _INC, STATE), STATE).
-define(update_max_time(_TIME, STATE), STATE).
-define(update_other(_OTHER, _VAR, _INCVAL, STATE), STATE).
diff --git a/lib/kernel/src/logger_std_h.erl b/lib/kernel/src/logger_std_h.erl
index 65f5b3876e..c8f1acfca4 100644
--- a/lib/kernel/src/logger_std_h.erl
+++ b/lib/kernel/src/logger_std_h.erl
@@ -29,7 +29,7 @@
-export([filesync/1]).
%% logger_h_common callbacks
--export([init/2, check_config/4, reset_state/2,
+-export([init/2, check_config/4, config_changed/3, reset_state/2,
filesync/3, write/4, handle_info/3, terminate/3]).
%% logger callbacks
@@ -105,85 +105,169 @@ filter_config(Config) ->
%%%===================================================================
%%% logger_h_common callbacks
%%%===================================================================
-init(Name, #{type := Type}) ->
- case open_log_file(Name, Type) of
+init(Name, Config) ->
+ MyConfig = maps:with([type,file,modes,file_check,max_no_bytes,
+ max_no_files,compress_on_rotate],Config),
+ case file_ctrl_start(Name, MyConfig) of
{ok,FileCtrlPid} ->
- {ok,#{type=>Type,file_ctrl_pid=>FileCtrlPid}};
+ {ok,MyConfig#{file_ctrl_pid=>FileCtrlPid}};
Error ->
Error
end.
-check_config(_Name,set,undefined,NewHConfig) ->
- check_config(maps:merge(get_default_config(),NewHConfig));
-check_config(_Name,SetOrUpdate,OldHConfig,NewHConfig0) ->
- WriteOnce = maps:with([type],OldHConfig),
+check_config(Name,set,undefined,NewHConfig) ->
+ check_h_config(merge_default_config(Name,normalize_config(NewHConfig)));
+check_config(Name,SetOrUpdate,OldHConfig,NewHConfig0) ->
+ WriteOnce = maps:with([type,file,modes],OldHConfig),
Default =
case SetOrUpdate of
set ->
%% Do not reset write-once fields to defaults
- maps:merge(get_default_config(),WriteOnce);
+ merge_default_config(Name,WriteOnce);
update ->
OldHConfig
end,
- NewHConfig = maps:merge(Default, NewHConfig0),
+ NewHConfig = maps:merge(Default, normalize_config(NewHConfig0)),
%% Fail if write-once fields are changed
- case maps:with([type],NewHConfig) of
+ case maps:with([type,file,modes],NewHConfig) of
WriteOnce ->
- check_config(NewHConfig);
+ check_h_config(NewHConfig);
Other ->
{error,{illegal_config_change,?MODULE,WriteOnce,Other}}
end.
-check_config(#{type:=Type}=HConfig) ->
- case check_h_config(maps:to_list(HConfig)) of
- ok when is_atom(Type) ->
- {ok,HConfig#{filesync_repeat_interval=>no_repeat}};
+check_h_config(HConfig) ->
+ case check_h_config(maps:get(type,HConfig),maps:to_list(HConfig)) of
ok ->
- {ok,HConfig};
+ {ok,fix_file_opts(HConfig)};
{error,{Key,Value}} ->
{error,{invalid_config,?MODULE,#{Key=>Value}}}
end.
-check_h_config([{type,Type} | Config]) when Type == standard_io;
- Type == standard_error ->
- check_h_config(Config);
-check_h_config([{type,{file,File}} | Config]) when is_list(File) ->
- check_h_config(Config);
-check_h_config([{type,{file,File,Modes}} | Config]) when is_list(File),
- is_list(Modes) ->
- check_h_config(Config);
-check_h_config([Other | _]) ->
+check_h_config(Type,[{type,Type} | Config]) when Type =:= standard_io;
+ Type =:= standard_error;
+ Type =:= file ->
+ check_h_config(Type,Config);
+check_h_config(file,[{file,File} | Config]) when is_list(File) ->
+ check_h_config(file,Config);
+check_h_config(file,[{modes,Modes} | Config]) when is_list(Modes) ->
+ check_h_config(file,Config);
+check_h_config(file,[{max_no_bytes,Size} | Config])
+ when (is_integer(Size) andalso Size>0) orelse Size=:=infinity ->
+ check_h_config(file,Config);
+check_h_config(file,[{max_no_files,Num} | Config]) when is_integer(Num), Num>=0 ->
+ check_h_config(file,Config);
+check_h_config(file,[{compress_on_rotate,Bool} | Config]) when is_boolean(Bool) ->
+ check_h_config(file,Config);
+check_h_config(file,[{file_check,FileCheck} | Config])
+ when is_integer(FileCheck), FileCheck>=0 ->
+ check_h_config(file,Config);
+check_h_config(_Type,[Other | _]) ->
{error,Other};
-check_h_config([]) ->
+check_h_config(_Type,[]) ->
ok.
-get_default_config() ->
- #{type => standard_io}.
+normalize_config(#{type:={file,File}}=HConfig) ->
+ HConfig#{type=>file,file=>File};
+normalize_config(#{type:={file,File,Modes}}=HConfig) ->
+ HConfig#{type=>file,file=>File,modes=>Modes};
+normalize_config(HConfig) ->
+ HConfig.
+
+merge_default_config(Name,#{type:=Type}=HConfig) ->
+ merge_default_config(Name,Type,HConfig);
+merge_default_config(Name,#{file:=_}=HConfig) ->
+ merge_default_config(Name,file,HConfig);
+merge_default_config(Name,HConfig) ->
+ merge_default_config(Name,standard_io,HConfig).
+
+merge_default_config(Name,Type,HConfig) ->
+ maps:merge(get_default_config(Name,Type),HConfig).
+
+get_default_config(Name,file) ->
+ #{type => file,
+ file => atom_to_list(Name),
+ modes => [raw,append],
+ file_check => 0,
+ max_no_bytes => infinity,
+ max_no_files => 0,
+ compress_on_rotate => false};
+get_default_config(_Name,Type) ->
+ #{type => Type}.
+
+fix_file_opts(#{modes:=Modes}=HConfig) ->
+ HConfig#{modes=>fix_modes(Modes)};
+fix_file_opts(HConfig) ->
+ HConfig#{filesync_repeat_interval=>no_repeat}.
+
+fix_modes(Modes) ->
+ %% Ensure write|append|exclusive
+ Modes1 =
+ case [M || M <- Modes,
+ lists:member(M,[write,append,exclusive])] of
+ [] -> [append|Modes];
+ _ -> Modes
+ end,
+ %% Ensure raw
+ Modes2 =
+ case lists:member(raw,Modes) of
+ false -> [raw|Modes1];
+ true -> Modes1
+ end,
+ %% Ensure delayed_write
+ case lists:partition(fun(delayed_write) -> true;
+ ({delayed_write,_,_}) -> true;
+ (_) -> false
+ end, Modes2) of
+ {[],_} ->
+ [delayed_write|Modes2];
+ _ ->
+ Modes2
+ end.
-filesync(_Name, _Mode, #{type := Type}=State) when is_atom(Type) ->
- {ok,State};
-filesync(_Name, async, #{file_ctrl_pid := FileCtrlPid} = State) ->
- ok = file_ctrl_filesync_async(FileCtrlPid),
- {ok,State};
-filesync(_Name, sync, #{file_ctrl_pid := FileCtrlPid} = State) ->
- Result = file_ctrl_filesync_sync(FileCtrlPid),
+config_changed(_Name,
+ #{file_check:=FileCheck,
+ max_no_bytes:=Size,
+ max_no_files:=Count,
+ compress_on_rotate:=Compress},
+ #{file_check:=FileCheck,
+ max_no_bytes:=Size,
+ max_no_files:=Count,
+ compress_on_rotate:=Compress}=State) ->
+ State;
+config_changed(_Name,
+ #{file_check:=FileCheck,
+ max_no_bytes:=Size,
+ max_no_files:=Count,
+ compress_on_rotate:=Compress},
+ #{file_ctrl_pid := FileCtrlPid} = State) ->
+ FileCtrlPid ! {update_config,#{file_check=>FileCheck,
+ max_no_bytes=>Size,
+ max_no_files=>Count,
+ compress_on_rotate=>Compress}},
+ State#{file_check:=FileCheck,
+ max_no_bytes:=Size,
+ max_no_files:=Count,
+ compress_on_rotate:=Compress};
+config_changed(_Name,_NewHConfig,State) ->
+ State.
+
+filesync(_Name, SyncAsync, #{file_ctrl_pid := FileCtrlPid} = State) ->
+ Result = file_ctrl_filesync(SyncAsync, FileCtrlPid),
{Result,State}.
-write(_Name, async, Bin, #{file_ctrl_pid:=FileCtrlPid} = State) ->
- ok = file_write_async(FileCtrlPid, Bin),
- {ok,State};
-write(_Name, sync, Bin, #{file_ctrl_pid:=FileCtrlPid} = State) ->
- Result = file_write_sync(FileCtrlPid, Bin),
+write(_Name, SyncAsync, Bin, #{file_ctrl_pid:=FileCtrlPid} = State) ->
+ Result = file_write(SyncAsync, FileCtrlPid, Bin),
{Result,State}.
reset_state(_Name, State) ->
State.
-handle_info(_Name, {'EXIT',Pid,Why}, #{type := FileInfo, file_ctrl_pid := Pid}) ->
+handle_info(_Name, {'EXIT',Pid,Why}, #{file_ctrl_pid := Pid}=State) ->
%% file_ctrl_pid died, file error, terminate handler
- exit({error,{write_failed,FileInfo,Why}});
+ exit({error,{write_failed,maps:with([type,file,modes],State),Why}});
handle_info(_, _, State) ->
State.
@@ -211,27 +295,33 @@ terminate(_Name, _Reason, #{file_ctrl_pid:=FWPid}) ->
%%%-----------------------------------------------------------------
%%%
-open_log_file(HandlerName, FileInfo) ->
- case file_ctrl_start(HandlerName, FileInfo) of
- OK = {ok,_FileCtrlPid} -> OK;
- Error -> Error
- end.
-
-do_open_log_file({file,FileName}) ->
- do_open_log_file({file,FileName,[raw,append,delayed_write]});
-
-do_open_log_file({file,FileName,[]}) ->
- do_open_log_file({file,FileName,[raw,append,delayed_write]});
-
-do_open_log_file({file,FileName,Modes}) ->
+open_log_file(HandlerName,#{type:=file,
+ file:=FileName,
+ modes:=Modes,
+ file_check:=FileCheck,
+ max_no_bytes:=Size,
+ max_no_files:=Count,
+ compress_on_rotate:=Compress}) ->
try
case filelib:ensure_dir(FileName) of
ok ->
case file:open(FileName, Modes) of
{ok, Fd} ->
{ok,#file_info{inode=INode}} =
- file:read_file_info(FileName),
- {ok, {Fd, INode}};
+ file:read_file_info(FileName,[raw]),
+ UpdateModes = [append | Modes--[write,append,exclusive]],
+ State0 = #{handler_name=>HandlerName,
+ file_name=>FileName,
+ modes=>UpdateModes,
+ file_check=>FileCheck,
+ fd=>Fd,
+ inode=>INode,
+ last_check=>timestamp(),
+ synced=>false,
+ write_res=>ok,
+ sync_res=>ok},
+ State = update_rotation({Size,Count,Compress},State0),
+ {ok,State};
Error ->
Error
end;
@@ -242,21 +332,23 @@ do_open_log_file({file,FileName,Modes}) ->
_:Reason -> {error,Reason}
end.
-close_log_file(Std) when Std == standard_io; Std == standard_error ->
- ok;
-close_log_file({Fd,_}) ->
+close_log_file(#{fd:=Fd}) ->
_ = file:datasync(Fd),
- _ = file:close(Fd).
+ _ = file:close(Fd),
+ ok;
+close_log_file(_) ->
+ ok.
+
%%%-----------------------------------------------------------------
%%% File control process
-file_ctrl_start(HandlerName, FileInfo) ->
+file_ctrl_start(HandlerName, HConfig) ->
Starter = self(),
FileCtrlPid =
spawn_link(fun() ->
- file_ctrl_init(HandlerName, FileInfo, Starter)
+ file_ctrl_init(HandlerName, HConfig, Starter)
end),
receive
{FileCtrlPid,ok} ->
@@ -271,18 +363,16 @@ file_ctrl_start(HandlerName, FileInfo) ->
file_ctrl_stop(Pid) ->
Pid ! stop.
-file_write_async(Pid, Bin) ->
+file_write(async, Pid, Bin) ->
Pid ! {log,Bin},
- ok.
-
-file_write_sync(Pid, Bin) ->
+ ok;
+file_write(sync, Pid, Bin) ->
file_ctrl_call(Pid, {log,Bin}).
-file_ctrl_filesync_async(Pid) ->
+file_ctrl_filesync(async, Pid) ->
Pid ! filesync,
- ok.
-
-file_ctrl_filesync_sync(Pid) ->
+ ok;
+file_ctrl_filesync(sync, Pid) ->
file_ctrl_call(Pid, filesync).
file_ctrl_call(Pid, Msg) ->
@@ -299,98 +389,255 @@ file_ctrl_call(Pid, Msg) ->
{error,{no_response,Pid}}
end.
-file_ctrl_init(HandlerName, FileInfo, Starter) when is_tuple(FileInfo) ->
+file_ctrl_init(HandlerName,
+ #{type:=file,
+ file:=FileName} = HConfig,
+ Starter) ->
process_flag(message_queue_data, off_heap),
- FileName = element(2, FileInfo),
- case do_open_log_file(FileInfo) of
- {ok,File} ->
+ case open_log_file(HandlerName,HConfig) of
+ {ok,State} ->
Starter ! {self(),ok},
- file_ctrl_loop(File, FileName, false, ok, ok, HandlerName);
+ file_ctrl_loop(State);
{error,Reason} ->
Starter ! {self(),{error,{open_failed,FileName,Reason}}}
end;
-file_ctrl_init(HandlerName, StdDev, Starter) ->
+file_ctrl_init(HandlerName, #{type:=StdDev}, Starter) ->
Starter ! {self(),ok},
- file_ctrl_loop(StdDev, StdDev, false, ok, ok, HandlerName).
+ file_ctrl_loop(#{handler_name=>HandlerName,dev=>StdDev}).
-file_ctrl_loop(File, DevName, Synced,
- PrevWriteResult, PrevSyncResult, HandlerName) ->
+file_ctrl_loop(State) ->
receive
%% asynchronous event
{log,Bin} ->
- File1 = ensure(File, DevName),
- Result = write_to_dev(File1, Bin, DevName,
- PrevWriteResult, HandlerName),
- file_ctrl_loop(File1, DevName, false,
- Result, PrevSyncResult, HandlerName);
+ State1 = write_to_dev(Bin,State),
+ file_ctrl_loop(State1);
%% synchronous event
{{log,Bin},{From,MRef}} ->
- File1 = ensure(File, DevName),
- Result = write_to_dev(File1, Bin, DevName,
- PrevWriteResult, HandlerName),
+ State1 = ensure_file(State),
+ State2 = write_to_dev(Bin,State1),
From ! {MRef,ok},
- file_ctrl_loop(File1, DevName, false,
- Result, PrevSyncResult, HandlerName);
+ file_ctrl_loop(State2);
filesync ->
- File1 = ensure(File, DevName),
- Result = sync_dev(File1, DevName, Synced,
- PrevSyncResult, HandlerName),
- file_ctrl_loop(File1, DevName, true,
- PrevWriteResult, Result, HandlerName);
+ State1 = sync_dev(State),
+ file_ctrl_loop(State1);
{filesync,{From,MRef}} ->
- File1 = ensure(File, DevName),
- Result = sync_dev(File1, DevName, Synced,
- PrevSyncResult, HandlerName),
+ State1 = ensure_file(State),
+ State2 = sync_dev(State1),
From ! {MRef,ok},
- file_ctrl_loop(File1, DevName, true,
- PrevWriteResult, Result, HandlerName);
+ file_ctrl_loop(State2);
+
+ {update_config,#{file_check:=FileCheck,
+ max_no_bytes:=Size,
+ max_no_files:=Count,
+ compress_on_rotate:=Compress}} ->
+ State1 = update_rotation({Size,Count,Compress},State),
+ file_ctrl_loop(State1#{file_check=>FileCheck});
stop ->
- _ = close_log_file(File),
+ close_log_file(State),
stopped
end.
+maybe_ensure_file(#{file_check:=0}=State) ->
+ ensure_file(State);
+maybe_ensure_file(#{last_check:=T0,file_check:=CheckInt}=State)
+ when is_integer(CheckInt) ->
+ T = timestamp(),
+ if T-T0 > CheckInt -> ensure_file(State);
+ true -> State
+ end;
+maybe_ensure_file(State) ->
+ State.
+
%% In order to play well with tools like logrotate, we need to be able
%% to re-create the file if it has disappeared (e.g. if rotated by
%% logrotate)
-ensure(Fd,DevName) when is_atom(DevName) ->
- Fd;
-ensure({Fd,INode},FileName) ->
- case file:read_file_info(FileName) of
- {ok,#file_info{inode=INode}} ->
- {Fd,INode};
+ensure_file(#{fd:=Fd0,inode:=INode0,file_name:=FileName,modes:=Modes}=State) ->
+ case file:read_file_info(FileName,[raw]) of
+ {ok,#file_info{inode=INode0}} ->
+ State#{last_check=>timestamp()};
_ ->
- _ = file:close(Fd),
- _ = file:close(Fd), % delayed_write cause close not to close
- case do_open_log_file({file,FileName}) of
- {ok,File} ->
- File;
+ close_log_file(Fd0),
+ case file:open(FileName,Modes) of
+ {ok,Fd} ->
+ {ok,#file_info{inode=INode}} =
+ file:read_file_info(FileName,[raw]),
+ State#{fd=>Fd,inode=>INode,
+ last_check=>timestamp(),
+ synced=>true,sync_res=>ok};
Error ->
exit({could_not_reopen_file,Error})
end
- end.
+ end;
+ensure_file(State) ->
+ State.
-write_to_dev(DevName, Bin, _DevName, _PrevWriteResult, _HandlerName)
- when is_atom(DevName) ->
- io:put_chars(DevName, Bin);
-write_to_dev({Fd,_}, Bin, FileName, PrevWriteResult, HandlerName) ->
+write_to_dev(Bin,#{dev:=DevName}=State) ->
+ io:put_chars(DevName, Bin),
+ State;
+write_to_dev(Bin, State) ->
+ State1 = #{fd:=Fd} = maybe_ensure_file(State),
Result = ?file_write(Fd, Bin),
- maybe_notify_error(write,Result,PrevWriteResult,FileName,HandlerName).
+ State2 = maybe_rotate_file(Bin,State1),
+ maybe_notify_error(write,Result,State2),
+ State2#{synced=>false,write_res=>Result}.
-sync_dev(_, _FileName, true, PrevSyncResult, _HandlerName) ->
- PrevSyncResult;
-sync_dev({Fd,_}, FileName, false, PrevSyncResult, HandlerName) ->
+sync_dev(#{synced:=false}=State) ->
+ State1 = #{fd:=Fd} = maybe_ensure_file(State),
Result = ?file_datasync(Fd),
- maybe_notify_error(filesync,Result,PrevSyncResult,FileName,HandlerName).
+ maybe_notify_error(filesync,Result,State1),
+ State1#{synced=>true,sync_res=>Result};
+sync_dev(State) ->
+ State.
-maybe_notify_error(_Op, ok, _PrevResult, _FileName, _HandlerName) ->
+update_rotation({infinity,_,_},State) ->
+ maybe_remove_archives(0,State),
+ maps:remove(rotation,State);
+update_rotation({Size,Count,Compress},#{file_name:=FileName} = State) ->
+ maybe_remove_archives(Count,State),
+ {ok,#file_info{size=CurrSize}} = file:read_file_info(FileName,[raw]),
+ State1 = State#{rotation=>#{size=>Size,
+ count=>Count,
+ compress=>Compress,
+ curr_size=>CurrSize}},
+ maybe_update_compress(0,State1),
+ maybe_rotate_file(0,State1).
+
+maybe_remove_archives(Count,#{file_name:=FileName}=State) ->
+ Archive = rot_file_name(FileName,Count,false),
+ CompressedArchive = rot_file_name(FileName,Count,true),
+ case {file:read_file_info(Archive,[raw]),
+ file:read_file_info(CompressedArchive,[raw])} of
+ {{error,enoent},{error,enoent}} ->
+ ok;
+ _ ->
+ _ = file:delete(Archive),
+ _ = file:delete(CompressedArchive),
+ maybe_remove_archives(Count+1,State)
+ end.
+
+maybe_update_compress(Count,#{rotation:=#{count:=Count}}) ->
+ ok;
+maybe_update_compress(N,#{file_name:=FileName,
+ rotation:=#{compress:=Compress}}=State) ->
+ Archive = rot_file_name(FileName,N,not Compress),
+ case file:read_file_info(Archive,[raw]) of
+ {ok,_} when Compress ->
+ compress_file(Archive);
+ {ok,_} ->
+ decompress_file(Archive);
+ _ ->
+ ok
+ end,
+ maybe_update_compress(N+1,State).
+
+maybe_rotate_file(Bin,#{rotation:=_}=State) when is_binary(Bin) ->
+ maybe_rotate_file(byte_size(Bin),State);
+maybe_rotate_file(AddSize,#{rotation:=#{size:=RotSize,
+ curr_size:=CurrSize}=Rotation}=State) ->
+ NewSize = CurrSize + AddSize,
+ if NewSize>RotSize ->
+ rotate_file(State#{rotation=>Rotation#{curr_size=>NewSize}});
+ true ->
+ State#{rotation=>Rotation#{curr_size=>NewSize}}
+ end;
+maybe_rotate_file(_Bin,State) ->
+ State.
+
+rotate_file(#{fd:=Fd0,file_name:=FileName,modes:=Modes,rotation:=Rotation}=State) ->
+ State1 = sync_dev(State),
+ _ = file:close(Fd0),
+ _ = file:close(Fd0),
+ rotate_files(FileName,maps:get(count,Rotation),maps:get(compress,Rotation)),
+ case file:open(FileName,Modes) of
+ {ok,Fd} ->
+ {ok,#file_info{inode=INode}} = file:read_file_info(FileName,[raw]),
+ State1#{fd=>Fd,inode=>INode,rotation=>Rotation#{curr_size=>0}};
+ Error ->
+ exit({could_not_reopen_file,Error})
+ end.
+
+rotate_files(FileName,0,_Compress) ->
+ _ = file:delete(FileName),
+ ok;
+rotate_files(FileName,1,Compress) ->
+ FileName0 = FileName++".0",
+ _ = file:rename(FileName,FileName0),
+ if Compress -> compress_file(FileName0);
+ true -> ok
+ end,
ok;
-maybe_notify_error(_Op, PrevResult, PrevResult, _FileName, _HandlerName) ->
+rotate_files(FileName,Count,Compress) ->
+ _ = file:rename(rot_file_name(FileName,Count-2,Compress),
+ rot_file_name(FileName,Count-1,Compress)),
+ rotate_files(FileName,Count-1,Compress).
+
+rot_file_name(FileName,Count,false) ->
+ FileName ++ "." ++ integer_to_list(Count);
+rot_file_name(FileName,Count,true) ->
+ rot_file_name(FileName,Count,false) ++ ".gz".
+
+compress_file(FileName) ->
+ {ok,In} = file:open(FileName,[read,binary]),
+ {ok,Out} = file:open(FileName++".gz",[write]),
+ Z = zlib:open(),
+ zlib:deflateInit(Z, default, deflated, 31, 8, default),
+ compress_data(Z,In,Out),
+ zlib:deflateEnd(Z),
+ zlib:close(Z),
+ _ = file:close(In),
+ _ = file:close(Out),
+ _ = file:delete(FileName),
+ ok.
+
+compress_data(Z,In,Out) ->
+ case file:read(In,100000) of
+ {ok,Data} ->
+ Compressed = zlib:deflate(Z, Data),
+ _ = file:write(Out,Compressed),
+ compress_data(Z,In,Out);
+ eof ->
+ Compressed = zlib:deflate(Z, <<>>, finish),
+ _ = file:write(Out,Compressed),
+ ok
+ end.
+
+decompress_file(FileName) ->
+ {ok,In} = file:open(FileName,[read,binary]),
+ {ok,Out} = file:open(filename:rootname(FileName,".gz"),[write]),
+ Z = zlib:open(),
+ zlib:inflateInit(Z, 31),
+ decompress_data(Z,In,Out),
+ zlib:inflateEnd(Z),
+ zlib:close(Z),
+ _ = file:close(In),
+ _ = file:close(Out),
+ _ = file:delete(FileName),
+ ok.
+
+decompress_data(Z,In,Out) ->
+ case file:read(In,1000) of
+ {ok,Data} ->
+ Decompressed = zlib:inflate(Z, Data),
+ _ = file:write(Out,Decompressed),
+ decompress_data(Z,In,Out);
+ eof ->
+ ok
+ end.
+
+maybe_notify_error(_Op, ok, _State) ->
+ ok;
+maybe_notify_error(Op, Result, #{write_res:=WR,sync_res:=SR})
+ when (Op==write andalso Result==WR) orelse
+ (Op==filesync andalso Result==SR) ->
%% don't report same error twice
- PrevResult;
-maybe_notify_error(Op, Error, _PrevResult, FileName, HandlerName) ->
+ ok;
+maybe_notify_error(Op, Error, #{handler_name:=HandlerName,file_name:=FileName}) ->
logger_h_common:error_notify({HandlerName,Op,FileName,Error}),
- Error.
+ ok.
+
+timestamp() ->
+ erlang:monotonic_time(millisecond).
diff --git a/lib/kernel/test/kernel.spec b/lib/kernel/test/kernel.spec
index 62afc9f97b..eaa17f3a59 100644
--- a/lib/kernel/test/kernel.spec
+++ b/lib/kernel/test/kernel.spec
@@ -2,3 +2,4 @@
{config, "../test_server/ts.unix.config"}.
{suites,"../kernel_test", all}.
+{skip_suites,"../kernel_test",[logger_stress_SUITE],"Benchmarks only"}.
diff --git a/lib/kernel/test/kernel_config_SUITE.erl b/lib/kernel/test/kernel_config_SUITE.erl
index 9207025a2c..57c44c2498 100644
--- a/lib/kernel/test/kernel_config_SUITE.erl
+++ b/lib/kernel/test/kernel_config_SUITE.erl
@@ -21,7 +21,8 @@
-include_lib("common_test/include/ct.hrl").
--export([all/0, suite/0,groups/0,init_per_group/2,end_per_group/2, sync/1]).
+-export([all/0, suite/0,groups/0,init_per_group/2,end_per_group/2,
+ start_distribution_false/1, sync/1]).
-export([init_per_suite/1, end_per_suite/1]).
@@ -30,7 +31,7 @@ suite() ->
{timetrap,{minutes,2}}].
all() ->
- [sync].
+ [sync, start_distribution_false].
groups() ->
[].
@@ -59,12 +60,9 @@ from(H, [H | T]) -> T;
from(H, [_ | T]) -> from(H, T);
from(_, []) -> [].
-%%-----------------------------------------------------------------
-%% Test suite for sync_nodes. This is quite tricky.
-%%
+%% Test sync_nodes. This is quite tricky.
%% Should be started in a CC view with:
%% erl -sname XXX where XX not in [cp1, cp2]
-%%-----------------------------------------------------------------
sync(Conf) when is_list(Conf) ->
%% Write a config file
Dir = proplists:get_value(priv_dir,Conf),
@@ -106,9 +104,18 @@ wait_for_node(Node) ->
_Other -> wait_for_node(Node)
end.
-
stop_node(Node) ->
M = list_to_atom(lists:concat([Node,
[$@],
from($@,atom_to_list(node()))])),
rpc:cast(M, erlang, halt, []).
+
+start_distribution_false(Config) when is_list(Config) ->
+ %% When distribution is disabled, -sname/-name has no effect
+ Str = os:cmd(ct:get_progname()
+ ++ " -kernel start_distribution false"
+ ++ " -sname no_distribution"
+ ++ " -eval \"erlang:display(node())\""
+ ++ " -noshell -s erlang halt"),
+ "'nonode@nohost'" ++ _ = Str,
+ ok.
diff --git a/lib/kernel/test/logger_SUITE.erl b/lib/kernel/test/logger_SUITE.erl
index 70bb775db8..035e5d8974 100644
--- a/lib/kernel/test/logger_SUITE.erl
+++ b/lib/kernel/test/logger_SUITE.erl
@@ -1048,8 +1048,11 @@ kernel_config(Config) ->
ok = rpc:call(Node,logger,internal_init_logger,[]),
ok = rpc:call(Node,logger,add_handlers,[kernel]),
#{primary:=#{filter_default:=log,filters:=[]},
- handlers:=[#{id:=default,filters:=DF,config:=#{type:={file,F}}}],
+ handlers:=[#{id:=default,filters:=DF,
+ config:=#{type:=file,file:=F,modes:=Modes}}],
module_levels:=[]} = rpc:call(Node,logger,get_config,[]),
+ [append,delayed_write,raw] = lists:sort(Modes),
+
%% Same, but using 'logger' parameter instead of 'error_logger'
ok = rpc:call(Node,logger,remove_handler,[default]),% so it can be added again
@@ -1060,26 +1063,27 @@ kernel_config(Config) ->
ok = rpc:call(Node,logger,internal_init_logger,[]),
ok = rpc:call(Node,logger,add_handlers,[kernel]),
#{primary:=#{filter_default:=log,filters:=[]},
- handlers:=[#{id:=default,filters:=DF,config:=#{type:={file,F}}}],
+ handlers:=[#{id:=default,filters:=DF,
+ config:=#{type:=file,file:=F,modes:=Modes}}],
module_levels:=[]} = rpc:call(Node,logger,get_config,[]),
%% Same, but with type={file,File,Modes}
ok = rpc:call(Node,logger,remove_handler,[default]),% so it can be added again
ok = rpc:call(Node,application,unset_env,[kernel,error_logger]),
- M = [raw,write,delayed_write],
+ M = [raw,write],
ok = rpc:call(Node,application,set_env,[kernel,logger,
[{handler,default,logger_std_h,
#{config=>#{type=>{file,F,M}}}}]]),
ok = rpc:call(Node,logger,internal_init_logger,[]),
ok = rpc:call(Node,logger,add_handlers,[kernel]),
#{primary:=#{filter_default:=log,filters:=[]},
- handlers:=[#{id:=default,filters:=DF,config:=#{type:={file,F,M}}}],
+ handlers:=[#{id:=default,filters:=DF,
+ config:=#{type:=file,file:=F,modes:=[delayed_write|M]}}],
module_levels:=[]} = rpc:call(Node,logger,get_config,[]),
%% Same, but with disk_log handler
ok = rpc:call(Node,logger,remove_handler,[default]),% so it can be added again
ok = rpc:call(Node,application,unset_env,[kernel,error_logger]),
- M = [raw,write,delayed_write],
ok = rpc:call(Node,application,set_env,[kernel,logger,
[{handler,default,logger_disk_log_h,
#{config=>#{file=>F}}}]]),
diff --git a/lib/kernel/test/logger_std_h_SUITE.erl b/lib/kernel/test/logger_std_h_SUITE.erl
index b2c2c8ba67..0c5516f82b 100644
--- a/lib/kernel/test/logger_std_h_SUITE.erl
+++ b/lib/kernel/test/logger_std_h_SUITE.erl
@@ -112,6 +112,8 @@ all() ->
add_remove_instance_standard_error,
add_remove_instance_file1,
add_remove_instance_file2,
+ add_remove_instance_file3,
+ add_remove_instance_file4,
default_formatter,
filter_config,
errors,
@@ -142,7 +144,12 @@ all() ->
restart_after,
handler_requests_under_load,
recreate_deleted_log,
- reopen_changed_log
+ reopen_changed_log,
+ rotate_size,
+ rotate_size_compressed,
+ rotate_size_reopen,
+ rotation_opts,
+ rotation_opts_restart_handler
].
add_remove_instance_tty(_Config) ->
@@ -179,10 +186,27 @@ add_remove_instance_file2(Config) ->
add_remove_instance_file2(cleanup,_Config) ->
logger_std_h_remove().
-add_remove_instance_file(Log, Type) ->
+add_remove_instance_file3(_Config) ->
+ Log = atom_to_list(?MODULE),
+ StdHConfig = #{type=>file},
+ add_remove_instance_file(Log, StdHConfig).
+add_remove_instance_file3(cleanup,_Config) ->
+ logger_std_h_remove().
+
+add_remove_instance_file4(Config) ->
+ Dir = ?config(priv_dir,Config),
+ Log = filename:join(Dir,"stdlog4.txt"),
+ StdHConfig = #{file=>Log,modes=>[]},
+ add_remove_instance_file(Log, StdHConfig).
+add_remove_instance_file4(cleanup,_Config) ->
+ logger_std_h_remove().
+
+add_remove_instance_file(Log, Type) when not is_map(Type) ->
+ add_remove_instance_file(Log,#{type=>Type});
+add_remove_instance_file(Log, StdHConfig) when is_map(StdHConfig) ->
ok = logger:add_handler(?MODULE,
logger_std_h,
- #{config => #{type => Type},
+ #{config => StdHConfig,
filter_default=>stop,
filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]),
formatter=>{?MODULE,self()}}),
@@ -257,9 +281,10 @@ errors(Config) ->
end,
{error,
- {handler_not_added,{open_failed,Log,_}}} =
+ {handler_not_added,
+ {invalid_config,logger_std_h,#{modes:=bad_file_opt}}}} =
logger:add_handler(myh3,logger_std_h,
- #{config=>#{type=>{file,Log,[bad_file_opt]}}}),
+ #{config=>#{type=>{file,Log,bad_file_opt}}}),
ok = logger:notice(?msg).
@@ -607,24 +632,51 @@ reconfig(cleanup, _Config) ->
file_opts(Config) ->
Dir = ?config(priv_dir,Config),
Log = filename:join(Dir, lists:concat([?FUNCTION_NAME,".log"])),
- BadFileOpts = [raw],
- BadType = {file,Log,BadFileOpts},
- {error,{handler_not_added,{open_failed,Log,enoent}}} =
- logger:add_handler(?MODULE, logger_std_h,
- #{config => #{type => BadType}}),
+ MissingOpts = [raw],
+ Type1 = {file,Log,MissingOpts},
+ ok = logger:add_handler(?MODULE, logger_std_h,
+ #{config => #{type => Type1}}),
+ {ok,#{config:=#{type:=file,file:=Log,modes:=Modes1}}} =
+ logger:get_handler_config(?MODULE),
+ [append,delayed_write,raw] = lists:sort(Modes1),
+ ok = logger:remove_handler(?MODULE),
OkFileOpts = [raw,append],
OkType = {file,Log,OkFileOpts},
ok = logger:add_handler(?MODULE,
logger_std_h,
- #{config => #{type => OkType},
+ #{config => #{type => OkType}, % old format
filter_default=>log,
filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]),
formatter=>{?MODULE,self()}}),
- #{cb_state := #{handler_state := #{type := OkType}}} =
+ ModOpts = [delayed_write|OkFileOpts],
+ #{cb_state := #{handler_state := #{type:=file,
+ file:=Log,
+ modes:=ModOpts}}} =
logger_olp:info(h_proc_name()),
- {ok,#{config := #{type := OkType}}} = logger:get_handler_config(?MODULE),
+ {ok,#{config := #{type:=file,
+ file:=Log,
+ modes:=ModOpts}}} = logger:get_handler_config(?MODULE),
+ ok = logger:remove_handler(?MODULE),
+
+ ok = logger:add_handler(?MODULE,
+ logger_std_h,
+ #{config => #{type => file,
+ file => Log,
+ modes => OkFileOpts}, % new format
+ filter_default=>log,
+ filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]),
+ formatter=>{?MODULE,self()}}),
+
+ #{cb_state := #{handler_state := #{type:=file,
+ file:=Log,
+ modes:=ModOpts}}} =
+ logger_olp:info(h_proc_name()),
+ {ok,#{config := #{type:=file,
+ file:=Log,
+ modes:=ModOpts}}} =
+ logger:get_handler_config(?MODULE),
logger:notice(M1=?msg,?domain),
?check(M1),
B1 = ?bin(M1),
@@ -640,13 +692,14 @@ sync(Config) ->
Type = {file,Log},
ok = logger:add_handler(?MODULE,
logger_std_h,
- #{config => #{type => Type},
+ #{config => #{type => Type,
+ file_check => 10000},
filter_default=>log,
filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]),
formatter=>{?MODULE,nl}}),
%% check repeated filesync happens
- start_tracer([{logger_std_h, write_to_dev, 5},
+ start_tracer([{logger_std_h, write_to_dev, 2},
{file, datasync, 1}],
[{logger_std_h, write_to_dev, <<"first\n">>},
{file,datasync}]),
@@ -656,7 +709,7 @@ sync(Config) ->
check_tracer(filesync_rep_int()*2),
%% check that explicit filesync is only done once
- start_tracer([{logger_std_h, write_to_dev, 5},
+ start_tracer([{logger_std_h, write_to_dev, 2},
{file, datasync, 1}],
[{logger_std_h, write_to_dev, <<"second\n">>},
{file,datasync},
@@ -675,7 +728,7 @@ sync(Config) ->
#{filesync_repeat_interval => no_repeat}),
no_repeat = maps:get(filesync_repeat_interval,
maps:get(cb_state, logger_olp:info(h_proc_name()))),
- start_tracer([{logger_std_h, write_to_dev, 5},
+ start_tracer([{logger_std_h, write_to_dev, 2},
{file, datasync, 1}],
[{logger_std_h, write_to_dev, <<"third\n">>},
{file,datasync},
@@ -1285,6 +1338,331 @@ reopen_changed_log(Config) ->
reopen_changed_log(cleanup, _Config) ->
ok = stop_handler(?MODULE).
+rotate_size(Config) ->
+ {Log,_HConfig,_StdHConfig} =
+ start_handler(?MODULE, ?FUNCTION_NAME, Config),
+ ok = logger:update_handler_config(?MODULE,#{config=>#{max_no_bytes=>1000,
+ max_no_files=>2}}),
+
+ Str = lists:duplicate(19,$a),
+ [logger:notice(Str,?domain) || _ <- lists:seq(1,50)],
+ logger_std_h:filesync(?MODULE),
+ {ok,#file_info{size=1000}} = file:read_file_info(Log),
+ {error,enoent} = file:read_file_info(Log++".0"),
+
+ logger:notice(Str,?domain),
+ logger_std_h:filesync(?MODULE),
+ {ok,#file_info{size=0}} = file:read_file_info(Log),
+ {ok,#file_info{size=1020}} = file:read_file_info(Log++".0"),
+ {error,enoent} = file:read_file_info(Log++".1"),
+
+ [logger:notice(Str,?domain) || _ <- lists:seq(1,51)],
+ logger_std_h:filesync(?MODULE),
+ {ok,#file_info{size=0}} = file:read_file_info(Log),
+ {ok,#file_info{size=1020}} = file:read_file_info(Log++".0"),
+ {ok,#file_info{size=1020}} = file:read_file_info(Log++".1"),
+ {error,enoent} = file:read_file_info(Log++".2"),
+
+ [logger:notice(Str,?domain) || _ <- lists:seq(1,50)],
+ logger_std_h:filesync(?MODULE),
+ {ok,#file_info{size=1000}} = file:read_file_info(Log),
+ {ok,#file_info{size=1020}} = file:read_file_info(Log++".0"),
+ {ok,#file_info{size=1020}} = file:read_file_info(Log++".1"),
+ {error,enoent} = file:read_file_info(Log++".2"),
+
+ logger:notice("bbbb",?domain),
+ logger_std_h:filesync(?MODULE),
+ {ok,#file_info{size=0}} = file:read_file_info(Log),
+ {ok,#file_info{size=1005}} = file:read_file_info(Log++".0"),
+ {ok,#file_info{size=1020}} = file:read_file_info(Log++".1"),
+ {error,enoent} = file:read_file_info(Log++".2"),
+
+ ok.
+rotate_size(cleanup,_Config) ->
+ ok = stop_handler(?MODULE).
+
+rotate_size_compressed(Config) ->
+ {Log,_HConfig,_StdHConfig} =
+ start_handler(?MODULE, ?FUNCTION_NAME, Config),
+ ok = logger:update_handler_config(?MODULE,
+ #{config=>#{max_no_bytes=>1000,
+ max_no_files=>2,
+ compress_on_rotate=>true}}),
+ Str = lists:duplicate(19,$a),
+ [logger:notice(Str,?domain) || _ <- lists:seq(1,50)],
+ logger_std_h:filesync(?MODULE),
+ {ok,#file_info{size=1000}} = file:read_file_info(Log),
+ {error,enoent} = file:read_file_info(Log++".0"),
+ {error,enoent} = file:read_file_info(Log++".0.gz"),
+
+ logger:notice(Str,?domain),
+ logger_std_h:filesync(?MODULE),
+ {ok,#file_info{size=0}} = file:read_file_info(Log),
+ {error,enoent} = file:read_file_info(Log++".0"),
+ {ok,#file_info{size=35}} = file:read_file_info(Log++".0.gz"),
+ {error,enoent} = file:read_file_info(Log++".1"),
+ {error,enoent} = file:read_file_info(Log++".1.gz"),
+
+ [logger:notice(Str,?domain) || _ <- lists:seq(1,51)],
+ logger_std_h:filesync(?MODULE),
+ {ok,#file_info{size=0}} = file:read_file_info(Log),
+ {error,enoent} = file:read_file_info(Log++".0"),
+ {ok,#file_info{size=35}} = file:read_file_info(Log++".0.gz"),
+ {error,enoent} = file:read_file_info(Log++".1"),
+ {ok,#file_info{size=35}} = file:read_file_info(Log++".1.gz"),
+ {error,enoent} = file:read_file_info(Log++".2"),
+ {error,enoent} = file:read_file_info(Log++".2.gz"),
+
+ [logger:notice(Str,?domain) || _ <- lists:seq(1,50)],
+ logger_std_h:filesync(?MODULE),
+ {ok,#file_info{size=1000}} = file:read_file_info(Log),
+ {error,enoent} = file:read_file_info(Log++".0"),
+ {ok,#file_info{size=35}} = file:read_file_info(Log++".0.gz"),
+ {error,enoent} = file:read_file_info(Log++".1"),
+ {ok,#file_info{size=35}} = file:read_file_info(Log++".1.gz"),
+ {error,enoent} = file:read_file_info(Log++".2"),
+ {error,enoent} = file:read_file_info(Log++".2.gz"),
+
+ logger:notice("bbbb",?domain),
+ logger_std_h:filesync(?MODULE),
+ {ok,#file_info{size=0}} = file:read_file_info(Log),
+ {error,enoent} = file:read_file_info(Log++".0"),
+ {ok,#file_info{size=38}} = file:read_file_info(Log++".0.gz"),
+ {error,enoent} = file:read_file_info(Log++".1"),
+ {ok,#file_info{size=35}} = file:read_file_info(Log++".1.gz"),
+ {error,enoent} = file:read_file_info(Log++".2"),
+ {error,enoent} = file:read_file_info(Log++".2.gz"),
+
+ ok.
+rotate_size_compressed(cleanup,_Config) ->
+ ok = stop_handler(?MODULE).
+
+rotate_size_reopen(Config) ->
+ {Log,_HConfig,_StdHConfig} =
+ start_handler(?MODULE, ?FUNCTION_NAME, Config),
+ ok = logger:update_handler_config(?MODULE,#{config=>#{max_no_bytes=>1000,
+ max_no_files=>2}}),
+
+ Str = lists:duplicate(19,$a),
+ [logger:notice(Str,?domain) || _ <- lists:seq(1,40)],
+ logger_std_h:filesync(?MODULE),
+ {ok,#file_info{size=800}} = file:read_file_info(Log),
+
+ {ok,HConfig} = logger:get_handler_config(?MODULE),
+ ok = logger:remove_handler(?MODULE),
+ ok = logger:add_handler(?MODULE,maps:get(module,HConfig),HConfig),
+ {ok,#file_info{size=800}} = file:read_file_info(Log),
+
+ [logger:notice(Str,?domain) || _ <- lists:seq(1,40)],
+ logger_std_h:filesync(?MODULE),
+ {ok,#file_info{size=580}} = file:read_file_info(Log),
+ {ok,#file_info{size=1020}} = file:read_file_info(Log++".0"),
+ ok.
+rotate_size_reopen(cleanup,_Config) ->
+ ok = stop_handler(?MODULE).
+
+rotation_opts(Config) ->
+ {Log,_HConfig,StdHConfig} =
+ start_handler(?MODULE, ?FUNCTION_NAME, Config),
+ #{max_no_bytes:=infinity,
+ max_no_files:=0,
+ compress_on_rotate:=false} = StdHConfig,
+
+ %% Test bad rotation config
+ {error,{invalid_config,_,_}} =
+ logger:update_handler_config(?MODULE,config,#{max_no_bytes=>0}),
+ {error,{invalid_config,_,_}} =
+ logger:update_handler_config(?MODULE,config,#{max_no_files=>infinity}),
+ {error,{invalid_config,_,_}} =
+ logger:update_handler_config(?MODULE,config,
+ #{compress_on_rotate=>undefined}),
+
+
+ %% Test good rotation config - start with no rotation
+ Str = lists:duplicate(19,$a),
+ [logger:notice(Str,?domain) || _ <- lists:seq(1,10)],
+ logger_std_h:filesync(?MODULE),
+ {ok,#file_info{size=200}} = file:read_file_info(Log),
+ [] = filelib:wildcard(Log++".*"),
+
+ %% Turn on rotation, check that existing file is rotated since its
+ %% size exceeds max_no_bytes
+ ok = logger:update_handler_config(?MODULE,
+ config,
+ #{max_no_bytes=>100,
+ max_no_files=>2}),
+ timer:sleep(100), % give some time to execute config_changed
+ {ok,#file_info{size=0}} = file:read_file_info(Log),
+ Log0 = Log++".0",
+ {ok,#file_info{size=200}} = file:read_file_info(Log0),
+ [Log0] = filelib:wildcard(Log++".*"),
+
+ %% Fill all logs
+ [logger:notice(Str,?domain) || _ <- lists:seq(1,13)],
+ logger_std_h:filesync(?MODULE),
+ {ok,#file_info{size=20}} = file:read_file_info(Log),
+ {ok,#file_info{size=120}} = file:read_file_info(Log0),
+ {ok,#file_info{size=120}} = file:read_file_info(Log++".1"),
+ [_,_] = filelib:wildcard(Log++".*"),
+
+ %% Extend size and count and check that nothing changes with existing files
+ ok = logger:update_handler_config(?MODULE,
+ config,
+ #{max_no_bytes=>200,
+ max_no_files=>3}),
+ timer:sleep(100), % give some time to execute config_changed
+ {ok,#file_info{size=20}} = file:read_file_info(Log),
+ {ok,#file_info{size=120}} = file:read_file_info(Log0),
+ {ok,#file_info{size=120}} = file:read_file_info(Log++".1"),
+ [_,_] = filelib:wildcard(Log++".*"),
+
+ %% Add more log events and see that extended size and count works
+ [logger:notice(Str,?domain) || _ <- lists:seq(1,10)],
+ logger_std_h:filesync(?MODULE),
+ {ok,#file_info{size=0}} = file:read_file_info(Log),
+ {ok,#file_info{size=220}} = file:read_file_info(Log0),
+ {ok,#file_info{size=120}} = file:read_file_info(Log++".1"),
+ {ok,#file_info{size=120}} = file:read_file_info(Log++".2"),
+ [_,_,_] = filelib:wildcard(Log++".*"),
+
+ %% Reduce count and check that archive files that exceed the new
+ %% count are moved
+ ok = logger:update_handler_config(?MODULE,
+ config,
+ #{max_no_files=>1}),
+ timer:sleep(100), % give some time to execute config_changed
+ {ok,#file_info{size=0}} = file:read_file_info(Log),
+ {ok,#file_info{size=220}} = file:read_file_info(Log0),
+ [Log0] = filelib:wildcard(Log++".*"),
+
+ %% Extend size and count again, and turn on compression. Check
+ %% that archives are compressed
+ ok = logger:update_handler_config(?MODULE,
+ config,
+ #{max_no_bytes=>100,
+ max_no_files=>2,
+ compress_on_rotate=>true}),
+ timer:sleep(100), % give some time to execute config_changed
+ {ok,#file_info{size=0}} = file:read_file_info(Log),
+ Log0gz = Log0++".gz",
+ {ok,#file_info{size=29}} = file:read_file_info(Log0gz),
+ [Log0gz] = filelib:wildcard(Log++".*"),
+
+ %% Fill all logs
+ [logger:notice(Str,?domain) || _ <- lists:seq(1,13)],
+ logger_std_h:filesync(?MODULE),
+ {ok,#file_info{size=20}} = file:read_file_info(Log),
+ {ok,#file_info{size=29}} = file:read_file_info(Log0gz),
+ {ok,#file_info{size=29}} = file:read_file_info(Log++".1.gz"),
+ [_,_] = filelib:wildcard(Log++".*"),
+
+ %% Reduce count and turn off compression. Check that archives that
+ %% exceeds the new count are removed, and the rest are
+ %% uncompressed.
+ ok = logger:update_handler_config(?MODULE,
+ config,
+ #{max_no_files=>1,
+ compress_on_rotate=>false}),
+ timer:sleep(100), % give some time to execute config_changed
+ {ok,#file_info{size=20}} = file:read_file_info(Log),
+ {ok,#file_info{size=120}} = file:read_file_info(Log0),
+ [Log0] = filelib:wildcard(Log++".*"),
+
+ %% Check that config and handler state agree on the current rotation settings
+ {ok,#{config:=#{max_no_bytes:=100,
+ max_no_files:=1,
+ compress_on_rotate:=false}}} =
+ logger:get_handler_config(?MODULE),
+ #{cb_state:=#{handler_state:=#{max_no_bytes:=100,
+ max_no_files:=1,
+ compress_on_rotate:=false}}} =
+ logger_olp:info(h_proc_name()),
+ ok.
+rotation_opts(cleanup,_Config) ->
+ ok = stop_handler(?MODULE).
+
+rotation_opts_restart_handler(Config) ->
+ {Log,_HConfig,_StdHConfig} =
+ start_handler(?MODULE, ?FUNCTION_NAME, Config),
+ ok = logger:update_handler_config(?MODULE,
+ config,
+ #{max_no_bytes=>100,
+ max_no_files=>2}),
+
+ %% Fill all logs
+ Str = lists:duplicate(19,$a),
+ [logger:notice(Str,?domain) || _ <- lists:seq(1,15)],
+ logger_std_h:filesync(?MODULE),
+ {ok,#file_info{size=60}} = file:read_file_info(Log),
+ {ok,#file_info{size=120}} = file:read_file_info(Log++".0"),
+ {ok,#file_info{size=120}} = file:read_file_info(Log++".1"),
+ [_,_] = filelib:wildcard(Log++".*"),
+
+ %% Stop/start handler and turn off rotation. Check that archives are removed.
+ {ok,#{config:=StdHConfig1}=HConfig1} = logger:get_handler_config(?MODULE),
+ ok = logger:remove_handler(?MODULE),
+ ok = logger:add_handler(
+ ?MODULE,logger_std_h,
+ HConfig1#{config=>StdHConfig1#{max_no_bytes=>infinity}}),
+ timer:sleep(100),
+ {ok,#file_info{size=60}} = file:read_file_info(Log),
+ [] = filelib:wildcard(Log++".*"),
+
+ %% Add some log events and check that file is no longer rotated.
+ [logger:notice(Str,?domain) || _ <- lists:seq(1,10)],
+ logger_std_h:filesync(?MODULE),
+ {ok,#file_info{size=260}} = file:read_file_info(Log),
+ [] = filelib:wildcard(Log++".*"),
+
+ %% Stop/start handler and trun on rotation. Check that file is rotated.
+ {ok,#{config:=StdHConfig2}=HConfig2} = logger:get_handler_config(?MODULE),
+ ok = logger:remove_handler(?MODULE),
+ ok = logger:add_handler(
+ ?MODULE,logger_std_h,
+ HConfig2#{config=>StdHConfig2#{max_no_bytes=>100,
+ max_no_files=>2}}),
+ timer:sleep(100),
+ {ok,#file_info{size=0}} = file:read_file_info(Log),
+ {ok,#file_info{size=260}} = file:read_file_info(Log++".0"),
+ [_] = filelib:wildcard(Log++".*"),
+
+ %% Fill all logs
+ [logger:notice(Str,?domain) || _ <- lists:seq(1,10)],
+ logger_std_h:filesync(?MODULE),
+ {ok,#file_info{size=80}} = file:read_file_info(Log),
+ {ok,#file_info{size=120}} = file:read_file_info(Log++".0"),
+ {ok,#file_info{size=260}} = file:read_file_info(Log++".1"),
+
+ %% Stop/start handler, reduce count and turn on compression. Check
+ %% that excess archives are removed, and the rest compressed.
+ {ok,#{config:=StdHConfig3}=HConfig3} = logger:get_handler_config(?MODULE),
+ ok = logger:remove_handler(?MODULE),
+ ok = logger:add_handler(
+ ?MODULE,logger_std_h,
+ HConfig3#{config=>StdHConfig3#{max_no_bytes=>75,
+ max_no_files=>1,
+ compress_on_rotate=>true}}),
+ timer:sleep(100),
+ {ok,#file_info{size=0}} = file:read_file_info(Log),
+ {ok,#file_info{size=29}} = file:read_file_info(Log++".0.gz"),
+ [_] = filelib:wildcard(Log++".*"),
+
+ %% Stop/start handler and turn off compression. Check that achives
+ %% are decompressed.
+ {ok,#{config:=StdHConfig4}=HConfig4} = logger:get_handler_config(?MODULE),
+ ok = logger:remove_handler(?MODULE),
+ ok = logger:add_handler(
+ ?MODULE,logger_std_h,
+ HConfig4#{config=>StdHConfig4#{compress_on_rotate=>false}}),
+ timer:sleep(100),
+ {ok,#file_info{size=0}} = file:read_file_info(Log),
+ {ok,#file_info{size=80}} = file:read_file_info(Log++".0"),
+ [_] = filelib:wildcard(Log++".*"),
+
+ ok.
+rotation_opts_restart_handler(cleanup,_Config) ->
+ ok = stop_handler(?MODULE).
+
%%%-----------------------------------------------------------------
%%%
send_requests(TO, Reqs = [{Mod,Func,Args,Res}|Rs]) ->
@@ -1305,8 +1683,8 @@ start_handler(Name, TTY, _Config) when TTY == standard_io;
ok = logger:add_handler(Name,
logger_std_h,
#{config => #{type => TTY},
- filter_default=>log,
- filters=>?DEFAULT_HANDLER_FILTERS([Name]),
+ filter_default=>stop,
+ filters=>filter_only_this_domain(Name),
formatter=>{?MODULE,op}}),
{ok,HConfig = #{config := StdHConfig}} = logger:get_handler_config(Name),
{HConfig,StdHConfig};
@@ -1320,12 +1698,17 @@ start_handler(Name, FuncName, Config) ->
ok = logger:add_handler(Name,
logger_std_h,
#{config => #{type => Type},
- filter_default=>log,
- filters=>?DEFAULT_HANDLER_FILTERS([Name]),
+ filter_default=>stop,
+ filters=>filter_only_this_domain(Name),
formatter=>{?MODULE,op}}),
{ok,HConfig = #{config := StdHConfig}} = logger:get_handler_config(Name),
{Log,HConfig,StdHConfig}.
+
+filter_only_this_domain(Name) ->
+ [{remote_gl,{fun logger_filters:remote_gl/2,stop}},
+ {domain,{fun logger_filters:domain/2,{log,super,[Name]}}}].
+
stop_handler(Name) ->
R = logger:remove_handler(Name),
ct:pal("Handler ~p stopped! Result: ~p", [Name,R]),
@@ -1658,7 +2041,7 @@ tpl([]) ->
tracer({trace,_,call,{logger_h_common,handle_cast,[Op|_]}},
{Pid,[{Mod,Func,Op}|Expected]}) ->
maybe_tracer_done(Pid,Expected,{Mod,Func,Op});
-tracer({trace,_,call,{Mod=logger_std_h,Func=write_to_dev,[_,Data,_,_,_]}},
+tracer({trace,_,call,{Mod=logger_std_h,Func=write_to_dev,[Data,_]}},
{Pid,[{Mod,Func,Data}|Expected]}) ->
maybe_tracer_done(Pid,Expected,{Mod,Func,Data});
tracer({trace,_,call,{Mod,Func,_}}, {Pid,[{Mod,Func}|Expected]}) ->
@@ -1742,4 +2125,3 @@ filesync_rep_int() ->
file_delete(Log) ->
file:delete(Log).
-
diff --git a/lib/kernel/test/logger_stress_SUITE.erl b/lib/kernel/test/logger_stress_SUITE.erl
index 4072e8c86a..1a278fb1b2 100644
--- a/lib/kernel/test/logger_stress_SUITE.erl
+++ b/lib/kernel/test/logger_stress_SUITE.erl
@@ -67,6 +67,10 @@ all() ->
reject_events,
std_handler,
disk_log_handler,
+ std_handler_time,
+ std_handler_time_big,
+ disk_log_handler_time,
+ disk_log_handler_time_big,
emulator_events,
remote_events,
remote_to_disk_log,
@@ -123,6 +127,20 @@ std_handler(cleanup,_Config) ->
_ = file:delete("default.log"),
ok.
+%% Disable overload protection and just print a lot - measure time.
+%% The IOPS reported is the number of log events written per millisecond.
+std_handler_time(Config) ->
+ measure_handler_time(logger_std_h,#{type=>{file,"default.log"}},Config).
+std_handler_time(cleanup,_Config) ->
+ _ = file:delete("default.log"),
+ ok.
+
+std_handler_time_big(Config) ->
+ measure_handler_time_big(logger_std_h,#{type=>{file,"default.log"}},Config).
+std_handler_time_big(cleanup,_Config) ->
+ _ = file:delete("default.log"),
+ ok.
+
%% Cascading failure that produce gen_server and proc_lib reports -
%% how many of the produced log events are actually written to a log
%% with logger_disk_log_h wrap file handler.
@@ -140,6 +158,20 @@ disk_log_handler(cleanup,_Config) ->
[_ = file:delete(F) || F <- Files],
ok.
+%% Disable overload protection and just print a lot - measure time.
+%% The IOPS reported is the number of log events written per millisecond.
+disk_log_handler_time(Config) ->
+ measure_handler_time(logger_disk_log_h,#{type=>halt},Config).
+disk_log_handler_time(cleanup,_Config) ->
+ _ = file:delete("default"),
+ ok.
+
+disk_log_handler_time_big(Config) ->
+ measure_handler_time_big(logger_disk_log_h,#{type=>halt},Config).
+disk_log_handler_time_big(cleanup,_Config) ->
+ _ = file:delete("default"),
+ ok.
+
%% Cascading failure that produce log events from the emulator - how
%% many of the produced log events pass through the proxy.
emulator_events(Config) ->
@@ -221,6 +253,68 @@ remote_emulator_to_disk_log(cleanup,_Config) ->
%%%-----------------------------------------------------------------
%%% Internal functions
+measure_handler_time(Module,HCfg,Config) ->
+ measure_handler_time(Module,100000,small_fa(),millisecond,HCfg,#{},Config).
+
+measure_handler_time_big(Module,HCfg,Config) ->
+ FCfg = #{chars_limit=>4096, max_size=>1024},
+ measure_handler_time(Module,100,big_fa(),second,HCfg,FCfg,Config).
+
+measure_handler_time(Module,N,FA,Unit,HCfg,FCfg,Config) ->
+ {ok,_,Node} =
+ logger_test_lib:setup(
+ Config,
+ [{logger,
+ [{handler,default,Module,
+ #{formatter => {logger_formatter,
+ maps:merge(#{legacy_header=>false,
+ single_line=>true},FCfg)},
+ config=>maps:merge(#{sync_mode_qlen => N+1,
+ drop_mode_qlen => N+1,
+ flush_qlen => N+1,
+ burst_limit_enable => false,
+ filesync_repeat_interval => no_repeat},
+ HCfg)}}]}]),
+ %% HPid = rpc:call(Node,erlang,whereis,[?name_to_reg_name(Module,default)]),
+ %% {links,[_,FCPid]} = rpc:call(Node,erlang,process_info,[HPid,links]),
+ T0 = erlang:monotonic_time(millisecond),
+ ok = rpc:call(Node,?MODULE,nlogs_wait,[N,FA,Module]),
+ %% ok = rpc:call(Node,fprof,apply,[fun ?MODULE:nlogs_wait/2,[N div 10,FA,Module],[{procs,[HPid,FCPid]}]]),
+ T1 = erlang:monotonic_time(millisecond),
+ T = T1-T0,
+ M = case Unit of
+ millisecond -> 1;
+ second -> 1000
+ end,
+ IOPS = M*N/T,
+ ct:pal("N: ~p~nT: ~p~nIOPS: ~.2f events pr ~w",[N,T,IOPS,Unit]),
+ %% Stats = rpc:call(Node,logger_olp,info,[?name_to_reg_name(Module,default)]),
+ %% ct:pal("Stats: ~p",[Stats]),
+ ct_event:notify(#event{name = benchmark_data,
+ data = [{value,IOPS}]}),
+ {comment,io_lib:format("~.2f events written pr ~w",[IOPS,Unit])}.
+
+nlogs_wait(N,{F,A},Module) ->
+ group_leader(whereis(user),self()),
+ [?LOG_NOTICE(F,A) || _ <- lists:seq(1,N)],
+ wait(Module).
+
+wait(Module) ->
+ case Module:filesync(default) of
+ {error,handler_busy} ->
+ wait(Module);
+ ok ->
+ ok
+ end.
+
+small_fa() ->
+ Str = "\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "[\\]^_`abcdefghijklmnopqr",
+ {"~ts",[Str]}.
+
+big_fa() ->
+ {"~p",[lists:duplicate(1048576,"a")]}.
+
nlogs(N) ->
group_leader(whereis(user),self()),
Str = "\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ"
diff --git a/lib/kernel/vsn.mk b/lib/kernel/vsn.mk
index 4b43c6ae9d..7bebe1ba70 100644
--- a/lib/kernel/vsn.mk
+++ b/lib/kernel/vsn.mk
@@ -1 +1 @@
-KERNEL_VSN = 6.2
+KERNEL_VSN = 6.3
diff --git a/lib/mnesia/doc/src/notes.xml b/lib/mnesia/doc/src/notes.xml
index 8fc3610bb6..01d1666b8d 100644
--- a/lib/mnesia/doc/src/notes.xml
+++ b/lib/mnesia/doc/src/notes.xml
@@ -39,7 +39,22 @@
thus constitutes one section in this document. The title of each
section is the version number of Mnesia.</p>
- <section><title>Mnesia 4.15.5</title>
+ <section><title>Mnesia 4.15.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Avoid overload warnings caused by a race condition.</p>
+ <p>
+ Own Id: OTP-15619 Aux Id: ERIERL-310 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Mnesia 4.15.5</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/mnesia/vsn.mk b/lib/mnesia/vsn.mk
index 1cfb35c774..781a4830a0 100644
--- a/lib/mnesia/vsn.mk
+++ b/lib/mnesia/vsn.mk
@@ -1 +1 @@
-MNESIA_VSN = 4.15.5
+MNESIA_VSN = 4.15.6
diff --git a/lib/observer/doc/src/notes.xml b/lib/observer/doc/src/notes.xml
index 22035af982..2d914f8c61 100644
--- a/lib/observer/doc/src/notes.xml
+++ b/lib/observer/doc/src/notes.xml
@@ -32,6 +32,45 @@
<p>This document describes the changes made to the Observer
application.</p>
+<section><title>Observer 2.9</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Since Logger was introduced in OTP-21.0, menu choice
+ <em>Log &gt; Toggle Log View</em> in observer would cause
+ a crash unless an <c>error_logger</c> event handler was
+ explicitly installed. This is now corrected.</p>
+ <p>
+ Own Id: OTP-15553 Aux Id: ERL-848 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Since persistent_term was introduced, observer would
+ sometimes crash when expanding a term from a process
+ state. This is now corrected.</p>
+ <p>
+ Own Id: OTP-15493 Aux Id: ERL-810 </p>
+ </item>
+ <item>
+ <p>
+ Add <c>OBSERVER_SCALE</c> environment variable for HiDPI
+ support.</p>
+ <p>
+ Own Id: OTP-15586 Aux Id: PR-2105 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Observer 2.8.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/observer/vsn.mk b/lib/observer/vsn.mk
index 5ce0aca589..0e9c8b302c 100644
--- a/lib/observer/vsn.mk
+++ b/lib/observer/vsn.mk
@@ -1 +1 @@
-OBSERVER_VSN = 2.8.2
+OBSERVER_VSN = 2.9
diff --git a/lib/odbc/doc/src/notes.xml b/lib/odbc/doc/src/notes.xml
index dba7663bb9..696fcaa479 100644
--- a/lib/odbc/doc/src/notes.xml
+++ b/lib/odbc/doc/src/notes.xml
@@ -32,7 +32,22 @@
<p>This document describes the changes made to the odbc application.
</p>
- <section><title>ODBC 2.12.2</title>
+ <section><title>ODBC 2.12.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Enhance error handling to avoid stack corruption</p>
+ <p>
+ Own Id: OTP-15667 Aux Id: ERL-808, PR-2065 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>ODBC 2.12.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/odbc/vsn.mk b/lib/odbc/vsn.mk
index bb21016fad..ff023e666b 100644
--- a/lib/odbc/vsn.mk
+++ b/lib/odbc/vsn.mk
@@ -1 +1 @@
-ODBC_VSN = 2.12.2
+ODBC_VSN = 2.12.3
diff --git a/lib/public_key/doc/src/notes.xml b/lib/public_key/doc/src/notes.xml
index b3e6023c41..f6bc0dc797 100644
--- a/lib/public_key/doc/src/notes.xml
+++ b/lib/public_key/doc/src/notes.xml
@@ -35,6 +35,21 @@
<file>notes.xml</file>
</header>
+<section><title>Public_Key 1.6.5</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add export of dialyzer type</p>
+ <p>
+ Own Id: OTP-15624</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Public_Key 1.6.4</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/public_key/vsn.mk b/lib/public_key/vsn.mk
index 5e2643f0ea..11c06fb158 100644
--- a/lib/public_key/vsn.mk
+++ b/lib/public_key/vsn.mk
@@ -1 +1 @@
-PUBLIC_KEY_VSN = 1.6.4
+PUBLIC_KEY_VSN = 1.6.5
diff --git a/lib/runtime_tools/doc/src/notes.xml b/lib/runtime_tools/doc/src/notes.xml
index 810bb8207c..58a2a66c4b 100644
--- a/lib/runtime_tools/doc/src/notes.xml
+++ b/lib/runtime_tools/doc/src/notes.xml
@@ -32,6 +32,21 @@
<p>This document describes the changes made to the Runtime_Tools
application.</p>
+<section><title>Runtime_Tools 1.13.2</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Update of systemtap trace example scripts.</p>
+ <p>
+ Own Id: OTP-15670</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Runtime_Tools 1.13.1</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/runtime_tools/vsn.mk b/lib/runtime_tools/vsn.mk
index aa3d702997..fa2f338ec2 100644
--- a/lib/runtime_tools/vsn.mk
+++ b/lib/runtime_tools/vsn.mk
@@ -1 +1 @@
-RUNTIME_TOOLS_VSN = 1.13.1
+RUNTIME_TOOLS_VSN = 1.13.2
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml
index 0bc4baf5eb..17f14bdea2 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -30,6 +30,23 @@
<file>notes.xml</file>
</header>
+<section><title>Ssh 4.7.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ SSH sftp daemon now accepts an SSH_FXP_STAT message
+ encoded according to the wrong sftp version. Some clients
+ sends such messages.</p>
+ <p>
+ Own Id: OTP-15498 Aux Id: ERL-822, PR-2077 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.7.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/ssh/test/ssh_protocol_SUITE.erl b/lib/ssh/test/ssh_protocol_SUITE.erl
index 3e3e151781..b12ddfeef6 100644
--- a/lib/ssh/test/ssh_protocol_SUITE.erl
+++ b/lib/ssh/test/ssh_protocol_SUITE.erl
@@ -38,7 +38,11 @@
-define(EXTRA_KEX, 'diffie-hellman-group1-sha1').
-define(CIPHERS, ['aes256-ctr','aes192-ctr','aes128-ctr','aes128-cbc','3des-cbc']).
--define(DEFAULT_CIPHERS, [{client2server,?CIPHERS}, {server2client,?CIPHERS}]).
+-define(DEFAULT_CIPHERS, (fun() -> Ciphs = filter_supported(cipher, ?CIPHERS),
+ [{client2server,Ciphs}, {server2client,Ciphs}]
+ end)()
+ ).
+
-define(v(Key, Config), proplists:get_value(Key, Config)).
-define(v(Key, Config, Default), proplists:get_value(Key, Config, Default)).
diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk
index 2890d7fe5b..0f9eee887c 100644
--- a/lib/ssh/vsn.mk
+++ b/lib/ssh/vsn.mk
@@ -1,4 +1,4 @@
#-*-makefile-*- ; force emacs to enter makefile-mode
-SSH_VSN = 4.7.3
+SSH_VSN = 4.7.4
APP_VSN = "ssh-$(SSH_VSN)"
diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml
index 82eb8ff700..732fdc71e7 100644
--- a/lib/ssl/doc/src/notes.xml
+++ b/lib/ssl/doc/src/notes.xml
@@ -27,6 +27,93 @@
</header>
<p>This document describes the changes made to the SSL application.</p>
+<section><title>SSL 9.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix bug that an incorrect return value for gen_statem
+ could be created when alert was a result of handling
+ renegotiation info extension</p>
+ <p>
+ Own Id: OTP-15502</p>
+ </item>
+ <item>
+ <p>
+ Correct check for 3des_ede_cbc, could cause ssl to claim
+ to support 3des_ede_cbc when cryptolib does not.</p>
+ <p>
+ Own Id: OTP-15539</p>
+ </item>
+ <item>
+ <p>
+ Improved DTLS error handling, avoids unexpected
+ connection failure in rare cases.</p>
+ <p>
+ Own Id: OTP-15561</p>
+ </item>
+ <item>
+ <p>
+ Corrected active once emulation bug that could cause the
+ ssl_closed meassage to not be sent. Bug introduced by
+ OTP-15449</p>
+ <p>
+ Own Id: OTP-15666 Aux Id: ERIERL-316, </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add client option {reuse_session, SessionID::binary()}
+ that can be used together with new option value
+ {reuse_sessions, save}. This makes it possible to reuse a
+ session from a specific connection establishment.</p>
+ <p>
+ Own Id: OTP-15369</p>
+ </item>
+ <item>
+ <p>
+ The Reason part of of the error return from the functions
+ connect and handshake has a better and documented format.
+ This will sometimes differ from previous returned
+ reasons, however those where only documented as term()
+ and should for that reason not be relied on.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15423</p>
+ </item>
+ <item>
+ <p>
+ Refactor of state handling to improve TLS application
+ data throughput and reduce CPU overhead</p>
+ <p>
+ Own Id: OTP-15445</p>
+ </item>
+ <item>
+ <p>
+ The SSL code has been optimized in many small ways to
+ reduce CPU load for encryption/decryption, especially for
+ Erlang's distribution protocol over TLS.</p>
+ <p>
+ Own Id: OTP-15529</p>
+ </item>
+ <item>
+ <p>
+ Add support for active N</p>
+ <p>
+ Own Id: OTP-15665 Aux Id: ERL-811, PR-2072 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SSL 9.1.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index 254b7f3f4d..1e97fe046b 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -2737,6 +2737,11 @@ ssl_options_list([Key | Keys], [Value | Values], Acc) ->
handle_active_option(false, connection = StateName, To, Reply, State) ->
hibernate_after(StateName, State, [{reply, To, Reply}]);
+handle_active_option(_, connection = StateName, To, _Reply, #state{connection_env = #connection_env{terminated = true},
+ user_data_buffer = {_,0,_}} = State) ->
+ handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, all_data_deliverd), StateName,
+ State#state{start_or_recv_from = To}),
+ {stop,{shutdown, peer_close}, State};
handle_active_option(_, connection = StateName0, To, Reply, #state{static_env = #static_env{protocol_cb = Connection},
user_data_buffer = {_,0,_}} = State0) ->
case Connection:next_event(StateName0, no_record, State0) of
diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk
index 3527062a8a..0d9f907d5c 100644
--- a/lib/ssl/vsn.mk
+++ b/lib/ssl/vsn.mk
@@ -1 +1 @@
-SSL_VSN = 9.1.2
+SSL_VSN = 9.2
diff --git a/lib/stdlib/doc/src/ets.xml b/lib/stdlib/doc/src/ets.xml
index d2ac6a75b1..2cb677785d 100644
--- a/lib/stdlib/doc/src/ets.xml
+++ b/lib/stdlib/doc/src/ets.xml
@@ -642,12 +642,11 @@ Error: fun containing local Erlang function calls
<p><marker id="info_2_safe_fixed_monotonic_time"/></p>
<p><c>Item=safe_fixed|safe_fixed_monotonic_time,
Value={FixationTime,Info}|false</c></p>
- <p>If the table has been fixed using
+ <p>If the table is fixed using
<seealso marker="#safe_fixtable/2">
<c>safe_fixtable/2</c></seealso>,
the call returns a tuple where <c>FixationTime</c> is the
- time when the table was first fixed by a process, which either
- is or is not one of the processes it is fixed by now.</p>
+ last time when the table changed from unfixed to fixed.</p>
<p>The format and value of <c>FixationTime</c> depends on
<c>Item</c>:</p>
<taglist>
@@ -679,8 +678,15 @@ Error: fun containing local Erlang function calls
table is fixed by now. <c>RefCount</c> is the value
of the reference counter and it keeps track of how many times
the table has been fixed by the process.</p>
- <p>If the table never has been fixed, the call returns
- <c>false</c>.</p>
+ <p>Table fixations are not limited to <seealso marker="#safe_fixtable/2">
+ <c>safe_fixtable/2</c></seealso>. Temporary fixations may also
+ be done by for example <seealso marker="#traversal">traversing
+ functions</seealso> like <c>select</c> and <c>match</c>. Such
+ table fixations are automatically released before the
+ corresponding functions returns, but they may be seen by a
+ concurrent call to
+ <c>ets:info(T,safe_fixed|safe_fixed_monotonic_time)</c>.</p>
+ <p>If the table is not fixed at all, the call returns <c>false</c>.</p>
</item>
<item>
<p><c>Item=stats, Value=tuple()</c></p>
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index 0cad5929ca..d4a4ba268b 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -320,12 +320,13 @@ erlang:'!' -----> Module:StateName/3
</p>
<p>
There is also a server start option
- <seealso marker="#type-hibernate_after_opt">
+ <seealso marker="#type-enter_loop_opt">
<c>{hibernate_after, Timeout}</c>
</seealso>
for
- <seealso marker="#start/3"><c>start/3,4</c></seealso> or
- <seealso marker="#start_link/3"><c>start_link/3,4</c></seealso>
+ <seealso marker="#start/3"><c>start/3,4</c></seealso>,
+ <seealso marker="#start_link/3"><c>start_link/3,4</c></seealso> or
+ <seealso marker="#enter_loop/4"><c>enter_loop/4,5,6</c></seealso>,
that may be used to automatically hibernate the server.
</p>
</description>
diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml
index dee7136eb1..23c3f6e981 100644
--- a/lib/stdlib/doc/src/notes.xml
+++ b/lib/stdlib/doc/src/notes.xml
@@ -31,6 +31,57 @@
</header>
<p>This document describes the changes made to the STDLIB application.</p>
+<section><title>STDLIB 3.8</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p> Fix a bug in the Erlang Pretty Printer: long atom
+ names in combination with <c>&lt;&lt;&gt;&gt;</c> could
+ cause a crash. </p>
+ <p>
+ Own Id: OTP-15592 Aux Id: ERL-818 </p>
+ </item>
+ <item>
+ <p> Fix bugs that could cause wrong results or bad
+ performance when formatting lists of characters using the
+ control sequences <c>p</c> or <c>P</c> and limiting the
+ output with the option <c>chars_limit</c>. </p>
+ <p>
+ Own Id: OTP-15639</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Improved ETS documentation about safe table traversal and
+ the partially bound key optimization for
+ <c>ordered_set</c>.</p>
+ <p>
+ Own Id: OTP-15545 Aux Id: PR-2103, PR-2139 </p>
+ </item>
+ <item>
+ <p> Optimize <c>calendar:gregorian_days_to_date/1</c>.
+ </p>
+ <p>
+ Own Id: OTP-15572 Aux Id: PR-2121 </p>
+ </item>
+ <item>
+ <p> Optimize functions
+ <c>calendar:rfc3339_to_system_time()</c> and
+ <c>calendar:system_time_to_rfc3339()</c>. </p>
+ <p>
+ Own Id: OTP-15630</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>STDLIB 3.7.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/stdlib/src/io_lib_pretty.erl b/lib/stdlib/src/io_lib_pretty.erl
index 5483ea87b5..8f2fd7ea8f 100644
--- a/lib/stdlib/src/io_lib_pretty.erl
+++ b/lib/stdlib/src/io_lib_pretty.erl
@@ -721,7 +721,7 @@ printable_list(_L, 1, _T, _Enc) ->
printable_list(L, _D, T, latin1) when T < 0 ->
io_lib:printable_latin1_list(L);
printable_list(L, _D, T, Enc) when T >= 0 ->
- case slice(L, tsub(T, 2)) of
+ case slice(L, tsub(T, 2), Enc) of
false ->
false;
{prefix, Prefix} when Enc =:= latin1 ->
@@ -737,20 +737,46 @@ printable_list(L, _D, T, Enc) when T >= 0 ->
printable_list(L, _D, T, _Uni) when T < 0->
io_lib:printable_list(L).
-slice(L, N) ->
- try io_lib:chars_length(L) =< N of
- true ->
+slice(L, N, latin1) ->
+ try lists:split(N, L) of
+ {_, []} ->
all;
- false ->
- case string:slice(L, 0, N) of
- "" ->
- false;
- Prefix ->
- {prefix, Prefix}
+ {[], _} ->
+ false;
+ {L1, _} ->
+ {prefix, L1}
+ catch
+ _:_ ->
+ all
+ end;
+slice(L, N, _Uni) ->
+ %% Be careful not to traverse more of L than necessary.
+ try string:slice(L, 0, N) of
+ "" ->
+ false;
+ Prefix ->
+ %% Assume no binaries are introduced by string:slice().
+ case is_flat(L, lists:flatlength(Prefix)) of
+ true ->
+ case string:equal(Prefix, L) of
+ true ->
+ all;
+ false ->
+ {prefix, Prefix}
+ end;
+ false ->
+ false
end
catch _:_ -> false
end.
+is_flat(_L, 0) ->
+ true;
+is_flat([C|Cs], N) when is_integer(C) ->
+ is_flat(Cs, N - 1);
+is_flat(_, _N) ->
+ false.
+
printable_bin0(Bin, D, T, Enc) ->
Len = case D >= 0 of
true ->
diff --git a/lib/stdlib/src/stdlib.appup.src b/lib/stdlib/src/stdlib.appup.src
index 9e5d6a3bd8..37ea97c353 100644
--- a/lib/stdlib/src/stdlib.appup.src
+++ b/lib/stdlib/src/stdlib.appup.src
@@ -40,7 +40,8 @@
{<<"^3\\.6$">>,[restart_new_emulator]},
{<<"^3\\.6\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.7$">>,[restart_new_emulator]},
- {<<"^3\\.7\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}],
+ {<<"^3\\.7\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.7\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
[{<<"^3\\.4$">>,[restart_new_emulator]},
{<<"^3\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
@@ -54,4 +55,5 @@
{<<"^3\\.6$">>,[restart_new_emulator]},
{<<"^3\\.6\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.7$">>,[restart_new_emulator]},
- {<<"^3\\.7\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}]}.
+ {<<"^3\\.7\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.7\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl
index 22c77aa172..8561491d50 100644
--- a/lib/stdlib/test/ets_SUITE.erl
+++ b/lib/stdlib/test/ets_SUITE.erl
@@ -56,9 +56,11 @@
-export([t_match_spec_run/1]).
-export([t_bucket_disappears/1]).
-export([t_named_select/1]).
+-export([select_fixtab_owner_change/1]).
-export([otp_5340/1]).
-export([otp_6338/1]).
-export([otp_6842_select_1000/1]).
+-export([select_mbuf_trapping/1]).
-export([otp_7665/1]).
-export([meta_wb/1]).
-export([grow_shrink/1, grow_pseudo_deleted/1, shrink_pseudo_deleted/1]).
@@ -129,9 +131,10 @@ all() ->
t_insert_list, t_test_ms, t_select_delete, t_select_replace,
t_select_replace_next_bug,
t_ets_dets, memory, t_select_reverse, t_bucket_disappears,
- t_named_select,
+ t_named_select, select_fixtab_owner_change,
select_fail, t_insert_new, t_repair_continuation,
otp_5340, otp_6338, otp_6842_select_1000, otp_7665,
+ select_mbuf_trapping,
otp_8732, meta_wb, grow_shrink, grow_pseudo_deleted,
shrink_pseudo_deleted, {group, meta_smp}, smp_insert,
smp_fixed_delete, smp_unfix_fix, smp_select_replace,
@@ -247,7 +250,64 @@ t_named_select_do(Opts) ->
verify_etsmem(EtsMem).
+%% Verify select and friends release fixtab as they should
+%% even when owneship is changed between traps.
+select_fixtab_owner_change(_Config) ->
+ T = ets:new(xxx, [protected]),
+ NKeys = 2000,
+ [ets:insert(T,{K,K band 7}) || K <- lists:seq(1,NKeys)],
+ %% Buddy and Papa will ping-pong table ownership between them
+ %% and the aim is to give Buddy the table when he is
+ %% in the middle of a yielding select* call.
+ {Buddy,_} = spawn_opt(fun() -> sfoc_buddy_loop(T, 1, undefined) end,
+ [link,monitor]),
+
+ sfoc_papa_loop(T, Buddy),
+
+ receive {'DOWN', _, process, Buddy, _} -> ok end,
+ ets:delete(T),
+ ok.
+
+sfoc_buddy_loop(T, I, State0) ->
+ receive
+ {'ETS-TRANSFER', T, Papa, _} ->
+ ets:give_away(T, Papa, State0),
+ case State0 of
+ done ->
+ ok;
+ _ ->
+ State1 = sfoc_traverse(T, I, State0),
+ %% Verify no fixation left
+ {I, false} = {I, ets:info(T, safe_fixed_monotonic_time)},
+ sfoc_buddy_loop(T, I+1, State1)
+ end
+ end.
+
+sfoc_papa_loop(T, Buddy) ->
+ ets:give_away(T, Buddy, "Catch!"),
+ receive
+ {'ETS-TRANSFER', T, Buddy, State} ->
+ case State of
+ done ->
+ ok;
+ _ ->
+ sfoc_papa_loop(T, Buddy)
+ end
+ end.
+
+sfoc_traverse(T, 1, S) ->
+ ets:select(T, [{{'$1',7}, [], ['$1']}]), S;
+sfoc_traverse(T, 2, S) ->
+ 0 = ets:select_count(T, [{{'$1',7}, [], [false]}]), S;
+sfoc_traverse(T, 3, _) ->
+ Limit = ets:info(T, size) div 2,
+ {_, Continuation} = ets:select(T, [{{'$1',7}, [], ['$1']}],
+ Limit),
+ Continuation;
+sfoc_traverse(_T, 4, Continuation) ->
+ _ = ets:select(Continuation),
+ done.
%% Check ets:match_spec_run/2.
t_match_spec_run(Config) when is_list(Config) ->
@@ -5292,6 +5352,61 @@ otp_6338(Config) when is_list(Config) ->
end),
ok.
+%% OTP-15660: Verify select not doing excessive trapping
+%% when process have mbuf heap fragments.
+select_mbuf_trapping(Config) when is_list(Config) ->
+ select_mbuf_trapping_do(set),
+ select_mbuf_trapping_do(ordered_set).
+
+select_mbuf_trapping_do(Type) ->
+ T = ets:new(xxx, [Type]),
+ NKeys = 50,
+ [ets:insert(T, {K, value}) || K <- lists:seq(1,NKeys)],
+
+ {priority, Prio} = process_info(self(), priority),
+ Tracee = self(),
+ [SchedTracer]
+ = start_loopers(1, Prio,
+ fun (SC) ->
+ receive
+ {trace, Tracee, out, _} ->
+ SC+1;
+ done ->
+ Tracee ! {schedule_count, SC},
+ exit(normal)
+ end
+ end,
+ 0),
+
+ erlang:garbage_collect(),
+ 1 = erlang:trace(self(), true, [running,{tracer,SchedTracer}]),
+
+ %% Artificially create an mbuf heap fragment
+ MbufTerm = "Frag me up",
+ MbufTerm = erts_debug:set_internal_state(mbuf, MbufTerm),
+
+ Keys = ets:select(T, [{{'$1', value}, [], ['$1']}]),
+ NKeys = length(Keys),
+
+ 1 = erlang:trace(self(), false, [running]),
+ Ref = erlang:trace_delivered(Tracee),
+ receive
+ {trace_delivered, Tracee, Ref} ->
+ SchedTracer ! done
+ end,
+ receive
+ {schedule_count, N} ->
+ io:format("~p context switches: ~p", [Type,N]),
+ if
+ N < 3 -> ok;
+ true -> ct:fail(failed)
+ end
+ end,
+ true = ets:delete(T),
+ ok.
+
+
+
%% Elements could come in the wrong order in a bag if a rehash occurred.
otp_5340(Config) when is_list(Config) ->
repeat_for_opts(fun otp_5340_do/1).
diff --git a/lib/stdlib/test/io_SUITE.erl b/lib/stdlib/test/io_SUITE.erl
index f097552e8c..824f5d19f2 100644
--- a/lib/stdlib/test/io_SUITE.erl
+++ b/lib/stdlib/test/io_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2019. 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.
@@ -32,7 +32,7 @@
io_with_huge_message_queue/1, format_string/1,
maps/1, coverage/1, otp_14178_unicode_atoms/1, otp_14175/1,
otp_14285/1, limit_term/1, otp_14983/1, otp_15103/1, otp_15076/1,
- otp_15159/1]).
+ otp_15159/1, otp_15639/1]).
-export([pretty/2, trf/3]).
@@ -64,7 +64,8 @@ all() ->
io_lib_print_binary_depth_one, otp_10302, otp_10755, otp_10836,
io_lib_width_too_small, io_with_huge_message_queue,
format_string, maps, coverage, otp_14178_unicode_atoms, otp_14175,
- otp_14285, limit_term, otp_14983, otp_15103, otp_15076, otp_15159].
+ otp_14285, limit_term, otp_14983, otp_15103, otp_15076, otp_15159,
+ otp_15639].
%% Error cases for output.
error_1(Config) when is_list(Config) ->
@@ -2647,3 +2648,35 @@ otp_15076(_Config) ->
{'EXIT', {badarg, _}} = (catch io_lib:build_text(L)),
{'EXIT', {badarg, _}} = (catch io_lib:build_text(L, [])),
ok.
+
+otp_15639(_Config) ->
+ L = lists:duplicate(10, "a"),
+ LOpts = [{encoding, latin1}, {chars_limit, 10}],
+ UOpts = [{encoding, unicode}, {chars_limit, 10}],
+ "[[...]|...]" = pretty(L, LOpts),
+ "[[...]|...]" = pretty(L, UOpts),
+ "[\"a\",[...]|...]" =
+ pretty(L, [{chars_limit, 12}, {encoding, latin1}]),
+ "[\"a\",[...]|...]" =
+ pretty(L, [{chars_limit, 12}, {encoding, unicode}]),
+
+ %% Latin-1
+ "\"12345678\"" = pretty("12345678", LOpts),
+ "\"12345678\"..." = pretty("123456789", LOpts),
+ "\"\\r\\n123456\"..." = pretty("\r\n1234567", LOpts),
+ "\"\\r1234567\"..." = pretty("\r12345678", LOpts),
+ "\"\\r\\n123456\"..." = pretty("\r\n12345678", LOpts),
+ "\"12345678\"..." = pretty("12345678"++[x], LOpts),
+ "[49,50|...]" = pretty("1234567"++[x], LOpts),
+ "[49,x]" = pretty("1"++[x], LOpts),
+ "[[...]|...]" = pretty(["1","2","3","4","5","6","7","8"], LOpts),
+ %% Unicode
+ "\"12345678\"" = pretty("12345678", UOpts),
+ "\"12345678\"..." = pretty("123456789", UOpts),
+ "\"\\r\\n1234567\"" = pretty("\r\n1234567", UOpts),
+ "\"\\r1234567\"..." = pretty("\r12345678", UOpts),
+ "\"\\r\\n1234567\"..." = pretty("\r\n12345678", UOpts),
+ "[49,50|...]" = pretty("12345678"++[x], UOpts),
+ "\"12345678\"..." = pretty("123456789"++[x], UOpts),
+ "[[...]|...]" = pretty(["1","2","3","4","5","6","7","8"], UOpts),
+ ok.
diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk
index d46173497b..cbefd6590a 100644
--- a/lib/stdlib/vsn.mk
+++ b/lib/stdlib/vsn.mk
@@ -1 +1 @@
-STDLIB_VSN = 3.7.1
+STDLIB_VSN = 3.8
diff --git a/lib/syntax_tools/doc/src/notes.xml b/lib/syntax_tools/doc/src/notes.xml
index dc13fe474b..772f5e6e04 100644
--- a/lib/syntax_tools/doc/src/notes.xml
+++ b/lib/syntax_tools/doc/src/notes.xml
@@ -32,6 +32,20 @@
<p>This document describes the changes made to the Syntax_Tools
application.</p>
+<section><title>Syntax_Tools 2.1.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p> Fix pretty-printing of type funs. </p>
+ <p>
+ Own Id: OTP-15519 Aux Id: ERL-815 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Syntax_Tools 2.1.6</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/syntax_tools/vsn.mk b/lib/syntax_tools/vsn.mk
index 8959ebbd04..538c71dc24 100644
--- a/lib/syntax_tools/vsn.mk
+++ b/lib/syntax_tools/vsn.mk
@@ -1 +1 @@
-SYNTAX_TOOLS_VSN = 2.1.6
+SYNTAX_TOOLS_VSN = 2.1.7
diff --git a/lib/tools/c_src/Makefile.in b/lib/tools/c_src/Makefile.in
index cfe91917f8..289322b6fa 100644
--- a/lib/tools/c_src/Makefile.in
+++ b/lib/tools/c_src/Makefile.in
@@ -29,7 +29,6 @@ CC=@CC@
LD=@LD@
AR=@AR@
RANLIB=@RANLIB@
-RM=@RM@
MKDIR=@MKDIR@
INSTALL=@INSTALL@
INSTALL_DIR=@INSTALL_DIR@
@@ -158,9 +157,9 @@ $(ERTS_LIB):
docs:
clean:
- $(RM) -rf ../obj/*
- $(RM) -rf ../bin/*
- $(RM) -f ./*~
+ $(RM) -r ../obj/*
+ $(RM) -r ../bin/*
+ $(RM) ./*~
.PHONY: all erts_lib docs clean
diff --git a/lib/tools/doc/src/notes.xml b/lib/tools/doc/src/notes.xml
index a6781dfdb3..28f8346a19 100644
--- a/lib/tools/doc/src/notes.xml
+++ b/lib/tools/doc/src/notes.xml
@@ -31,6 +31,45 @@
</header>
<p>This document describes the changes made to the Tools application.</p>
+<section><title>Tools 3.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Minor fixes for <c>make clean</c>.</p>
+ <p>
+ Own Id: OTP-15657</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ In the HTML file generated by
+ <c>cover:analyse_to_file/1,2</c>, a link is now added to
+ the line number. This makes it easier to share pointers
+ to specific lines.</p>
+ <p>
+ Own Id: OTP-15541</p>
+ </item>
+ <item>
+ <p>
+ Uncovered lines are now marked with a sad face,
+ <c>:-(</c>, in the HTML output from
+ <c>cover:analyse_to_file/1,2</c>. This is to make these
+ lines easier to find by search.</p>
+ <p>
+ Own Id: OTP-15542</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Tools 3.0.2</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/tools/vsn.mk b/lib/tools/vsn.mk
index bb8305e9f1..5700885549 100644
--- a/lib/tools/vsn.mk
+++ b/lib/tools/vsn.mk
@@ -1 +1 @@
-TOOLS_VSN = 3.0.2
+TOOLS_VSN = 3.1
diff --git a/lib/wx/doc/src/notes.xml b/lib/wx/doc/src/notes.xml
index 1061e73138..33d02e22ba 100644
--- a/lib/wx/doc/src/notes.xml
+++ b/lib/wx/doc/src/notes.xml
@@ -32,6 +32,25 @@
<p>This document describes the changes made to the wxErlang
application.</p>
+<section><title>Wx 1.8.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Improved support for wxWidgets 3.1.3 which have changed
+ <c>wxFONTWEIGTH</c>, also added <c>wxGCDC</c> and
+ <c>wxDisplay</c> modules.</p>
+ <p>
+ Fixed a crash on Mojave and check for events more often.</p>
+ <p>
+ Own Id: OTP-15587</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Wx 1.8.6</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/wx/vsn.mk b/lib/wx/vsn.mk
index d241a7a1b4..dac219fa98 100644
--- a/lib/wx/vsn.mk
+++ b/lib/wx/vsn.mk
@@ -1 +1 @@
-WX_VSN = 1.8.6
+WX_VSN = 1.8.7