summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--erts/config.h.in3
-rwxr-xr-xerts/configure20
-rw-r--r--erts/configure.ac13
-rw-r--r--erts/doc/src/notes.xml96
-rw-r--r--erts/emulator/beam/dist.c15
-rw-r--r--erts/emulator/beam/erl_bif_info.c7
-rw-r--r--erts/emulator/beam/erl_db_util.c107
-rw-r--r--erts/emulator/beam/erl_map.c688
-rw-r--r--erts/emulator/beam/erl_map.h30
-rw-r--r--erts/emulator/beam/erl_nif.c2
-rw-r--r--erts/emulator/beam/erl_node_tables.c14
-rw-r--r--erts/emulator/beam/erl_node_tables.h8
-rw-r--r--erts/emulator/beam/erl_printf_term.c67
-rw-r--r--erts/emulator/beam/erl_process.c2
-rw-r--r--erts/emulator/beam/erl_process_lock.h2
-rw-r--r--erts/emulator/beam/erl_term.h2
-rw-r--r--erts/emulator/beam/erl_term_hashing.c67
-rw-r--r--erts/emulator/beam/erl_term_hashing.h8
-rw-r--r--erts/emulator/beam/external.c49
-rw-r--r--erts/emulator/beam/jit/arm/instr_map.cpp86
-rw-r--r--erts/emulator/beam/jit/x86/instr_map.cpp92
-rw-r--r--erts/emulator/beam/utils.c31
-rw-r--r--erts/emulator/nifs/common/prim_socket_int.h1
-rw-r--r--erts/emulator/nifs/common/prim_socket_nif.c14
-rw-r--r--erts/emulator/nifs/common/socket_int.h1
-rw-r--r--erts/emulator/nifs/common/socket_util.c12
-rw-r--r--erts/emulator/nifs/common/socket_util.h3
-rw-r--r--erts/emulator/nifs/unix/unix_socket_syncio.c3
-rw-r--r--erts/emulator/nifs/win32/win_socket_asyncio.c1990
-rw-r--r--erts/emulator/test/distribution_SUITE.erl127
-rw-r--r--erts/emulator/test/map_SUITE.erl70
-rw-r--r--erts/etc/common/escript.c2
-rw-r--r--erts/preloaded/ebin/prim_socket.beambin36524 -> 36556 bytes
-rw-r--r--erts/preloaded/src/prim_socket.erl12
-rw-r--r--erts/test/otp_SUITE.erl2
-rw-r--r--erts/vsn.mk2
-rw-r--r--lib/common_test/doc/src/ct_suite.xml8
-rw-r--r--lib/common_test/doc/src/run_test_chapter.xml2
-rw-r--r--lib/common_test/doc/src/write_test_chapter.xml6
-rw-r--r--lib/common_test/src/ct_suite.erl4
-rw-r--r--lib/compiler/doc/src/notes.xml37
-rw-r--r--lib/compiler/src/beam_call_types.erl32
-rw-r--r--lib/compiler/src/beam_jump.erl1
-rw-r--r--lib/compiler/src/beam_ssa.erl15
-rw-r--r--lib/compiler/src/beam_ssa_check.erl5
-rw-r--r--lib/compiler/src/beam_ssa_codegen.erl6
-rw-r--r--lib/compiler/src/beam_ssa_private_append.erl21
-rw-r--r--lib/compiler/src/beam_types.erl25
-rw-r--r--lib/compiler/src/beam_validator.erl14
-rw-r--r--lib/compiler/test/beam_jump_SUITE.erl8
-rw-r--r--lib/compiler/test/beam_ssa_SUITE.erl90
-rw-r--r--lib/compiler/test/beam_ssa_check_SUITE_data/private_append.erl27
-rw-r--r--lib/compiler/test/beam_ssa_check_SUITE_data/sanity_checks.erl16
-rw-r--r--lib/compiler/test/beam_type_SUITE.erl81
-rw-r--r--lib/compiler/test/beam_validator_SUITE.erl21
-rw-r--r--lib/compiler/test/bif_SUITE.erl43
-rw-r--r--lib/compiler/vsn.mk2
-rw-r--r--lib/crypto/c_src/dh.c2
-rw-r--r--lib/crypto/c_src/openssl_config.h9
-rw-r--r--lib/crypto/doc/src/notes.xml16
-rw-r--r--lib/crypto/src/crypto.erl2
-rw-r--r--lib/crypto/test/crypto_SUITE.erl25
-rw-r--r--lib/crypto/vsn.mk2
-rw-r--r--lib/dialyzer/doc/src/dialyzer.xml26
-rw-r--r--lib/dialyzer/src/dialyzer.app.src2
-rw-r--r--lib/dialyzer/src/dialyzer.erl4
-rw-r--r--lib/dialyzer/src/dialyzer_analysis_callgraph.erl9
-rw-r--r--lib/dialyzer/src/dialyzer_cl_parse.erl28
-rw-r--r--lib/dialyzer/src/dialyzer_dataflow.erl23
-rw-r--r--lib/dialyzer/src/dialyzer_options.erl78
-rw-r--r--lib/dialyzer/test/incremental_SUITE.erl176
-rw-r--r--lib/dialyzer/test/incremental_SUITE_data/extra_modules/ebin/.gitignore5
-rw-r--r--lib/dialyzer/test/incremental_SUITE_data/extra_modules/src/extra_module.erl14
-rw-r--r--lib/dialyzer/test/incremental_SUITE_data/extra_modules/src/extra_modules.app.src8
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/bs_segments3
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/gh_71533
-rw-r--r--lib/dialyzer/test/small_SUITE_data/src/bs_segments.erl7
-rw-r--r--lib/dialyzer/test/small_SUITE_data/src/gh_7153.erl5
-rw-r--r--lib/diameter/src/diameter.appup.src8
-rw-r--r--lib/edoc/test/edoc_SUITE.erl15
-rw-r--r--lib/edoc/test/edoc_SUITE_data/module_with_feature.erl2
-rw-r--r--lib/eldap/doc/src/eldap.xml2
-rw-r--r--lib/eldap/doc/src/notes.xml16
-rw-r--r--lib/eldap/vsn.mk2
-rw-r--r--lib/erl_interface/doc/src/notes.xml16
-rw-r--r--lib/erl_interface/src/connect/ei_connect.c4
-rw-r--r--lib/erl_interface/src/prog/erl_call.c8
-rw-r--r--lib/erl_interface/vsn.mk2
-rw-r--r--lib/inets/doc/src/notes.xml28
-rw-r--r--lib/inets/src/http_client/httpc.erl4
-rw-r--r--lib/inets/vsn.mk2
-rw-r--r--lib/kernel/Makefile2
-rw-r--r--lib/kernel/doc/src/code.xml2
-rw-r--r--lib/kernel/doc/src/disk_log.xml2
-rw-r--r--lib/kernel/doc/src/socket.xml131
-rw-r--r--lib/kernel/src/gen_tcp_socket.erl16
-rw-r--r--lib/kernel/src/socket.erl10
-rw-r--r--lib/kernel/test/gen_udp_SUITE.erl8
-rw-r--r--lib/kernel/test/socket_SUITE.erl222
-rw-r--r--lib/os_mon/doc/src/notes.xml17
-rw-r--r--lib/os_mon/src/cpu_sup.erl4
-rw-r--r--lib/os_mon/src/memsup.erl2
-rw-r--r--lib/os_mon/vsn.mk2
-rw-r--r--lib/snmp/doc/src/notes.xml18
-rw-r--r--lib/snmp/vsn.mk2
-rw-r--r--lib/ssl/doc/src/notes.xml24
-rw-r--r--lib/ssl/doc/src/ssl.xml2
-rw-r--r--lib/ssl/src/dtls_connection.erl9
-rw-r--r--lib/ssl/src/ssl.erl8
-rw-r--r--lib/ssl/test/openssl_alpn_SUITE.erl2
-rw-r--r--lib/ssl/test/openssl_renegotiate_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_api_SUITE.erl8
-rw-r--r--lib/ssl/test/ssl_test_lib.erl26
-rw-r--r--lib/ssl/vsn.mk2
-rw-r--r--lib/stdlib/doc/src/gb_sets.xml46
-rw-r--r--lib/stdlib/doc/src/notes.xml16
-rw-r--r--lib/stdlib/doc/src/ordsets.xml5
-rw-r--r--lib/stdlib/doc/src/sets.xml65
-rw-r--r--lib/stdlib/src/erl_lint.erl48
-rw-r--r--lib/stdlib/src/sets.erl17
-rw-r--r--lib/stdlib/src/stdlib.appup.src8
-rw-r--r--lib/stdlib/test/erl_lint_SUITE.erl168
-rw-r--r--lib/stdlib/vsn.mk2
-rw-r--r--lib/syntax_tools/src/epp_dodger.erl2
-rw-r--r--lib/wx/api_gen/gl_gen.erl2
-rw-r--r--lib/wx/api_gen/gl_gen_erl.erl2
-rw-r--r--lib/wx/api_gen/gl_gen_nif.erl2
-rw-r--r--lib/wx/api_gen/wx_gen_nif.erl2
-rw-r--r--lib/wx/api_gen/wxapi.conf2
-rw-r--r--lib/wx/c_src/egl_impl.c2
-rw-r--r--lib/wx/c_src/wxe_gl.cpp2
-rw-r--r--lib/wx/c_src/wxe_gl.h2
-rw-r--r--lib/wx/c_src/wxe_impl.cpp2
-rw-r--r--lib/wx/c_src/wxe_impl.h2
-rw-r--r--lib/wx/c_src/wxe_nif.c2
-rw-r--r--lib/wx/c_src/wxe_return.cpp2
-rw-r--r--lib/wx/doc/src/gl.xml2
-rw-r--r--lib/wx/doc/src/notes.xml16
-rw-r--r--lib/wx/src/wxe_master.erl2
-rw-r--r--lib/wx/src/wxe_util.erl2
-rw-r--r--lib/wx/test/wx_class_SUITE.erl2
-rw-r--r--lib/wx/test/wx_opengl_SUITE.erl2
-rw-r--r--lib/wx/vsn.mk2
-rw-r--r--otp_versions.table2
-rw-r--r--system/doc/general_info/upcoming_incompatibilities.xml60
145 files changed, 4066 insertions, 1614 deletions
diff --git a/erts/config.h.in b/erts/config.h.in
index ee9390f1b9..d7fecf8631 100644
--- a/erts/config.h.in
+++ b/erts/config.h.in
@@ -89,6 +89,9 @@
/* Socket address dl length */
#undef ESOCK_SDL_LEN
+/* Use extended error info */
+#undef ESOCK_USE_EXTENDED_ERROR_INFO
+
/* Interface hwaddr supported */
#undef ESOCK_USE_HWADDR
diff --git a/erts/configure b/erts/configure
index 64db499912..a09c84ff09 100755
--- a/erts/configure
+++ b/erts/configure
@@ -860,6 +860,7 @@ with_threadnames
enable_builtin_zlib
enable_esock
enable_esock_use_rcvsndtimeo
+enable_esock_extended_error_info
with_esock_counter_size
enable_esock_socket_registry
with_clock_resolution
@@ -1596,6 +1597,10 @@ Optional Features:
--disable-esock-rcvsndtimeo
disable use of the option(s) rcvtimeo and sndtimeo
(default)
+ --enable-esock-extended-error-info
+ enable use of extended error info
+ --disable-esock-extended-error-info
+ disable use of extended error info (default)
--enable-esock-socket-registry
enable use of the socket registry by default
(default)
@@ -16070,6 +16075,21 @@ fi
+# Check whether --enable-esock_extended_error_info was given.
+if test ${enable_esock_extended_error_info+y}
+then :
+ enableval=$enable_esock_extended_error_info;
+fi
+
+
+if test "x$enable_esock_extended_error_info" != "xno"; then
+
+printf "%s\n" "#define ESOCK_USE_EXTENDED_ERROR_INFO 1" >>confdefs.h
+
+fi
+
+
+
# Check whether --with-esock-counter-size was given.
if test ${with_esock_counter_size+y}
diff --git a/erts/configure.ac b/erts/configure.ac
index d614637830..540cc4b3cb 100644
--- a/erts/configure.ac
+++ b/erts/configure.ac
@@ -1356,6 +1356,19 @@ if test "x$enable_esock_rcvsndtimeo" = "xyes"; then
fi
+dnl *** ESOCK_USE_EXTERNDED_ERROR_INFO ***
+
+AC_ARG_ENABLE(esock_extended_error_info,
+AS_HELP_STRING([--enable-esock-extended-error-info], [enable use of extended error info])
+AS_HELP_STRING([--disable-esock-extended-error-info], [disable use of extended error info (default)]))
+
+dnl Temporary! Currently we require eei to be *explicitly*
+dnl disabled (for debug reasons).
+if test "x$enable_esock_extended_error_info" != "xno"; then
+ AC_DEFINE(ESOCK_USE_EXTENDED_ERROR_INFO, [1], [Use extended error info])
+fi
+
+
dnl *** ESOCK_COUNTER_SIZE ***
AC_ARG_WITH(esock-counter-size,
diff --git a/erts/doc/src/notes.xml b/erts/doc/src/notes.xml
index c3e77c0fbd..1f372823e8 100644
--- a/erts/doc/src/notes.xml
+++ b/erts/doc/src/notes.xml
@@ -31,6 +31,102 @@
</header>
<p>This document describes the changes made to the ERTS application.</p>
+<section><title>Erts 13.2.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ If a runtime system which was starting the distribution
+ already had existing pids, ports, or references referring
+ to a node with the same nodename/creation pair that the
+ runtime system was about to use, these already existing
+ pids, ports, or references would not work as expected in
+ various situations after the node had gone alive. This
+ could only occur if the runtime system was communicated
+ such pids, ports, or references prior to the distribution
+ was started. That is, it was extremely unlikely to happen
+ unless the distribution was started dynamically and was
+ even then very unlikely to happen. The runtime system now
+ checks for already existing pids, ports, and references
+ with the same nodename/creation pair that it is about to
+ use. If such are found another creation will be chosen in
+ order to avoid these issues.</p>
+ <p>
+ Own Id: OTP-18570 Aux Id: PR-7190 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 13.2.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed a bug in the loader that prevented certain
+ modules compiled with <c>no_ssa_opt</c> from being
+ loaded.</p>
+ <p>
+ Own Id: OTP-18519 Aux Id: GH-7024 </p>
+ </item>
+ <item>
+ <p>
+ Implementations of the <seecref
+ marker="erts:driver_entry#call"><c>call()</c></seecref>
+ driver callback that returned a faulty encoded result
+ could cause a memory leak and could cause invalid data on
+ the heap of the processes calling <seemfa
+ marker="erts:erlang#port_call/3"><c>erlang:port_call/3</c></seemfa>.</p>
+ <p>
+ Own Id: OTP-18525 Aux Id: PR-7049 </p>
+ </item>
+ <item>
+ <p>Fixed a memory corruption issue when upgrading code.
+ The bug was introduced in <c>OTP 25.3</c></p>
+ <p>
+ Own Id: OTP-18553</p>
+ </item>
+ <item>
+ <p>Fixed configure tests for a few ARM-specific
+ instructions, which prevented the emulator from being
+ built on some platforms.</p>
+ <p>
+ Own Id: OTP-18554</p>
+ </item>
+ <item>
+ <p>
+ Aliases created in combination with a monitor using the
+ <c>{alias, explicit_unalias}</c> option stopped working
+ from remote nodes when a <c>'DOWN'</c> signal had been
+ received due to the monitor or if the monitor was removed
+ using the <c>erlang:demonitor()</c> BIF.</p>
+ <p>
+ This bug was introduced in OTP 24.3.4.10 and OTP 25.3.</p>
+ <p>
+ Own Id: OTP-18557 Aux Id: PR-7131, OTP-18496 </p>
+ </item>
+ <item>
+ <p>In rare circumstances, bit syntax matching of an
+ invalid code point for a <c>utf32</c> would crash the
+ runtime system.</p>
+ <p>
+ Own Id: OTP-18560</p>
+ </item>
+ <item>
+ <p>
+ Building the runtime system failed when native atomic
+ support was missing. Note that execution on such systems
+ have only been rudimentary tested.</p>
+ <p>
+ Own Id: OTP-18563 Aux Id: GH-7114, PR-7159 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erts 13.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/erts/emulator/beam/dist.c b/erts/emulator/beam/dist.c
index cd67b8db2a..158f7116a2 100644
--- a/erts/emulator/beam/dist.c
+++ b/erts/emulator/beam/dist.c
@@ -4884,8 +4884,21 @@ BIF_RETTYPE setnode_2(BIF_ALIST_2)
erts_thr_progress_block();
success = (!ERTS_PROC_IS_EXITING(net_kernel)
- & !ERTS_PROC_GET_DIST_ENTRY(net_kernel));
+ && !ERTS_PROC_GET_DIST_ENTRY(net_kernel));
if (success) {
+ /*
+ * Ensure we don't use a nodename-creation pair with
+ * external identifiers existing in the system.
+ */
+ while (!0) {
+ ErlNode *nep;
+ if (creation < 4)
+ creation = 4;
+ nep = erts_find_node(BIF_ARG_1, creation);
+ if (!nep || erts_node_refc(nep) == 0)
+ break;
+ creation++;
+ }
inc_no_nodes();
erts_set_this_node(BIF_ARG_1, (Uint32) creation);
erts_this_dist_entry->creation = creation;
diff --git a/erts/emulator/beam/erl_bif_info.c b/erts/emulator/beam/erl_bif_info.c
index 8ba4a7a3ae..4ba368ec7c 100644
--- a/erts/emulator/beam/erl_bif_info.c
+++ b/erts/emulator/beam/erl_bif_info.c
@@ -4275,6 +4275,13 @@ BIF_RETTYPE erts_debug_get_internal_state_1(BIF_ALIST_1)
BIF_RET(am_ok);
}
#endif
+ else if (ERTS_IS_ATOM_STR("hashmap_collision_bonanza", BIF_ARG_1)) {
+#ifdef DBG_HASHMAP_COLLISION_BONANZA
+ return am_true;
+#else
+ return am_false;
+#endif
+ }
}
else if (is_tuple(BIF_ARG_1)) {
Eterm* tp = tuple_val(BIF_ARG_1);
diff --git a/erts/emulator/beam/erl_db_util.c b/erts/emulator/beam/erl_db_util.c
index 38d755f7f6..af5aa09a5c 100644
--- a/erts/emulator/beam/erl_db_util.c
+++ b/erts/emulator/beam/erl_db_util.c
@@ -947,8 +947,8 @@ static Eterm dmc_lookup_bif_reversed(void *f);
static int cmp_uint(void *a, void *b);
static int cmp_guard_bif(void *a, void *b);
static int match_compact(ErlHeapFragment *expr, DMCErrInfo *err_info);
-static Uint my_size_object(Eterm t);
-static Eterm my_copy_struct(Eterm t, Eterm **hp, ErlOffHeap* off_heap);
+static Uint my_size_object(Eterm t, int is_hashmap_node);
+static Eterm my_copy_struct(Eterm t, Eterm **hp, ErlOffHeap* off_heap, int);
/* Guard subroutines */
static void
@@ -4130,11 +4130,11 @@ static void do_emit_constant(DMCContext *context, DMC_STACK_TYPE(UWord) *text,
if (is_immed(t)) {
tmp = t;
} else {
- sz = my_size_object(t);
+ sz = my_size_object(t, 0);
if (sz) {
emb = new_message_buffer(sz);
hp = emb->mem;
- tmp = my_copy_struct(t,&hp,&(emb->off_heap));
+ tmp = my_copy_struct(t,&hp,&(emb->off_heap), 0);
emb->next = context->save;
context->save = emb;
}
@@ -5672,33 +5672,39 @@ static int match_compact(ErlHeapFragment *expr, DMCErrInfo *err_info)
/*
** Simple size object that takes care of function calls and constant tuples
*/
-static Uint my_size_object(Eterm t)
+static Uint my_size_object(Eterm t, int is_hashmap_node)
{
Uint sum = 0;
- Eterm tmp;
Eterm *p;
switch (t & _TAG_PRIMARY_MASK) {
case TAG_PRIMARY_LIST:
- sum += 2 + my_size_object(CAR(list_val(t))) +
- my_size_object(CDR(list_val(t)));
+ sum += 2 + my_size_object(CAR(list_val(t)), 0) +
+ my_size_object(CDR(list_val(t)), 0);
break;
case TAG_PRIMARY_BOXED:
if (is_tuple(t)) {
- if (tuple_val(t)[0] == make_arityval(1) && is_tuple(tmp = tuple_val(t)[1])) {
- Uint i,n;
- p = tuple_val(tmp);
- n = arityval(p[0]);
- sum += 1 + n;
- for (i = 1; i <= n; ++i)
- sum += my_size_object(p[i]);
- } else if (tuple_val(t)[0] == make_arityval(2) &&
- is_atom(tmp = tuple_val(t)[1]) &&
- tmp == am_const) {
+ Eterm* tpl = tuple_val(t);
+ Uint i,n;
+
+ if (is_hashmap_node) {
+ /* hashmap collision node, no matchspec syntax here */
+ }
+ else if (tpl[0] == make_arityval(1) && is_tuple(tpl[1])) {
+ tpl = tuple_val(tpl[1]);
+ }
+ else if (tpl[0] == make_arityval(2) && tpl[1] == am_const) {
sum += size_object(tuple_val(t)[2]);
- } else {
+ break;
+ }
+ else {
erts_exit(ERTS_ERROR_EXIT,"Internal error, sizing unrecognized object in "
"(d)ets:match compilation.");
}
+
+ n = arityval(tpl[0]);
+ sum += 1 + n;
+ for (i = 1; i <= n; ++i)
+ sum += my_size_object(tpl[i], 0);
break;
} else if (is_map(t)) {
if (is_flatmap(t)) {
@@ -5711,7 +5717,7 @@ static Uint my_size_object(Eterm t)
n = arityval(p[0]);
sum += 1 + n;
for (int i = 1; i <= n; ++i)
- sum += my_size_object(p[i]);
+ sum += my_size_object(p[i], 0);
/* Calculate size of values */
p = (Eterm *)mp;
@@ -5719,18 +5725,19 @@ static Uint my_size_object(Eterm t)
sum += n + 3;
p += 3; /* hdr + size + keys words */
while (n--) {
- sum += my_size_object(*p++);
+ sum += my_size_object(*p++, 0);
}
} else {
Eterm *head = (Eterm *)hashmap_val(t);
Eterm hdr = *head;
Uint sz;
+
sz = hashmap_bitcount(MAP_HEADER_VAL(hdr));
sum += 1 + sz + header_arity(hdr);
head += 1 + header_arity(hdr);
while(sz-- > 0) {
- sum += my_size_object(head[sz]);
+ sum += my_size_object(head[sz], 1);
}
}
break;
@@ -5743,46 +5750,54 @@ static Uint my_size_object(Eterm t)
return sum;
}
-static Eterm my_copy_struct(Eterm t, Eterm **hp, ErlOffHeap* off_heap)
+static Eterm my_copy_struct(Eterm t, Eterm **hp, ErlOffHeap* off_heap,
+ int is_hashmap_node)
{
Eterm ret = NIL, a, b;
Eterm *p;
Uint sz;
switch (t & _TAG_PRIMARY_MASK) {
case TAG_PRIMARY_LIST:
- a = my_copy_struct(CAR(list_val(t)), hp, off_heap);
- b = my_copy_struct(CDR(list_val(t)), hp, off_heap);
+ a = my_copy_struct(CAR(list_val(t)), hp, off_heap, 0);
+ b = my_copy_struct(CDR(list_val(t)), hp, off_heap, 0);
ret = CONS(*hp, a, b);
*hp += 2;
break;
case TAG_PRIMARY_BOXED:
if (is_tuple(t)) {
- if (tuple_val(t)[0] == make_arityval(1) &&
- is_tuple(a = tuple_val(t)[1])) {
- Uint i,n;
- p = tuple_val(a);
- n = arityval(p[0]);
- if (n == 0) {
- ret = ERTS_GLOBAL_LIT_EMPTY_TUPLE;
- } else {
- Eterm *savep = *hp;
- ret = make_tuple(savep);
- *hp += n + 1;
- *savep++ = make_arityval(n);
- for(i = 1; i <= n; ++i)
- *savep++ = my_copy_struct(p[i], hp, off_heap);
- }
+ Eterm* tpl = tuple_val(t);
+ Uint i,n;
+ Eterm *savep;
+
+ if (is_hashmap_node) {
+ /* hashmap collision node, no matchspec syntax here */
+ }
+ else if (tpl[0] == make_arityval(1) && is_tuple(tpl[1])) {
+ /* A {{...}} expression */
+ tpl = tuple_val(tpl[1]);
}
- else if (tuple_val(t)[0] == make_arityval(2) &&
- tuple_val(t)[1] == am_const) {
+ else if (tpl[0] == make_arityval(2) && tpl[1] == am_const) {
/* A {const, XXX} expression */
- b = tuple_val(t)[2];
+ b = tpl[2];
sz = size_object(b);
ret = copy_struct(b,sz,hp,off_heap);
+ break;
} else {
erts_exit(ERTS_ERROR_EXIT, "Trying to constant-copy non constant expression "
"0x%bex in (d)ets:match compilation.", t);
}
+ n = arityval(tpl[0]);
+ if (n == 0) {
+ ret = ERTS_GLOBAL_LIT_EMPTY_TUPLE;
+ } else {
+ savep = *hp;
+ ret = make_tuple(savep);
+ *hp += n + 1;
+ *savep++ = tpl[0];
+ for(i = 1; i <= n; ++i)
+ *savep++ = my_copy_struct(tpl[i], hp, off_heap, 0);
+ }
+
} else if (is_map(t)) {
if (is_flatmap(t)) {
Uint i,n;
@@ -5803,7 +5818,7 @@ static Eterm my_copy_struct(Eterm t, Eterm **hp, ErlOffHeap* off_heap)
*hp += n + 1;
*savep++ = make_arityval(n);
for(i = 1; i <= n; ++i)
- *savep++ = my_copy_struct(p[i], hp, off_heap);
+ *savep++ = my_copy_struct(p[i], hp, off_heap, 0);
}
savep = *hp;
ret = make_flatmap(savep);
@@ -5815,7 +5830,7 @@ static Eterm my_copy_struct(Eterm t, Eterm **hp, ErlOffHeap* off_heap)
*savep++ = keys;
p += 3; /* hdr + size + keys words */
for (i = 0; i < n; i++)
- *savep++ = my_copy_struct(p[i], hp, off_heap);
+ *savep++ = my_copy_struct(p[i], hp, off_heap, 0);
erts_usort_flatmap((flatmap_t*)flatmap_val(ret));
} else {
Eterm *head = hashmap_val(t);
@@ -5832,7 +5847,7 @@ static Eterm my_copy_struct(Eterm t, Eterm **hp, ErlOffHeap* off_heap)
*savep++ = *head++; /* map size */
for (int i = 0; i < sz; i++) {
- *savep++ = my_copy_struct(head[i],hp,off_heap);
+ *savep++ = my_copy_struct(head[i],hp,off_heap, 1);
}
}
} else {
diff --git a/erts/emulator/beam/erl_map.c b/erts/emulator/beam/erl_map.c
index 8a8265968a..337567406b 100644
--- a/erts/emulator/beam/erl_map.c
+++ b/erts/emulator/beam/erl_map.c
@@ -95,8 +95,8 @@ static Uint hashmap_subtree_size(Eterm node);
static Eterm hashmap_delete(Process *p, Uint32 hx, Eterm key, Eterm node, Eterm *value);
static Eterm flatmap_from_validated_list(Process *p, Eterm list, Eterm fill_value, Uint size);
static Eterm hashmap_from_unsorted_array(ErtsHeapFactory*, hxnode_t *hxns, Uint n, int reject_dupkeys, ErtsAlcType_t temp_memory_allocator);
-static Eterm hashmap_from_sorted_unique_array(ErtsHeapFactory*, hxnode_t *hxns, Uint n, int is_root, ErtsAlcType_t temp_memory_allocator);
-static Eterm hashmap_from_chunked_array(ErtsHeapFactory*, hxnode_t *hxns, Uint n, Uint size, int is_root, ErtsAlcType_t temp_memory_allocator);
+static Eterm hashmap_from_sorted_unique_array(ErtsHeapFactory*, hxnode_t *hxns, Uint n, ErtsAlcType_t temp_memory_allocator);
+static Eterm hashmap_from_chunked_array(ErtsHeapFactory*, hxnode_t *hxns, Uint n, Uint size, ErtsAlcType_t temp_memory_allocator);
static Eterm hashmap_info(Process *p, Eterm node);
static Eterm hashmap_bld_tuple_uint(Uint **hpp, Uint *szp, Uint n, Uint nums[]);
static int hxnodecmp(const void* a, const void* b);
@@ -112,8 +112,7 @@ static int hxnodecmpkey(const void* a, const void* b);
#define maskval(V,L) (((V) >> ((7 - (L))*4)) & 0xf)
#define DBG_PRINT(X)
/*erts_printf X*/
-#define HALLOC_EXTRA_HASHMAP_FROM_CHUNKED_ARRAY 200
-#define HALLOC_EXTRA HALLOC_EXTRA_HASHMAP_FROM_CHUNKED_ARRAY
+#define HALLOC_EXTRA 200
/* *******************************
* ** Yielding C Fun (YCF) Note **
* *******************************
@@ -135,7 +134,6 @@ static int hxnodecmpkey(const void* a, const void* b);
#include "erl_map.ycf.h"
#endif
#define NOT_YCF_YIELDING_VERSION 1
-#undef HALLOC_EXTRA
#define YCF_CONSUME_REDS(X) while(0){}
void erts_init_map(void) {
@@ -282,6 +280,7 @@ BIF_RETTYPE map_get_2(BIF_ALIST_2) {
* means that the code has to follow some restrictions. See note about
* YCF near the top of the file for more information.
*/
+
#ifdef INCLUDE_YCF_TRANSFORMED_ONLY_FUNCTIONS
static BIF_RETTYPE maps_from_keys_2_helper(Process* p, Eterm* bif_args) {
Eterm list = bif_args[0];
@@ -677,33 +676,20 @@ Eterm erts_hashmap_from_array(ErtsHeapFactory* factory, Eterm *leafs, Uint n,
static ERTS_INLINE Eterm
from_ks_and_vs(ErtsHeapFactory *factory, Eterm *ks, Eterm *vs,
- Uint n, Eterm *key_tuple, flatmap_t **fmpp)
+ Uint n, flatmap_t **fmpp)
{
if (n <= MAP_SMALL_MAP_LIMIT) {
Eterm *hp;
flatmap_t *fmp;
Eterm keys;
- if (key_tuple && is_value(*key_tuple)) {
- keys = *key_tuple;
- hp = erts_produce_heap(factory, MAP_HEADER_FLATMAP_SZ + n, 0);
- ASSERT(is_tuple_arity(keys, n));
- ASSERT(n == 0 || sys_memcmp((void *) (tuple_val(keys) + 1),
- (void *) ks,
- n * sizeof(Eterm)) == 0);
- }
- else if (n == 0) {
+ if (n == 0) {
keys = ERTS_GLOBAL_LIT_EMPTY_TUPLE;
- if (key_tuple)
- *key_tuple = keys;
hp = erts_produce_heap(factory, MAP_HEADER_FLATMAP_SZ + n, 0);
}
else {
hp = erts_produce_heap(factory, 1 + MAP_HEADER_FLATMAP_SZ + 2*n, 0);
keys = make_tuple(hp);
- if (key_tuple) {
- *key_tuple = keys;
- }
*hp++ = make_arityval(n);
sys_memcpy((void *) hp,
(void *) ks,
@@ -720,15 +706,10 @@ from_ks_and_vs(ErtsHeapFactory *factory, Eterm *ks, Eterm *vs,
sys_memcpy((void *) hp, (void *) vs, n * sizeof(Eterm));
- if (fmpp) {
- *fmpp = fmp;
- return THE_NON_VALUE;
- }
- return make_flatmap(fmp);
+ *fmpp = fmp;
+ return THE_NON_VALUE;
} else {
- if (fmpp) {
- *fmpp = NULL;
- }
+ *fmpp = NULL;
return erts_hashmap_from_ks_and_vs(factory, ks, vs, n);
}
}
@@ -738,7 +719,7 @@ Eterm erts_map_from_ks_and_vs(ErtsHeapFactory *factory, Eterm *ks, Eterm *vs, Ui
Eterm res;
flatmap_t *fmp;
- res = from_ks_and_vs(factory, ks, vs, n, NULL, &fmp);
+ res = from_ks_and_vs(factory, ks, vs, n, &fmp);
if (fmp) {
if (erts_validate_and_sort_flatmap(fmp)) {
res = make_flatmap(fmp);
@@ -806,14 +787,7 @@ static Eterm hashmap_from_unsorted_array(ErtsHeapFactory* factory,
Uint cx;
Eterm res;
- if (n == 0) {
- Eterm *hp;
- hp = erts_produce_heap(factory, 2, 0);
- hp[0] = MAP_HEADER_HAMT_HEAD_BITMAP(0);
- hp[1] = 0;
-
- return make_hashmap(hp);
- }
+ ASSERT(n > 0);
/* sort and compact array (remove non-unique entries) */
erts_qsort(hxns, n, sizeof(hxnode_t), hxnodecmp);
@@ -823,7 +797,7 @@ static Eterm hashmap_from_unsorted_array(ErtsHeapFactory* factory,
if (hxns[ix].hx == hxns[ix+1].hx) {
/* find region of equal hash values */
- jx = ix + 1;
+ jx = ix + 2;
while(jx < n && hxns[ix].hx == hxns[jx].hx) { jx++; }
/* find all correct keys from region
* (last in list but now hash sorted so we check highest id instead) */
@@ -866,7 +840,8 @@ static Eterm hashmap_from_unsorted_array(ErtsHeapFactory* factory,
if (cx > 1) {
/* recursive decompose array */
- res = hashmap_from_sorted_unique_array(factory, hxns, cx, 0, temp_memory_allocator);
+ res = hashmap_from_sorted_unique_array(factory, hxns, cx,
+ temp_memory_allocator);
} else {
Eterm *hp;
@@ -893,51 +868,49 @@ static Eterm hashmap_from_unsorted_array(ErtsHeapFactory* factory,
* YCF near the top of the file for more information.
*/
static Eterm hashmap_from_sorted_unique_array(ErtsHeapFactory* factory,
- hxnode_t *hxns, Uint n, int lvl,
+ hxnode_t *hxns, Uint n,
ErtsAlcType_t temp_memory_allocator) {
Eterm res = NIL;
- Uint i;
Uint ix;
- Uint jx;
Uint elems;
- Uint32 sw;
- Uint32 hx;
- Eterm val;
hxnode_t *tmp = NULL;
- ASSERT(lvl < 32);
+
ix = 0;
elems = 1;
while (ix < n - 1) {
if (hxns[ix].hx == hxns[ix+1].hx) {
- jx = ix + 1;
- while (jx < n && hxns[ix].hx == hxns[jx].hx) { jx++; }
- tmp = (hxnode_t *)erts_alloc(temp_memory_allocator, ((jx - ix)) * sizeof(hxnode_t));
-
- for(i = 0; i < jx - ix; i++) {
- val = hxns[i + ix].val;
- hx = hashmap_restore_hash(lvl + 8, CAR(list_val(val)));
- swizzle32(sw,hx);
- tmp[i].hx = sw;
- tmp[i].val = val;
- tmp[i].i = i;
- tmp[i].skip = 1;
- }
+ Uint n_colliders;
+ Eterm* hp;
+ Eterm collision_node;
+ Uint jx = ix + 2;
+ Uint i;
+
+ while (jx < n && hxns[ix].hx == hxns[jx].hx)
+ jx++;
+
+ n_colliders = jx - ix;
+ hp = erts_produce_heap(factory, HAMT_COLLISION_NODE_SZ(n_colliders),
+ HALLOC_EXTRA);
+ collision_node = make_tuple(hp);
+
+ *hp++ = MAP_HEADER_HAMT_COLLISION_NODE(n_colliders);
+ for (i = 0; i < n_colliders; i++) {
+ *hp++ = hxns[ix + i].val;
+ ASSERT(i == 0
+ || CMP_TERM(CAR(list_val(hxns[ix+i-1].val)),
+ CAR(list_val(hxns[ix+i].val))) < 0);
+ }
- erts_qsort(tmp, jx - ix, sizeof(hxnode_t), hxnodecmp);
+ hxns[ix].val = collision_node;
+ hxns[ix].skip = n_colliders;
+ ix = jx;
- hxns[ix].skip = jx - ix;
- hxns[ix].val =
- hashmap_from_sorted_unique_array(factory, tmp, jx - ix, lvl + 8, temp_memory_allocator);
- erts_free(temp_memory_allocator, (void *) tmp);
- /* Memory management depend on the statement below */
- tmp = NULL;
- ix = jx;
- if (ix < n) { elems++; }
- continue;
+ if (ix < n) { elems++; }
+ continue;
}
- hxns[ix].skip = 1;
- elems++;
- ix++;
+ hxns[ix].skip = 1;
+ elems++;
+ ix++;
}
YCF_SPECIAL_CODE_START(ON_DESTROY_STATE);
{
@@ -948,14 +921,13 @@ static Eterm hashmap_from_sorted_unique_array(ErtsHeapFactory* factory,
}
}
YCF_SPECIAL_CODE_END();
- res = hashmap_from_chunked_array(factory, hxns, elems, n, !lvl, temp_memory_allocator);
+ res = hashmap_from_chunked_array(factory, hxns, elems, n, temp_memory_allocator);
ERTS_FACTORY_HOLE_CHECK(factory);
return res;
}
-#define HALLOC_EXTRA HALLOC_EXTRA_HASHMAP_FROM_CHUNKED_ARRAY
/* **Important Note**
*
* A yielding version of this function is generated with YCF. This
@@ -963,7 +935,7 @@ static Eterm hashmap_from_sorted_unique_array(ErtsHeapFactory* factory,
* YCF near the top of the file for more information.
*/
static Eterm hashmap_from_chunked_array(ErtsHeapFactory *factory, hxnode_t *hxns, Uint n,
- Uint size, int is_root,
+ Uint size,
ErtsAlcType_t temp_memory_allocator) {
Uint ix;
Uint d;
@@ -1015,16 +987,11 @@ static Eterm hashmap_from_chunked_array(ErtsHeapFactory *factory, hxnode_t *hxns
}
slot = maskval(v,0);
- hp = erts_produce_heap(factory, (is_root ? 3 : 2), 0);
+ hp = erts_produce_heap(factory, 3, 0);
- if (is_root) {
- hp[0] = MAP_HEADER_HAMT_HEAD_BITMAP(1 << slot);
- hp[1] = size;
- hp[2] = res;
- } else {
- hp[0] = MAP_HEADER_HAMT_NODE_BITMAP(1 << slot);
- hp[1] = res;
- }
+ hp[0] = MAP_HEADER_HAMT_HEAD_BITMAP(1 << slot);
+ hp[1] = size;
+ hp[2] = res;
return make_hashmap(hp);
}
@@ -1186,15 +1153,11 @@ static Eterm hashmap_from_chunked_array(ErtsHeapFactory *factory, hxnode_t *hxns
bp = 1 << slot;
hdr |= bp;
sz = hashmap_bitcount(hdr);
- hp = erts_produce_heap(factory, sz + /* hdr + item */ (is_root ? 2 : 1), 0);
+ hp = erts_produce_heap(factory, sz + /* hdr + item */ 2, 0);
nhp = hp;
- if (is_root) {
- *hp++ = (hdr == 0xffff) ? MAP_HEADER_HAMT_HEAD_ARRAY : MAP_HEADER_HAMT_HEAD_BITMAP(hdr);
- *hp++ = size;
- } else {
- *hp++ = MAP_HEADER_HAMT_NODE_BITMAP(hdr);
- }
+ *hp++ = (hdr == 0xffff) ? MAP_HEADER_HAMT_HEAD_ARRAY : MAP_HEADER_HAMT_HEAD_BITMAP(hdr);
+ *hp++ = size;
*hp++ = res; sz--;
while (sz--) { *hp++ = ESTACK_POP(stack); }
@@ -1206,7 +1169,6 @@ static Eterm hashmap_from_chunked_array(ErtsHeapFactory *factory, hxnode_t *hxns
ERTS_FACTORY_HOLE_CHECK(factory);
return res;
}
-#undef HALLOC_EXTRA
static int hxnodecmpkey(const void *va, const void *vb) {
const hxnode_t *a = (const hxnode_t*) va;
@@ -1555,9 +1517,57 @@ BIF_RETTYPE maps_merge_trap_1(BIF_ALIST_1) {
(HashmapMergeContext*) ERTS_MAGIC_BIN_DATA(ctx_bin));
}
-#define HALLOC_EXTRA 200
#define MAP_MERGE_LOOP_FACTOR 8
+static Eterm merge_collision_node(Process* p,
+ Eterm* srcA, Uint szA,
+ Eterm* srcB, Uint szB,
+ Uint* map_sizep)
+{
+ Eterm *hp;
+ Eterm *hdr_ptr;
+ Eterm *hp_end;
+ Uint arity;
+
+ ERTS_ASSERT(szA >= 1 && szB >= 1);
+ arity = szA + szB;
+ hp = HAlloc(p, HAMT_COLLISION_NODE_SZ(arity));
+ hp_end = hp + HAMT_COLLISION_NODE_SZ(arity);
+ hdr_ptr = hp++;
+
+ while (szA && szB) {
+ Eterm keyA = CAR(list_val(*srcA));
+ Eterm keyB = CAR(list_val(*srcB));
+ const Sint key_cmp = CMP_TERM(keyA, keyB);
+
+ if (key_cmp < 0) {
+ *hp++ = *srcA++;
+ szA--;
+ }
+ else {
+ *hp++ = *srcB++;
+ szB--;
+ if (key_cmp == 0) {
+ srcA++;
+ szA--;
+ arity--;
+ (*map_sizep)--;
+ }
+ }
+ }
+ ASSERT(arity >= 2);
+
+ for ( ; szA; szA--)
+ *hp++ = *srcA++;
+ for ( ; szB; szB--)
+ *hp++ = *srcB++;
+
+ HRelease(p, hp_end, hp);
+ *hdr_ptr = make_arityval(arity);
+ return make_tuple(hdr_ptr);
+}
+
+
static BIF_RETTYPE hashmap_merge(Process *p, Eterm map_A, Eterm map_B,
int swap_args, HashmapMergeContext* ctx) {
#define PSTACK_TYPE struct HashmapMergePStackType
@@ -1571,6 +1581,7 @@ static BIF_RETTYPE hashmap_merge(Process *p, Eterm map_A, Eterm map_B,
Eterm trap_ret;
Sint initial_reds = (Sint) (ERTS_BIF_REDS_LEFT(p) * MAP_MERGE_LOOP_FACTOR);
Sint reds = initial_reds;
+ Uint coll_szA = 0, coll_szB = 0;
/*
* Strategy: Do depth-first traversal of both trees (at the same time)
@@ -1630,8 +1641,13 @@ recurse:
goto merge_nodes;
}
}
- hx = hashmap_restore_hash(ctx->lvl, keyA);
- sp->abm = 1 << hashmap_index(hx);
+ if (ctx->lvl < HAMT_MAX_LEVEL) {
+ hx = hashmap_restore_hash(ctx->lvl, keyA);
+ sp->abm = 1 << hashmap_index(hx);
+ }
+ else {
+ coll_szA = 1;
+ }
/* keep srcA pointing at the leaf */
}
else { /* A is NODE */
@@ -1640,25 +1656,35 @@ recurse:
ASSERT(is_header(hdrA));
switch (hdrA & _HEADER_MAP_SUBTAG_MASK) {
case HAMT_SUBTAG_HEAD_ARRAY: {
+ ASSERT(ctx->lvl < HAMT_MAX_LEVEL);
sp->srcA++;
sp->abm = 0xffff;
break;
}
case HAMT_SUBTAG_HEAD_BITMAP: sp->srcA++;
case HAMT_SUBTAG_NODE_BITMAP: {
+ ASSERT(ctx->lvl < HAMT_MAX_LEVEL);
sp->abm = MAP_HEADER_VAL(hdrA);
break;
}
- default:
- erts_exit(ERTS_ABORT_EXIT, "bad header %ld\r\n", hdrA);
+ default: /* collision node */
+ ERTS_ASSERT(is_arity_value(hdrA));
+ ASSERT(ctx->lvl == HAMT_MAX_LEVEL);
+ coll_szA = arityval(hdrA);
+ ASSERT(coll_szA >= 2);
}
}
if (is_list(sp->nodeB)) { /* B is LEAF */
Eterm keyB = CAR(list_val(sp->nodeB));
- hx = hashmap_restore_hash(ctx->lvl, keyB);
- sp->bbm = 1 << hashmap_index(hx);
+ if (ctx->lvl < HAMT_MAX_LEVEL) {
+ hx = hashmap_restore_hash(ctx->lvl, keyB);
+ sp->bbm = 1 << hashmap_index(hx);
+ }
+ else {
+ coll_szB = 1;
+ }
/* keep srcB pointing at the leaf */
}
else { /* B is NODE */
@@ -1667,17 +1693,22 @@ recurse:
ASSERT(is_header(hdrB));
switch (hdrB & _HEADER_MAP_SUBTAG_MASK) {
case HAMT_SUBTAG_HEAD_ARRAY: {
+ ASSERT(ctx->lvl < HAMT_MAX_LEVEL);
sp->srcB++;
sp->bbm = 0xffff;
break;
}
case HAMT_SUBTAG_HEAD_BITMAP: sp->srcB++;
case HAMT_SUBTAG_NODE_BITMAP: {
+ ASSERT(ctx->lvl < HAMT_MAX_LEVEL);
sp->bbm = MAP_HEADER_VAL(hdrB);
break;
}
- default:
- erts_exit(ERTS_ABORT_EXIT, "bad header %ld\r\n", hdrB);
+ default: /* collision node */
+ ERTS_ASSERT(is_arity_value(hdrB));
+ ASSERT(ctx->lvl == HAMT_MAX_LEVEL);
+ coll_szB = arityval(hdrB);
+ ASSERT(coll_szB >= 2);
}
}
}
@@ -1701,13 +1732,22 @@ merge_nodes:
sp->srcA++;
sp->srcB++;
}
- } else { /* Start build a node */
+ }
+ else if (ctx->lvl < HAMT_MAX_LEVEL) { /* Start build a node */
sp->ix = 0;
sp->rbm = sp->abm | sp->bbm;
ASSERT(!(sp->rbm == 0 && ctx->lvl > 0));
}
+ else {
+ res = merge_collision_node(p, sp->srcA, coll_szA,
+ sp->srcB, coll_szB, &ctx->size);
+ sp->mix = 3;
+ coll_szA = coll_szB = 0;
+ continue;
+ }
if (--reds <= 0) {
+ ASSERT(!coll_szA && !coll_szB);
goto trap;
}
resume_from_trap:
@@ -1851,21 +1891,14 @@ static int hash_cmp(Uint32 ha, Uint32 hb)
int hashmap_key_hash_cmp(Eterm* ap, Eterm* bp)
{
- unsigned int lvl = 0;
-
if (ap && bp) {
+ Uint32 ha, hb;
ASSERT(CMP_TERM(CAR(ap), CAR(bp)) != 0);
- for (;;) {
- Uint32 ha = hashmap_restore_hash(lvl, CAR(ap));
- Uint32 hb = hashmap_restore_hash(lvl, CAR(bp));
- int cmp = hash_cmp(ha, hb);
- if (cmp) {
- return cmp;
- }
- lvl += 8;
- }
+ ha = hashmap_make_hash(CAR(ap));
+ hb = hashmap_make_hash(CAR(bp));
+ return hash_cmp(ha, hb);
}
-
+ ASSERT(ap || bp);
return ap ? -1 : 1;
}
@@ -2300,7 +2333,9 @@ Uint hashmap_node_size(Eterm hdr, Eterm **nodep)
ASSERT(sz < 17);
break;
default:
- erts_exit(ERTS_ABORT_EXIT, "bad header");
+ ERTS_ASSERT(is_arity_value(hdr));
+ sz = arityval(hdr);
+ break;
}
return sz;
}
@@ -2394,7 +2429,7 @@ Eterm* hashmap_iterator_prev(ErtsWStack* s) {
const Eterm *
erts_hashmap_get(Uint32 hx, Eterm key, Eterm node)
{
- Eterm *ptr, hdr, *res;
+ Eterm *ptr, hdr;
Uint ix, lvl = 0;
Uint32 hval,bp;
@@ -2405,15 +2440,16 @@ erts_hashmap_get(Uint32 hx, Eterm key, Eterm node)
ASSERT(is_hashmap_header_head(hdr));
ptr++;
- for (;;) {
+ do {
+ ASSERT(lvl == 0 || is_hashmap_header_node(hdr));
+
hval = MAP_HEADER_VAL(hdr);
ix = hashmap_index(hx);
if (hval != 0xffff) {
bp = 1 << ix;
if (!(bp & hval)) {
/* not occupied */
- res = NULL;
- break;
+ return NULL;
}
ix = hashmap_bitcount(hval & (bp - 1));
}
@@ -2421,8 +2457,7 @@ erts_hashmap_get(Uint32 hx, Eterm key, Eterm node)
if (is_list(node)) { /* LEAF NODE [K|V] */
ptr = list_val(node);
- res = EQ(CAR(ptr), key) ? &(CDR(ptr)) : NULL;
- break;
+ return EQ(CAR(ptr), key) ? &(CDR(ptr)) : NULL;
}
hx = hashmap_shift_hash(hx,lvl,key);
@@ -2431,10 +2466,17 @@ erts_hashmap_get(Uint32 hx, Eterm key, Eterm node)
ptr = boxed_val(node);
hdr = *ptr;
ASSERT(is_header(hdr));
- ASSERT(!is_hashmap_header_head(hdr));
- }
+ } while (!is_arity_value(hdr));
- return res;
+ /* collision node */
+ ix = arityval(hdr);
+ ASSERT(ix > 1);
+ do {
+ Eterm* kv = list_val(*(++ptr));
+ if (EQ(CAR(kv), key))
+ return &(CDR(kv));
+ } while (--ix > 0);
+ return NULL;
}
Eterm erts_hashmap_insert(Process *p, Uint32 hx, Eterm key, Eterm value,
@@ -2448,6 +2490,8 @@ Eterm erts_hashmap_insert(Process *p, Uint32 hx, Eterm key, Eterm value,
/* We are putting a new value (under a new or existing key) */
hp = HAlloc(p, size);
res = erts_hashmap_insert_up(hp, key, value, upsz, &stack);
+ ASSERT(hashmap_val(res) + 2 + hashmap_bitcount(MAP_HEADER_VAL(*hashmap_val(res)))
+ == hp + size);
}
else {
/* We are putting the same key-value */
@@ -2555,9 +2599,34 @@ int erts_hashmap_insert_down(Uint32 hx, Eterm key, Eterm value, Eterm node, Uint
}
size += HAMT_HEAD_BITMAP_SZ(n+1);
goto unroll;
- default:
- erts_exit(ERTS_ERROR_EXIT, "bad header tag %ld\r\n", hdr & _HEADER_MAP_SUBTAG_MASK);
- break;
+ default:
+ ERTS_ASSERT(is_arity_value(hdr));
+ n = arityval(hdr);
+ ASSERT(n >= 2);
+ for (slot = 0; slot < n; slot++) {
+ Eterm* kv = list_val(ptr[1+slot]);
+ Sint c;
+ ckey = CAR(kv);
+ c = CMP_TERM(key, ckey);
+ if (c == 0) {
+ if (CDR(ptr) == value) {
+ *sz = 0;
+ return 1;
+ }
+ *update_size = 0;
+ size += HAMT_COLLISION_NODE_SZ(n);
+ ESTACK_PUSH3(*sp, slot, 0, node);
+ goto unroll;
+ }
+ if (c < 0)
+ break;
+ }
+ if (is_update) {
+ return 0;
+ }
+ size += HAMT_COLLISION_NODE_SZ(n+1);
+ ESTACK_PUSH3(*sp, slot, 1, node);
+ goto unroll;
}
break;
default:
@@ -2566,21 +2635,25 @@ int erts_hashmap_insert_down(Uint32 hx, Eterm key, Eterm value, Eterm node, Uint
}
}
insert_subnodes:
- clvl = lvl;
- chx = hashmap_restore_hash(clvl,ckey);
- size += HAMT_NODE_BITMAP_SZ(2);
- ix = hashmap_index(hx);
- cix = hashmap_index(chx);
-
- while (cix == ix) {
- ESTACK_PUSH4(*sp, 0, 1 << ix, 0, MAP_HEADER_HAMT_NODE_BITMAP(0));
- size += HAMT_NODE_BITMAP_SZ(1);
- hx = hashmap_shift_hash(hx,lvl,key);
- chx = hashmap_shift_hash(chx,clvl,ckey);
- ix = hashmap_index(hx);
- cix = hashmap_index(chx);
- }
- ESTACK_PUSH3(*sp, cix, ix, node);
+ if (lvl < HAMT_MAX_LEVEL) {
+ clvl = lvl;
+ chx = hashmap_restore_hash(clvl,ckey);
+ do {
+ ix = hashmap_index(hx);
+ cix = hashmap_index(chx);
+ if (cix != ix) {
+ size += HAMT_NODE_BITMAP_SZ(2);
+ ESTACK_PUSH4(*sp, cix, ix, 0, node);
+ goto unroll;
+ }
+ ESTACK_PUSH4(*sp, 0, 1 << ix, 0, MAP_HEADER_HAMT_NODE_BITMAP(0));
+ size += HAMT_NODE_BITMAP_SZ(1);
+ hx = hashmap_shift_hash(hx,lvl,key);
+ chx = hashmap_shift_hash(chx,clvl,ckey);
+ } while (lvl < HAMT_MAX_LEVEL);
+ }
+ size += HAMT_COLLISION_NODE_SZ(2);
+ ESTACK_PUSH2(*sp, 1, node);
unroll:
*sz = size + /* res cons */ 2;
@@ -2594,17 +2667,29 @@ Eterm erts_hashmap_insert_up(Eterm *hp, Eterm key, Eterm value,
Eterm *nhp = NULL;
Uint32 ix, cix, bp, hval;
Uint slot, n;
- /* Needed for halfword */
- DeclareTmpHeapNoproc(fake,1);
- UseTmpHeapNoproc(1);
+ Eterm fake;
res = CONS(hp, key, value); hp += 2;
do {
node = ESTACK_POP(*sp);
switch(primary_tag(node)) {
- case TAG_PRIMARY_LIST:
- ix = (Uint32) ESTACK_POP(*sp);
+ case TAG_PRIMARY_LIST: {
+ const int is_collision_node = (int) ESTACK_POP(*sp);
+ if (is_collision_node) {
+ nhp = hp;
+ *hp++ = MAP_HEADER_HAMT_COLLISION_NODE(2);
+ if (CMP_TERM(key, CAR(list_val(node))) < 0){
+ *hp++ = res;
+ *hp++ = node;
+ } else {
+ *hp++ = node;
+ *hp++ = res;
+ }
+ res = make_hashmap(nhp);
+ break;
+ }
+ ix = (Uint32)ESTACK_POP(*sp);
cix = (Uint32) ESTACK_POP(*sp);
nhp = hp;
@@ -2618,10 +2703,11 @@ Eterm erts_hashmap_insert_up(Eterm *hp, Eterm key, Eterm value,
}
res = make_hashmap(nhp);
break;
+ }
case TAG_PRIMARY_HEADER:
/* subnodes, fake it */
- *fake = node;
- node = make_boxed(fake);
+ fake = node;
+ node = make_boxed(&fake);
case TAG_PRIMARY_BOXED:
ptr = boxed_val(node);
hdr = *ptr;
@@ -2674,9 +2760,27 @@ Eterm erts_hashmap_insert_up(Eterm *hp, Eterm key, Eterm value,
}
res = make_hashmap(nhp);
break;
- default:
- erts_exit(ERTS_ERROR_EXIT, "bad header tag %x\r\n", hdr & _HEADER_MAP_SUBTAG_MASK);
- break;
+ default: {
+ int is_insert;
+ ERTS_ASSERT(is_arity_value(hdr));
+ n = arityval(hdr);
+ ASSERT(n >= 2);
+ is_insert = (int) ESTACK_POP(*sp);
+ slot = (Uint) ESTACK_POP(*sp);
+ nhp = hp;
+ n += is_insert;
+ *hp++ = MAP_HEADER_HAMT_COLLISION_NODE(n); ptr++;
+ ix = 0;
+ while (ix++ < slot)
+ *hp++ = *ptr++;
+ *hp++ = res;
+ if (!is_insert)
+ ptr++;
+ while (ix++ < n)
+ *hp++ = *ptr++;
+ res = make_hashmap(nhp);
+ break;
+ }
}
break;
default:
@@ -2855,9 +2959,22 @@ static Eterm hashmap_delete(Process *p, Uint32 hx, Eterm key,
/* not occupied */
res = THE_NON_VALUE;
goto not_found;
- default:
- erts_exit(ERTS_ERROR_EXIT, "bad header tag %ld\r\n", hdr & _HEADER_MAP_SUBTAG_MASK);
- break;
+ default: /* collision node */
+ ERTS_ASSERT(is_arity_value(hdr));
+ n = arityval(hdr);
+ ASSERT(n >= 2);
+ for (slot = 0; slot < n; slot++) {
+ Eterm* kv = list_val(ptr[1+slot]);
+ if (EQ(key, CAR(kv))) {
+ if (value)
+ *value = CDR(kv);
+ ESTACK_PUSH2(stack, slot, node);
+ size += HAMT_COLLISION_NODE_SZ(n);
+ goto unroll;
+ }
+ }
+ res = THE_NON_VALUE;
+ goto not_found;
}
break;
default:
@@ -3039,8 +3156,25 @@ unroll:
}
res = make_hashmap(nhp);
break;
- default:
- erts_exit(ERTS_ERROR_EXIT, "bad header tag %x\r\n", hdr & _HEADER_MAP_SUBTAG_MASK);
+ default: /* collision node */
+ ERTS_ASSERT(is_arity_value(hdr));
+ n = arityval(hdr);
+ ASSERT(n >= 2);
+ slot = (Uint) ESTACK_POP(stack);
+ ASSERT(slot < n);
+ if (n > 2) { /* Shrink collision node */
+ nhp = hp;
+ *hp++ = MAP_HEADER_HAMT_COLLISION_NODE(n-1); ptr++;
+ n -= slot + 1;
+ while (slot--) { *hp++ = *ptr++; }
+ ptr++;
+ while(n--) { *hp++ = *ptr++; }
+ res = make_hashmap(nhp);
+ }
+ else { /* Collapse collision node */
+ ASSERT(res == THE_NON_VALUE);
+ res = ptr[1 + (1-slot)];
+ }
break;
}
} while(!ESTACK_ISEMPTY(stack));
@@ -3315,8 +3449,10 @@ BIF_RETTYPE erts_internal_map_hashmap_children_1(BIF_ALIST_1) {
sz = 16;
ptr += 2;
break;
- default:
- erts_exit(ERTS_ERROR_EXIT, "bad header\r\n");
+ default: /* collision node */
+ ERTS_ASSERT(is_arity_value(hdr));
+ sz = arityval(hdr);
+ ASSERT(sz >= 2);
break;
}
ASSERT(sz < 17);
@@ -3338,15 +3474,17 @@ static Eterm hashmap_info(Process *p, Eterm node) {
DECL_AM(leafs);
DECL_AM(bitmaps);
DECL_AM(arrays);
- Uint nleaf=0, nbitmap=0, narray=0;
- Uint bitmap_usage[16], leaf_usage[16];
- Uint lvl = 0, clvl;
+ DECL_AM(collisions);
+ Uint nleaf=0, nbitmap=0, narray=0, ncollision = 0;
+ Uint bitmap_usage[16];
+ Uint collision_usage[16];
+ Uint leaf_usage[HAMT_MAX_LEVEL + 2];
+ Uint max_depth = 0, clvl;
DECLARE_ESTACK(stack);
- for (sz = 0; sz < 16; sz++) {
- bitmap_usage[sz] = 0;
- leaf_usage[sz] = 0;
- }
+ sys_memzero(bitmap_usage, sizeof(bitmap_usage));
+ sys_memzero(collision_usage, sizeof(collision_usage));
+ sys_memzero(leaf_usage, sizeof(leaf_usage));
ptr = boxed_val(node);
ESTACK_PUSH(stack, 0);
@@ -3354,8 +3492,6 @@ static Eterm hashmap_info(Process *p, Eterm node) {
do {
node = ESTACK_POP(stack);
clvl = ESTACK_POP(stack);
- if (lvl < clvl)
- lvl = clvl;
switch(primary_tag(node)) {
case TAG_PRIMARY_LIST:
nleaf++;
@@ -3371,45 +3507,49 @@ static Eterm hashmap_info(Process *p, Eterm node) {
sz = hashmap_bitcount(MAP_HEADER_VAL(hdr));
ASSERT(sz < 17);
bitmap_usage[sz-1] += 1;
- while(sz--) {
- ESTACK_PUSH(stack, clvl + 1);
- ESTACK_PUSH(stack, ptr[sz+1]);
- }
break;
case HAMT_SUBTAG_HEAD_BITMAP:
nbitmap++;
sz = hashmap_bitcount(MAP_HEADER_VAL(hdr));
bitmap_usage[sz-1] += 1;
- while(sz--) {
- ESTACK_PUSH(stack, clvl + 1);
- ESTACK_PUSH(stack, ptr[sz+2]);
- }
+ ptr++;
break;
case HAMT_SUBTAG_HEAD_ARRAY:
narray++;
sz = 16;
- while(sz--) {
- ESTACK_PUSH(stack, clvl + 1);
- ESTACK_PUSH(stack, ptr[sz+2]);
- }
- break;
- default:
- erts_exit(ERTS_ERROR_EXIT, "bad header\r\n");
+ ptr++;
break;
+ default: /* collision node */
+ ERTS_ASSERT(is_arity_value(hdr));
+ ncollision++;
+ sz = arityval(hdr);
+ ASSERT(sz >= 2);
+ collision_usage[(sz > 16 ? 16 : sz) - 1] += 1;
+ break;
}
+ ASSERT(sz >= 1);
+ clvl++;
+ ASSERT(clvl <= HAMT_MAX_LEVEL+1);
+ if (max_depth < clvl)
+ max_depth = clvl;
+ while(sz--) {
+ ESTACK_PUSH(stack, clvl);
+ ESTACK_PUSH(stack, ptr[sz+1]);
+ }
}
} while(!ESTACK_ISEMPTY(stack));
/* size */
sz = 0;
- hashmap_bld_tuple_uint(NULL,&sz,16,leaf_usage);
- hashmap_bld_tuple_uint(NULL,&sz,16,bitmap_usage);
+ hashmap_bld_tuple_uint(NULL, &sz, HAMT_MAX_LEVEL+2, leaf_usage);
+ hashmap_bld_tuple_uint(NULL, &sz, 16, bitmap_usage);
+ hashmap_bld_tuple_uint(NULL, &sz, 16, collision_usage);
/* alloc */
- hp = HAlloc(p, 2+3 + 3*(2+4) + sz);
+ hp = HAlloc(p, 2+3 + 4*(2+4) + sz);
- info = hashmap_bld_tuple_uint(&hp,NULL,16,leaf_usage);
+ info = hashmap_bld_tuple_uint(&hp, NULL, HAMT_MAX_LEVEL+2, leaf_usage);
tup = TUPLE3(hp, AM_leafs, make_small(nleaf),info); hp += 4;
res = CONS(hp, tup, res); hp += 2;
@@ -3417,10 +3557,14 @@ static Eterm hashmap_info(Process *p, Eterm node) {
tup = TUPLE3(hp, AM_bitmaps, make_small(nbitmap), info); hp += 4;
res = CONS(hp, tup, res); hp += 2;
+ info = hashmap_bld_tuple_uint(&hp, NULL, 16, collision_usage);
+ tup = TUPLE3(hp, AM_collisions, make_small(ncollision), info); hp += 4;
+ res = CONS(hp, tup, res); hp += 2;
+
tup = TUPLE3(hp, AM_arrays, make_small(narray),NIL); hp += 4;
res = CONS(hp, tup, res); hp += 2;
- tup = TUPLE2(hp, AM_depth, make_small(lvl)); hp += 3;
+ tup = TUPLE2(hp, AM_depth, make_small(max_depth)); hp += 3;
res = CONS(hp, tup, res); hp += 2;
DESTROY_ESTACK(stack);
@@ -3448,12 +3592,16 @@ static Eterm hashmap_bld_tuple_uint(Uint **hpp, Uint *szp, Uint n, Uint nums[])
* Since each hashmap node can only be up to 16 elements
* large we use 4 bits per level in the path.
*
- * So a Path with value 0x110 will first get the 0:th
+ * So a Path with value 0x210 will first get the 0:th
* slot in the head node, and then the 1:st slot in the
- * resulting node and then finally the 1:st slot in the
+ * resulting node and then finally the 2:st slot in the
* node beneath. If that slot is not a leaf, then the path
* continues down the 0:th slot until it finds a leaf.
*
+ * Collision nodes may (theoretically and in debug) have more
+ * than 16 elements. To not complicate the 4-bit path format
+ * we avoid yielding in collision nodes.
+ *
* Once the leaf has been found, the return value is created
* by traversing the tree using the stack that was built
* when searching for the first leaf to return.
@@ -3596,7 +3744,12 @@ BIF_RETTYPE erts_internal_map_next_3(BIF_ALIST_3) {
Uint path_length = 0;
Uint *path_rest = NULL;
int i, elems, orig_elems;
- Eterm node = map, res, *patch_ptr = NULL, *hp;
+ Eterm node = map, res, *patch_ptr = NULL;
+ Eterm *hp = NULL;
+ Eterm *hp_end;
+ Eterm *ptr;
+ Uint sz, words_per_elem;
+ Uint idx;
/* A stack WSTACK is used when traversing the hashmap.
* It contains: node, idx, sz, ptr
@@ -3654,61 +3807,29 @@ BIF_RETTYPE erts_internal_map_next_3(BIF_ALIST_3) {
BIF_ERROR(BIF_P, BADARG);
}
- if (type == iterator) {
- /*
- * Iterator uses the format {K1, V1, {K2, V2, {K3, V3, [Path | Map]}}},
- * so each element is 4 words large.
- * To make iteration order independent of input reductions
- * the KV-pairs are here built in DESTRUCTIVE non-reverse order.
- */
- hp = HAlloc(BIF_P, 4 * elems);
- } else {
- /*
- * List used the format [Path, Map, {K3,V3}, {K2,V2}, {K1,V1} | BIF_ARG_3],
- * so each element is 2+3 words large.
- * To make list order independent of input reductions
- * the KV-pairs are here built in FUNCTIONAL reverse order
- * as this is how the list as a whole is constructed.
- */
- hp = HAlloc(BIF_P, (2 + 3) * elems);
- }
-
- orig_elems = elems;
-
/* First we look for the leaf to start at using the
path given. While doing so, we push each map node
and the index onto the stack to use later. */
for (i = 1; ; i++) {
- Eterm *ptr = hashmap_val(node),
- hdr = *ptr++;
- Uint sz;
+ Eterm hdr;
+
+ ptr = hashmap_val(node);
+ hdr = *ptr++;
sz = hashmap_node_size(hdr, &ptr);
- if (PATH_ELEM(curr_path) >= sz)
+ idx = PATH_ELEM(curr_path);
+ if (idx >= sz)
goto badarg;
- WSTACK_PUSH4(stack, node, PATH_ELEM(curr_path)+1, sz, (UWord)ptr);
-
- /* We have found a leaf, return it and the next X elements */
- if (is_list(ptr[PATH_ELEM(curr_path)])) {
- Eterm *lst = list_val(ptr[PATH_ELEM(curr_path)]);
- if (type == iterator) {
- res = make_tuple(hp);
- hp[0] = make_arityval(3);
- hp[1] = CAR(lst);
- hp[2] = CDR(lst);
- patch_ptr = &hp[3];
- hp += 4;
- } else {
- Eterm tup = TUPLE2(hp, CAR(lst), CDR(lst)); hp += 3;
- res = CONS(hp, tup, BIF_ARG_3); hp += 2;
- }
- elems--;
+ if (is_list(ptr[idx])) {
+ /* We have found a leaf, return it and the next X elements */
break;
}
- node = ptr[PATH_ELEM(curr_path)];
+ WSTACK_PUSH4(stack, node, idx+1, sz, (UWord)ptr);
+
+ node = ptr[idx];
curr_path >>= PATH_ELEM_SIZE;
@@ -3726,12 +3847,50 @@ BIF_RETTYPE erts_internal_map_next_3(BIF_ALIST_3) {
}
}
+ if (type == iterator) {
+ /*
+ * Iterator uses the format {K1, V1, {K2, V2, {K3, V3, [Path | Map]}}},
+ * so each element is 4 words large.
+ * To make iteration order independent of input reductions
+ * the KV-pairs are here built in DESTRUCTIVE non-reverse order.
+ */
+ words_per_elem = 4;
+ patch_ptr = &res;
+ } else {
+ /*
+ * List used the format [Path, Map, {K3,V3}, {K2,V2}, {K1,V1} | BIF_ARG_3],
+ * so each element is 2+3 words large.
+ * To make list order independent of input reductions
+ * the KV-pairs are here built in FUNCTIONAL reverse order
+ * as this is how the list as a whole is constructed.
+ */
+ words_per_elem = 2 + 3;
+ res = BIF_ARG_3;
+ }
+ hp = HAlloc(BIF_P, words_per_elem * elems);
+ hp_end = hp + words_per_elem * elems;
+
+ orig_elems = elems;
+
/* We traverse the hashmap and return at most `elems` elements */
while(1) {
- Eterm *ptr = (Eterm*)WSTACK_POP(stack);
- Uint sz = (Uint)WSTACK_POP(stack);
- Uint idx = (Uint)WSTACK_POP(stack);
- Eterm node = (Eterm)WSTACK_POP(stack);
+
+ if (idx == 0) {
+ if (elems < sz && is_arity_value(*hashmap_val(node))) {
+ /*
+ * This is a collision node!
+ * Make sure 'elems' is large enough not to yield in the
+ * middle of it. Collision nodes may be larger than 16
+ * and that would complicate the 4-bit path format.
+ */
+ elems = sz;
+ HRelease(BIF_P, hp_end, hp);
+ hp = HAlloc(BIF_P, words_per_elem * elems);
+ hp_end = hp + words_per_elem * elems;
+ }
+ }
+ else
+ ASSERT(!is_arity_value(*hashmap_val(node)));
while (idx < sz && elems != 0 && is_list(ptr[idx])) {
Eterm *lst = list_val(ptr[idx]);
@@ -3750,6 +3909,8 @@ BIF_RETTYPE erts_internal_map_next_3(BIF_ALIST_3) {
idx++;
}
+ ASSERT(idx == sz || !is_arity_value(*hashmap_val(node)));
+
if (elems == 0) {
if (idx < sz) {
/* There are more elements in this node to explore */
@@ -3768,26 +3929,29 @@ BIF_RETTYPE erts_internal_map_next_3(BIF_ALIST_3) {
}
}
break;
- } else {
- if (idx < sz) {
- Eterm hdr;
- /* Push next idx in current node */
- WSTACK_PUSH4(stack, node, idx+1, sz, (UWord)ptr);
-
- /* Push first idx in child node */
- node = ptr[idx];
- ptr = hashmap_val(ptr[idx]);
- hdr = *ptr++;
- sz = hashmap_node_size(hdr, &ptr);
- WSTACK_PUSH4(stack, node, 0, sz, (UWord)ptr);
- }
}
-
- /* There are no more element in the hashmap */
- if (WSTACK_ISEMPTY(stack)) {
+ else if (idx < sz) {
+ Eterm hdr;
+ /* Push next idx in current node */
+ WSTACK_PUSH4(stack, node, idx+1, sz, (UWord)ptr);
+
+ /* Continue with first idx in child node */
+ node = ptr[idx];
+ ptr = hashmap_val(ptr[idx]);
+ hdr = *ptr++;
+ sz = hashmap_node_size(hdr, &ptr);
+ idx = 0;
+ }
+ else if (!WSTACK_ISEMPTY(stack)) {
+ ptr = (Eterm*)WSTACK_POP(stack);
+ sz = (Uint)WSTACK_POP(stack);
+ idx = (Uint)WSTACK_POP(stack);
+ node = (Eterm)WSTACK_POP(stack);
+ }
+ else {
+ /* There are no more element in the hashmap */
break;
}
-
}
if (!WSTACK_ISEMPTY(stack)) {
@@ -3846,24 +4010,16 @@ BIF_RETTYPE erts_internal_map_next_3(BIF_ALIST_3) {
res = CONS(hp, path, res); hp += 2;
}
} else {
- if (type == iterator) {
+ if (type == iterator)
*patch_ptr = am_none;
- HRelease(BIF_P, hp + 4 * elems, hp);
- } else {
- HRelease(BIF_P, hp + (2+3) * elems, hp);
- }
+ HRelease(BIF_P, hp_end, hp);
}
BIF_P->fcalls -= 4 * (orig_elems - elems);
DESTROY_WSTACK(stack);
BIF_RET(res);
badarg:
- if (type == iterator) {
- HRelease(BIF_P, hp + 4 * elems, hp);
- } else {
- HRelease(BIF_P, hp + (2+3) * elems, hp);
- }
- BIF_P->fcalls -= 4 * (orig_elems - elems);
+ ASSERT(hp == NULL);
DESTROY_WSTACK(stack);
BIF_ERROR(BIF_P, BADARG);
}
diff --git a/erts/emulator/beam/erl_map.h b/erts/emulator/beam/erl_map.h
index d3a023bc07..05d09557e7 100644
--- a/erts/emulator/beam/erl_map.h
+++ b/erts/emulator/beam/erl_map.h
@@ -56,17 +56,15 @@ typedef struct flatmap_s {
/* the head-node is a bitmap or array with an untagged size */
#define hashmap_size(x) (((hashmap_head_t*) hashmap_val(x))->size)
-#define hashmap_make_hash(Key) make_map_hash(Key, 0)
+#define hashmap_make_hash(Key) make_map_hash(Key)
#define hashmap_restore_hash(Lvl, Key) \
- (((Lvl) < 8) ? \
- hashmap_make_hash(Key) >> (4*(Lvl)) : \
- make_map_hash(Key, ((Lvl) >> 3)) >> (4 * ((Lvl) & 7)))
+ (ASSERT(Lvl < 8), \
+ hashmap_make_hash(Key) >> (4*(Lvl)))
#define hashmap_shift_hash(Hx, Lvl, Key) \
- (((++(Lvl)) & 7) ? \
- (Hx) >> 4 : \
- make_map_hash(Key, ((Lvl) >> 3)))
+ (++(Lvl), ASSERT(Lvl <= HAMT_MAX_LEVEL), /* we allow one level too much */\
+ (Hx) >> 4)
/* erl_term.h stuff */
#define flatmap_get_values(x) (((Eterm *)(x)) + sizeof(flatmap_t)/sizeof(Eterm))
@@ -149,7 +147,8 @@ typedef struct hashmap_head_s {
/* erl_map.h stuff */
-#define is_hashmap_header_head(x) ((MAP_HEADER_TYPE(x) & (0x2)))
+#define is_hashmap_header_head(x) (MAP_HEADER_TYPE(x) & (0x2))
+#define is_hashmap_header_node(x) (MAP_HEADER_TYPE(x) == 1)
#define MAKE_MAP_HEADER(Type,Arity,Val) \
(_make_header(((((Uint16)(Val)) << MAP_HEADER_ARITY_SZ) | (Arity)) << MAP_HEADER_TAG_SZ | (Type) , _TAG_HEADER_MAP))
@@ -166,12 +165,21 @@ typedef struct hashmap_head_s {
#define MAP_HEADER_HAMT_NODE_BITMAP(Bmp) \
MAKE_MAP_HEADER(MAP_HEADER_TAG_HAMT_NODE_BITMAP,0x0,Bmp)
+#define MAP_HEADER_HAMT_COLLISION_NODE(Arity) make_arityval(Arity)
+
#define MAP_HEADER_FLATMAP_SZ (sizeof(flatmap_t) / sizeof(Eterm))
#define HAMT_NODE_ARRAY_SZ (17)
#define HAMT_HEAD_ARRAY_SZ (18)
#define HAMT_NODE_BITMAP_SZ(n) (1 + n)
#define HAMT_HEAD_BITMAP_SZ(n) (2 + n)
+#define HAMT_COLLISION_NODE_SZ(n) (1 + n)
+/*
+ * Collision nodes are used when all hash bits have been exhausted.
+ * They are normal tuples of arity 2 or larger. The elements of a collision
+ * node tuple contain key-value cons cells like the other nodes,
+ * but they are sorted in map-key order.
+ */
/* 2 bits maps tag + 4 bits subtag + 2 ignore bits */
#define _HEADER_MAP_SUBTAG_MASK (0xfc)
@@ -185,11 +193,17 @@ typedef struct hashmap_head_s {
#define hashmap_index(hash) (((Uint32)hash) & 0xf)
+#define HAMT_MAX_LEVEL 8
+
/* hashmap heap size:
[one cons cell + one list term in parent node] per key
[one header + one boxed term in parent node] per inner node
[one header + one size word] for root node
Observed average number of nodes per key is about 0.35.
+
+ Amendment: This size estimation does not take collision nodes into account.
+ It should be good enough though, as collision nodes are rare
+ and only make the size smaller compared to unlimited HAMT depth.
*/
#define HASHMAP_WORDS_PER_KEY 3
#define HASHMAP_WORDS_PER_NODE 2
diff --git a/erts/emulator/beam/erl_nif.c b/erts/emulator/beam/erl_nif.c
index 3718fb3c96..a9661dc780 100644
--- a/erts/emulator/beam/erl_nif.c
+++ b/erts/emulator/beam/erl_nif.c
@@ -2922,7 +2922,7 @@ void erts_nif_demonitored(ErtsResource* resource)
ASSERT(resource->type->fn.down);
erts_mtx_lock(&rmp->lock);
- free_me = ((rmon_refc_dec_read(rmp) == 0) & !!rmon_is_dying(rmp));
+ free_me = ((rmon_refc_dec_read(rmp) == 0) && !!rmon_is_dying(rmp));
erts_mtx_unlock(&rmp->lock);
if (free_me)
diff --git a/erts/emulator/beam/erl_node_tables.c b/erts/emulator/beam/erl_node_tables.c
index 2bec8ff20e..dac24d0310 100644
--- a/erts/emulator/beam/erl_node_tables.c
+++ b/erts/emulator/beam/erl_node_tables.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2001-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2001-2023. 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.
@@ -885,6 +885,18 @@ erts_node_table_info(fmtfn_t to, void *to_arg)
erts_rwmtx_runlock(&erts_node_table_rwmtx);
}
+ErlNode *erts_find_node(Eterm sysname, Uint32 creation)
+{
+ ErlNode *res;
+ ErlNode ne;
+ ne.sysname = sysname;
+ ne.creation = creation;
+
+ erts_rwmtx_rlock(&erts_node_table_rwmtx);
+ res = hash_get(&erts_node_table, (void *) &ne);
+ erts_rwmtx_runlock(&erts_node_table_rwmtx);
+ return res;
+}
ErlNode *erts_find_or_insert_node(Eterm sysname, Uint32 creation, Eterm book)
{
diff --git a/erts/emulator/beam/erl_node_tables.h b/erts/emulator/beam/erl_node_tables.h
index c12198a23c..56696586c6 100644
--- a/erts/emulator/beam/erl_node_tables.h
+++ b/erts/emulator/beam/erl_node_tables.h
@@ -255,6 +255,7 @@ void erts_set_dist_entry_not_connected(DistEntry *);
void erts_set_dist_entry_pending(DistEntry *);
void erts_set_dist_entry_connected(DistEntry *, Eterm, Uint64);
ErlNode *erts_find_or_insert_node(Eterm, Uint32, Eterm);
+ErlNode *erts_find_node(Eterm, Uint32);
void erts_schedule_delete_node(ErlNode *);
void erts_set_this_node(Eterm, Uint32);
Uint erts_node_table_size(void);
@@ -282,6 +283,7 @@ ERTS_GLB_INLINE void erts_deref_node_entry__(ErlNode *np, Eterm term, char *file
ERTS_GLB_INLINE erts_aint_t erts_ref_node_entry(ErlNode *np, int min_val, Eterm term);
ERTS_GLB_INLINE void erts_deref_node_entry(ErlNode *np, Eterm term);
#endif
+ERTS_GLB_INLINE erts_aint_t erts_node_refc(ErlNode *np);
ERTS_GLB_INLINE void erts_de_rlock(DistEntry *dep);
ERTS_GLB_INLINE void erts_de_runlock(DistEntry *dep);
ERTS_GLB_INLINE void erts_de_rwlock(DistEntry *dep);
@@ -332,6 +334,12 @@ erts_deref_node_entry(ErlNode *np, Eterm term)
erts_schedule_delete_node(np);
}
+ERTS_GLB_INLINE erts_aint_t
+erts_node_refc(ErlNode *np)
+{
+ return erts_refc_read(&np->refc, 0);
+}
+
#endif
ERTS_GLB_INLINE void
diff --git a/erts/emulator/beam/erl_printf_term.c b/erts/emulator/beam/erl_printf_term.c
index bb8a422343..18858a21bc 100644
--- a/erts/emulator/beam/erl_printf_term.c
+++ b/erts/emulator/beam/erl_printf_term.c
@@ -722,71 +722,46 @@ print_term(fmtfn_t fn, void* arg, Eterm obj, long *dcount) {
}
} else {
Uint n, mapval;
+ Eterm* assoc;
+ Eterm key, val;
+
mapval = MAP_HEADER_VAL(*head);
switch (MAP_HEADER_TYPE(*head)) {
case MAP_HEADER_TAG_HAMT_HEAD_ARRAY:
case MAP_HEADER_TAG_HAMT_HEAD_BITMAP:
PRINT_STRING(res, fn, arg, "#{");
WSTACK_PUSH(s, PRT_CLOSE_TUPLE);
- n = hashmap_bitcount(mapval);
- ASSERT(n < 17);
- head += 2;
- if (n > 0) {
- Eterm* assoc;
- Eterm key, val;
- n--;
- if (is_list(head[n])) {
- assoc = list_val(head[n]);
- key = CAR(assoc);
- val = CDR(assoc);
- WSTACK_PUSH5(s, val, PRT_TERM, PRT_ASSOC, key, PRT_TERM);
- } else {
- WSTACK_PUSH(s, head[n]);
- WSTACK_PUSH(s, PRT_TERM);
- }
- while (n--) {
- if (is_list(head[n])) {
- assoc = list_val(head[n]);
- key = CAR(assoc);
- val = CDR(assoc);
- WSTACK_PUSH6(s, PRT_COMMA, val, PRT_TERM, PRT_ASSOC, key, PRT_TERM);
- } else {
- WSTACK_PUSH(s, PRT_COMMA);
- WSTACK_PUSH(s, head[n]);
- WSTACK_PUSH(s, PRT_TERM);
- }
- }
- }
- break;
+ head++;
+ /* fall through */
case MAP_HEADER_TAG_HAMT_NODE_BITMAP:
n = hashmap_bitcount(mapval);
- head++;
- ASSERT(n < 17);
- if (n > 0) {
- Eterm* assoc;
- Eterm key, val;
- n--;
+ ASSERT(0 < n && n < 17);
+ while (1) {
if (is_list(head[n])) {
assoc = list_val(head[n]);
key = CAR(assoc);
val = CDR(assoc);
WSTACK_PUSH5(s, val, PRT_TERM, PRT_ASSOC, key, PRT_TERM);
- } else {
- WSTACK_PUSH(s, head[n]);
- WSTACK_PUSH(s, PRT_TERM);
}
- while (n--) {
- if (is_list(head[n])) {
- assoc = list_val(head[n]);
+ else if (is_tuple(head[n])) { /* collision node */
+ Eterm *tpl = tuple_val(head[n]);
+ Uint arity = arityval(tpl[0]);
+ ASSERT(arity >= 2);
+ while (1) {
+ assoc = list_val(tpl[arity]);
key = CAR(assoc);
val = CDR(assoc);
- WSTACK_PUSH6(s, PRT_COMMA, val, PRT_TERM, PRT_ASSOC, key, PRT_TERM);
- } else {
+ WSTACK_PUSH5(s, val, PRT_TERM, PRT_ASSOC, key, PRT_TERM);
+ if (--arity == 0)
+ break;
WSTACK_PUSH(s, PRT_COMMA);
- WSTACK_PUSH(s, head[n]);
- WSTACK_PUSH(s, PRT_TERM);
}
+ } else {
+ WSTACK_PUSH2(s, head[n], PRT_TERM);
}
+ if (--n == 0)
+ break;
+ WSTACK_PUSH(s, PRT_COMMA);
}
break;
}
diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c
index a70c267ba2..7a89df49e0 100644
--- a/erts/emulator/beam/erl_process.c
+++ b/erts/emulator/beam/erl_process.c
@@ -7397,7 +7397,7 @@ static ERTS_INLINE int
have_dirty_work(void)
{
return !(ERTS_EMPTY_RUNQ(ERTS_DIRTY_CPU_RUNQ)
- | ERTS_EMPTY_RUNQ(ERTS_DIRTY_IO_RUNQ));
+ || ERTS_EMPTY_RUNQ(ERTS_DIRTY_IO_RUNQ));
}
#define ERTS_MSB_NONE_PRIO_BIT PORT_BIT
diff --git a/erts/emulator/beam/erl_process_lock.h b/erts/emulator/beam/erl_process_lock.h
index 1dd9f14317..76e8616280 100644
--- a/erts/emulator/beam/erl_process_lock.h
+++ b/erts/emulator/beam/erl_process_lock.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2007-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2007-2023. 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.
diff --git a/erts/emulator/beam/erl_term.h b/erts/emulator/beam/erl_term.h
index 2877c5f5ba..03211a6acd 100644
--- a/erts/emulator/beam/erl_term.h
+++ b/erts/emulator/beam/erl_term.h
@@ -331,7 +331,7 @@ _ET_DECLARE_CHECKED(Uint,header_arity,Eterm)
#define is_sane_arity_value(x) ((((x) & _TAG_HEADER_MASK) == _TAG_HEADER_ARITYVAL) && \
(((x) >> _HEADER_ARITY_OFFS) <= MAX_ARITYVAL))
#define is_not_arity_value(x) (!is_arity_value((x)))
-#define _unchecked_arityval(x) _unchecked_header_arity((x))
+#define _unchecked_arityval(x) ((x) >> _HEADER_ARITY_OFFS)
_ET_DECLARE_CHECKED(Uint,arityval,Eterm)
#define arityval(x) _ET_APPLY(arityval,(x))
diff --git a/erts/emulator/beam/erl_term_hashing.c b/erts/emulator/beam/erl_term_hashing.c
index 52c36503ee..848757c2f2 100644
--- a/erts/emulator/beam/erl_term_hashing.c
+++ b/erts/emulator/beam/erl_term_hashing.c
@@ -1141,7 +1141,18 @@ make_hash2_helper(Eterm term_param, const int can_trap, Eterm* state_mref_write_
}
else {
ASSERT(is_boxed(*ctx.ptr));
- ESTACK_PUSH(s, *ctx.ptr);
+ if (is_tuple(*ctx.ptr)) { /* collision node */
+ Eterm *coll_ptr = tuple_val(*ctx.ptr);
+ Uint n = arityval(*coll_ptr);
+ ASSERT(n >= 2);
+ coll_ptr++;
+ for (; n; n--, coll_ptr++) {
+ Eterm* cons = list_val(*coll_ptr);
+ ESTACK_PUSH3(s, HASH_MAP_PAIR, CDR(cons), CAR(cons));
+ }
+ }
+ else
+ ESTACK_PUSH(s, *ctx.ptr);
}
ctx.i--; ctx.ptr++;
TRAP_LOCATION(map_subtag);
@@ -1543,12 +1554,10 @@ trapping_make_hash2(Eterm term, Eterm* state_mref_write_back, Process* p)
* with a new ErlNode struct, externals from that node will hash different than
* before.
*
- * One IMPORTANT property must hold (for hamt).
- * EVERY BIT of the term that is significant for equality (see EQ)
- * MUST BE USED AS INPUT FOR THE HASH. Two different terms must always have a
- * chance of hashing different when salted.
- *
- * This is why we cannot use cached hash values for atoms for example.
+ * The property "EVERY BIT of the term that is significant for equality
+ * MUST BE USED AS INPUT FOR THE HASH" is nice but no longer crucial for the
+ * hashmap implementation that now uses collision nodes at the bottom of
+ * the HAMT when all hash bits are exhausted.
*
*/
@@ -1716,6 +1725,8 @@ make_internal_hash(Eterm term, Uint32 salt)
}
else {
ASSERT(is_boxed(*ptr));
+ /* no special treatment of collision nodes needed,
+ hash them as the tuples they are */
ESTACK_PUSH(s, *ptr);
}
i--; ptr++;
@@ -1951,15 +1962,43 @@ make_internal_hash(Eterm term, Uint32 salt)
}
-/* Term hash function for maps, with a separate depth parameter */
-Uint32 make_map_hash(Eterm key, int depth) {
- Uint32 hash = 0;
-
- if (depth > 0) {
- UINT32_HASH_2(depth, 1, HCONST_22);
+#ifdef DBG_HASHMAP_COLLISION_BONANZA
+Uint32 erts_dbg_hashmap_collision_bonanza(Uint32 hash, Eterm key)
+{
+/*{
+ static Uint32 hashvec[7] = {
+ 0x02345678,
+ 0x12345678,
+ 0xe2345678,
+ 0xf2345678,
+ 0x12abcdef,
+ 0x13abcdef,
+ 0xcafebabe
+ };
+ hash = hashvec[hash % (sizeof(hashvec) / sizeof(hashvec[0]))];
+ }*/
+ const Uint32 bad_hash = (hash & 0x12482481) * 1442968193;
+ const Uint32 bad_bits = hash % 67;
+ if (bad_bits < 32) {
+ /* Mix in a number of high good bits to get "randomly" close
+ to the collision nodes */
+ const Uint32 bad_mask = (1 << bad_bits) - 1;
+ return (hash & ~bad_mask) | (bad_hash & bad_mask);
}
+ return bad_hash;
+}
+#endif
- return make_internal_hash(key, hash);
+/* Term hash function for hashmaps */
+Uint32 make_map_hash(Eterm key) {
+ Uint32 hash;
+
+ hash = make_internal_hash(key, 0);
+
+#ifdef DBG_HASHMAP_COLLISION_BONANZA
+ hash = erts_dbg_hashmap_collision_bonanza(hash, key);
+#endif
+ return hash;
}
#undef CONST_HASH
diff --git a/erts/emulator/beam/erl_term_hashing.h b/erts/emulator/beam/erl_term_hashing.h
index ec4a49359d..8a898b7c52 100644
--- a/erts/emulator/beam/erl_term_hashing.h
+++ b/erts/emulator/beam/erl_term_hashing.h
@@ -53,7 +53,13 @@ Uint32 make_hash2(Eterm);
Uint32 trapping_make_hash2(Eterm, Eterm*, struct process*);
Uint32 make_hash(Eterm);
Uint32 make_internal_hash(Eterm, Uint32 salt);
-Uint32 make_map_hash(Eterm key, int level);
+#ifdef DEBUG
+# define DBG_HASHMAP_COLLISION_BONANZA
+#endif
+#ifdef DBG_HASHMAP_COLLISION_BONANZA
+Uint32 erts_dbg_hashmap_collision_bonanza(Uint32 hash, Eterm key);
+#endif
+Uint32 make_map_hash(Eterm key);
void erts_block_hash_init(ErtsBlockHashState *state,
const byte *ptr,
Uint len,
diff --git a/erts/emulator/beam/external.c b/erts/emulator/beam/external.c
index a380454699..081ce23e49 100644
--- a/erts/emulator/beam/external.c
+++ b/erts/emulator/beam/external.c
@@ -3116,7 +3116,6 @@ dec_pid(ErtsDistExternal *edep, ErtsHeapFactory* factory, const byte* ep,
#define ENC_BIN_COPY ((Eterm) 3)
#define ENC_MAP_PAIR ((Eterm) 4)
#define ENC_HASHMAP_NODE ((Eterm) 5)
-#define ENC_STORE_MAP_ELEMENT ((Eterm) 6)
#define ENC_START_SORTING_MAP ((Eterm) 7)
#define ENC_CONTINUE_SORTING_MAP ((Eterm) 8)
#define ENC_PUSH_SORTED_MAP ((Eterm) 9)
@@ -3315,18 +3314,32 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
case ENC_HASHMAP_NODE:
if (is_list(obj)) { /* leaf node [K|V] */
ptr = list_val(obj);
+ if (dflags & DFLAG_DETERMINISTIC) {
+ *next_map_element++ = CAR(ptr);
+ *next_map_element++ = CDR(ptr);
+ goto outer_loop;
+ }
WSTACK_PUSH2(s, ENC_TERM, CDR(ptr));
obj = CAR(ptr);
}
- break;
- case ENC_STORE_MAP_ELEMENT: /* option `deterministic` */
- if (is_list(obj)) { /* leaf node [K|V] */
- ptr = list_val(obj);
- *next_map_element++ = CAR(ptr);
- *next_map_element++ = CDR(ptr);
+ else if (is_tuple(obj)) { /* collision node */
+ Uint tpl_sz;
+ ptr = tuple_val(obj);
+ tpl_sz = arityval(*ptr);
+ ASSERT(tpl_sz >= 2);
+ ptr++;
+ WSTACK_RESERVE(s, tpl_sz * 2);
+ while(tpl_sz--) {
+ ASSERT(is_list(*ptr));
+ WSTACK_FAST_PUSH(s, ENC_HASHMAP_NODE);
+ WSTACK_FAST_PUSH(s, *ptr++);
+ }
goto outer_loop;
- }
- break;
+ }
+ else
+ ASSERT((*boxed_val(obj) & _HEADER_MAP_SUBTAG_MASK)
+ == HAMT_SUBTAG_NODE_BITMAP);
+ break;
case ENC_START_SORTING_MAP: /* option `deterministic` */
{
long num_reductions = r;
@@ -3654,7 +3667,6 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
} else {
Eterm hdr;
Uint node_sz;
- Eterm node_processor;
ptr = boxed_val(obj);
hdr = *ptr;
ASSERT(is_header(hdr));
@@ -3695,17 +3707,14 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
node_sz = hashmap_bitcount(MAP_HEADER_VAL(hdr));
ASSERT(node_sz < 17);
break;
- default:
- erts_exit(ERTS_ERROR_EXIT, "bad header\r\n");
- }
-
+ default:
+ erts_exit(ERTS_ERROR_EXIT, "bad header\r\n");
+ }
ptr++;
- node_processor = (dflags & DFLAG_DETERMINISTIC) ?
- ENC_STORE_MAP_ELEMENT : ENC_HASHMAP_NODE;
WSTACK_RESERVE(s, node_sz*2);
while(node_sz--) {
- WSTACK_FAST_PUSH(s, node_processor);
- WSTACK_FAST_PUSH(s, *ptr++);
+ WSTACK_FAST_PUSH(s, ENC_HASHMAP_NODE);
+ WSTACK_FAST_PUSH(s, *ptr++);
}
}
break;
@@ -5459,8 +5468,8 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
node_sz = hashmap_bitcount(MAP_HEADER_VAL(hdr));
ASSERT(node_sz < 17);
break;
- default:
- erts_exit(ERTS_ERROR_EXIT, "bad header\r\n");
+ default:
+ erts_exit(ERTS_ERROR_EXIT, "bad header\r\n");
}
ptr++;
diff --git a/erts/emulator/beam/jit/arm/instr_map.cpp b/erts/emulator/beam/jit/arm/instr_map.cpp
index b751436881..8d7ad6f45f 100644
--- a/erts/emulator/beam/jit/arm/instr_map.cpp
+++ b/erts/emulator/beam/jit/arm/instr_map.cpp
@@ -30,13 +30,10 @@ extern "C"
}
static const Uint32 INTERNAL_HASH_SALT = 3432918353;
-static const Uint32 HCONST_22 = 0x98C475E6UL;
static const Uint32 HCONST = 0x9E3779B9;
-/* ARG3 = incoming hash
- * ARG6 = lower 32
+/* ARG6 = lower 32
* ARG7 = upper 32
- * ARG8 = type constant
*
* Helper function for calculating the internal hash of keys before looking
* them up in a map.
@@ -50,6 +47,9 @@ void BeamGlobalAssembler::emit_internal_hash_helper() {
a64::Gp hash = ARG3.w(), lower = ARG6.w(), upper = ARG7.w(),
constant = ARG8.w();
+ mov_imm(hash, INTERNAL_HASH_SALT);
+ mov_imm(constant, HCONST);
+
a.add(lower, lower, constant);
a.add(upper, upper, constant);
@@ -82,6 +82,24 @@ void BeamGlobalAssembler::emit_internal_hash_helper() {
}
#endif
+#ifdef DBG_HASHMAP_COLLISION_BONANZA
+ emit_enter_runtime_frame();
+ emit_enter_runtime();
+
+ a.stp(ARG1, ARG2, TMP_MEM1q);
+ a.str(ARG4, TMP_MEM3q);
+
+ a.mov(ARG1, ARG3);
+ runtime_call<2>(erts_dbg_hashmap_collision_bonanza);
+ a.mov(ARG3, ARG1);
+
+ a.ldp(ARG1, ARG2, TMP_MEM1q);
+ a.ldr(ARG4, TMP_MEM3q);
+
+ emit_leave_runtime();
+ emit_leave_runtime_frame();
+#endif
+
a.ret(a64::x30);
}
@@ -95,7 +113,7 @@ void BeamGlobalAssembler::emit_hashmap_get_element() {
Label node_loop = a.newLabel();
arm::Gp node = ARG1, key = ARG2, key_hash = ARG3, header_val = ARG4,
- depth = TMP4, index = TMP5;
+ depth = TMP5, index = TMP6;
const int header_shift =
(_HEADER_ARITY_OFFS + MAP_HEADER_TAG_SZ + MAP_HEADER_ARITY_SZ);
@@ -107,8 +125,9 @@ void BeamGlobalAssembler::emit_hashmap_get_element() {
a.bind(node_loop);
{
- Label fail = a.newLabel(), leaf_node = a.newLabel(),
- skip_index_adjustment = a.newLabel(), update_hash = a.newLabel();
+ Label done = a.newLabel(), leaf_node = a.newLabel(),
+ skip_index_adjustment = a.newLabel(),
+ collision_node = a.newLabel();
/* Find out which child we should follow, and shift the hash for the
* next round. */
@@ -128,7 +147,7 @@ void BeamGlobalAssembler::emit_hashmap_get_element() {
* Note that we jump directly to the return sequence as ZF is clear
* at this point. */
a.lsr(TMP1, header_val, index);
- a.tbz(TMP1, imm(0), fail);
+ a.tbz(TMP1, imm(0), done);
/* The actual offset of our entry is the number of bits set (in
* essence "entries present") before our index in the bitmap. Clear
@@ -152,11 +171,11 @@ void BeamGlobalAssembler::emit_hashmap_get_element() {
* word. */
a.ldr(header_val, arm::Mem(node).post(sizeof(Eterm)));
- /* After 8 nodes we've run out of the 32 bits we started with, so we
- * need to update the hash to keep going. */
- a.tst(depth, imm(0x7));
- a.b_eq(update_hash);
- a.b(node_loop);
+ /* After 8 nodes we've run out of the 32 bits we started with
+ * and we end up in a collision node. */
+ a.cmp(depth, imm(HAMT_MAX_LEVEL));
+ a.b_ne(node_loop);
+ a.b(collision_node);
a.bind(leaf_node);
{
@@ -166,36 +185,33 @@ void BeamGlobalAssembler::emit_hashmap_get_element() {
a.cmp(TMP1, key);
/* See comment at the jump. */
- a.bind(fail);
+ a.bind(done);
a.ret(a64::x30);
}
- /* After 8 nodes we've run out of the 32 bits we started with, so we
- * must calculate a new hash to continue.
- *
- * This is a manual expansion `make_map_hash` from utils.c, and all
- * changes to that function must be mirrored here. */
- a.bind(update_hash);
+ /* A collision node is a tuple of leafs where we do linear search.*/
+ a.bind(collision_node);
{
- emit_enter_runtime_frame();
+ Label linear_loop = a.newLabel();
+
+ a.lsr(TMP1, header_val, imm(_HEADER_ARITY_OFFS - 3));
- /* NOTE: ARG3 (key_hash) is always 0 at this point. */
- a.lsr(ARG6, depth, imm(3));
- mov_imm(ARG7, 1);
- mov_imm(ARG8, HCONST_22);
- a.bl(labels[internal_hash_helper]);
+ a.bind(linear_loop);
+ {
+ a.sub(TMP1, TMP1, imm(8));
- mov_imm(TMP1, INTERNAL_HASH_SALT);
- a.eor(ARG3, ARG3, TMP1);
+ a.ldr(TMP2, arm::Mem(node, TMP1));
- a.mov(ARG6.w(), key.w());
- a.lsr(ARG7, key, imm(32));
- mov_imm(ARG8, HCONST);
- a.bl(labels[internal_hash_helper]);
+ emit_untag_ptr(TMP2, TMP2);
+ a.ldp(TMP3, TMP4, arm::Mem(TMP2));
+ a.cmp(key, TMP3);
+ a.csel(ARG1, node, TMP4, imm(arm::CondCode::kNE));
+ a.b_eq(done);
- emit_leave_runtime_frame();
+ a.cbnz(TMP1, linear_loop);
+ }
- a.b(node_loop);
+ a.ret(a64::x30);
}
}
}
@@ -362,10 +378,8 @@ void BeamGlobalAssembler::emit_i_get_map_element_shared() {
emit_enter_runtime_frame();
/* Calculate the internal hash of ARG2 before diving into the HAMT. */
- mov_imm(ARG3, INTERNAL_HASH_SALT);
a.mov(ARG6.w(), ARG2.w());
a.lsr(ARG7, ARG2, imm(32));
- mov_imm(ARG8, HCONST);
a.bl(labels[internal_hash_helper]);
emit_leave_runtime_frame();
diff --git a/erts/emulator/beam/jit/x86/instr_map.cpp b/erts/emulator/beam/jit/x86/instr_map.cpp
index 679b2a6609..5f89077ba6 100644
--- a/erts/emulator/beam/jit/x86/instr_map.cpp
+++ b/erts/emulator/beam/jit/x86/instr_map.cpp
@@ -30,13 +30,11 @@ extern "C"
}
static const Uint32 INTERNAL_HASH_SALT = 3432918353;
-static const Uint32 HCONST_22 = 0x98C475E6UL;
static const Uint32 HCONST = 0x9E3779B9;
-/* ARG3 = incoming hash
+/*
* ARG4 = lower 32
* ARG5 = upper 32
- * ARG6 = type constant
*
* Helper function for calculating the internal hash of keys before looking
* them up in a map.
@@ -47,15 +45,16 @@ static const Uint32 HCONST = 0x9E3779B9;
*
* Result is returned in ARG3. */
void BeamGlobalAssembler::emit_internal_hash_helper() {
- x86::Gp hash = ARG3d, lower = ARG4d, upper = ARG5d, constant = ARG6d;
+ x86::Gp hash = ARG3d, lower = ARG4d, upper = ARG5d;
- a.add(lower, constant);
- a.add(upper, constant);
+ a.mov(hash, imm(INTERNAL_HASH_SALT));
+ a.add(lower, imm(HCONST));
+ a.add(upper, imm(HCONST));
#if defined(ERL_INTERNAL_HASH_CRC32C)
- a.mov(constant, hash);
+ a.mov(ARG6d, hash);
a.crc32(hash, lower);
- a.add(hash, constant);
+ a.add(hash, ARG6d);
a.crc32(hash, upper);
#else
using rounds =
@@ -88,6 +87,23 @@ void BeamGlobalAssembler::emit_internal_hash_helper() {
}
#endif
+#ifdef DBG_HASHMAP_COLLISION_BONANZA
+ a.mov(TMP_MEM1q, ARG1);
+ a.mov(TMP_MEM2q, ARG2);
+ a.mov(TMP_MEM3q, RET);
+
+ a.mov(ARG1, ARG3);
+ emit_enter_runtime();
+ runtime_call<2>(erts_dbg_hashmap_collision_bonanza);
+ emit_leave_runtime();
+
+ a.mov(ARG3d, RETd);
+
+ a.mov(ARG1, TMP_MEM1q);
+ a.mov(ARG2, TMP_MEM2q);
+ a.mov(RET, TMP_MEM3q);
+#endif
+
a.ret();
}
@@ -109,8 +125,9 @@ void BeamGlobalAssembler::emit_hashmap_get_element() {
a.bind(node_loop);
{
- Label fail = a.newLabel(), leaf_node = a.newLabel(),
- skip_index_adjustment = a.newLabel(), update_hash = a.newLabel();
+ Label done = a.newLabel(), leaf_node = a.newLabel(),
+ skip_index_adjustment = a.newLabel(),
+ collision_node = a.newLabel();
/* Find out which child we should follow, and shift the hash for the
* next round. */
@@ -131,7 +148,7 @@ void BeamGlobalAssembler::emit_hashmap_get_element() {
* Note that we jump directly to a `RET` instruction, as `BT` only
* affects CF, and ZF ("not found") is clear at this point. */
a.bt(header_val, index);
- a.short_().jnc(fail);
+ a.short_().jnc(done);
/* The actual offset of our entry is the number of bits set (in
* essence "entries present") before our index in the bitmap. */
@@ -154,11 +171,11 @@ void BeamGlobalAssembler::emit_hashmap_get_element() {
/* Nope, we have to search another node. */
a.mov(header_val, emit_boxed_val(node, 0, sizeof(Uint32)));
- /* After 8 nodes we've run out of the 32 bits we started with, so we
- * need to update the hash to keep going. */
- a.test(depth, imm(0x7));
- a.short_().jz(update_hash);
- a.short_().jmp(node_loop);
+ /* After 8 nodes we've run out of the 32 bits we started with
+ * and we end up in a collision node. */
+ a.test(depth, imm(HAMT_MAX_LEVEL - 1));
+ a.short_().jnz(node_loop);
+ a.short_().jmp(collision_node);
a.bind(leaf_node);
{
@@ -168,36 +185,33 @@ void BeamGlobalAssembler::emit_hashmap_get_element() {
a.mov(RET, getCDRRef(node));
/* See comment at the jump. */
- a.bind(fail);
+ a.bind(done);
a.ret();
}
- /* After 8 nodes we've run out of the 32 bits we started with, so we
- * must calculate a new hash to continue.
- *
- * This is a manual expansion `make_map_hash` from utils.c, and all
- * changes to that function must be mirrored here. */
- a.bind(update_hash);
+ /* A collision node is a tuple of leafs where we do linear search.*/
+ a.bind(collision_node);
{
- a.mov(TMP_MEM1d, depth);
+ Label linear_loop = a.newLabel();
- /* NOTE: ARG3d is always 0 at this point. */
- a.mov(ARG4d, depth);
- a.shr(ARG4d, imm(3));
- mov_imm(ARG5d, 1);
- a.mov(ARG6d, imm(HCONST_22));
- a.call(labels[internal_hash_helper]);
+ a.shr(header_val, imm(_HEADER_ARITY_OFFS));
+ a.lea(ARG6d, x86::qword_ptr(header_val, -1));
- a.xor_(ARG3d, imm(INTERNAL_HASH_SALT));
- a.mov(ARG4d, key.r32());
- a.mov(ARG5, key);
- a.shr(ARG5, imm(32));
- a.mov(ARG6d, imm(HCONST));
- a.call(labels[internal_hash_helper]);
+ a.bind(linear_loop);
+ {
+ a.mov(ARG3,
+ x86::qword_ptr(node, ARG6, 3, 8 - TAG_PRIMARY_BOXED));
- a.mov(depth, TMP_MEM1d);
+ emit_ptr_val(ARG3, ARG3);
+ a.cmp(key, getCARRef(ARG3));
+ a.mov(RET, getCDRRef(ARG3));
+ a.short_().jz(done);
- a.jmp(node_loop);
+ a.dec(ARG6d);
+ a.short_().jns(linear_loop);
+ }
+
+ a.ret();
}
}
}
@@ -381,8 +395,6 @@ void BeamGlobalAssembler::emit_i_get_map_element_shared() {
a.shr(ARG5, imm(32));
a.mov(ARG4d, ARG2d);
- a.mov(ARG3d, imm(INTERNAL_HASH_SALT));
- a.mov(ARG6d, imm(HCONST));
a.call(labels[internal_hash_helper]);
emit_hashmap_get_element();
diff --git a/erts/emulator/beam/utils.c b/erts/emulator/beam/utils.c
index 1f043869ce..8e2b13136b 100644
--- a/erts/emulator/beam/utils.c
+++ b/erts/emulator/beam/utils.c
@@ -1441,8 +1441,8 @@ tailrecur_ne:
sz = hashmap_bitcount(MAP_HEADER_VAL(hdr));
ASSERT(sz > 0 && sz < 17);
break;
- default:
- erts_exit(ERTS_ERROR_EXIT, "Unknown hashmap subsubtag\n");
+ default:
+ erts_exit(ERTS_ERROR_EXIT, "bad header");
}
goto term_array;
}
@@ -1983,6 +1983,11 @@ tailrecur_ne:
A minimal key can only be candidate as tie-breaker if we
have passed that hash value in the other tree (which means
the key did not exist in the other tree).
+
+ Collision node amendment:
+ The leafs in collision nodes are sorted in map-key order.
+ If keys are different but hashes are equal we advance the
+ one lagging behind key-wise.
*/
sp = PSTACK_PUSH(map_stack);
@@ -2395,12 +2400,19 @@ pop_next:
ASSERT(sp->is_hashmap);
if (j) {
/* Key diff found, enter phase 2 */
- if (hashmap_key_hash_cmp(sp->ap, sp->bp) < 0) {
+ int hash_cmp = hashmap_key_hash_cmp(sp->ap, sp->bp);
+ if (hash_cmp == 0) {
+ /* Hash collision. Collision nodes are sorted by map key
+ * order, so we advance the one with the lesser key */
+ hash_cmp = j;
+ }
+ if (hash_cmp < 0) {
sp->min_key = CAR(sp->ap);
sp->cmp_res = -1;
sp->ap = hashmap_iterator_next(&stack);
}
else {
+ ASSERT(hash_cmp > 0);
sp->min_key = CAR(sp->bp);
sp->cmp_res = 1;
sp->bp = hashmap_iterator_next(&b_stack);
@@ -2452,7 +2464,7 @@ pop_next:
sp->bp = hashmap_iterator_next(&b_stack);
if (!sp->ap) {
/* end of maps with identical keys */
- ASSERT(!sp->bp);
+ ASSERT(!sp->bp); /* as we assume indentical map sizes */
j = sp->cmp_res;
exact = sp->was_exact;
(void) PSTACK_POP(map_stack);
@@ -2488,14 +2500,21 @@ pop_next:
/* fall through */
case_HASHMAP_PHASE2_NEXT_STEP:
if (sp->ap || sp->bp) {
- if (hashmap_key_hash_cmp(sp->ap, sp->bp) < 0) {
+ int hash_cmp = hashmap_key_hash_cmp(sp->ap, sp->bp);
+ if (hash_cmp == 0) {
+ /* Hash collision. Collision nodes are sorted by map key
+ * order, so we advance the one with the lesser key */
+ hash_cmp = j;
+ }
+ if (hash_cmp < 0) {
ASSERT(sp->ap);
a = CAR(sp->ap);
b = sp->min_key;
ASSERT(exact);
WSTACK_PUSH(stack, OP_WORD(HASHMAP_PHASE2_IS_MIN_KEY_A));
}
- else { /* hash_cmp > 0 */
+ else {
+ ASSERT(hash_cmp > 0);
ASSERT(sp->bp);
a = CAR(sp->bp);
b = sp->min_key;
diff --git a/erts/emulator/nifs/common/prim_socket_int.h b/erts/emulator/nifs/common/prim_socket_int.h
index 90937d308e..9f753bf80b 100644
--- a/erts/emulator/nifs/common/prim_socket_int.h
+++ b/erts/emulator/nifs/common/prim_socket_int.h
@@ -352,6 +352,7 @@ typedef struct {
/* XXX Should be locked but too awkward and small gain */
BOOLEAN_T dbg;
BOOLEAN_T useReg;
+ BOOLEAN_T eei;
/* Registry stuff */
ErlNifPid regPid; /* Constant - not locked */
diff --git a/erts/emulator/nifs/common/prim_socket_nif.c b/erts/emulator/nifs/common/prim_socket_nif.c
index a46e44d53b..9d6755387d 100644
--- a/erts/emulator/nifs/common/prim_socket_nif.c
+++ b/erts/emulator/nifs/common/prim_socket_nif.c
@@ -1955,6 +1955,7 @@ static const struct in6_addr in6addr_loopback =
GLOBAL_ATOM_DECL(cmsg_cloexec); \
GLOBAL_ATOM_DECL(command); \
GLOBAL_ATOM_DECL(completion); \
+ GLOBAL_ATOM_DECL(completion_status); \
GLOBAL_ATOM_DECL(confirm); \
GLOBAL_ATOM_DECL(congestion); \
GLOBAL_ATOM_DECL(connect); \
@@ -2291,6 +2292,7 @@ ERL_NIF_TERM esock_atom_socket_tag; // This has a "special" name ('$socket')
LOCAL_ATOM_DECL(do); \
LOCAL_ATOM_DECL(dont); \
LOCAL_ATOM_DECL(dtor); \
+ LOCAL_ATOM_DECL(eei); \
LOCAL_ATOM_DECL(exclude); \
LOCAL_ATOM_DECL(false); \
LOCAL_ATOM_DECL(frag_needed); \
@@ -3844,7 +3846,7 @@ ERL_NIF_TERM esock_global_info(ErlNifEnv* env)
numBits, numSockets, numTypeDGrams, numTypeStreams,
numTypeSeqPkgs, numDomLocal, numDomInet, numDomInet6,
numProtoIP, numProtoTCP, numProtoUDP, numProtoSCTP,
- sockDbg, iovMax, dbg, useReg, iow;
+ sockDbg, iovMax, dbg, useReg, iow, eei;
MLOCK(data.cntMtx);
numBits = MKUI(env, ESOCK_COUNTER_SIZE);
@@ -3860,6 +3862,7 @@ ERL_NIF_TERM esock_global_info(ErlNifEnv* env)
numProtoUDP = MKUI(env, data.numProtoUDP);
numProtoSCTP = MKUI(env, data.numProtoSCTP);
sockDbg = BOOL2ATOM(data.sockDbg);
+ eei = BOOL2ATOM(data.eei);
MUNLOCK(data.cntMtx);
iovMax = MKI(env, data.iov_max);
@@ -3891,6 +3894,7 @@ ERL_NIF_TERM esock_global_info(ErlNifEnv* env)
ERL_NIF_TERM
keys[] = {esock_atom_debug,
atom_socket_debug,
+ atom_eei,
esock_atom_use_registry,
atom_iow,
esock_atom_counters,
@@ -3898,6 +3902,7 @@ ERL_NIF_TERM esock_global_info(ErlNifEnv* env)
atom_io_backend},
vals[] = {dbg,
sockDbg,
+ eei,
useReg,
iow,
gcnt,
@@ -13141,6 +13146,13 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
atom_iow,
ESOCK_NIF_IOW_DEFAULT);
+ /* --enable-extended-error-info */
+#if defined(ESOCK_USE_EXTENDED_ERROR_INFO)
+ data.eei = TRUE;
+#else
+ data.eei = FALSE;
+#endif
+
/* --esock-debug-file=<filename> */
{
char *debug_filename;
diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h
index 2613fe5a1e..9c3381f975 100644
--- a/erts/emulator/nifs/common/socket_int.h
+++ b/erts/emulator/nifs/common/socket_int.h
@@ -242,6 +242,7 @@ typedef long ssize_t;
GLOBAL_ATOM_DEF(cmsg_cloexec); \
GLOBAL_ATOM_DEF(command); \
GLOBAL_ATOM_DEF(completion); \
+ GLOBAL_ATOM_DEF(completion_status); \
GLOBAL_ATOM_DEF(confirm); \
GLOBAL_ATOM_DEF(congestion); \
GLOBAL_ATOM_DEF(connect); \
diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c
index 7028bacd69..a1c3e7eddc 100644
--- a/erts/emulator/nifs/common/socket_util.c
+++ b/erts/emulator/nifs/common/socket_util.c
@@ -2371,6 +2371,18 @@ ERL_NIF_TERM esock_errno_to_term(ErlNifEnv* env, int err)
break;
#endif
+#if defined(ERROR_INVALID_NETNAME)
+ case ERROR_INVALID_NETNAME:
+ return MKA(env, "invalid_netname");
+ break;
+#endif
+
+#if defined(ERROR_MORE_DATA)
+ case ERROR_MORE_DATA:
+ return MKA(env, "more_data");
+ break;
+#endif
+
default:
{
char* str = erl_errno_id(err);
diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h
index 94b62d7f0b..c16803bbdc 100644
--- a/erts/emulator/nifs/common/socket_util.h
+++ b/erts/emulator/nifs/common/socket_util.h
@@ -53,8 +53,7 @@
__LINE__, \
(RI), (I))
-#define ESOCK_VERBOSE_ERRNO 1
-#if defined(ESOCK_VERBOSE_ERRNO)
+#if defined(ESOCK_USE_EXTENDED_ERROR_INFO)
#define ENO2T(E, ENO) MKEEI((E), \
MKI((E), (ENO)), \
esock_errno_to_term((E), (ENO)))
diff --git a/erts/emulator/nifs/unix/unix_socket_syncio.c b/erts/emulator/nifs/unix/unix_socket_syncio.c
index 3e3cfcaeef..644400aeb5 100644
--- a/erts/emulator/nifs/unix/unix_socket_syncio.c
+++ b/erts/emulator/nifs/unix/unix_socket_syncio.c
@@ -29,6 +29,8 @@
# include "config.h"
#endif
+#ifdef ESOCK_ENABLE
+
#ifdef HAVE_SENDFILE
#if defined(__linux__) || (defined(__sun) && defined(__SVR4))
#include <sys/sendfile.h>
@@ -7385,3 +7387,4 @@ void essio_down_reader(ErlNifEnv* env,
}
+#endif
diff --git a/erts/emulator/nifs/win32/win_socket_asyncio.c b/erts/emulator/nifs/win32/win_socket_asyncio.c
index f91e7edc48..b7d1f424fd 100644
--- a/erts/emulator/nifs/win32/win_socket_asyncio.c
+++ b/erts/emulator/nifs/win32/win_socket_asyncio.c
@@ -83,6 +83,8 @@
# include "config.h"
#endif
+#ifdef ESOCK_ENABLE
+
// #include <Ws2def.h>
// #include <winsock2.h>
// #include <windows.h>
@@ -719,6 +721,21 @@ static BOOLEAN_T esaio_completion_send(ESAIOThreadData* dataP,
ErlNifPid* opCaller,
ESAIOOpDataSend* opDataP,
int error);
+static void esaio_completion_send_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSend* opDataP);
+static void esaio_completion_send_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSend* opDataP);
+static void esaio_completion_send_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSend* opDataP,
+ int error);
static void esaio_completion_send_completed(ErlNifEnv* env,
ESockDescriptor* descP,
OVERLAPPED* ovl,
@@ -748,6 +765,21 @@ static BOOLEAN_T esaio_completion_sendto(ESAIOThreadData* dataP,
ErlNifPid* opCaller,
ESAIOOpDataSendTo* opDataP,
int error);
+static void esaio_completion_sendto_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendTo* opDataP);
+static void esaio_completion_sendto_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendTo* opDataP);
+static void esaio_completion_sendto_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendTo* opDataP,
+ int error);
static void esaio_completion_sendto_fail(ErlNifEnv* env,
ESockDescriptor* descP,
int error,
@@ -759,6 +791,21 @@ static BOOLEAN_T esaio_completion_sendmsg(ESAIOThreadData* dataP,
ErlNifPid* opCaller,
ESAIOOpDataSendMsg* opDataP,
int error);
+static void esaio_completion_sendmsg_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendMsg* opDataP);
+static void esaio_completion_sendmsg_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendMsg* opDataP);
+static void esaio_completion_sendmsg_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendMsg* opDataP,
+ int error);
static void esaio_completion_sendmsg_fail(ErlNifEnv* env,
ESockDescriptor* descP,
int error,
@@ -770,6 +817,21 @@ static BOOLEAN_T esaio_completion_recv(ESAIOThreadData* dataP,
ErlNifPid* opCaller,
ESAIOOpDataRecv* opDataP,
int error);
+static void esaio_completion_recv_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecv* opDataP);
+static void esaio_completion_recv_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecv* opDataP);
+static void esaio_completion_recv_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecv* opDataP,
+ int error);
static void esaio_completion_recv_completed(ErlNifEnv* env,
ESockDescriptor* descP,
OVERLAPPED* ovl,
@@ -815,6 +877,27 @@ static BOOLEAN_T esaio_completion_recvfrom(ESAIOThreadData* dataP,
ErlNifPid* opCaller,
ESAIOOpDataRecvFrom* opDataP,
int error);
+static void esaio_completion_recvfrom_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvFrom* opDataP);
+static void esaio_completion_recvfrom_more_data(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvFrom* opDataP,
+ int error);
+static void esaio_completion_recvfrom_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvFrom* opDataP);
+static void esaio_completion_recvfrom_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvFrom* opDataP,
+ int error);
static void esaio_completion_recvfrom_completed(ErlNifEnv* env,
ESockDescriptor* descP,
OVERLAPPED* ovl,
@@ -845,6 +928,21 @@ static BOOLEAN_T esaio_completion_recvmsg(ESAIOThreadData* dataP,
ErlNifPid* opCaller,
ESAIOOpDataRecvMsg* opDataP,
int error);
+static void esaio_completion_recvmsg_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvMsg* opDataP);
+static void esaio_completion_recvmsg_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvMsg* opDataP);
+static void esaio_completion_recvmsg_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvMsg* opDataP,
+ int error);
static void esaio_completion_recvmsg_completed(ErlNifEnv* env,
ESockDescriptor* descP,
OVERLAPPED* ovl,
@@ -5719,7 +5817,7 @@ void esaio_completion_connect_failure(ErlNifEnv* env,
if (descP->connectorP != NULL) {
/* Figure out the reason */
ERL_NIF_TERM reason = MKT2(env,
- esock_atom_get_overlapped_result,
+ esock_atom_completion_status,
ENO2T(env, error));
/* Inform the user waiting for a reply */
@@ -6007,6 +6105,12 @@ void esaio_completion_accept_success(ErlNifEnv* env,
/* *Maybe* update socket (read) state
* (depends on if the queue is now empty)
*/
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_accept_success(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->acceptorsQ.first == NULL)), descP->readState) );
if (descP->acceptorsQ.first == NULL)
descP->readState &= ~ESOCK_STATE_SELECTED;
}
@@ -6029,7 +6133,6 @@ void esaio_completion_accept_aborted(ErlNifEnv* env,
ESAIOOpDataAccept* opDataP)
{
ESockRequestor req;
- ERL_NIF_TERM reason;
if (esock_acceptor_get(env, descP,
&opDataP->accRef,
@@ -6069,6 +6172,12 @@ void esaio_completion_accept_aborted(ErlNifEnv* env,
/* *Maybe* update socket (read) state
* (depends on if the queue is now empty)
*/
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_accept_aborted(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->acceptorsQ.first == NULL)), descP->readState) );
if (descP->acceptorsQ.first == NULL) {
descP->readState &= ~ESOCK_STATE_SELECTED;
}
@@ -6095,7 +6204,7 @@ void esaio_completion_accept_failure(ErlNifEnv* env,
&req)) {
reason = MKT2(env,
- esock_atom_get_overlapped_result,
+ esock_atom_completion_status,
ENO2T(env, error));
/* Inform the user waiting for a reply */
@@ -6109,6 +6218,12 @@ void esaio_completion_accept_failure(ErlNifEnv* env,
/* *Maybe* update socket (read) state
* (depends on if the queue is now empty)
*/
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_accept_failure(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->acceptorsQ.first == NULL)), descP->readState) );
if (descP->acceptorsQ.first == NULL) {
descP->readState &= ~ESOCK_STATE_SELECTED;
}
@@ -6370,40 +6485,9 @@ BOOLEAN_T esaio_completion_send(ESAIOThreadData* dataP,
("WIN-ESAIO", "esaio_completion_send(%d) -> no error"
"\r\n", descP->sock) );
MLOCK(descP->writeMtx);
- if (esock_writer_get(env, descP,
- &opDataP->sendRef,
- opCaller,
- &req)) {
- if (IS_OPEN(descP->writeState)) {
- esaio_completion_send_completed(env, descP,
- ovl,
- opEnv,
- opCaller,
- opDataP->sockRef,
- opDataP->sendRef,
- opDataP->wbuf.len,
- &req);
- } else {
- /* A completed (active) request for a socket that is not open.
- * Is this even possible?
- * A race (completed just as the socket was closed).
- */
- esaio_completion_send_not_active(descP);
- }
- } else {
- /* Request was actually completed directly
- * (and was therefor not put into the "queue")
- * => Nothing to do here, other than cleanup (see below).
- */
- }
-
- /* *Maybe* update socket (write) state
- * (depends on if the queue is now empty)
- */
- if (descP->writersQ.first == NULL) {
- descP->writeState &= ~ESOCK_STATE_SELECTED;
- }
+ esaio_completion_send_success(env, descP, ovl, opEnv,
+ opCaller, opDataP);
MUNLOCK(descP->writeMtx);
break;
@@ -6416,55 +6500,9 @@ BOOLEAN_T esaio_completion_send(ESAIOThreadData* dataP,
/* *** SAME MTX LOCK ORDER FOR ALL OPs *** */
MLOCK(descP->readMtx);
MLOCK(descP->writeMtx);
- /* The only thing *we* do that could cause an abort is the
- * 'CancelIoEx' call, which we do when closing the socket
- * (or cancel a request).
- * But if we have done that;
- * - Socket state will not be 'open' and
- * - we have also set closer (pid and ref).
- */
-
- if (esock_writer_get(env, descP,
- &opDataP->sendRef,
- opCaller,
- &req)) {
-
- reason = esock_atom_closed,
-
- /* Inform the user waiting for a reply */
- esock_send_abort_msg(env, descP, opDataP->sockRef,
- &req, reason);
-
- /* The socket not being open (assumed closing),
- * means we are in the closing phase...
- */
- if (! IS_OPEN(descP->writeState)) {
-
- /* We can only send the 'close' message to the closer
- * when all requests has been processed!
- */
-
- /* Check "our" queue */
- if (descP->writersQ.first == NULL) {
-
- /* Check "other" queue(s) and if there is a closer pid */
- if ((descP->readersQ.first == NULL) &&
- (descP->acceptorsQ.first == NULL)) {
-
- esaio_stop(env, descP);
- }
- }
- }
-
- /* *Maybe* update socket (write) state
- * (depends on if the queue is now empty)
- */
- if (descP->writersQ.first == NULL) {
- descP->writeState &= ~ESOCK_STATE_SELECTED;
- }
+ esaio_completion_send_aborted(env, descP, opCaller, opDataP);
- }
MUNLOCK(descP->writeMtx);
MUNLOCK(descP->readMtx);
break;
@@ -6475,36 +6513,9 @@ BOOLEAN_T esaio_completion_send(ESAIOThreadData* dataP,
"esaio_completion_send(%d) -> operation unknown failure"
"\r\n", descP->sock) );
MLOCK(descP->writeMtx);
- /* We do not know what this is
- * but we can "assume" that the request failed so we need to
- * remove it from the "queue" if its still there...
- * And cleanup...
- */
- if (esock_writer_get(env, descP,
- &opDataP->sendRef,
- opCaller,
- &req)) {
-
- reason = MKT2(env,
- esock_atom_get_overlapped_result,
- ENO2T(env, error));
-
- /* Inform the user waiting for a reply */
- esock_send_abort_msg(env, descP, opDataP->sockRef,
- &req, reason);
- esaio_completion_send_fail(env, descP, error, FALSE);
-
- } else {
- esaio_completion_send_fail(env, descP, error, TRUE);
- }
-
- /* *Maybe* update socket (write) state
- * (depends on if the queue is now empty)
- */
- if (descP->writersQ.first == NULL) {
- descP->writeState &= ~ESOCK_STATE_SELECTED;
- }
+ esaio_completion_send_failure(env, descP, opCaller, opDataP, error);
+
MUNLOCK(descP->writeMtx);
break;
}
@@ -6528,6 +6539,184 @@ BOOLEAN_T esaio_completion_send(ESAIOThreadData* dataP,
}
+
+/* *** esaio_completion_send_success ***
+ * The 'send' operation was successful.
+ */
+static
+void esaio_completion_send_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSend* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_writer_get(env, descP,
+ &opDataP->sendRef,
+ opCaller,
+ &req)) {
+ if (IS_OPEN(descP->writeState)) {
+ esaio_completion_send_completed(env, descP, ovl, opEnv,
+ opCaller,
+ opDataP->sockRef,
+ opDataP->sendRef,
+ opDataP->wbuf.len,
+ &req);
+ } else {
+ /* A completed (active) request for a socket that is not open.
+ * Is this even possible?
+ * A race (completed just as the socket was closed).
+ */
+ esaio_completion_send_not_active(descP);
+ }
+
+ } else {
+ /* Request was actually completed directly
+ * (and was therefor not put into the "queue")
+ * => Nothing to do here, other than cleanup (see below).
+ */
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_send_success(%d) -> "
+ "maybe (%s) update (write) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->writersQ.first == NULL)), descP->writeState) );
+ if (descP->writersQ.first == NULL) {
+ descP->writeState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+
+/* *** esaio_completion_send_aborted ***
+ * The only thing *we* do that could cause an abort is the
+ * 'CancelIoEx' call, which we do when closing the socket
+ * (or cancel a request).
+ * But if we have done that;
+ * - Socket state will not be 'open' and
+ * - we have also set closer (pid and ref).
+ */
+
+static
+void esaio_completion_send_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSend* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_writer_get(env, descP,
+ &opDataP->sendRef,
+ opCaller,
+ &req)) {
+
+ ERL_NIF_TERM reason = esock_atom_closed;
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+
+ /* The socket not being open (assumed closing),
+ * means we are in the closing phase...
+ */
+ if (! IS_OPEN(descP->writeState)) {
+
+ /* We can only send the 'close' message to the closer
+ * when all requests has been processed!
+ */
+
+ /* Check "our" queue */
+ if (descP->writersQ.first == NULL) {
+
+ /* Check "other" queue(s) and if there is a closer pid */
+ if ((descP->readersQ.first == NULL) &&
+ (descP->acceptorsQ.first == NULL)) {
+
+ esaio_stop(env, descP);
+
+ }
+ }
+ }
+
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_send_aborted(%d) -> "
+ "maybe (%s) update (write) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->writersQ.first == NULL)), descP->writeState) );
+ if (descP->writersQ.first == NULL) {
+ descP->writeState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+
+/* *** esaio_completion_send_failure *
+ * A "general" failure happened while performing the 'send' operation.
+ */
+static
+void esaio_completion_send_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSend* opDataP,
+ int error)
+{
+ ESockRequestor req;
+ ERL_NIF_TERM reason;
+
+ /* We do not know what this is
+ * but we can "assume" that the request failed so we need to
+ * remove it from the "queue" if its still there...
+ * And cleanup...
+ */
+ if (esock_writer_get(env, descP,
+ &opDataP->sendRef,
+ opCaller,
+ &req)) {
+
+ reason = MKT2(env,
+ esock_atom_completion_status,
+ ENO2T(env, error));
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+ esaio_completion_send_fail(env, descP, error, FALSE);
+
+ } else {
+ esaio_completion_send_fail(env, descP, error, TRUE);
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_send_failure(%d) -> "
+ "maybe (%s) update (write) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->writersQ.first == NULL)), descP->writeState) );
+ if (descP->writersQ.first == NULL) {
+ descP->writeState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
/* *** esaio_completion_send_completed ***
* The send request has completed.
*/
@@ -6785,40 +6974,9 @@ BOOLEAN_T esaio_completion_sendto(ESAIOThreadData* dataP,
("WIN-ESAIO", "esaio_completion_sendto(%d) -> no error"
"\r\n", descP->sock) );
MLOCK(descP->writeMtx);
- if (esock_writer_get(env, descP,
- &opDataP->sendRef,
- opCaller,
- &req)) {
- if (IS_OPEN(descP->writeState)) {
- esaio_completion_send_completed(env, descP,
- ovl,
- opEnv,
- opCaller,
- opDataP->sockRef,
- opDataP->sendRef,
- opDataP->wbuf.len,
- &req);
- } else {
- /* A completed (active) request for a socket that is not open.
- * Is this even possible?
- * A race (completed just as the socket was closed).
- */
- esaio_completion_send_not_active(descP);
- }
- } else {
- /* Request was actually completed directly
- * (and was therefor not put into the "queue")
- * => Nothing to do here, other than cleanup (see below).
- */
- }
-
- /* *Maybe* update socket (write) state
- * (depends on if the queue is now empty)
- */
- if (descP->writersQ.first == NULL) {
- descP->writeState &= ~ESOCK_STATE_SELECTED;
- }
+ esaio_completion_sendto_success(env, descP, ovl, opEnv,
+ opCaller, opDataP);
MUNLOCK(descP->writeMtx);
break;
@@ -6831,55 +6989,8 @@ BOOLEAN_T esaio_completion_sendto(ESAIOThreadData* dataP,
/* *** SAME MTX LOCK ORDER FOR ALL OPs *** */
MLOCK(descP->readMtx);
MLOCK(descP->writeMtx);
- /* The only thing *we* do that could cause an abort is the
- * 'CancelIoEx' call, which we do when closing the socket
- * (or cancel a request).
- * But if we have done that;
- * - Socket state will not be 'open' and
- * - we have also set closer (pid and ref).
- */
-
- if (esock_writer_get(env, descP,
- &opDataP->sendRef,
- opCaller,
- &req)) {
-
- reason = esock_atom_closed,
-
- /* Inform the user waiting for a reply */
- esock_send_abort_msg(env, descP, opDataP->sockRef,
- &req, reason);
-
- /* The socket not being open (assumed closing),
- * means we are in the closing phase...
- */
- if (! IS_OPEN(descP->writeState)) {
-
- /* We can only send the 'close' message to the closer
- * when all requests has been processed!
- */
-
- /* Check "our" queue */
- if (descP->writersQ.first == NULL) {
-
- /* Check "other" queue(s) and if there is a closer pid */
- if ((descP->readersQ.first == NULL) &&
- (descP->acceptorsQ.first == NULL)) {
-
- esaio_stop(env, descP);
-
- }
- }
- }
-
- }
- /* *Maybe* update socket (write) state
- * (depends on if the queue is now empty)
- */
- if (descP->writersQ.first == NULL) {
- descP->writeState &= ~ESOCK_STATE_SELECTED;
- }
+ esaio_completion_sendto_aborted(env, descP, opCaller, opDataP);
MUNLOCK(descP->writeMtx);
MUNLOCK(descP->readMtx);
@@ -6891,35 +7002,8 @@ BOOLEAN_T esaio_completion_sendto(ESAIOThreadData* dataP,
"esaio_completion_sendto(%d) -> operation unknown failure"
"\r\n", descP->sock) );
MLOCK(descP->writeMtx);
- /* We do not know what this is
- * but we can "assume" that the request failed so we need to
- * remove it from the "queue" if its still there...
- * And cleanup...
- */
- if (esock_writer_get(env, descP,
- &opDataP->sendRef,
- opCaller,
- &req)) {
- reason = MKT2(env,
- esock_atom_get_overlapped_result,
- ENO2T(env, error));
-
- /* Inform the user waiting for a reply */
- esock_send_abort_msg(env, descP, opDataP->sockRef,
- &req, reason);
- esaio_completion_sendto_fail(env, descP, error, FALSE);
-
- } else {
- esaio_completion_sendto_fail(env, descP, error, TRUE);
- }
-
- /* *Maybe* update socket (write) state
- * (depends on if the queue is now empty)
- */
- if (descP->writersQ.first == NULL) {
- descP->writeState &= ~ESOCK_STATE_SELECTED;
- }
+ esaio_completion_sendto_failure(env, descP, opCaller, opDataP, error);
MUNLOCK(descP->writeMtx);
break;
@@ -6945,6 +7029,180 @@ BOOLEAN_T esaio_completion_sendto(ESAIOThreadData* dataP,
+/* *** esaio_completion_sendto_suuccess *** */
+static
+void esaio_completion_sendto_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendTo* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_writer_get(env, descP,
+ &opDataP->sendRef,
+ opCaller,
+ &req)) {
+ if (IS_OPEN(descP->writeState)) {
+ esaio_completion_send_completed(env, descP, ovl, opEnv,
+ opCaller,
+ opDataP->sockRef,
+ opDataP->sendRef,
+ opDataP->wbuf.len,
+ &req);
+ } else {
+ /* A completed (active) request for a socket that is not open.
+ * Is this even possible?
+ * A race (completed just as the socket was closed).
+ */
+ esaio_completion_send_not_active(descP);
+ }
+
+ } else {
+ /* Request was actually completed directly
+ * (and was therefor not put into the "queue")
+ * => Nothing to do here, other than cleanup (see below).
+ */
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_sendto_success(%d) -> "
+ "maybe (%s) update (write) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->writersQ.first == NULL)), descP->writeState) );
+ if (descP->writersQ.first == NULL) {
+ descP->writeState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+
+/* *** esaio_completion_sendto_aborted ***
+ * The only thing *we* do that could cause an abort is the
+ * 'CancelIoEx' call, which we do when closing the socket
+ * (or cancel a request).
+ * But if we have done that;
+ * - Socket state will not be 'open' and
+ * - we have also set closer (pid and ref).
+ */
+
+static
+void esaio_completion_sendto_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendTo* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_writer_get(env, descP,
+ &opDataP->sendRef,
+ opCaller,
+ &req)) {
+
+ ERL_NIF_TERM reason = esock_atom_closed;
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+
+ /* The socket not being open (assumed closing),
+ * means we are in the closing phase...
+ */
+ if (! IS_OPEN(descP->writeState)) {
+
+ /* We can only send the 'close' message to the closer
+ * when all requests has been processed!
+ */
+
+ /* Check "our" queue */
+ if (descP->writersQ.first == NULL) {
+
+ /* Check "other" queue(s) and if there is a closer pid */
+ if ((descP->readersQ.first == NULL) &&
+ (descP->acceptorsQ.first == NULL)) {
+
+ esaio_stop(env, descP);
+
+ }
+ }
+ }
+
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_sendto_aborted(%d) -> "
+ "maybe (%s) update (write) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->writersQ.first == NULL)), descP->writeState) );
+ if (descP->writersQ.first == NULL) {
+ descP->writeState &= ~ESOCK_STATE_SELECTED;
+ }
+}
+
+
+/* *** esaio_completion_sendto_failure *
+ * A "general" failure happened while performing the 'sendto' operation.
+ */
+static
+void esaio_completion_sendto_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendTo* opDataP,
+ int error)
+{
+ ESockRequestor req;
+ ERL_NIF_TERM reason;
+
+ /* We do not know what this is
+ * but we can "assume" that the request failed so we need to
+ * remove it from the "queue" if its still there...
+ * And cleanup...
+ */
+ if (esock_writer_get(env, descP,
+ &opDataP->sendRef,
+ opCaller,
+ &req)) {
+
+ reason = MKT2(env,
+ esock_atom_completion_status,
+ ENO2T(env, error));
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+ esaio_completion_sendto_fail(env, descP, error, FALSE);
+
+ } else {
+ esaio_completion_sendto_fail(env, descP, error, TRUE);
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_sendto_failure(%d) -> "
+ "maybe (%s) update (write) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->writersQ.first == NULL)), descP->writeState) );
+ if (descP->writersQ.first == NULL) {
+ descP->writeState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+
/* *** esaio_completion_sendto_fail ***
* Unknown operation failure.
*/
@@ -7007,50 +7265,10 @@ BOOLEAN_T esaio_completion_sendmsg(ESAIOThreadData* dataP,
("WIN-ESAIO", "esaio_completion_sendmsg(%d) -> no error"
"\r\n", descP->sock) );
MLOCK(descP->writeMtx);
- if (esock_writer_get(env, descP,
- &opDataP->sendRef,
- opCaller,
- &req)) {
- if (IS_OPEN(descP->writeState)) {
-
- DWORD toWrite = 0;
-
- /* Calculate how much data *in total*
- * we was supposed to write */
- for (int i = 0; i < opDataP->iovec->iovcnt; i++) {
- toWrite += opDataP->iovec->iov[i].iov_len;
- }
- esaio_completion_send_completed(env, descP,
- ovl,
- opEnv,
- opCaller,
- opDataP->sockRef,
- opDataP->sendRef,
- toWrite,
- &req);
+ esaio_completion_sendmsg_success(env, descP, ovl, opEnv,
+ opCaller, opDataP);
- } else {
- /* A completed (active) request for a socket that is not open.
- * Is this even possible?
- * A race (completed just as the socket was closed).
- */
- esaio_completion_send_not_active(descP);
- }
-
- /* *Maybe* update socket (write) state
- * (depends on if the queue is now empty)
- */
- if (descP->writersQ.first == NULL) {
- descP->writeState &= ~ESOCK_STATE_SELECTED;
- }
-
- } else {
- /* Request was actually completed directly
- * (and was therefor not put into the "queue")
- * => Nothing to do here, other than cleanup (see below).
- */
- }
MUNLOCK(descP->writeMtx);
break;
@@ -7062,55 +7280,9 @@ BOOLEAN_T esaio_completion_sendmsg(ESAIOThreadData* dataP,
/* *** SAME MTX LOCK ORDER FOR ALL OPs *** */
MLOCK(descP->readMtx);
MLOCK(descP->writeMtx);
- /* The only thing *we* do that could cause an abort is the
- * 'CancelIoEx' call, which we do when closing the socket
- * (or cancel a request).
- * But if we have done that;
- * - Socket state will not be 'open' and
- * - we have also set closer (pid and ref).
- */
-
- if (esock_writer_get(env, descP,
- &opDataP->sendRef,
- opCaller,
- &req)) {
-
- reason = esock_atom_closed,
-
- /* Inform the user waiting for a reply */
- esock_send_abort_msg(env, descP, opDataP->sockRef,
- &req, reason);
-
- /* The socket not being open (assumed closing),
- * means we are in the closing phase...
- */
- if (! IS_OPEN(descP->writeState)) {
-
- /* We can only send the 'close' message to the closer
- * when all requests has been processed!
- */
-
- /* Check "our" queue */
- if (descP->writersQ.first == NULL) {
-
- /* Check "other" queue(s) and if there is a closer pid */
- if ((descP->readersQ.first == NULL) &&
- (descP->acceptorsQ.first == NULL)) {
-
- esaio_stop(env, descP);
- }
- }
- }
-
- /* *Maybe* update socket (write) state
- * (depends on if the queue is now empty)
- */
- if (descP->writersQ.first == NULL) {
- descP->writeState &= ~ESOCK_STATE_SELECTED;
- }
+ esaio_completion_sendmsg_aborted(env, descP, opCaller, opDataP);
- }
MUNLOCK(descP->writeMtx);
MUNLOCK(descP->readMtx);
break;
@@ -7121,35 +7293,9 @@ BOOLEAN_T esaio_completion_sendmsg(ESAIOThreadData* dataP,
"esaio_completion_sendmsg(%d) -> operation unknown failure"
"\r\n", descP->sock) );
MLOCK(descP->writeMtx);
- /* We do not know what this is
- * but we can "assume" that the request failed so we need to
- * remove it from the "queue" if its still there...
- * And cleanup...
- */
- if (esock_writer_get(env, descP,
- &opDataP->sendRef,
- opCaller,
- &req)) {
-
- reason = MKT2(env,
- esock_atom_get_overlapped_result,
- ENO2T(env, error));
-
- /* Inform the user waiting for a reply */
- esock_send_abort_msg(env, descP, opDataP->sockRef,
- &req, reason);
- esaio_completion_sendmsg_fail(env, descP, error, FALSE);
-
- /* *Maybe* update socket (write) state
- * (depends on if the queue is now empty)
- */
- if (descP->writersQ.first == NULL) {
- descP->writeState &= ~ESOCK_STATE_SELECTED;
- }
- } else {
- esaio_completion_sendmsg_fail(env, descP, error, TRUE);
- }
+ esaio_completion_sendmsg_failure(env, descP, opCaller, opDataP, error);
+
MUNLOCK(descP->writeMtx);
break;
}
@@ -7176,6 +7322,188 @@ BOOLEAN_T esaio_completion_sendmsg(ESAIOThreadData* dataP,
}
+/* *** esaio_completion_sendmsg_suuccess *** */
+static
+void esaio_completion_sendmsg_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendMsg* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_writer_get(env, descP,
+ &opDataP->sendRef,
+ opCaller,
+ &req)) {
+ if (IS_OPEN(descP->writeState)) {
+
+ DWORD toWrite = 0;
+
+ /* Calculate how much data *in total*
+ * we was supposed to write */
+ for (int i = 0; i < opDataP->iovec->iovcnt; i++) {
+ toWrite += opDataP->iovec->iov[i].iov_len;
+ }
+
+ esaio_completion_send_completed(env, descP, ovl, opEnv,
+ opCaller,
+ opDataP->sockRef,
+ opDataP->sendRef,
+ toWrite,
+ &req);
+
+ } else {
+ /* A completed (active) request for a socket that is not open.
+ * Is this even possible?
+ * A race (completed just as the socket was closed).
+ */
+ esaio_completion_send_not_active(descP);
+ }
+
+ } else {
+ /* Request was actually completed directly
+ * (and was therefor not put into the "queue")
+ * => Nothing to do here, other than cleanup (see below).
+ */
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_sendmsg_success(%d) -> "
+ "maybe (%s) update (write) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->writersQ.first == NULL)), descP->writeState) );
+ if (descP->writersQ.first == NULL) {
+ descP->writeState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_sendmsg_aborted ***
+ * The only thing *we* do that could cause an abort is the
+ * 'CancelIoEx' call, which we do when closing the socket
+ * (or cancel a request).
+ * But if we have done that;
+ * - Socket state will not be 'open' and
+ * - we have also set closer (pid and ref).
+ */
+
+static
+void esaio_completion_sendmsg_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendMsg* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_writer_get(env, descP,
+ &opDataP->sendRef,
+ opCaller,
+ &req)) {
+
+ ERL_NIF_TERM reason = esock_atom_closed;
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+
+ /* The socket not being open (assumed closing),
+ * means we are in the closing phase...
+ */
+ if (! IS_OPEN(descP->writeState)) {
+
+ /* We can only send the 'close' message to the closer
+ * when all requests has been processed!
+ */
+
+ /* Check "our" queue */
+ if (descP->writersQ.first == NULL) {
+
+ /* Check "other" queue(s) and if there is a closer pid */
+ if ((descP->readersQ.first == NULL) &&
+ (descP->acceptorsQ.first == NULL)) {
+
+ esaio_stop(env, descP);
+
+ }
+ }
+ }
+
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_sendmsg_aborted(%d) -> "
+ "maybe (%s) update (write) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->writersQ.first == NULL)), descP->writeState) );
+ if (descP->writersQ.first == NULL) {
+ descP->writeState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_sendmsg_failure *
+ * A "general" failure happened while performing the 'sendmsg' operation.
+ */
+static
+void esaio_completion_sendmsg_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataSendMsg* opDataP,
+ int error)
+{
+ ESockRequestor req;
+ ERL_NIF_TERM reason;
+
+ /* We do not know what this is
+ * but we can "assume" that the request failed so we need to
+ * remove it from the "queue" if its still there...
+ * And cleanup...
+ */
+ if (esock_writer_get(env, descP,
+ &opDataP->sendRef,
+ opCaller,
+ &req)) {
+
+ reason = MKT2(env,
+ esock_atom_completion_status,
+ ENO2T(env, error));
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+ esaio_completion_sendmsg_fail(env, descP, error, FALSE);
+
+ } else {
+ esaio_completion_sendmsg_fail(env, descP, error, TRUE);
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_sendmsg_success(%d) -> "
+ "maybe (%s) update (write) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->writersQ.first == NULL)), descP->writeState) );
+ if (descP->writersQ.first == NULL) {
+ descP->writeState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
/* *** esaio_completion_sendmsg_fail ***
* Unknown operation failure.
@@ -7238,39 +7566,10 @@ BOOLEAN_T esaio_completion_recv(ESAIOThreadData* dataP,
("WIN-ESAIO", "esaio_completion_recv(%d) -> no error"
"\r\n", descP->sock) );
MLOCK(descP->readMtx);
- if (esock_reader_get(env, descP,
- &opDataP->recvRef,
- opCaller,
- &req)) {
- if (IS_OPEN(descP->readState)) {
- esaio_completion_recv_completed(env, descP,
- ovl,
- opEnv, opCaller, opDataP,
- &req);
- } else {
- /* A completed (active) request for a socket that is not open.
- * Is this even possible?
- * A race (completed just as the socket was closed).
- */
- esaio_completion_recv_not_active(descP);
- FREE_BIN( &opDataP->buf );
- }
- /* *Maybe* update socket (write) state
- * (depends on if the queue is now empty)
- */
- if (descP->readersQ.first == NULL) {
- descP->readState &= ~ESOCK_STATE_SELECTED;
- }
+ esaio_completion_recv_success(env, descP, ovl, opEnv,
+ opCaller, opDataP);
- } else {
- /* Request was actually completed directly
- * (and was therefor not put into the "queue")
- * => Nothing to do here, other than cleanup (see below).
- * => But we do not free the "buffer" since it was "used up"
- * when we (as assumed) got the result (directly)...
- */
- }
MUNLOCK(descP->readMtx);
break;
@@ -7282,55 +7581,9 @@ BOOLEAN_T esaio_completion_recv(ESAIOThreadData* dataP,
/* *** SAME MTX LOCK ORDER FOR ALL OPs *** */
MLOCK(descP->readMtx);
MLOCK(descP->writeMtx);
- /* The only thing *we* do that could cause an abort is the
- * 'CancelIoEx' call, which we do when closing the socket
- * (or cancel a request).
- * But if we have done that;
- * - Socket state will not be 'open' and
- * - we have also set closer (pid and ref).
- */
-
- if (esock_reader_get(env, descP,
- &opDataP->recvRef,
- opCaller,
- &req)) {
- reason = esock_atom_closed,
+ esaio_completion_recv_aborted(env, descP, opCaller, opDataP);
- /* Inform the user waiting for a reply */
- esock_send_abort_msg(env, descP, opDataP->sockRef,
- &req, reason);
-
- /* The socket not being open (assumed closing),
- * means we are in the closing phase...
- */
- if (! IS_OPEN(descP->readState)) {
-
- /* We can only send the 'close' message to the closer
- * when all requests has been processed!
- */
-
- /* Check "our" queue */
- if (descP->readersQ.first == NULL) {
-
- /* Check "other" queue(s) and if there is a closer pid */
- if ((descP->writersQ.first == NULL) &&
- (descP->acceptorsQ.first == NULL)) {
-
- esaio_stop(env, descP);
-
- }
- }
- }
-
- /* *Maybe* update socket (write) state
- * (depends on if the queue is now empty)
- */
- if (descP->readersQ.first == NULL) {
- descP->readState &= ~ESOCK_STATE_SELECTED;
- }
- }
- FREE_BIN( &opDataP->buf );
MUNLOCK(descP->writeMtx);
MUNLOCK(descP->readMtx);
break;
@@ -7341,36 +7594,9 @@ BOOLEAN_T esaio_completion_recv(ESAIOThreadData* dataP,
"esaio_completion_recv(%d) -> operation unknown failure"
"\r\n", descP->sock) );
MLOCK(descP->readMtx);
- /* We do not know what this is
- * but we can "assume" that the request failed so we need to
- * remove it from the "queue" if its still there...
- * And cleanup...
- */
- if (esock_reader_get(env, descP,
- &opDataP->recvRef,
- opCaller,
- &req)) {
- /* Figure out the reason */
- reason = MKT2(env,
- esock_atom_get_overlapped_result,
- ENO2T(env, error));
-
- /* Inform the user waiting for a reply */
- esock_send_abort_msg(env, descP, opDataP->sockRef,
- &req, reason);
- esaio_completion_recv_fail(env, descP, error, FALSE);
-
- /* *Maybe* update socket (write) state
- * (depends on if the queue is now empty)
- */
- if (descP->readersQ.first == NULL) {
- descP->readState &= ~ESOCK_STATE_SELECTED;
- }
-
- } else {
- esaio_completion_recv_fail(env, descP, error, TRUE);
- }
- FREE_BIN( &opDataP->buf );
+
+ esaio_completion_recv_failure(env, descP, opCaller, opDataP, error);
+
MUNLOCK(descP->readMtx);
break;
}
@@ -7385,13 +7611,187 @@ BOOLEAN_T esaio_completion_recv(ESAIOThreadData* dataP,
esock_free_env("esaio_completion_recv - op cleanup", opEnv);
SSDBG( descP,
- ("WIN-ESAIO", "esaio_completion_recv {%d} -> done\r\n",
+ ("WIN-ESAIO", "esaio_completion_recv(%d) -> done\r\n",
descP->sock) );
return FALSE;
}
+static
+void esaio_completion_recv_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecv* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_reader_get(env, descP,
+ &opDataP->recvRef,
+ opCaller,
+ &req)) {
+ if (IS_OPEN(descP->readState)) {
+ esaio_completion_recv_completed(env, descP, ovl, opEnv,
+ opCaller, opDataP,
+ &req);
+ } else {
+ /* A completed (active) request for a socket that is not open.
+ * Is this even possible?
+ * A race (completed just as the socket was closed).
+ */
+ esaio_completion_recv_not_active(descP);
+ FREE_BIN( &opDataP->buf );
+ }
+
+ } else {
+ /* Request was actually completed directly
+ * (and was therefor not put into the "queue")
+ * => Nothing to do here, other than cleanup (see below).
+ * => But we do not free the "buffer" since it was "used up"
+ * when we (as assumed) got the result (directly)...
+ */
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv_success(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->readersQ.first == NULL)), descP->readState) );
+ if (descP->readersQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_recv_aborted ***
+ * The only thing *we* do that could cause an abort is the
+ * 'CancelIoEx' call, which we do when closing the socket
+ * (or cancel a request).
+ * But if we have done that;
+ * - Socket state will not be 'open' and
+ * - we have also set closer (pid and ref).
+ */
+
+static
+void esaio_completion_recv_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecv* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_reader_get(env, descP,
+ &opDataP->recvRef,
+ opCaller,
+ &req)) {
+
+ ERL_NIF_TERM reason = esock_atom_closed;
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+
+ /* The socket not being open (assumed closing),
+ * means we are in the closing phase...
+ */
+ if (! IS_OPEN(descP->readState)) {
+
+ /* We can only send the 'close' message to the closer
+ * when all requests has been processed!
+ */
+
+ /* Check "our" queue */
+ if (descP->readersQ.first == NULL) {
+
+ /* Check "other" queue(s) and if there is a closer pid */
+ if ((descP->writersQ.first == NULL) &&
+ (descP->acceptorsQ.first == NULL)) {
+
+ esaio_stop(env, descP);
+
+ }
+ }
+ }
+ }
+
+ FREE_BIN( &opDataP->buf );
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv_aborted(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->readersQ.first == NULL)), descP->readState) );
+ if (descP->readersQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_recv_failure *
+ * A "general" failure happened while performing the 'recv' operation.
+ */
+static
+void esaio_completion_recv_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecv* opDataP,
+ int error)
+{
+ ESockRequestor req;
+ ERL_NIF_TERM reason;
+
+ /* We do not know what this is
+ * but we can "assume" that the request failed so we need to
+ * remove it from the "queue" if its still there...
+ * And cleanup...
+ */
+ if (esock_reader_get(env, descP,
+ &opDataP->recvRef,
+ opCaller,
+ &req)) {
+ /* Figure out the reason */
+ reason = MKT2(env,
+ esock_atom_completion_status,
+ ENO2T(env, error));
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+ esaio_completion_recv_fail(env, descP, error, FALSE);
+
+ } else {
+ esaio_completion_recv_fail(env, descP, error, TRUE);
+ }
+
+ FREE_BIN( &opDataP->buf );
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recv_failure(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->readersQ.first == NULL)), descP->readState) );
+ if (descP->readersQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
/* *** esaio_completion_recv_completed ***
* The recv request has completed.
@@ -7863,38 +8263,24 @@ BOOLEAN_T esaio_completion_recvfrom(ESAIOThreadData* dataP,
("WIN-ESAIO", "esaio_completion_recvfrom(%d) -> no error"
"\r\n", descP->sock) );
MLOCK(descP->readMtx);
- if (esock_reader_get(env, descP,
- &opDataP->recvRef,
- opCaller,
- &req)) {
- if (IS_OPEN(descP->readState)) {
- esaio_completion_recvfrom_completed(env, descP,
- ovl, opEnv, opCaller,
- opDataP, &req);
- } else {
- /* A completed (active) request for a socket that is not open.
- * Is this even possible?
- * A race (completed just as the socket was closed).
- */
- esaio_completion_recv_not_active(descP);
- FREE_BIN( &opDataP->buf );
- }
- /* *Maybe* update socket (write) state
- * (depends on if the queue is now empty)
- */
- if (descP->readersQ.first == NULL) {
- descP->readState &= ~ESOCK_STATE_SELECTED;
- }
+ esaio_completion_recvfrom_success(env, descP, ovl, opEnv,
+ opCaller, opDataP);
+
+ MUNLOCK(descP->readMtx);
+ break;
+
+ case ERROR_MORE_DATA:
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom(%d) -> more data"
+ "\r\n", descP->sock) );
+ MLOCK(descP->readMtx);
+
+ esaio_completion_recvfrom_more_data(env, descP,
+ opEnv, opCaller, opDataP,
+ error);
- } else {
- /* Request was actually completed directly
- * (and was therefor not put into the "queue")
- * => Nothing to do here, other than cleanup (see below).
- * => But we do not free the "buffer" since it was "used up"
- * when we (as assumed) got the result (directly)...
- */
- }
MUNLOCK(descP->readMtx);
break;
@@ -7906,56 +8292,9 @@ BOOLEAN_T esaio_completion_recvfrom(ESAIOThreadData* dataP,
/* *** SAME MTX LOCK ORDER FOR ALL OPs *** */
MLOCK(descP->readMtx);
MLOCK(descP->writeMtx);
- /* The only thing *we* do that could cause an abort is the
- * 'CancelIoEx' call, which we do when closing the socket
- * (or cancel a request).
- * But if we have done that;
- * - Socket state will not be 'open' and
- * - we have also set closer (pid and ref).
- */
-
- if (esock_reader_get(env, descP,
- &opDataP->recvRef,
- opCaller,
- &req)) {
-
- reason = esock_atom_closed,
- /* Inform the user waiting for a reply */
- esock_send_abort_msg(env, descP, opDataP->sockRef,
- &req, reason);
+ esaio_completion_recvfrom_aborted(env, descP, opCaller, opDataP);
- /* The socket not being open (assumed closing),
- * means we are in the closing phase...
- */
- if (! IS_OPEN(descP->readState)) {
-
- /* We can only send the 'close' message to the closer
- * when all requests has been processed!
- */
-
- /* Check "our" queue */
- if (descP->readersQ.first == NULL) {
-
- /* Check "other" queue(s) and if there is a closer pid */
- if ((descP->writersQ.first == NULL) &&
- (descP->acceptorsQ.first == NULL)) {
-
- esaio_stop(env, descP);
-
- }
- }
- }
-
- /* *Maybe* update socket (write) state
- * (depends on if the queue is now empty)
- */
- if (descP->readersQ.first == NULL) {
- descP->readState &= ~ESOCK_STATE_SELECTED;
- }
-
- }
- FREE_BIN( &opDataP->buf );
MUNLOCK(descP->writeMtx);
MUNLOCK(descP->readMtx);
break;
@@ -7966,36 +8305,9 @@ BOOLEAN_T esaio_completion_recvfrom(ESAIOThreadData* dataP,
"esaio_completion_recvfrom(%d) -> operation unknown failure"
"\r\n", descP->sock) );
MLOCK(descP->readMtx);
- /* We do not know what this is
- * but we can "assume" that the request failed so we need to
- * remove it from the "queue" if its still there...
- * And cleanup...
- */
- if (esock_reader_get(env, descP,
- &opDataP->recvRef,
- opCaller,
- &req)) {
-
- reason = MKT2(env,
- esock_atom_get_overlapped_result,
- ENO2T(env, error));
-
- /* Inform the user waiting for a reply */
- esock_send_abort_msg(env, descP, opDataP->sockRef,
- &req, reason);
- esaio_completion_recvfrom_fail(env, descP, error, FALSE);
-
- /* *Maybe* update socket (write) state
- * (depends on if the queue is now empty)
- */
- if (descP->readersQ.first == NULL) {
- descP->readState &= ~ESOCK_STATE_SELECTED;
- }
- } else {
- esaio_completion_recvfrom_fail(env, descP, error, TRUE);
- }
- FREE_BIN( &opDataP->buf );
+ esaio_completion_recvfrom_failure(env, descP, opCaller, opDataP, error);
+
MUNLOCK(descP->readMtx);
break;
}
@@ -8015,6 +8327,250 @@ BOOLEAN_T esaio_completion_recvfrom(ESAIOThreadData* dataP,
}
+static
+void esaio_completion_recvfrom_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvFrom* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_reader_get(env, descP,
+ &opDataP->recvRef,
+ opCaller,
+ &req)) {
+ if (IS_OPEN(descP->readState)) {
+ esaio_completion_recvfrom_completed(env, descP,
+ ovl, opEnv, opCaller,
+ opDataP, &req);
+ } else {
+ /* A completed (active) request for a socket that is not open.
+ * Is this even possible?
+ * A race (completed just as the socket was closed).
+ */
+ esaio_completion_recv_not_active(descP);
+ FREE_BIN( &opDataP->buf );
+ }
+
+ } else {
+ /* Request was actually completed directly
+ * (and was therefor not put into the "queue")
+ * => Nothing to do here, other than cleanup (see below).
+ * => But we do not free the "buffer" since it was "used up"
+ * when we (as assumed) got the result (directly)...
+ */
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom_success(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->readersQ.first == NULL)), descP->readState) );
+ if (descP->readersQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+static
+void esaio_completion_recvfrom_more_data(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvFrom* opDataP,
+ int error)
+{
+ ESockRequestor req;
+
+ if (esock_reader_get(env, descP,
+ &opDataP->recvRef,
+ opCaller,
+ &req)) {
+ if (IS_OPEN(descP->readState)) {
+ /* We do not actually need to call this function
+ * since we already know its 'more_data', but just
+ * get the same format...
+ */
+ ERL_NIF_TERM reason = MKT2(env,
+ esock_atom_completion_status,
+ ENO2T(env, error));
+ ERL_NIF_TERM completionStatus = esock_make_error(env, reason);
+ ERL_NIF_TERM completionInfo = MKT2(opEnv,
+ opDataP->recvRef,
+ completionStatus);
+
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom_more_data(%d) -> "
+ "send completion message: "
+ "\r\n Completion Status: %T"
+ "\r\n", descP->sock, completionStatus) );
+
+ /* Send a 'recvfrom' completion message */
+ esaio_send_completion_msg(env, // Send env
+ descP, // Descriptor
+ opCaller, // Msg destination
+ opEnv, // Msg env
+ opDataP->sockRef, // Dest socket
+ completionInfo); // Info
+
+ }
+
+ FREE_BIN( &opDataP->buf );
+
+ } else {
+ /* Request was actually completed directly
+ * (and was therefor not put into the "queue")
+ * => Nothing to do here, other than cleanup (see below).
+ * => But we do not free the "buffer" since it was "used up"
+ * when we (as assumed) got the result (directly)...
+ */
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom_more_data(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->readersQ.first == NULL)), descP->readState) );
+ if (descP->readersQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_recvfrom_aborted ***
+ * The only thing *we* do that could cause an abort is the
+ * 'CancelIoEx' call, which we do when closing the socket
+ * (or cancel a request).
+ * But if we have done that;
+ * - Socket state will not be 'open' and
+ * - we have also set closer (pid and ref).
+ */
+static
+void esaio_completion_recvfrom_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvFrom* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_reader_get(env, descP,
+ &opDataP->recvRef,
+ opCaller,
+ &req)) {
+
+ ERL_NIF_TERM reason = esock_atom_closed;
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+
+ /* The socket not being open (assumed closing),
+ * means we are in the closing phase...
+ */
+ if (! IS_OPEN(descP->readState)) {
+
+ /* We can only send the 'close' message to the closer
+ * when all requests has been processed!
+ */
+
+ /* Check "our" queue */
+ if (descP->readersQ.first == NULL) {
+
+ /* Check "other" queue(s) and if there is a closer pid */
+ if ((descP->writersQ.first == NULL) &&
+ (descP->acceptorsQ.first == NULL)) {
+
+ esaio_stop(env, descP);
+
+ }
+ }
+ }
+ }
+
+ FREE_BIN( &opDataP->buf );
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom_aborted(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->readersQ.first == NULL)), descP->readState) );
+ if (descP->readersQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_recvfrom_failure *
+ * A "general" failure happened while performing the 'recvfrom' operation.
+ */
+static
+void esaio_completion_recvfrom_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvFrom* opDataP,
+ int error)
+{
+ ESockRequestor req;
+ ERL_NIF_TERM reason;
+
+ /* We do not know what this is
+ * but we can "assume" that the request failed so we need to
+ * remove it from the "queue" if its still there...
+ * And cleanup...
+ */
+ if (esock_reader_get(env, descP,
+ &opDataP->recvRef,
+ opCaller,
+ &req)) {
+
+ reason = MKT2(env,
+ esock_atom_completion_status,
+ ENO2T(env, error));
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+ esaio_completion_recvfrom_fail(env, descP, error, FALSE);
+
+ } else {
+ esaio_completion_recvfrom_fail(env, descP, error, TRUE);
+ }
+
+ FREE_BIN( &opDataP->buf );
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvfrom_failure(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->readersQ.first == NULL)), descP->readState) );
+ if (descP->readersQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
/* *** esaio_completion_recvfrom_completed ***
* The recvfrom request has completed.
@@ -8329,40 +8885,10 @@ BOOLEAN_T esaio_completion_recvmsg(ESAIOThreadData* dataP,
descP->sock,
opDataP->recvRef, MKPID(env, opCaller)) );
MLOCK(descP->readMtx);
- if (esock_reader_get(env, descP,
- &opDataP->recvRef,
- opCaller,
- &req)) {
- if (IS_OPEN(descP->readState)) {
- esaio_completion_recvmsg_completed(env, descP,
- ovl, opEnv, opCaller,
- opDataP,
- &req);
- } else {
- /* A completed (active) request for a socket that is not open.
- * Is this even possible?
- * A race (completed just as the socket was closed).
- */
- esaio_completion_recv_not_active(descP);
- FREE_BIN( &opDataP->data[0] );
- FREE_BIN( &opDataP->ctrl );
- }
- /* *Maybe* update socket (write) state
- * (depends on if the queue is now empty)
- */
- if (descP->readersQ.first == NULL) {
- descP->readState &= ~ESOCK_STATE_SELECTED;
- }
+ esaio_completion_recvmsg_success(env, descP, ovl, opEnv,
+ opCaller, opDataP);
- } else {
- /* Request was actually completed directly
- * (and was therefor not put into the "queue")
- * => Nothing to do here, other than cleanup (see below).
- * => But we do not free the "buffer" since it was "used up"
- * when we (as assumed) got the result (directly)...
- */
- }
MUNLOCK(descP->readMtx);
break;
@@ -8374,57 +8900,9 @@ BOOLEAN_T esaio_completion_recvmsg(ESAIOThreadData* dataP,
/* *** SAME MTX LOCK ORDER FOR ALL OPs *** */
MLOCK(descP->readMtx);
MLOCK(descP->writeMtx);
- /* The only thing *we* do that could cause an abort is the
- * 'CancelIoEx' call, which we do when closing the socket
- * (or cancel a request).
- * But if we have done that;
- * - Socket state will not be 'open' and
- * - we have also set closer (pid and ref).
- */
-
- if (esock_reader_get(env, descP,
- &opDataP->recvRef,
- opCaller,
- &req)) {
-
- reason = esock_atom_closed,
- /* Inform the user waiting for a reply */
- esock_send_abort_msg(env, descP, opDataP->sockRef,
- &req, reason);
+ esaio_completion_recvmsg_aborted(env, descP, opCaller, opDataP);
- /* The socket not being open (assumed closing),
- * means we are in the closing phase...
- */
- if (! IS_OPEN(descP->readState)) {
-
- /* We can only send the 'close' message to the closer
- * when all requests has been processed!
- */
-
- /* Check "our" queue */
- if (descP->readersQ.first == NULL) {
-
- /* Check "other" queue(s) and if there is a closer pid */
- if ((descP->writersQ.first == NULL) &&
- (descP->acceptorsQ.first == NULL)) {
-
- esaio_stop(env, descP);
-
- }
- }
- }
-
- /* *Maybe* update socket (write) state
- * (depends on if the queue is now empty)
- */
- if (descP->readersQ.first == NULL) {
- descP->readState &= ~ESOCK_STATE_SELECTED;
- }
-
- }
- FREE_BIN( &opDataP->data[0] );
- FREE_BIN( &opDataP->ctrl );
MUNLOCK(descP->writeMtx);
MUNLOCK(descP->readMtx);
break;
@@ -8432,40 +8910,12 @@ BOOLEAN_T esaio_completion_recvmsg(ESAIOThreadData* dataP,
default:
SSDBG( descP,
("WIN-ESAIO",
- "esaio_completion_recvmsg(%d) -> operation unknown failure"
+ "esaio_completion_recvmsg(%d) -> unknown operation failure"
"\r\n", descP->sock) );
MLOCK(descP->readMtx);
- /* We do not know what this is
- * but we can "assume" that the request failed so we need to
- * remove it from the "queue" if its still there...
- * And cleanup...
- */
- if (esock_reader_get(env, descP,
- &opDataP->recvRef,
- opCaller,
- &req)) {
-
- reason = MKT2(env,
- esock_atom_get_overlapped_result,
- ENO2T(env, error));
-
- /* Inform the user waiting for a reply */
- esock_send_abort_msg(env, descP, opDataP->sockRef,
- &req, reason);
- esaio_completion_recvmsg_fail(env, descP, error, FALSE);
-
- /* *Maybe* update socket (write) state
- * (depends on if the queue is now empty)
- */
- if (descP->readersQ.first == NULL) {
- descP->readState &= ~ESOCK_STATE_SELECTED;
- }
- } else {
- esaio_completion_recvmsg_fail(env, descP, error, TRUE);
- }
- FREE_BIN( &opDataP->data[0] );
- FREE_BIN( &opDataP->ctrl );
+ esaio_completion_recvmsg_failure(env, descP, opCaller, opDataP, error);
+
MUNLOCK(descP->readMtx);
break;
}
@@ -8487,6 +8937,184 @@ BOOLEAN_T esaio_completion_recvmsg(ESAIOThreadData* dataP,
}
+static
+void esaio_completion_recvmsg_success(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ OVERLAPPED* ovl,
+ ErlNifEnv* opEnv,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvMsg* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_reader_get(env, descP,
+ &opDataP->recvRef,
+ opCaller,
+ &req)) {
+ if (IS_OPEN(descP->readState)) {
+ esaio_completion_recvmsg_completed(env, descP, ovl, opEnv,
+ opCaller, opDataP,
+ &req);
+ } else {
+ /* A completed (active) request for a socket that is not open.
+ * Is this even possible?
+ * A race (completed just as the socket was closed).
+ */
+ esaio_completion_recv_not_active(descP);
+ FREE_BIN( &opDataP->data[0] );
+ FREE_BIN( &opDataP->ctrl );
+ }
+
+ } else {
+ /* Request was actually completed directly
+ * (and was therefor not put into the "queue")
+ * => Nothing to do here, other than cleanup (see below).
+ * => But we do not free the "buffer" since it was "used up"
+ * when we (as assumed) got the result (directly)...
+ */
+ }
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvmsg_success(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->readersQ.first == NULL)), descP->readState) );
+ if (descP->readersQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_recvmsg_aborted ***
+ * The only thing *we* do that could cause an abort is the
+ * 'CancelIoEx' call, which we do when closing the socket
+ * (or cancel a request).
+ * But if we have done that;
+ * - Socket state will not be 'open' and
+ * - we have also set closer (pid and ref).
+ */
+
+static
+void esaio_completion_recvmsg_aborted(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvMsg* opDataP)
+{
+ ESockRequestor req;
+
+ if (esock_reader_get(env, descP,
+ &opDataP->recvRef,
+ opCaller,
+ &req)) {
+
+ ERL_NIF_TERM reason = esock_atom_closed;
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+
+ /* The socket not being open (assumed closing),
+ * means we are in the closing phase...
+ */
+ if (! IS_OPEN(descP->readState)) {
+
+ /* We can only send the 'close' message to the closer
+ * when all requests has been processed!
+ */
+
+ /* Check "our" queue */
+ if (descP->readersQ.first == NULL) {
+
+ /* Check "other" queue(s) and if there is a closer pid */
+ if ((descP->writersQ.first == NULL) &&
+ (descP->acceptorsQ.first == NULL)) {
+
+ esaio_stop(env, descP);
+
+ }
+ }
+ }
+
+ }
+
+ FREE_BIN( &opDataP->data[0] );
+ FREE_BIN( &opDataP->ctrl );
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvmsg_aborted(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->readersQ.first == NULL)), descP->readState) );
+ if (descP->readersQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
+
+/* *** esaio_completion_recvmsg_failure *
+ * A "general" failure happened while performing the 'recvmsg' operation.
+ */
+static
+void esaio_completion_recvmsg_failure(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ErlNifPid* opCaller,
+ ESAIOOpDataRecvMsg* opDataP,
+ int error)
+{
+ ESockRequestor req;
+ ERL_NIF_TERM reason;
+
+ /* We do not know what this is
+ * but we can "assume" that the request failed so we need to
+ * remove it from the "queue" if its still there...
+ * And cleanup...
+ */
+ if (esock_reader_get(env, descP,
+ &opDataP->recvRef,
+ opCaller,
+ &req)) {
+
+ reason = MKT2(env,
+ esock_atom_completion_status,
+ ENO2T(env, error));
+
+ /* Inform the user waiting for a reply */
+ esock_send_abort_msg(env, descP, opDataP->sockRef,
+ &req, reason);
+ esaio_completion_recvmsg_fail(env, descP, error, FALSE);
+
+ } else {
+ esaio_completion_recvmsg_fail(env, descP, error, TRUE);
+ }
+
+ FREE_BIN( &opDataP->data[0] );
+ FREE_BIN( &opDataP->ctrl );
+
+ /* *Maybe* update socket (write) state
+ * (depends on if the queue is now empty)
+ */
+ SSDBG( descP,
+ ("WIN-ESAIO",
+ "esaio_completion_recvmsg_failure(%d) -> "
+ "maybe (%s) update (read) state (ox%X)\r\n",
+ descP->sock,
+ B2S((descP->readersQ.first == NULL)), descP->readState) );
+ if (descP->readersQ.first == NULL) {
+ descP->readState &= ~ESOCK_STATE_SELECTED;
+ }
+
+}
+
/* *** esaio_completion_recvmsg_completed ***
* The recvmsg request has completed.
@@ -8802,7 +9430,7 @@ void esaio_completion_fail(ErlNifEnv* env,
if (inform)
esock_warning_msg("[WIN-ESAIO] Unknown (%s) operation failure: "
"\r\n Descriptor: %d"
- "\r\n Errno: %T"
+ "\r\n Error: %T"
"\r\n",
opStr, descP->sock, ENO2T(env, error));
@@ -8865,10 +9493,12 @@ void esaio_dtor(ErlNifEnv* env,
* so we must have closed it properly to get here
*/
if (! IS_CLOSED(descP->readState) )
- esock_warning_msg("Socket Read State not CLOSED at dtor\r\n");
+ esock_warning_msg("Socket Read State not CLOSED (0x%X) "
+ "at dtor\r\n", descP->readState);
if (! IS_CLOSED(descP->writeState) )
- esock_warning_msg("Socket Write State not CLOSED at dtor\r\n");
+ esock_warning_msg("Socket Write State not CLOSED (0x%X) "
+ "at dtor\r\n", descP->writeState);
if ( descP->sock != INVALID_SOCKET )
esock_warning_msg("Socket %d still valid\r\n", descP->sock);
@@ -8949,7 +9579,21 @@ void esaio_stop(ErlNifEnv* env,
err = esock_close_socket(env, descP, FALSE);
- if (err != 0)
+ switch (err) {
+ case NO_ERROR:
+ break;
+ case WSAENOTSOCK:
+ if (descP->sock != INVALID_SOCKET)
+ esock_warning_msg("[WIN-ESAIO] Attempt to close an "
+ "already closed socket"
+ "\r\n(without a closer process): "
+ "\r\n Controlling Process: %T"
+ "\r\n socket fd: %d"
+ "\r\n",
+ descP->ctrlPid, descP->sock);
+ break;
+
+ default:
esock_warning_msg("[WIN-ESAIO] Failed closing socket without "
"closer process: "
"\r\n Controlling Process: %T"
@@ -8957,6 +9601,9 @@ void esaio_stop(ErlNifEnv* env,
"\r\n Errno: %T"
"\r\n",
descP->ctrlPid, descP->sock, ENO2T(env, err));
+ break;
+ }
+
}
SSDBG( descP,
@@ -9598,3 +10245,6 @@ ERL_NIF_TERM mk_completion_msg(ErlNifEnv* env,
return esock_mk_socket_msg(env, sockRef,
esock_atom_completion, info);
}
+
+
+#endif
diff --git a/erts/emulator/test/distribution_SUITE.erl b/erts/emulator/test/distribution_SUITE.erl
index f7fa527bd5..d577f2a1e5 100644
--- a/erts/emulator/test/distribution_SUITE.erl
+++ b/erts/emulator/test/distribution_SUITE.erl
@@ -84,7 +84,9 @@
dyn_node_name_monitor/1,
async_dist_flag/1,
async_dist_port_dctrlr/1,
- async_dist_proc_dctrlr/1]).
+ async_dist_proc_dctrlr/1,
+ creation_selection/1,
+ creation_selection_test/1]).
%% Internal exports.
-export([sender/3, receiver2/2, dummy_waiter/0, dead_process/0,
@@ -118,7 +120,7 @@ all() ->
start_epmd_false, no_epmd, epmd_module, system_limit,
hopefull_data_encoding, hopefull_export_fun_bug,
huge_iovec, is_alive, dyn_node_name_monitor_node, dyn_node_name_monitor,
- {group, async_dist}].
+ {group, async_dist}, creation_selection].
groups() ->
[{bulk_send, [], [bulk_send_small, bulk_send_big, bulk_send_bigbig]},
@@ -520,9 +522,6 @@ nodes2(Config) when is_list(Config) ->
end,
ok.
-id(X) ->
- X.
-
%% Test optimistic distribution flags toward pending connections (DFLAG_DIST_HOPEFULLY)
optimistic_dflags(Config) when is_list(Config) ->
{ok, PeerSender, _Sender} = ?CT_PEER(#{connection => 0, args => ["-setcookie", "NONE"]}),
@@ -3631,8 +3630,126 @@ async_dist_test(Node) ->
ok.
+creation_selection(Config) when is_list(Config) ->
+ register(creation_selection_test_supervisor, self()),
+ Name = atom_to_list(?FUNCTION_NAME) ++ "-"
+ ++ integer_to_list(erlang:system_time()),
+ Host = hostname(),
+ Cmd = lists:append(
+ [ct:get_progname(),
+ " -noshell",
+ " -setcookie ", atom_to_list(erlang:get_cookie()),
+ " -pa ", filename:dirname(code:which(?MODULE)),
+ " -s ", atom_to_list(?MODULE), " ",
+ " creation_selection_test ", atom_to_list(node()), " ",
+ atom_to_list(net_kernel:longnames()), " ", Name, " ", Host]),
+ ct:pal("Node command: ~p~n", [Cmd]),
+ Port = open_port({spawn, Cmd}, [exit_status]),
+ Node = list_to_atom(lists:append([Name, "@", Host])),
+ ok = receive_creation_selection_info(Port, Node).
+
+receive_creation_selection_info(Port, Node) ->
+ receive
+ {creation_selection_test, Node, Creations, InvalidCreation,
+ ClashResolvedCreation} = Msg ->
+ ct:log("Test result: ~p~n", [Msg]),
+ %% Verify that creation values are created as expected. The
+ %% list of creations is in reverse start order...
+ MaxC = (1 bsl 32) - 1,
+ MinC = 4,
+ StartOrderCreations = lists:reverse(Creations),
+ InvalidCreation = lists:foldl(fun (C, C) when is_integer(C),
+ MinC =< C,
+ C =< MaxC ->
+ %% Return next expected
+ %% creation...
+ if C == MaxC -> MinC;
+ true -> C+1
+ end
+ end,
+ hd(StartOrderCreations),
+ StartOrderCreations),
+ false = lists:member(ClashResolvedCreation, [InvalidCreation
+ | Creations]),
+ receive
+ {Port, {exit_status, 0}} ->
+ Port ! {self(), close},
+ ok;
+ {Port, {exit_status, EStat}} ->
+ ct:fail({"node exited abnormally: ", EStat})
+ end;
+ {Port, {exit_status, EStat}} ->
+ ct:fail({"node prematurely exited: ", EStat});
+ {Port, {data, Data}} ->
+ ct:log("~ts", [Data]),
+ receive_creation_selection_info(Port, Node)
+ end,
+ ok.
+
+creation_selection_test([TestSupNode, LongNames, Name, Host]) ->
+ try
+ StartArgs = [Name,
+ case LongNames of
+ true -> longnames;
+ false -> shortnames
+ end],
+ Node = list_to_atom(lists:append([atom_to_list(Name),
+ "@", atom_to_list(Host)])),
+ GoDistributed = fun (F) ->
+ {ok, _} = net_kernel:start(StartArgs),
+ Node = node(),
+ Creation = erlang:system_info(creation),
+ _ = F(Creation),
+ net_kernel:stop(),
+ Creation
+ end,
+ %% We start multiple times to verify that the creation values
+ %% we get from epmd are delivered in sequence. This is a
+ %% must for the test case such as it is written now, but can be
+ %% changed. If changed, this test case must be updated...
+ {Creations,
+ LastCreation} = lists:foldl(fun (_, {Cs, _LC}) ->
+ CFun = fun (X) -> X end,
+ C = GoDistributed(CFun),
+ {[C|Cs], C}
+ end, {[], 0}, lists:seq(1, 5)),
+ %% We create a pid with the creation that epmd will offer us the next
+ %% time we start the distribution and then start the distribution
+ %% once more. The node should avoid this creation, since this would
+ %% cause external identifiers in the system with same
+ %% nodename/creation pair as used by the local node, which in turn
+ %% would cause these identifers not to work as expected. That is, the
+ %% node should silently reject this creation and chose another one when
+ %% starting the distribution.
+ InvalidCreation = LastCreation+1,
+ Pid = erts_test_utils:mk_ext_pid({Node, InvalidCreation}, 4711, 0),
+ true = erts_debug:size(Pid) > 0, %% External pid
+ ResultFun = fun (ClashResolvedCreation) ->
+ pong = net_adm:ping(TestSupNode),
+ Msg = {creation_selection_test, node(), Creations,
+ InvalidCreation, ClashResolvedCreation},
+ {creation_selection_test_supervisor, TestSupNode}
+ ! Msg,
+ %% Wait a bit so the message have time to get
+ %% through before we take down the distribution...
+ receive after 500 -> ok end
+ end,
+ _ = GoDistributed(ResultFun),
+ %% Ensure Pid is not garbage collected before starting the
+ %% distribution...
+ _ = id(Pid),
+ erlang:halt(0)
+ catch
+ Class:Reason:StackTrace ->
+ erlang:display({Class, Reason, StackTrace}),
+ erlang:halt(17)
+ end.
+
%%% Utilities
+id(X) ->
+ X.
+
wait_until(Fun) ->
wait_until(Fun, 24*60*60*1000).
diff --git a/erts/emulator/test/map_SUITE.erl b/erts/emulator/test/map_SUITE.erl
index 317b3a1e77..5d7546c1a4 100644
--- a/erts/emulator/test/map_SUITE.erl
+++ b/erts/emulator/test/map_SUITE.erl
@@ -276,6 +276,8 @@ t_build_and_match_literals_large(Config) when is_list(Config) ->
60 = map_size(M0),
60 = maps:size(M0),
+ 60 = apply(erlang, id(map_size), [M0]),
+ 60 = apply(maps, id(size), [M0]),
% with repeating
M1 = id(#{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
@@ -312,6 +314,8 @@ t_build_and_match_literals_large(Config) when is_list(Config) ->
60 = map_size(M1),
60 = maps:size(M1),
+ 60 = apply(erlang, id(map_size), [M1]),
+ 60 = apply(maps, id(size), [M1]),
% with floats
@@ -365,6 +369,8 @@ t_build_and_match_literals_large(Config) when is_list(Config) ->
90 = map_size(M2),
90 = maps:size(M2),
+ 90 = apply(erlang, id(map_size), [M2]),
+ 90 = apply(maps, id(size), [M2]),
% with bignums
M3 = id(#{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
@@ -428,6 +434,8 @@ t_build_and_match_literals_large(Config) when is_list(Config) ->
98 = map_size(M3),
98 = maps:size(M3),
+ 98 = apply(erlang, id(map_size), [M3]),
+ 98 = apply(maps, id(size), [M3]),
%% with maps
@@ -548,6 +556,8 @@ t_build_and_match_literals_large(Config) when is_list(Config) ->
95 = map_size(M4),
95 = maps:size(M4),
+ 95 = apply(erlang, id(map_size), [M4]),
+ 95 = apply(maps, id(size), [M4]),
% call for value
@@ -645,6 +655,8 @@ t_build_and_match_literals_large(Config) when is_list(Config) ->
95 = map_size(M5),
95 = maps:size(M5),
+ 95 = apply(erlang, id(map_size), [M5]),
+ 95 = apply(maps, id(size), [M5]),
%% remember
@@ -2220,6 +2232,10 @@ t_bif_map_merge(Config) when is_list(Config) ->
{'EXIT',{{badmap,T},[{maps,merge,_,_}|_]}} =
(catch maps:merge(T, #{})),
{'EXIT',{{badmap,T},[{maps,merge,_,_}|_]}} =
+ (catch maps:merge(M11, T)),
+ {'EXIT',{{badmap,T},[{maps,merge,_,_}|_]}} =
+ (catch maps:merge(T, M11)),
+ {'EXIT',{{badmap,T},[{maps,merge,_,_}|_]}} =
(catch maps:merge(T, T))
end),
ok.
@@ -2462,6 +2478,16 @@ t_bif_erlang_phash2() ->
70249457 = erlang:phash2(M0), % 118679416
59617982 = erlang:phash2(M1), % 51612236
70249457 = erlang:phash2(M2), % 118679416
+
+ M1000 = maps:from_list([{K,K} || K <- lists:seq(1,1000)]),
+ 66609305 = erlang:phash2(M1000),
+
+ Mnested1 = #{flatmap => M0, M0 => flatmap, hashmap => M1000, M1000 => hashmap},
+ 113689339 = erlang:phash2(Mnested1),
+
+ Mnested2 = maps:merge(Mnested1, M1000),
+ 29167443 = erlang:phash2(Mnested2),
+
ok.
t_bif_erlang_phash() ->
@@ -2482,6 +2508,16 @@ t_bif_erlang_phash() ->
2620391445 = erlang:phash(M0,Sz), % 3590546636
1670235874 = erlang:phash(M1,Sz), % 4066388227
2620391445 = erlang:phash(M2,Sz), % 3590546636
+
+ M1000 = maps:from_list([{K,K} || K <- lists:seq(1,1000)]),
+ 1945662653 = erlang:phash(M1000, Sz),
+
+ Mnested1 = #{flatmap => M0, M0 => flatmap, hashmap => M1000, M1000 => hashmap},
+ 113694495 = erlang:phash(Mnested1, Sz),
+
+ Mnested2 = maps:merge(Mnested1, M1000),
+ 431825783 = erlang:phash(Mnested2, Sz),
+
ok.
t_map_encode_decode(Config) when is_list(Config) ->
@@ -2957,25 +2993,31 @@ t_maps_without(_Config) ->
%% Verify that the the number of nodes in hashmaps
%% of different types and sizes does not deviate too
%% much from the theoretical model.
+%% For debug with DBG_HASHMAP_COLLISION_BONANZA the test will expect
+%% the hashmaps to NOT be well balanced.
t_hashmap_balance(_Config) ->
+ erts_debug:set_internal_state(available_internal_state, true),
+ ExpectBalance = not erts_debug:get_internal_state(hashmap_collision_bonanza),
+ hashmap_balance(ExpectBalance),
+ erts_debug:set_internal_state(available_internal_state, false),
+ ok.
+
+hashmap_balance(EB) ->
io:format("Integer keys\n", []),
- hashmap_balance(fun(I) -> I end),
+ hashmap_balance(fun(I) -> I end, EB),
io:format("Float keys\n", []),
- hashmap_balance(fun(I) -> float(I) end),
+ hashmap_balance(fun(I) -> float(I) end, EB),
io:format("String keys\n", []),
- hashmap_balance(fun(I) -> integer_to_list(I) end),
+ hashmap_balance(fun(I) -> integer_to_list(I) end, EB),
io:format("Binary (big) keys\n", []),
- hashmap_balance(fun(I) -> <<I:16/big>> end),
+ hashmap_balance(fun(I) -> <<I:16/big>> end, EB),
io:format("Binary (little) keys\n", []),
- hashmap_balance(fun(I) -> <<I:16/little>> end),
+ hashmap_balance(fun(I) -> <<I:16/little>> end, EB),
io:format("Atom keys\n", []),
- erts_debug:set_internal_state(available_internal_state, true),
- hashmap_balance(fun(I) -> erts_debug:get_internal_state({atom,I}) end),
- erts_debug:set_internal_state(available_internal_state, false),
-
+ hashmap_balance(fun(I) -> erts_debug:get_internal_state({atom,I}) end, EB),
ok.
-hashmap_balance(KeyFun) ->
+hashmap_balance(KeyFun, ExpectBalance) ->
%% For uniformly distributed hash values, the average number of nodes N
%% in a hashmap varies between 0.3*K and 0.4*K where K is number of keys.
%% The standard deviation of N is about sqrt(K)/3.
@@ -3019,9 +3061,10 @@ hashmap_balance(KeyFun) ->
erts_debug:flat_size(MaxMap)])
end,
- true = (MaxDiff < 6), % The probability of this line failing is about 0.000000001
- % for a uniform hash. I've set the probability this "high" for now
- % to detect flaws in our make_internal_hash.
+ %% The probability of this line failing is about 0.000000001
+ %% for a uniform hash. I've set the probability this "high" for now
+ %% to detect flaws in our make_internal_hash.
+ ExpectBalance = (MaxDiff < 6),
ok.
hashmap_nodes(M) ->
@@ -3030,6 +3073,7 @@ hashmap_nodes(M) ->
case element(1,Tpl) of
bitmaps -> Acc + element(2,Tpl);
arrays -> Acc + element(2,Tpl);
+ collisions -> Acc + element(2,Tpl);
_ -> Acc
end
end,
diff --git a/erts/etc/common/escript.c b/erts/etc/common/escript.c
index fe474338fb..c7418b2ace 100644
--- a/erts/etc/common/escript.c
+++ b/erts/etc/common/escript.c
@@ -286,7 +286,7 @@ find_prog(char *origpath)
beg = end + 1;
continue;
}
- strncpy(dir, beg, sz);
+ memcpy(dir, beg, sz);
dir[sz] = '\0';
beg = end + 1;
diff --git a/erts/preloaded/ebin/prim_socket.beam b/erts/preloaded/ebin/prim_socket.beam
index ba50f42a01..45f5ab71d9 100644
--- a/erts/preloaded/ebin/prim_socket.beam
+++ b/erts/preloaded/ebin/prim_socket.beam
Binary files differ
diff --git a/erts/preloaded/src/prim_socket.erl b/erts/preloaded/src/prim_socket.erl
index fd4432cbc9..8da47bba82 100644
--- a/erts/preloaded/src/prim_socket.erl
+++ b/erts/preloaded/src/prim_socket.erl
@@ -512,8 +512,10 @@ send(SockRef, Bin, EFlags, SendRef) when is_integer(EFlags) ->
{select, Written} ->
<<_:Written/binary, RestBin/binary>> = Bin,
{select, RestBin, EFlags};
- completion ->
- completion;
+
+ completion = C ->
+ C;
+
{error, _Reason} = Result ->
Result
end;
@@ -560,11 +562,13 @@ sendto(SockRef, Bin, To, Flags, SendRef) ->
sockaddr ->
{error, {invalid, {Cause, To}}}
end;
+
ok ->
ok;
{ok, Written} ->
<<_:Written/binary, RestBin/binary>> = Bin,
{ok, RestBin};
+
select ->
Cont = {To, ETo, EFlags},
{select, Cont};
@@ -572,6 +576,10 @@ sendto(SockRef, Bin, To, Flags, SendRef) ->
<<_:Written/binary, RestBin/binary>> = Bin,
Cont = {To, ETo, EFlags},
{select, RestBin, Cont};
+
+ completion = C->
+ C;
+
{error, _Reason} = Result ->
Result
end
diff --git a/erts/test/otp_SUITE.erl b/erts/test/otp_SUITE.erl
index 40e2be0a85..a8424ea46f 100644
--- a/erts/test/otp_SUITE.erl
+++ b/erts/test/otp_SUITE.erl
@@ -542,7 +542,7 @@ ignore_undefs() ->
%% The following functions are optional dependencies for diameter
#{{dbg,ctp,0} => true,
{dbg,p,2} => true,
- {dbg,stop_clear,0} => true,
+ {dbg,stop,0} => true,
{dbg,trace_port,2} => true,
{dbg,tracer,2} => true,
{erl_prettypr,format,1} => true,
diff --git a/erts/vsn.mk b/erts/vsn.mk
index c9cabd73d4..67df301d2e 100644
--- a/erts/vsn.mk
+++ b/erts/vsn.mk
@@ -18,7 +18,7 @@
# %CopyrightEnd%
#
-VSN = 13.2
+VSN = 13.2.2
# Port number 4365 in 4.2
# Port number 4366 in 4.3
diff --git a/lib/common_test/doc/src/ct_suite.xml b/lib/common_test/doc/src/ct_suite.xml
index 8740a3ff79..cc926fbcbb 100644
--- a/lib/common_test/doc/src/ct_suite.xml
+++ b/lib/common_test/doc/src/ct_suite.xml
@@ -85,7 +85,9 @@
<fsummary>Returns the list of all test case groups and test cases
in the module.</fsummary>
<type>
- <v><seetype marker="#ct_test_def">ct_test_def()</seetype> = TestCase | {group, GroupName} | {group, GroupName, Properties} | {group, GroupName, Properties, SubGroups}</v>
+ <v><seetype marker="#ct_test_def">ct_test_def()</seetype> = TestCase |
+ {group, GroupName} | {group, GroupName, Properties} | {group, GroupName,
+ Properties, SubGroups} | {testcase, TestCase, TestCaseRepeatType}</v>
<v>TestCase = <seetype marker="#ct_testname">ct_testname()</seetype></v>
<v>GroupName = <seetype marker="#ct_groupname">ct_groupname()</seetype></v>
<v>Properties = [parallel | sequence | Shuffle | {RepeatType, N}] | default</v>
@@ -93,6 +95,7 @@
<v>Shuffle = shuffle | {shuffle, Seed}</v>
<v>Seed = {integer(), integer(), integer()}</v>
<v>RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v>
+ <v>TestCaseRepeatType = [{repeat, N} | {repeat_until_ok, N} | {repeat_until_fail, N}]</v>
<v>N = integer() | forever</v>
<v>Reason = term()</v>
</type>
@@ -135,11 +138,12 @@
<v><seetype marker="#ct_group_def">ct_group_def()</seetype> = {GroupName, Properties, GroupsAndTestCases}</v>
<v>GroupName = <seetype marker="#ct_groupname">ct_groupname()</seetype></v>
<v>Properties = [parallel | sequence | Shuffle | {RepeatType, N}]</v>
- <v>GroupsAndTestCases = [Group | {group, GroupName} | TestCase]</v>
+ <v>GroupsAndTestCases = [Group | {group, GroupName} | TestCase | {testcase, TestCase, TestCaseRepeatType}]</v>
<v>TestCase = <seetype marker="#ct_testname">ct_testname()</seetype></v>
<v>Shuffle = shuffle | {shuffle, Seed}</v>
<v>Seed = {integer(), integer(), integer()}</v>
<v>RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v>
+ <v>TestCaseRepeatType = [{repeat, N} | {repeat_until_ok, N} | {repeat_until_fail, N}]</v>
<v>N = integer() | forever</v>
</type>
diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml
index f9416e1bee..ea33b9d8f9 100644
--- a/lib/common_test/doc/src/run_test_chapter.xml
+++ b/lib/common_test/doc/src/run_test_chapter.xml
@@ -763,7 +763,7 @@
finally function <c>end_per_group</c>. Also, if particular
test cases in a group are specified, <c>init_per_group</c>
and <c>end_per_group</c>, for the group in question, are
- called. If a group defined (in <c>Suite:group/0</c>) as
+ called. If a group defined (in <c>Suite:groups/0</c>) as
a subgroup of another group, is specified (or if particular test
cases of a subgroup are), <c>Common Test</c> calls the configuration
functions for the top-level groups and for the subgroup
diff --git a/lib/common_test/doc/src/write_test_chapter.xml b/lib/common_test/doc/src/write_test_chapter.xml
index 8f7c7e8177..99571bbdae 100644
--- a/lib/common_test/doc/src/write_test_chapter.xml
+++ b/lib/common_test/doc/src/write_test_chapter.xml
@@ -515,14 +515,14 @@
<c>{group,GroupName,Properties,SubGroups}</c>
Where, <c>SubGroups</c> is a list of tuples, <c>{GroupName,Properties}</c> or
<c>{GroupName,Properties,SubGroups}</c> representing the subgroups.
- Any subgroups defined in <c>group/0</c> for a group, that are not specified
+ Any subgroups defined in <c>groups/0</c> for a group, that are not specified
in the <c>SubGroups</c> list, executes with their predefined
properties.</p>
<p><em>Example:</em></p>
<pre>
- groups() -> {tests1, [], [{tests2, [], [t2a,t2b]},
- {tests3, [], [t31,t3b]}]}.</pre>
+ groups() -> [{tests1, [], [{tests2, [], [t2a,t2b]},
+ {tests3, [], [t31,t3b]}]}].</pre>
<p>To execute group <c>tests1</c> twice with different properties for <c>tests2</c>
each time:</p>
<pre>
diff --git a/lib/common_test/src/ct_suite.erl b/lib/common_test/src/ct_suite.erl
index a2d23e15ef..860efe3ae2 100644
--- a/lib/common_test/src/ct_suite.erl
+++ b/lib/common_test/src/ct_suite.erl
@@ -47,9 +47,9 @@
{group, ct_groupname(), ct_group_props_ref()} |
{group, ct_groupname(), ct_group_props_ref(), ct_subgroups_def()}.
-type ct_testcase_ref() :: {testcase, ct_testname(), ct_testcase_repeat_prop()}.
--type ct_testcase_repeat_prop() :: {repeat, ct_test_repeat()} |
+-type ct_testcase_repeat_prop() :: [{repeat, ct_test_repeat()} |
{repeat_until_ok, ct_test_repeat()} |
- {repeat_until_fail, ct_test_repeat()}.
+ {repeat_until_fail, ct_test_repeat()}].
-type ct_info() :: {timetrap, ct_info_timetrap()} |
{require, ct_info_required()} |
{require, Name :: atom(), ct_info_required()} |
diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml
index af671ff7ba..f92505409c 100644
--- a/lib/compiler/doc/src/notes.xml
+++ b/lib/compiler/doc/src/notes.xml
@@ -32,6 +32,43 @@
<p>This document describes the changes made to the Compiler
application.</p>
+<section><title>Compiler 8.2.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed type handling bugs that could cause an internal
+ error in the compiler for correct code.</p>
+ <p>
+ Own Id: OTP-18565 Aux Id: GH-7147 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Compiler 8.2.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>When a map update such as <c>#{}#{key:=value}</c> that
+ should fail with an exception was unused, the exception
+ would be lost.</p>
+ <p>
+ Own Id: OTP-18497 Aux Id: GH-6960, PR-6965 </p>
+ </item>
+ <item>
+ <p>Fixed bug in the validator that made it reject valid
+ code.</p>
+ <p>
+ Own Id: OTP-18516 Aux Id: GH-6969 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Compiler 8.2.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/compiler/src/beam_call_types.erl b/lib/compiler/src/beam_call_types.erl
index 8b9cb836fd..4324080098 100644
--- a/lib/compiler/src/beam_call_types.erl
+++ b/lib/compiler/src/beam_call_types.erl
@@ -364,14 +364,30 @@ types(erlang, is_boolean, [Type]) ->
end;
types(erlang, is_float, [Type]) ->
sub_unsafe_type_test(Type, #t_float{});
-types(erlang, is_function, [Type, #t_integer{elements={Arity,Arity}}])
- when Arity >= 0, Arity =< ?MAX_FUNC_ARGS ->
- RetType =
- case meet(Type, #t_fun{arity=Arity}) of
- Type -> #t_atom{elements=[true]};
- none -> #t_atom{elements=[false]};
- _ -> beam_types:make_boolean()
- end,
+types(erlang, is_function, [Type, ArityType]) ->
+ RetType = case meet(ArityType, #t_integer{}) of
+ none ->
+ none;
+ #t_integer{elements={Arity,Arity}}
+ when is_integer(Arity) ->
+ if
+ Arity < 0 ->
+ none;
+ 0 =< Arity, Arity =< ?MAX_FUNC_ARGS ->
+ case meet(Type, #t_fun{arity=Arity}) of
+ Type -> #t_atom{elements=[true]};
+ none -> #t_atom{elements=[false]};
+ _ -> beam_types:make_boolean()
+ end;
+ Arity > ?MAX_FUNC_ARGS ->
+ #t_atom{elements=[false]}
+ end;
+ #t_integer{} ->
+ case meet(Type, #t_fun{}) of
+ none -> #t_atom{elements=[false]};
+ _ -> beam_types:make_boolean()
+ end
+ end,
sub_unsafe(RetType, [any, any]);
types(erlang, is_function, [Type]) ->
sub_unsafe_type_test(Type, #t_fun{});
diff --git a/lib/compiler/src/beam_jump.erl b/lib/compiler/src/beam_jump.erl
index 90672bab7c..9ae5a33d74 100644
--- a/lib/compiler/src/beam_jump.erl
+++ b/lib/compiler/src/beam_jump.erl
@@ -464,6 +464,7 @@ add_scope([I|Is], Scope) ->
[I|add_scope(Is, Scope)];
add_scope([], _Scope) -> [].
+is_shareable([{badmatch,_}|_]) -> false;
is_shareable([build_stacktrace|_]) -> false;
is_shareable([{case_end,_}|_]) -> false;
is_shareable([{'catch',_,_}|_]) -> false;
diff --git a/lib/compiler/src/beam_ssa.erl b/lib/compiler/src/beam_ssa.erl
index 619a9f032a..448b3b4313 100644
--- a/lib/compiler/src/beam_ssa.erl
+++ b/lib/compiler/src/beam_ssa.erl
@@ -383,10 +383,21 @@ successors(#b_blk{last=Terminator}) ->
-spec normalize(b_set() | terminator()) ->
b_set() | terminator().
-normalize(#b_set{op={bif,Bif},args=Args}=Set) ->
+normalize(#b_set{anno=Anno0,op={bif,Bif},args=Args}=Set) ->
case {is_commutative(Bif),Args} of
{true, [#b_literal{}=Lit,#b_var{}=Var]} ->
- Set#b_set{args=[Var,Lit]};
+ Anno = case Anno0 of
+ #{arg_types := ArgTypes0} ->
+ case ArgTypes0 of
+ #{1 := Type} ->
+ Anno0#{arg_types => #{0 => Type}};
+ #{} ->
+ Anno0
+ end;
+ #{} ->
+ Anno0
+ end,
+ Set#b_set{anno=Anno,args=[Var,Lit]};
{_, _} ->
Set
end;
diff --git a/lib/compiler/src/beam_ssa_check.erl b/lib/compiler/src/beam_ssa_check.erl
index 0d3faa0ee2..f344286943 100644
--- a/lib/compiler/src/beam_ssa_check.erl
+++ b/lib/compiler/src/beam_ssa_check.erl
@@ -285,8 +285,9 @@ env_post1(_Pattern, _Actual, _Env) ->
?DP("Failed to match ~p <-> ~p~n", [_Pattern, _Actual]),
error({internal_pattern_match_error,env_post1}).
-post_bitstring(Bytes, Actual, _Env) ->
- Actual = build_bitstring(Bytes, <<>>).
+post_bitstring(Bytes, Actual, Env) ->
+ Actual = build_bitstring(Bytes, <<>>),
+ Env.
%% Convert the parsed literal binary to an actual bitstring.
build_bitstring([{integer,_,V}|Bytes], Acc) ->
diff --git a/lib/compiler/src/beam_ssa_codegen.erl b/lib/compiler/src/beam_ssa_codegen.erl
index c4708e9b11..9f6169829b 100644
--- a/lib/compiler/src/beam_ssa_codegen.erl
+++ b/lib/compiler/src/beam_ssa_codegen.erl
@@ -1450,6 +1450,12 @@ cg_copy_1([], _St) -> [].
element(1, Val) =:= atom orelse
element(1, Val) =:= literal)).
+bif_to_test(min, Args, Fail, St) ->
+ %% The min/2 and max/2 BIFs can only be rewritten to tests when
+ %% both arguments are known to be booleans.
+ bif_to_test('and', Args, Fail, St);
+bif_to_test(max, Args, Fail, St) ->
+ bif_to_test('or', Args, Fail, St);
bif_to_test('or', [V1,V2], {f,Lbl}=Fail, St0) when Lbl =/= 0 ->
{SuccLabel,St} = new_label(St0),
{[{test,is_eq_exact,{f,SuccLabel},[V1,{atom,false}]},
diff --git a/lib/compiler/src/beam_ssa_private_append.erl b/lib/compiler/src/beam_ssa_private_append.erl
index bf51e8b81a..c295492762 100644
--- a/lib/compiler/src/beam_ssa_private_append.erl
+++ b/lib/compiler/src/beam_ssa_private_append.erl
@@ -86,6 +86,17 @@ find_appends_blk([], _, Found) ->
Found.
find_appends_is([#b_set{dst=Dst, op=bs_create_bin,
+ args=[#b_literal{val=append},
+ _,
+ Lit=#b_literal{val= <<>>}|_]}|Is],
+ Fun, Found0) ->
+ %% Special case for when the first fragment is a literal <<>> as
+ %% it won't be annotated as unique nor will it die with the
+ %% instruction.
+ AlreadyFound = maps:get(Fun, Found0, []),
+ Found = Found0#{Fun => [{append,Dst,Lit}|AlreadyFound]},
+ find_appends_is(Is, Fun, Found);
+find_appends_is([#b_set{dst=Dst, op=bs_create_bin,
args=[#b_literal{val=append},SegmentInfo,Var|_],
anno=#{first_fragment_dies:=Dies}=Anno}|Is],
Fun, Found0) ->
@@ -468,6 +479,16 @@ patch_appends_is([I0=#b_set{dst=Dst}|Rest], PD0, Cnt0, Acc, BlockAdditions0)
Ps = keysort(1, map(ExtractOpargs, Patches)),
{Is, Cnt} = patch_opargs(I0, Ps, Cnt0),
patch_appends_is(Rest, PD, Cnt, Is++Acc, BlockAdditions0);
+ [{append,Dst,#b_literal{val= <<>>}=Lit}] ->
+ %% Special case for when the first fragment is a literal
+ %% <<>> and it has to be replaced with a bs_init_writable.
+ #b_set{op=bs_create_bin,dst=Dst,args=Args0}=I0,
+ [#b_literal{val=append},SegInfo,Lit|OtherArgs] = Args0,
+ {V,Cnt} = new_var(Cnt0),
+ Init = #b_set{op=bs_init_writable,dst=V,args=[#b_literal{val=256}]},
+ I = I0#b_set{args=[#b_literal{val=private_append},
+ SegInfo,V|OtherArgs]},
+ patch_appends_is(Rest, PD, Cnt, [I,Init|Acc], BlockAdditions0);
[{append,Dst,_}] ->
#b_set{op=bs_create_bin,dst=Dst,args=Args0}=I0,
[#b_literal{val=append}|OtherArgs] = Args0,
diff --git a/lib/compiler/src/beam_types.erl b/lib/compiler/src/beam_types.erl
index 9c3da159c4..c3bf7c8fae 100644
--- a/lib/compiler/src/beam_types.erl
+++ b/lib/compiler/src/beam_types.erl
@@ -1179,14 +1179,23 @@ float_from_range(none) ->
none;
float_from_range(any) ->
#t_float{};
-float_from_range({'-inf','+inf'}) ->
- #t_float{};
-float_from_range({'-inf',Max}) ->
- #t_float{elements={'-inf',float(Max)}};
-float_from_range({Min,'+inf'}) ->
- #t_float{elements={float(Min),'+inf'}};
-float_from_range({Min,Max}) ->
- #t_float{elements={float(Min),float(Max)}}.
+float_from_range({Min0,Max0}) ->
+ case {safe_float(Min0),safe_float(Max0)} of
+ {'-inf','+inf'} ->
+ #t_float{};
+ {Min,Max} ->
+ #t_float{elements={Min,Max}}
+ end.
+
+safe_float(N) when is_number(N) ->
+ try
+ float(N)
+ catch
+ error:_ when N < 0 -> '-inf';
+ error:_ when N > 0 -> '+inf'
+ end;
+safe_float('-inf'=NegInf) -> NegInf;
+safe_float('+inf'=PosInf) -> PosInf.
integer_from_range(none) ->
none;
diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl
index 285db8a26a..217b7a2c97 100644
--- a/lib/compiler/src/beam_validator.erl
+++ b/lib/compiler/src/beam_validator.erl
@@ -1582,7 +1582,7 @@ update_create_bin_list([], Vst) -> Vst.
update_create_bin_type(append) -> #t_bitstring{};
update_create_bin_type(private_append) -> #t_bitstring{};
update_create_bin_type(binary) -> #t_bitstring{};
-update_create_bin_type(float) -> #t_float{};
+update_create_bin_type(float) -> #t_number{};
update_create_bin_type(integer) -> #t_integer{};
update_create_bin_type(utf8) -> #t_integer{};
update_create_bin_type(utf16) -> #t_integer{};
@@ -3435,7 +3435,17 @@ bif_types(Op, Ss, Vst) ->
Other
end;
{_,_} ->
- beam_call_types:types(erlang, Op, Args)
+ Res0 = beam_call_types:types(erlang, Op, Args),
+ {Ret0, ArgTypes, SubSafe} = Res0,
+
+ %% Match the non-converging range analysis done in
+ %% `beam_ssa_type:opt_ranges/1`. This is safe since the validator
+ %% doesn't have to worry about convergence.
+ case beam_call_types:arith_type({bif, Op}, Args) of
+ any -> Res0;
+ Ret0 -> Res0;
+ Ret -> {meet(Ret, Ret0), ArgTypes, SubSafe}
+ end
end.
join_tuple_elements(Tuple) ->
diff --git a/lib/compiler/test/beam_jump_SUITE.erl b/lib/compiler/test/beam_jump_SUITE.erl
index 713a1ea5ab..65fecd5b7c 100644
--- a/lib/compiler/test/beam_jump_SUITE.erl
+++ b/lib/compiler/test/beam_jump_SUITE.erl
@@ -82,6 +82,8 @@ ambiguous_catch_try_state(Config) ->
{'EXIT',{{badmatch,0},_}} = (catch ambiguous_catch_try_state_2()),
{'EXIT',{{badmatch,0},_}} = (catch ambiguous_catch_try_state_3()),
+ {'EXIT',{badarg,_}} = catch ambiguous_catch_try_state_4(),
+
ok.
river() -> song.
@@ -229,6 +231,12 @@ ambiguous_catch_try_state_3() ->
end.
+ambiguous_catch_try_state_4() ->
+ 0.0 = try binary_to_float(garbage_collect() orelse ((1.0 = tuple_to_list(ok)) -- ok))
+ after
+ ok
+ end.
+
-record(message2, {id, p1}).
-record(message3, {id, p1, p2}).
diff --git a/lib/compiler/test/beam_ssa_SUITE.erl b/lib/compiler/test/beam_ssa_SUITE.erl
index 6a91bdd7b8..0bb485c7f1 100644
--- a/lib/compiler/test/beam_ssa_SUITE.erl
+++ b/lib/compiler/test/beam_ssa_SUITE.erl
@@ -27,7 +27,7 @@
beam_ssa_dead_crash/1,stack_init/1,
mapfoldl/0,mapfoldl/1,
grab_bag/1,redundant_br/1,
- coverage/1]).
+ coverage/1,normalize/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -48,7 +48,8 @@ groups() ->
stack_init,
grab_bag,
redundant_br,
- coverage
+ coverage,
+ normalize
]}].
init_per_suite(Config) ->
@@ -1260,5 +1261,90 @@ coverage_5() ->
error
end#coverage{name = whatever}.
+%% Test beam_ssa:normalize/1, especially that argument types are
+%% correctly updated when arguments are swapped.
+normalize(_Config) ->
+ normalize_commutative({bif,'band'}),
+ normalize_commutative({bif,'+'}),
+
+ normalize_noncommutative({bif,'div'}),
+
+ ok.
+
+-record(b_var, {name}).
+-record(b_literal, {val}).
+
+normalize_commutative(Op) ->
+ A = #b_var{name=a},
+ B = #b_var{name=b},
+ Lit = #b_literal{val=42},
+
+ normalize_same(Op, [A,B]),
+ normalize_same(Op, [A,Lit]),
+
+ normalize_swapped(Op, [Lit,A]),
+
+ ok.
+
+normalize_noncommutative(Op) ->
+ A = #b_var{name=a},
+ B = #b_var{name=b},
+ Lit = #b_literal{val=42},
+
+ normalize_same(Op, [A,B]),
+ normalize_same(Op, [A,Lit]),
+
+ ArgTypes0 = [{1,beam_types:make_integer(0, 1023)}],
+ I1 = make_bset(ArgTypes0, Op, [Lit,A]),
+ I1 = beam_ssa:normalize(I1),
+
+ ok.
+
+normalize_same(Op, Args) ->
+ I0 = make_bset(#{}, Op, Args),
+ I0 = beam_ssa:normalize(I0),
+
+ ArgTypes0 = [{0,beam_types:make_integer(0, 1023)}],
+ I1 = make_bset(ArgTypes0, Op, Args),
+ I1 = beam_ssa:normalize(I1),
+
+ case Args of
+ [#b_var{},#b_var{}] ->
+ ArgTypes1 = [{0,beam_types:make_integer(0, 1023)},
+ {1,beam_types:make_integer(42)}],
+ I2 = make_bset(ArgTypes1, Op, Args),
+ I2 = beam_ssa:normalize(I2);
+ [_,_] ->
+ ok
+ end,
+
+ ok.
+
+normalize_swapped(Op, [#b_literal{}=Lit,#b_var{}=Var]=Args) ->
+ EmptyAnno = #{},
+ I0 = make_bset(EmptyAnno, Op, Args),
+ {b_set,EmptyAnno,#b_var{name=1000},Op,[Var,Lit]} = beam_ssa:normalize(I0),
+
+ EmptyTypes = #{arg_types => #{}},
+ I1 = make_bset(EmptyTypes, Op, Args),
+ {b_set,EmptyTypes,#b_var{name=1000},Op,[Var,Lit]} = beam_ssa:normalize(I1),
+
+ IntRange = beam_types:make_integer(0, 1023),
+ ArgTypes0 = [{1,IntRange}],
+ I2 = make_bset(ArgTypes0, Op, Args),
+ {[{0,IntRange}],Op,[Var,Lit]} = unpack_bset(beam_ssa:normalize(I2)),
+
+ ok.
+
+make_bset(ArgTypes, Op, Args) when is_list(ArgTypes) ->
+ Anno = #{arg_types => maps:from_list(ArgTypes)},
+ {b_set,Anno,#b_var{name=1000},Op,Args};
+make_bset(Anno, Op, Args) when is_map(Anno) ->
+ {b_set,Anno,#b_var{name=1000},Op,Args}.
+
+unpack_bset({b_set,Anno,{b_var,1000},Op,Args}) ->
+ ArgTypes = maps:get(arg_types, Anno, #{}),
+ {lists:sort(maps:to_list(ArgTypes)),Op,Args}.
+
%% The identity function.
id(I) -> I.
diff --git a/lib/compiler/test/beam_ssa_check_SUITE_data/private_append.erl b/lib/compiler/test/beam_ssa_check_SUITE_data/private_append.erl
index 4d4b0f1bbd..c1edc54460 100644
--- a/lib/compiler/test/beam_ssa_check_SUITE_data/private_append.erl
+++ b/lib/compiler/test/beam_ssa_check_SUITE_data/private_append.erl
@@ -22,6 +22,8 @@
%%
-module(private_append).
+-feature(maybe_expr, enable).
+
-export([transformable0/1,
transformable1/1,
transformable1b/1,
@@ -76,7 +78,9 @@
not_transformable14/0,
not_transformable15/2,
- id/1]).
+ id/1,
+
+ bs_create_bin_on_literal/0]).
%% Trivial smoke test
transformable0(L) ->
@@ -977,3 +981,24 @@ not_transformable15(_, V) ->
id(I) ->
I.
+
+%% Check that we don't try to private_append to something created by
+%% bs_create_bin `append`, _, `<<>>`, ...
+bs_create_bin_on_literal() ->
+%ssa% () when post_ssa_opt ->
+%ssa% X = bs_init_writable(_),
+%ssa% Y = bs_create_bin(private_append, _, X, ...),
+%ssa% Z = bs_create_bin(private_append, _, Y, ...),
+%ssa% ret(Z).
+ <<
+ <<
+ (maybe
+ 2147483647 ?= ok
+ else
+ <<_>> ->
+ ok;
+ _ ->
+ <<>>
+ end)/bytes
+ >>/binary
+ >>.
diff --git a/lib/compiler/test/beam_ssa_check_SUITE_data/sanity_checks.erl b/lib/compiler/test/beam_ssa_check_SUITE_data/sanity_checks.erl
index ae4bb28eea..47c60fd8d6 100644
--- a/lib/compiler/test/beam_ssa_check_SUITE_data/sanity_checks.erl
+++ b/lib/compiler/test/beam_ssa_check_SUITE_data/sanity_checks.erl
@@ -18,6 +18,8 @@
-module(sanity_checks).
+-compile(no_ssa_opt_private_append).
+
-export([check_fail/0,
check_wrong_pass/0,
check_xfail/0,
@@ -33,7 +35,9 @@
t25/0, t26/0, t27/0, t28/0, t29/0,
t30/0, t31/0, t32/1, t33/1, t34/1,
t35/1, t36/0, t37/0, t38/0, t39/1,
- t40/0, t41/0, t42/0, t43/0, t44/0]).
+ t40/0, t41/0, t42/0, t43/0, t44/0,
+
+ check_env/0]).
%% Check that we do not trigger on the wrong pass
check_wrong_pass() ->
@@ -325,3 +329,13 @@ t44() ->
%ssa% () when post_ssa_opt ->
%ssa% _ = call(fun e:f0/1, {...}).
e:f0({}).
+
+%% Ensure bug which trashed the environment after matching a literal
+%% bitstring stays fixed.
+check_env() ->
+%ssa% () when post_ssa_opt ->
+%ssa% X = bs_create_bin(append, _, <<>>, ...),
+%ssa% ret(X).
+ A = <<>>,
+ B = ex:f(),
+ <<A/binary, B/binary>>.
diff --git a/lib/compiler/test/beam_type_SUITE.erl b/lib/compiler/test/beam_type_SUITE.erl
index 0b00e6d71c..707c7ca80b 100644
--- a/lib/compiler/test/beam_type_SUITE.erl
+++ b/lib/compiler/test/beam_type_SUITE.erl
@@ -22,14 +22,16 @@
-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1,
init_per_group/2,end_per_group/2,
integers/1,numbers/1,coverage/1,booleans/1,setelement/1,
- cons/1,tuple/1,record_float/1,binary_float/1,float_compare/1,
+ cons/1,tuple/1,
+ record_float/1,binary_float/1,float_compare/1,float_overflow/1,
arity_checks/1,elixir_binaries/1,find_best/1,
test_size/1,cover_lists_functions/1,list_append/1,bad_binary_unit/1,
none_argument/1,success_type_oscillation/1,type_subtraction/1,
container_subtraction/1,is_list_opt/1,connected_tuple_elements/1,
switch_fail_inference/1,failures/1,
cover_maps_functions/1,min_max_mixed_types/1,
- not_equal/1,infer_relops/1,binary_unit/1,premature_concretization/1]).
+ not_equal/1,infer_relops/1,binary_unit/1,premature_concretization/1,
+ funs/1]).
%% Force id/1 to return 'any'.
-export([id/1]).
@@ -51,6 +53,7 @@ groups() ->
record_float,
binary_float,
float_compare,
+ float_overflow,
arity_checks,
elixir_binaries,
find_best,
@@ -71,7 +74,8 @@ groups() ->
not_equal,
infer_relops,
binary_unit,
- premature_concretization
+ premature_concretization,
+ funs
]}].
init_per_suite(Config) ->
@@ -741,11 +745,17 @@ record_float(R, N0) ->
binary_float(_Config) ->
<<-1/float>> = binary_negate_float(<<1/float>>),
+ {'EXIT',{badarg,_}} = catch binary_float_1(id(64.0), id(0)),
ok.
binary_negate_float(<<Float/float>>) ->
<<-Float/float>>.
+%% GH-7147.
+binary_float_1(X, Y) ->
+ _ = <<Y:(ceil(64.0 = X))/float, (binary_to_integer(ok))>>,
+ ceil(X) band Y.
+
float_compare(_Config) ->
false = do_float_compare(-42.0),
false = do_float_compare(-42),
@@ -765,6 +775,46 @@ do_float_compare(X) ->
_T -> Y > 0
end.
+float_overflow(_Config) ->
+ Res1 = id((1 bsl 1023) * two()),
+ Res1 = float_overflow_1(),
+
+ Res2 = id((-1 bsl 1023) * two()),
+ Res2 = float_overflow_2(),
+
+ {'EXIT',{{bad_filter,[0]},_}} = catch float_overflow_3(),
+
+ ok.
+
+%% GH-7178: There would be an overflow when converting a number range
+%% to a float range.
+float_overflow_1() ->
+ round(
+ try
+ round(float(1 bsl 1023)) * two()
+ catch
+ _:_ ->
+ 0.0
+ end
+ ).
+
+float_overflow_2() ->
+ round(
+ try
+ round(float(-1 bsl 1023)) * two()
+ catch
+ _:_ ->
+ 0.0
+ end
+ ).
+
+two() -> 2.
+
+float_overflow_3() ->
+ [0 || <<>> <= <<>>,
+ [0 || (floor(1.7976931348623157e308) bsl 1) >= (1.0 + map_size(#{}))]
+ ].
+
arity_checks(_Config) ->
%% ERL-549: an unsafe optimization removed a test_arity instruction,
%% causing the following to return 'broken' instead of 'ok'.
@@ -1363,5 +1413,30 @@ pm_concretization_2(_, Tagged) -> {error, Tagged}.
pm_concretization_3(_) -> ok.
pm_concretization_4(_) -> ok.
+funs(_Config) ->
+ {'EXIT',{badarg,_}} = catch gh_7179(),
+ false = is_function(id(fun() -> ok end), 1024),
+
+ {'EXIT',{badarg,_}} = catch gh_7197(),
+
+ ok.
+
+%% GH-7179: The beam_ssa_type pass would crash.
+gh_7179() ->
+ << <<0>> || is_function([0 || <<_>> <= <<>>], -1),
+ [] <- [] >>.
+
+%% GH-7197: The beam_ssa_type pass would crash.
+gh_7197() ->
+ [0 || is_function([ok || <<_>> <= <<>>], get_keys()),
+ fun (_) ->
+ ok
+ end].
+
+
+%%%
+%%% Common utilities.
+%%%
+
id(I) ->
I.
diff --git a/lib/compiler/test/beam_validator_SUITE.erl b/lib/compiler/test/beam_validator_SUITE.erl
index 6b63f561c1..2092d7401a 100644
--- a/lib/compiler/test/beam_validator_SUITE.erl
+++ b/lib/compiler/test/beam_validator_SUITE.erl
@@ -43,7 +43,7 @@
container_performance/1,
infer_relops/1,
not_equal_inference/1,bad_bin_unit/1,singleton_inference/1,
- inert_update_type/1]).
+ inert_update_type/1,range_inference/1]).
-include_lib("common_test/include/ct.hrl").
@@ -80,7 +80,7 @@ groups() ->
bs_saved_position_units,parent_container,
container_performance,infer_relops,
not_equal_inference,bad_bin_unit,singleton_inference,
- inert_update_type]}].
+ inert_update_type,range_inference]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -1129,5 +1129,22 @@ mike([Head | _Rest]) -> joe(Head).
joe({Name, 42}) -> Name;
joe({sys_period, {A, _B}}) -> {41, 42, A}.
+range_inference(_Config) ->
+ ok = range_inference_1(id(<<$a>>)),
+ ok = range_inference_1(id(<<0>>)),
+ ok = range_inference_1(id(<<1114111/utf8>>)),
+
+ ok.
+
+range_inference_1(<<X/utf8>>) ->
+ case 9223372036854775807 - abs(X) of
+ Y when X < Y ->
+ ok;
+ 9223372036854775807 ->
+ ok;
+ -2147483648 ->
+ ok
+ end.
+
id(I) ->
I.
diff --git a/lib/compiler/test/bif_SUITE.erl b/lib/compiler/test/bif_SUITE.erl
index fc1d9ddfc0..e58db29114 100644
--- a/lib/compiler/test/bif_SUITE.erl
+++ b/lib/compiler/test/bif_SUITE.erl
@@ -25,7 +25,8 @@
init_per_group/2,end_per_group/2,
beam_validator/1,trunc_and_friends/1,cover_safe_and_pure_bifs/1,
cover_trim/1,
- head_tail/1]).
+ head_tail/1,
+ min_max/1]).
suite() ->
[{ct_hooks,[ts_install_cth]}].
@@ -34,12 +35,13 @@ all() ->
[{group,p}].
groups() ->
- [{p,[parallel],
+ [{p,test_lib:parallel(),
[beam_validator,
trunc_and_friends,
cover_safe_and_pure_bifs,
cover_trim,
- head_tail
+ head_tail,
+ min_max
]}].
init_per_suite(Config) ->
@@ -192,5 +194,40 @@ tail_case() ->
X -> {X, ok}
end.
+min_max(_Config) ->
+ False = id(false),
+ True = id(true),
+
+ false = bool_min_false(False, False),
+ false = bool_min_false(False, True),
+ false = bool_min_false(True, False),
+ true = bool_min_true(True, True),
+
+ false = bool_max_false(False, False),
+ true = bool_max_true(False, True),
+ true = bool_max_true(True, False),
+ true = bool_max_true(True, True),
+
+ ok.
+
+%% GH-7170: The following functions would cause a crash in
+%% beam_ssa_codegen.
+
+bool_min_false(A, B) when is_boolean(A), is_boolean(B) ->
+ false = min(A, B).
+
+bool_min_true(A, B) when is_boolean(A), is_boolean(B) ->
+ true = min(A, B).
+
+bool_max_false(A, B) when is_boolean(A), is_boolean(B) ->
+ false = max(A, B).
+
+bool_max_true(A, B) when is_boolean(A), is_boolean(B) ->
+ true = max(A, B).
+
+%%%
+%%% Common utilities.
+%%%
+
id(I) ->
I.
diff --git a/lib/compiler/vsn.mk b/lib/compiler/vsn.mk
index 0a39fcf419..fa46ea4097 100644
--- a/lib/compiler/vsn.mk
+++ b/lib/compiler/vsn.mk
@@ -1 +1 @@
-COMPILER_VSN = 8.2.4
+COMPILER_VSN = 8.2.6
diff --git a/lib/crypto/c_src/dh.c b/lib/crypto/c_src/dh.c
index 92a339ab5f..15a08470a6 100644
--- a/lib/crypto/c_src/dh.c
+++ b/lib/crypto/c_src/dh.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2010-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2010-2023. 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.
diff --git a/lib/crypto/c_src/openssl_config.h b/lib/crypto/c_src/openssl_config.h
index 9c7b05660e..e356cf94fb 100644
--- a/lib/crypto/c_src/openssl_config.h
+++ b/lib/crypto/c_src/openssl_config.h
@@ -127,6 +127,15 @@
# define HAS_EVP_PKEY_CTX
# define HAVE_EVP_CIPHER_CTX_COPY
# endif
+# if LIBRESSL_VERSION_NUMBER >= 0x3070200fL
+# define HAVE_PKEY_new_raw_private_key
+# endif
+# if LIBRESSL_VERSION_NUMBER >= 0x3030300fL
+# define HAVE_EVP_PKEY_new_CMAC_key
+# endif
+# if LIBRESSL_VERSION_NUMBER >= 0x3040100fL
+# define HAVE_DigestSign_as_single_op
+# endif
#endif
#if defined(HAS_EVP_PKEY_CTX) \
diff --git a/lib/crypto/doc/src/notes.xml b/lib/crypto/doc/src/notes.xml
index 954d5cd664..fa5e9d5bea 100644
--- a/lib/crypto/doc/src/notes.xml
+++ b/lib/crypto/doc/src/notes.xml
@@ -31,6 +31,22 @@
</header>
<p>This document describes the changes made to the Crypto application.</p>
+<section><title>Crypto 5.1.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ With this change, random errors are fixed for
+ crypto:generate_key calls with OpenSSL 3.</p>
+ <p>
+ Own Id: OTP-18555</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Crypto 5.1.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl
index 69b01e6418..6abaacad5c 100644
--- a/lib/crypto/src/crypto.erl
+++ b/lib/crypto/src/crypto.erl
@@ -685,8 +685,6 @@ hash_final_xof(Context, Length) ->
-type hmac_hash_algorithm() :: sha1() | sha2() | sha3() | compatibility_only_hash().
-type cmac_cipher_algorithm() :: aes_128_cbc | aes_192_cbc | aes_256_cbc | aes_cbc
- | aes_128_cfb128 | aes_192_cfb128 | aes_256_cfb128 | aes_cfb128
- | aes_128_cfb8 | aes_192_cfb8 | aes_256_cfb8 | aes_cfb8
| blowfish_cbc
| des_cbc | des_ede3_cbc
| rc2_cbc
diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl
index 516ad02ee2..0572feaf33 100644
--- a/lib/crypto/test/crypto_SUITE.erl
+++ b/lib/crypto/test/crypto_SUITE.erl
@@ -1,7 +1,7 @@
%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. 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.
@@ -411,11 +411,11 @@ groups() ->
{ecdh, [], [compute, generate, use_all_ecdh_generate_compute]},
{eddh, [], [compute, generate, use_all_eddh_generate_compute]},
{srp, [], [generate_compute]},
- {des_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls]},
+ {des_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls, cmac, cmac_update]},
{des_cfb, [], [api_ng, api_ng_one_shot, api_ng_tls]},
- {des_ede3_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls, cmac]},
+ {des_ede3_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls, cmac, cmac_update]},
{des_ede3_cfb, [], [api_ng, api_ng_one_shot, api_ng_tls]},
- {rc2_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls]},
+ {rc2_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls, cmac, cmac_update]},
{aes_cfb8, [], []},
{aes_128_cfb8, [], [api_ng, api_ng_one_shot, api_ng_tls]},
{aes_192_cfb8, [], [api_ng, api_ng_one_shot, api_ng_tls]},
@@ -426,7 +426,7 @@ groups() ->
{aes_192_cfb128, [], [api_ng, api_ng_one_shot, api_ng_tls]},
{aes_256_cfb128, [], [api_ng, api_ng_one_shot, api_ng_tls]},
{no_aes_cfb128, [], [no_support]},
- {blowfish_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls]},
+ {blowfish_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls, cmac, cmac_update]},
{blowfish_ecb, [], [api_ng, api_ng_one_shot]},
{blowfish_cfb64, [], [api_ng, api_ng_one_shot, api_ng_tls]},
{blowfish_ofb64, [], [api_ng, api_ng_one_shot, api_ng_tls]},
@@ -473,15 +473,15 @@ groups() ->
{des_ede3_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls]},
{des_ede3_cfb, [], [api_ng, api_ng_one_shot, api_ng_tls]},
{aes_128_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls, cmac, cmac_update]},
- {aes_192_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls]},
- {aes_256_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls, cmac]},
+ {aes_192_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls, cmac, cmac_update]},
+ {aes_256_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls, cmac, cmac_update]},
{aes_128_ctr, [], [api_ng, api_ng_one_shot, api_ng_tls]},
{aes_192_ctr, [], [api_ng, api_ng_one_shot, api_ng_tls]},
{aes_256_ctr, [], [api_ng, api_ng_one_shot, api_ng_tls]},
{aes_128_ccm, [], [aead_ng, aead_bad_tag]},
{aes_192_ccm, [], [aead_ng, aead_bad_tag]},
{aes_256_ccm, [], [aead_ng, aead_bad_tag]},
- {aes_128_ecb, [], [api_ng, api_ng_one_shot, cmac_update]},
+ {aes_128_ecb, [], [api_ng, api_ng_one_shot]},
{aes_192_ecb, [], [api_ng, api_ng_one_shot]},
{aes_256_ecb, [], [api_ng, api_ng_one_shot]},
{aes_128_gcm, [], [aead_ng, aead_bad_tag]},
@@ -2378,6 +2378,8 @@ do_configure_mac(cmac, Cipher, Config) ->
case Cipher of
aes_128_cbc ->
fun() -> read_rsp(Config, Cipher, ["CMACGenAES128.rsp", "CMACVerAES128.rsp"]) end;
+ aes_192_cbc ->
+ fun() -> read_rsp(Config, Cipher, ["CMACGenAES192.rsp", "CMACVerAES192.rsp"]) end;
aes_256_cbc ->
fun() -> read_rsp(Config, Cipher, ["CMACGenAES256.rsp", "CMACVerAES256.rsp"]) end;
des_ede3_cbc ->
@@ -2933,10 +2935,9 @@ hmac_inc(_) ->
[<<"Sampl">>, <<"e #1">>].
-cmac_key(aes_128_cbc) ->
- hexstr2bin("8eeca0d146fd09ffbbe0d47edcddfcec");
-cmac_key(aes_128_ecb) ->
- hexstr2bin("8eeca0d146fd09ffbbe0d47edcddfcec").
+cmac_key(SubType) ->
+ rand:bytes(
+ maps:get(key_length, crypto:cipher_info(SubType))).
cmac_inc(_) ->
[<<"Sampl">>, <<"e #1">>].
diff --git a/lib/crypto/vsn.mk b/lib/crypto/vsn.mk
index 41dfbaef97..56f5a0f48e 100644
--- a/lib/crypto/vsn.mk
+++ b/lib/crypto/vsn.mk
@@ -1 +1 @@
-CRYPTO_VSN = 5.1.3
+CRYPTO_VSN = 5.1.4
diff --git a/lib/dialyzer/doc/src/dialyzer.xml b/lib/dialyzer/doc/src/dialyzer.xml
index 9a2b409348..334cfcb8d7 100644
--- a/lib/dialyzer/doc/src/dialyzer.xml
+++ b/lib/dialyzer/doc/src/dialyzer.xml
@@ -507,6 +507,32 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
<p>Currently the only option used is the
<seeerl marker="#error_location"><c>error_location</c></seeerl> option.
</p>
+
+ <p><em>Dialyzer configuration file:</em></p>
+
+ <p>Dialyzer's configuration file may also be used to augment the default
+ options and those given directly to the Dialyzer command. It is commonly
+ used to avoid repeating options which would otherwise need to be given
+ explicitly to Dialyzer on every invocation.
+ </p>
+
+ <p>The location of the configuration file can be set via the
+ <c>DIALYZER_CONFIG</c> environment variable, and defaults to
+ within the <c>user_config</c> from <seemfa marker="stdlib:filename#basedir/3">
+ <c>filename:basedir/3</c></seemfa>.
+ </p>
+
+ <p>An example configuration file's contents might be:</p>
+
+ <code type="none">
+ {incremental,
+ {default_apps,[stdlib,kernel,erts]},
+ {default_warning_apps,[stdlib]}
+ }.
+ {warnings, [no_improper_lists]}.
+ {add_pathsa,["/users/samwise/potatoes/ebin"]}.
+ {add_pathsz,["/users/smeagol/fish/ebin"]}.
+ </code>
</section>
<section>
diff --git a/lib/dialyzer/src/dialyzer.app.src b/lib/dialyzer/src/dialyzer.app.src
index 9693aa66cd..311c019a31 100644
--- a/lib/dialyzer/src/dialyzer.app.src
+++ b/lib/dialyzer/src/dialyzer.app.src
@@ -56,6 +56,6 @@
{registered, []},
{applications, [compiler, kernel, stdlib]},
{env, []},
- {runtime_dependencies, ["wx-2.0","syntax_tools-2.0","stdlib-4.0",
+ {runtime_dependencies, ["wx-2.0","syntax_tools-2.0","stdlib-@OTP-18558@",
"kernel-8.0","erts-12.0",
"compiler-8.0"]}]}.
diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl
index a502f7107d..ecfc509e34 100644
--- a/lib/dialyzer/src/dialyzer.erl
+++ b/lib/dialyzer/src/dialyzer.erl
@@ -415,8 +415,8 @@ message_to_string({app_call, [M, F, Args, Culprit, ExpectedType, FoundType]},
[M, F, a(Args, I), c(Culprit, I),
t(ExpectedType, I), t(FoundType, I)]);
message_to_string({bin_construction, [Culprit, Size, Seg, Type]}, I, _E) ->
- io_lib:format("Binary construction will fail since the ~s field ~s in"
- " segment ~s has type ~s\n",
+ io_lib:format("Binary construction will fail since the ~ts field ~ts in"
+ " segment ~ts has type ~ts\n",
[Culprit, c(Size, I), c(Seg, I), t(Type, I)]);
message_to_string({call, [M, F, Args, ArgNs, FailReason,
SigArgs, SigRet, Contract]}, I, _E) ->
diff --git a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl
index 6d163c2cf9..b5cc6c7392 100644
--- a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl
+++ b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl
@@ -504,10 +504,11 @@ expand_files(Analysis = #analysis{files = Files, start_from = StartFrom}) ->
case expand_files(Files, Ext, []) of
[] ->
Msg = "No " ++ Ext ++ " files to analyze" ++
- case StartFrom of
- byte_code -> " (no --src specified?)";
- src_code -> ""
- end,
+ case StartFrom of
+ byte_code -> " (no --src specified?)";
+ src_code -> ""
+ end ++
+ "\nConsider setting some default apps in your dialyzer.config file",
exit({error, Msg});
NewFiles ->
Analysis#analysis{files = NewFiles}
diff --git a/lib/dialyzer/src/dialyzer_cl_parse.erl b/lib/dialyzer/src/dialyzer_cl_parse.erl
index fe7ad2af01..2babea0073 100644
--- a/lib/dialyzer/src/dialyzer_cl_parse.erl
+++ b/lib/dialyzer/src/dialyzer_cl_parse.erl
@@ -252,6 +252,8 @@ Note:
the syntax of defines and includes is the same as that used by \"erlc\".
" ++ warning_options_msg() ++ "
+" ++ configuration_file_msg() ++ "
+
The exit status of the command line version is:
0 - No problems were encountered during the analysis and no
warnings were emitted.
@@ -383,3 +385,29 @@ They are primarily intended to be used with the -dialyzer attribute:
-Wno_missing_return
Suppress warnings about functions that return values that are not part of the specification.
".
+
+configuration_file_msg() ->
+ "Configuration file:
+ Dialyzer's configuration file may also be used to augment the default
+ options and those given directly to the Dialyzer command. It is commonly
+ used to avoid repeating options which would otherwise need to be given
+ explicitly to Dialyzer on every invocation.
+
+ The location of the configuration file can be set via the
+ DIALYZER_CONFIG environment variable, and defaults to
+ within the user_config location given by filename:basedir/3.
+
+ On your system, the location is currently configured as:
+ " ++ dialyzer_options:get_default_config_filename() ++
+ "
+
+ An example configuration file's contents might be:
+
+ {incremental,
+ {default_apps,[stdlib,kernel,erts]},
+ {default_warning_apps,[stdlib]}
+ }.
+ {warnings, [no_improper_lists]}.
+ {add_pathsa,[\"/users/samwise/potatoes/ebin\"]}.
+ {add_pathsz,[\"/users/smeagol/fish/ebin\"]}.
+".
diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl
index bb77ea972f..7e0a75f062 100644
--- a/lib/dialyzer/src/dialyzer_dataflow.erl
+++ b/lib/dialyzer/src/dialyzer_dataflow.erl
@@ -1602,20 +1602,23 @@ bind_bin_segs([Seg|Segs], BinType, Acc, Map, State) ->
UnitVal = cerl:concrete(cerl:bitstr_unit(Seg)),
Size = cerl:bitstr_size(Seg),
case bitstr_bitsize_type(Size) of
- all ->
- binary = SegType, [] = Segs, %% just an assert
+ {literal, all} ->
+ binary = SegType, [] = Segs, %Assertion.
T = t_inf(t_bitstr(UnitVal, 0), BinType),
{Map1, [Type]} = do_bind_pat_vars([Val], [T], Map,
State, false, []),
Type1 = remove_local_opaque_types(Type, State#state.opaques),
bind_bin_segs(Segs, t_bitstr(0, 0), [Type1|Acc], Map1, State);
- utf -> % XXX: can possibly be strengthened
- true = lists:member(SegType, [utf8, utf16, utf32]),
+ SizeType when SegType =:= utf8; SegType =:= utf16; SegType =:= utf32 ->
+ {literal, undefined} = SizeType, %Assertion.
{Map1, [_]} = do_bind_pat_vars([Val], [t_integer()],
Map, State, false, []),
Type = t_binary(),
bind_bin_segs(Segs, BinType, [Type|Acc], Map1, State);
- any ->
+ {literal, N} when not is_integer(N); N < 0 ->
+ %% Bogus literal size, fails in runtime.
+ bind_error([Seg], BinType, t_none(), bind);
+ _ ->
{Map1, [SizeType]} = do_bind_pat_vars([Size], [t_non_neg_integer()],
Map, State, false, []),
Opaques = State#state.opaques,
@@ -1668,14 +1671,8 @@ bind_bin_segs([], _BinType, Acc, Map, _State) ->
bitstr_bitsize_type(Size) ->
case cerl:is_literal(Size) of
- true ->
- case cerl:concrete(Size) of
- all -> all;
- undefined -> utf;
- _ -> any
- end;
- false ->
- any
+ true -> {literal, cerl:concrete(Size)};
+ false -> variable
end.
%% Return the infimum (meet) of ExpectedType and Type if it describes a
diff --git a/lib/dialyzer/src/dialyzer_options.erl b/lib/dialyzer/src/dialyzer_options.erl
index 27da2f9c83..de35f5f204 100644
--- a/lib/dialyzer/src/dialyzer_options.erl
+++ b/lib/dialyzer/src/dialyzer_options.erl
@@ -18,7 +18,7 @@
-module(dialyzer_options).
--export([build/1, build_warnings/2]).
+-export([build/1, build_warnings/2, get_default_config_filename/0]).
-include("dialyzer.hrl").
@@ -48,9 +48,12 @@ build(Opts) ->
?WARN_UNDEFINED_CALLBACK,
?WARN_UNKNOWN],
DefaultWarns1 = ordsets:from_list(DefaultWarns),
- DefaultOpts = #options{},
- DefaultOpts1 = DefaultOpts#options{legal_warnings = DefaultWarns1},
try
+ WarningsFromConfig = proplists:get_value(warnings, get_config(), []),
+ update_path_from_config(),
+ DefaultWarns2 = build_warnings(WarningsFromConfig, DefaultWarns1),
+ DefaultOpts = #options{},
+ DefaultOpts1 = DefaultOpts#options{legal_warnings = DefaultWarns2},
Opts1 = preprocess_opts(Opts),
Env = env_default_opts(),
ErrLoc = proplists:get_value(error_location, Env, ?ERROR_LOCATION),
@@ -61,6 +64,31 @@ build(Opts) ->
throw:{dialyzer_options_error, Msg} -> {error, Msg}
end.
+update_path_from_config() ->
+ Config = get_config(),
+ PAs = proplists:get_value(add_pathsa, Config, []),
+ PZs = proplists:get_value(add_pathsz, Config, []),
+ case is_list(PAs) of
+ true -> ok;
+ false -> bad_option("Bad list of paths in config", {add_pathsa, PAs})
+ end,
+ case is_list(PZs) of
+ true -> ok;
+ false -> bad_option("Bad list of paths in config", {add_pathsz, PZs})
+ end,
+ %% Add paths one-by-one so that we can report issues
+ %% if any path is invalid
+ %% (code:add_pathsa/1 and code:add_pathsz/1 always return ok)
+ [ case code:add_patha(PA) of
+ true -> ok;
+ {error, _} -> bad_option("Failed to add path from config", {add_patha, PA})
+ end || PA <- PAs ],
+ [ case code:add_pathz(PZ) of
+ true -> ok;
+ {error, _} -> bad_option("Failed to add path from config", {add_pathz, PZ})
+ end || PZ <- PZs ],
+ ok.
+
preprocess_opts([]) -> [];
preprocess_opts([{init_plt, File}|Opts]) ->
[{plts, [File]}|preprocess_opts(Opts)];
@@ -79,7 +107,7 @@ postprocess_opts(Opts = #options{}) ->
check_module_lookup_file_validity(Opts1),
Opts2 = check_output_plt(Opts1),
check_init_plt_kind(Opts2),
- Opts3 = manage_default_apps(Opts2),
+ Opts3 = manage_default_incremental_apps(Opts2),
adapt_get_warnings(Opts3).
check_metrics_file_validity(#options{analysis_type = incremental, metrics_file = none}) ->
@@ -160,31 +188,49 @@ check_init_plt_kind(#options{analysis_type = _NotIncremental, init_plts = InitPl
lists:foreach(RunCheck, InitPlts).
%% If no apps are set explicitly, we fall back to config
-manage_default_apps(Opts = #options{analysis_type = incremental, files = [], files_rec = [], warning_files = [], warning_files_rec = []}) ->
- DefaultConfig = get_default_config_filename(),
- case file:consult(DefaultConfig) of
- {ok, [{incremental, {default_apps, DefaultApps}=Term}]} when
+manage_default_incremental_apps(Opts = #options{analysis_type = incremental, files = [], files_rec = [], warning_files = [], warning_files_rec = []}) ->
+ set_default_apps(get_config(), Opts);
+manage_default_incremental_apps(Opts) ->
+ Opts.
+
+set_default_apps([ConfigElem|MoreConfig], Opts) ->
+ case ConfigElem of
+ {incremental, {default_apps, DefaultApps}=Term} when
is_list(DefaultApps) ->
AppDirs = get_app_dirs(DefaultApps),
assert_filenames_form(Term, AppDirs),
Opts#options{files_rec = AppDirs};
- {ok, [{incremental, {default_apps, DefaultApps}=TermApps,
- {default_warning_apps, DefaultWarningApps}=TermWarns}]} when
+ {incremental, {default_apps, DefaultApps}=TermApps,
+ {default_warning_apps, DefaultWarningApps}=TermWarns} when
is_list(DefaultApps), is_list(DefaultWarningApps) ->
- AppDirs = get_app_dirs(DefaultApps),
+ AppDirs = get_app_dirs(DefaultApps ++ DefaultWarningApps),
assert_filenames_form(TermApps, AppDirs),
WarningAppDirs = get_app_dirs(DefaultWarningApps),
assert_filenames_form(TermWarns, WarningAppDirs),
Opts#options{files_rec = AppDirs, warning_files_rec = WarningAppDirs};
- {ok, _Terms} ->
- bad_option("Given Erlang terms could not be understood as Dialyzer config", DefaultConfig);
- {error, Reason} ->
- bad_option(file:format_error(Reason), DefaultConfig)
+ _ when element(1, ConfigElem) =:= incremental ->
+ bad_option("Given Erlang terms in 'incremental' section could not be understood as Dialyzer config", ConfigElem);
+ _ ->
+ set_default_apps(MoreConfig, Opts)
end;
-manage_default_apps(Opts) ->
+set_default_apps([], Opts) ->
Opts.
+get_config() ->
+ DefaultConfig = get_default_config_filename(),
+ case filelib:is_regular(DefaultConfig) of
+ true ->
+ case file:consult(DefaultConfig) of
+ {ok, Config} when is_list(Config) -> Config;
+ {error, Reason} ->
+ bad_option(file:format_error(Reason), DefaultConfig)
+ end;
+ false ->
+ []
+ end.
+
% Intended to work like dialyzer_iplt:get_default_iplt_filename()
+-spec get_default_config_filename() -> string().
get_default_config_filename() ->
case os:getenv("DIALYZER_CONFIG") of
false ->
diff --git a/lib/dialyzer/test/incremental_SUITE.erl b/lib/dialyzer/test/incremental_SUITE.erl
index 831e718777..1bf5731db8 100644
--- a/lib/dialyzer/test/incremental_SUITE.erl
+++ b/lib/dialyzer/test/incremental_SUITE.erl
@@ -30,6 +30,8 @@
default_apps_config_xdg/1,
default_apps_config_env_var/1,
default_apps_config_env_var_prioritised_over_xdg/1,
+ legal_warnings_config_xdg/1,
+ paths_config_xdg/1,
multiple_plts_unsupported_in_incremental_mode/1]).
suite() ->
@@ -54,6 +56,8 @@ all() -> [report_new_plt_test,
default_apps_config_xdg,
default_apps_config_env_var,
default_apps_config_env_var_prioritised_over_xdg,
+ legal_warnings_config_xdg,
+ paths_config_xdg,
multiple_plts_unsupported_in_incremental_mode].
erlang_module() ->
@@ -739,7 +743,7 @@ default_apps_config_xdg(Config) ->
[{"HOME", TestHome}]
end,
- io:format("~p\n", [HomeEnv]),
+ io:format("~p~n", [HomeEnv]),
PrivDir = ?config(priv_dir, Config),
PltFile = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".iplt"),
@@ -892,6 +896,176 @@ default_apps_config_env_var_prioritised_over_xdg(Config) ->
peer:stop(Peer).
+legal_warnings_config_xdg(Config) ->
+ TestHome = filename:join(?config(priv_dir, Config), ?FUNCTION_NAME),
+
+ %% We change the $HOME of the emulator to run this test
+ HomeEnv =
+ case os:type() of
+ {win32, _} ->
+ [Drive | Path] = filename:split(TestHome),
+ [{"APPDATA", filename:join(TestHome, "AppData")},
+ {"HOMEDRIVE", Drive},
+ {"HOMEPATH", filename:join(Path)}];
+ _ ->
+ [{"HOME", TestHome}]
+ end,
+
+ io:format("~p~n", [HomeEnv]),
+
+ {ok, Peer, Node} = ?CT_PEER(#{ env => HomeEnv }),
+
+ SrcWithImproperList = <<"
+ -module(my_improper_list_module).
+ -export([g/0]).
+
+ g() -> [a|b]. % Improper list: Last element is not the empty list
+ ">>,
+
+ {ok, BeamFileWithImproperList} =
+ compile(Config, SrcWithImproperList, my_improper_list_module, []),
+
+ AppsConfig =
+ {incremental, {default_apps, [stdlib, kernel, erts, compiler, mnesia, ftp]}},
+
+ erpc:call(
+ Node,
+ fun() ->
+ %% Find out the path of the config file
+ HomeConfigFilename =
+ filename:join(filename:basedir(user_config, "erlang"),
+ "dialyzer.config"),
+ io:format("~ts\n", [HomeConfigFilename]),
+ ok = filelib:ensure_dir(HomeConfigFilename),
+
+ %% Write configuration file
+ WarningsConfig1 =
+ {warnings, [no_unknown, no_improper_lists]},
+ ok = file:write_file(HomeConfigFilename,
+ io_lib:format("~p.~n~p.~n", [AppsConfig, WarningsConfig1])),
+ WarningsWithConfigSet =
+ dialyzer:run([{analysis_type, incremental},
+ {files, [BeamFileWithImproperList]},
+ {from, byte_code}]),
+ ?assertEqual([], WarningsWithConfigSet),
+
+ %% Write alternative configuration file
+ WarningsConfig2 =
+ {warnings, [no_unknown]},
+ ok = file:write_file(HomeConfigFilename,
+ io_lib:format("~p.~n~p.~n", [AppsConfig, WarningsConfig2])),
+ WarningsWithoutConfigSet =
+ dialyzer:run([{analysis_type, incremental},
+ {files, [BeamFileWithImproperList]},
+ {from, byte_code}]),
+ ?assertMatch([{warn_non_proper_list, _Loc, _Msg}], WarningsWithoutConfigSet)
+ end),
+
+ peer:stop(Peer).
+
+paths_config_xdg(Config) ->
+ TestHome = filename:join(?config(priv_dir, Config), ?FUNCTION_NAME),
+
+ %% We change the $HOME of the emulator to run this test
+ HomeEnv =
+ case os:type() of
+ {win32, _} ->
+ [Drive | Path] = filename:split(TestHome),
+ [{"APPDATA", filename:join(TestHome, "AppData")},
+ {"HOMEDRIVE", Drive},
+ {"HOMEPATH", filename:join(Path)}];
+ _ ->
+ [{"HOME", TestHome}]
+ end,
+
+ io:format("~p~n", [HomeEnv]),
+
+ ExtraModulesDirOrig =
+ filename:join(?config(data_dir, Config), "extra_modules"),
+ ExtraModulesDir =
+ filename:join(?config(priv_dir, Config), "extra_modules"),
+ ok = filelib:ensure_path(ExtraModulesDir),
+ ok = filelib:ensure_path(filename:join(ExtraModulesDir,"ebin")),
+ ok = filelib:ensure_path(filename:join(ExtraModulesDir,"src")),
+
+ {ok, _} =
+ file:copy(
+ filename:join([ExtraModulesDirOrig, "src", "extra_modules.app.src"]),
+ filename:join([ExtraModulesDir, "src", "extra_modules.app.src"])
+ ),
+ {ok, _} =
+ file:copy(
+ filename:join([ExtraModulesDirOrig, "src", "extra_module.erl"]),
+ filename:join([ExtraModulesDir, "src", "extra_module.erl"])
+ ),
+
+ {ok, Peer, Node} = ?CT_PEER(#{ env => HomeEnv }),
+ {ok, _} =
+ compile:file(
+ filename:join([ExtraModulesDir,"src","extra_module.erl"]),
+ [{outdir, filename:join([ExtraModulesDir,"ebin"])}, debug_info]
+ ),
+
+ AppsConfig =
+ {incremental,
+ {default_apps,
+ [stdlib,
+ kernel,
+ erts,
+ extra_modules
+ ]
+ },
+ {default_warning_apps,
+ [extra_modules % Only on path if added explicitly via config below
+ ]
+ }
+ },
+
+ WarningsConfig =
+ {warnings, [no_unknown]},
+
+ erpc:call(
+ Node,
+ fun() ->
+ %% Find out the path of the config file
+ HomeConfigFilename =
+ filename:join(filename:basedir(user_config, "erlang"),
+ "dialyzer.config"),
+ io:format("~ts~n", [HomeConfigFilename]),
+ ok = filelib:ensure_dir(HomeConfigFilename),
+
+ %% Write configuration file
+ PathConfig = {add_pathsa, [ExtraModulesDir, filename:join(ExtraModulesDir, "ebin"), filename:join(ExtraModulesDir, "src")]},
+ ok =
+ file:write_file(
+ HomeConfigFilename,
+ io_lib:format(
+ "~p.~n~p.~n~p.~n",
+ [AppsConfig, WarningsConfig, PathConfig])),
+
+ {Warnings, ModAnalyzed} =
+ % Will analyse apps from config, including the `extra_modules` app
+ % which contains a Dialyzer error
+ dialyzer:run_report_modules_analyzed([
+ {analysis_type, incremental},
+ {from, byte_code}]),
+
+ % Check we did actually analyze the module
+ ?assert(
+ lists:member(extra_module, ModAnalyzed),
+ lists:flatten(io_lib:format("Looking for 'extra_module' in ~tp~n", [ModAnalyzed]))),
+
+ % Check we got the warnings we expected from modules
+ % added to the path
+ ?assertMatch(
+ [ {warn_contract_types, {_,_}, {invalid_contract, [extra_module,f,1, {[1],true}, "(atom()) -> string()", "(integer()) -> nonempty_improper_list(integer(),3)"]}},
+ {warn_non_proper_list, {_,_}, {improper_list_constr,["3"]}}
+ ],
+ Warnings)
+ end),
+
+ peer:stop(Peer).
+
multiple_plts_unsupported_in_incremental_mode(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
BazPltFile = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ "-baz.iplt"),
diff --git a/lib/dialyzer/test/incremental_SUITE_data/extra_modules/ebin/.gitignore b/lib/dialyzer/test/incremental_SUITE_data/extra_modules/ebin/.gitignore
new file mode 100644
index 0000000000..9aedd49be8
--- /dev/null
+++ b/lib/dialyzer/test/incremental_SUITE_data/extra_modules/ebin/.gitignore
@@ -0,0 +1,5 @@
+# Ignore everything in this directory
+*
+# Except this file, to force the directory to stick around
+# for the tests to later make use of
+!.gitignore
diff --git a/lib/dialyzer/test/incremental_SUITE_data/extra_modules/src/extra_module.erl b/lib/dialyzer/test/incremental_SUITE_data/extra_modules/src/extra_module.erl
new file mode 100644
index 0000000000..21bb42ee61
--- /dev/null
+++ b/lib/dialyzer/test/incremental_SUITE_data/extra_modules/src/extra_module.erl
@@ -0,0 +1,14 @@
+-module(extra_module).
+
+-export([start/2,stop/1,f/1]).
+
+start(StartType, StartArgs) ->
+ error.
+
+stop(State) ->
+ error.
+
+% Purposely broken to generate a warning if the module is loaded and analysed
+-spec f(atom()) -> string().
+f(N) when is_integer(N) ->
+ [N + 1|3].
diff --git a/lib/dialyzer/test/incremental_SUITE_data/extra_modules/src/extra_modules.app.src b/lib/dialyzer/test/incremental_SUITE_data/extra_modules/src/extra_modules.app.src
new file mode 100644
index 0000000000..01b38e4098
--- /dev/null
+++ b/lib/dialyzer/test/incremental_SUITE_data/extra_modules/src/extra_modules.app.src
@@ -0,0 +1,8 @@
+{application, extra_modules,
+ [{description, "An app with some extra modules"},
+ {vsn, "1"},
+ {modules, [extra_module]},
+ {registered, []},
+ {applications, [kernel, stdlib]},
+ {mod, {extra_module,[]}}
+ ]}.
diff --git a/lib/dialyzer/test/small_SUITE_data/results/bs_segments b/lib/dialyzer/test/small_SUITE_data/results/bs_segments
new file mode 100644
index 0000000000..0c3c9a0717
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/results/bs_segments
@@ -0,0 +1,3 @@
+
+bs_segments.erl:6:1: Function t/1 has no local return
+bs_segments.erl:6:1: The pattern <<_>> can never match the type any()
diff --git a/lib/dialyzer/test/small_SUITE_data/results/gh_7153 b/lib/dialyzer/test/small_SUITE_data/results/gh_7153
new file mode 100644
index 0000000000..c596a89f82
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/results/gh_7153
@@ -0,0 +1,3 @@
+
+gh_7153.erl:4:1: Function t/1 has no local return
+gh_7153.erl:5:7: Binary construction will fail since the value field X in segment X/utf8 has type '原子'
diff --git a/lib/dialyzer/test/small_SUITE_data/src/bs_segments.erl b/lib/dialyzer/test/small_SUITE_data/src/bs_segments.erl
new file mode 100644
index 0000000000..b1b8a2e866
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/src/bs_segments.erl
@@ -0,0 +1,7 @@
+-module(bs_segments).
+
+-export([t/1]).
+
+%% GH-7138: bogus segment sizes crashed the analysis.
+t(<<_:undefined>>) ->
+ ok.
diff --git a/lib/dialyzer/test/small_SUITE_data/src/gh_7153.erl b/lib/dialyzer/test/small_SUITE_data/src/gh_7153.erl
new file mode 100644
index 0000000000..ef2ef3a25b
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/src/gh_7153.erl
@@ -0,0 +1,5 @@
+-module(gh_7153).
+-export([t/1]).
+
+t(X = '原子') ->
+ <<X/utf8>>.
diff --git a/lib/diameter/src/diameter.appup.src b/lib/diameter/src/diameter.appup.src
index dca7ba72ef..4d6fedef61 100644
--- a/lib/diameter/src/diameter.appup.src
+++ b/lib/diameter/src/diameter.appup.src
@@ -69,8 +69,9 @@
{"2.2.4", [{restart_application, diameter}]}, %% 23.3.4
{"2.2.5", [{restart_application, diameter}]}, %% 24.3
{"2.2.6", [{restart_application, diameter}]}, %% 25.0
- {"2.2.7", [{restart_application, diameter}]} %% 25.1
- ],
+ {"2.2.7", [{restart_application, diameter}]}, %% 25.1
+ {"2.3", [{restart_application, diameter}]}
+],
[
{"0.9", [{restart_application, diameter}]},
{"0.10", [{restart_application, diameter}]},
@@ -120,6 +121,7 @@
{"2.2.4", [{restart_application, diameter}]},
{"2.2.5", [{restart_application, diameter}]},
{"2.2.6", [{restart_application, diameter}]},
- {"2.2.7", [{restart_application, diameter}]}
+ {"2.2.7", [{restart_application, diameter}]},
+ {"2.3", [{restart_application, diameter}]}
]
}.
diff --git a/lib/edoc/test/edoc_SUITE.erl b/lib/edoc/test/edoc_SUITE.erl
index c150ace008..d09e9a2ecb 100644
--- a/lib/edoc/test/edoc_SUITE.erl
+++ b/lib/edoc/test/edoc_SUITE.erl
@@ -24,13 +24,14 @@
%% Test cases
-export([app/1,appup/1,build_std/1,build_map_module/1,otp_12008/1,
- build_app/1, otp_14285/1, infer_module_app_test/1]).
+ build_app/1, otp_14285/1, infer_module_app_test/1,
+ module_with_feature/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[app,appup,build_std,build_map_module,otp_12008, build_app, otp_14285,
- infer_module_app_test].
+ infer_module_app_test, module_with_feature].
groups() ->
[].
@@ -159,3 +160,13 @@ infer_module_app_test_({M, Beam}) ->
R2 = filelib:is_regular(BeamPath2),
R1 orelse R2
end.
+
+module_with_feature(Config) ->
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ Source = filename:join(DataDir, "module_with_feature.erl"),
+ DodgerOpts = [{dir, PrivDir}],
+ ok = edoc:files([Source], DodgerOpts),
+ PreprocessOpts = [{preprocess, true}, {dir, PrivDir}],
+ ok = edoc:files([Source], PreprocessOpts),
+ ok.
diff --git a/lib/edoc/test/edoc_SUITE_data/module_with_feature.erl b/lib/edoc/test/edoc_SUITE_data/module_with_feature.erl
new file mode 100644
index 0000000000..0091b704b4
--- /dev/null
+++ b/lib/edoc/test/edoc_SUITE_data/module_with_feature.erl
@@ -0,0 +1,2 @@
+-module(module_with_feature).
+-feature(maybe_expr, enable).
diff --git a/lib/eldap/doc/src/eldap.xml b/lib/eldap/doc/src/eldap.xml
index 0b5577fe2f..b97bdebd16 100644
--- a/lib/eldap/doc/src/eldap.xml
+++ b/lib/eldap/doc/src/eldap.xml
@@ -549,7 +549,7 @@ Control2 = eldap:paged_result_control(PageSize, Cookie1),
</desc>
</func>
<func>
- <name since="OTP @OTP-18480@">info(Handle) -> connection_info()</name>
+ <name since="OTP 25.3.1">info(Handle) -> connection_info()</name>
<fsummary>Returns information about the LDAP connection.
</fsummary>
<type>
diff --git a/lib/eldap/doc/src/notes.xml b/lib/eldap/doc/src/notes.xml
index 563f75e827..881c1215b4 100644
--- a/lib/eldap/doc/src/notes.xml
+++ b/lib/eldap/doc/src/notes.xml
@@ -31,6 +31,22 @@
</header>
<p>This document describes the changes made to the Eldap application.</p>
+<section><title>Eldap 1.2.11</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Added a new function eldap:info/1 that returns the socket
+ and the transport protocol for the eldap connection.</p>
+ <p>
+ Own Id: OTP-18480</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Eldap 1.2.10</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/eldap/vsn.mk b/lib/eldap/vsn.mk
index 77d89248c9..a25f97cb89 100644
--- a/lib/eldap/vsn.mk
+++ b/lib/eldap/vsn.mk
@@ -1 +1 @@
-ELDAP_VSN = 1.2.10
+ELDAP_VSN = 1.2.11
diff --git a/lib/erl_interface/doc/src/notes.xml b/lib/erl_interface/doc/src/notes.xml
index a1bb84f224..7bac4913ab 100644
--- a/lib/erl_interface/doc/src/notes.xml
+++ b/lib/erl_interface/doc/src/notes.xml
@@ -31,6 +31,22 @@
</header>
<p>This document describes the changes made to the Erl_interface application.</p>
+<section><title>Erl_Interface 5.3.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed configure tests for a few ARM-specific
+ instructions, which prevented the emulator from being
+ built on some platforms.</p>
+ <p>
+ Own Id: OTP-18554</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erl_Interface 5.3.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/erl_interface/src/connect/ei_connect.c b/lib/erl_interface/src/connect/ei_connect.c
index e5f1c307fd..3f2becde5a 100644
--- a/lib/erl_interface/src/connect/ei_connect.c
+++ b/lib/erl_interface/src/connect/ei_connect.c
@@ -1058,11 +1058,11 @@ int ei_connect_init_ussi(ei_cnode* ec, const char* this_node_name,
strcpy(thishostname, hp->h_name);
}
}
- if (strlen(this_node_name) + 1 + strlen(thishostname) > MAXNODELEN) {
+ if (snprintf(thisnodename, sizeof(thisnodename), "%s@%s",
+ this_node_name, thishostname) > sizeof(thisnodename)) {
EI_TRACE_ERR0("ei_connect_init_ussi","this node name is too long");
return ERL_ERROR;
}
- sprintf(thisnodename, "%s@%s", this_node_name, thishostname);
res = ei_connect_xinit_ussi(ec, thishostname, thisalivename, thisnodename,
(struct in_addr *)*hp->h_addr_list, cookie, creation,
cbs, cbs_sz, setup_context);
diff --git a/lib/erl_interface/src/prog/erl_call.c b/lib/erl_interface/src/prog/erl_call.c
index 4548b9f4dd..1fb72c65cb 100644
--- a/lib/erl_interface/src/prog/erl_call.c
+++ b/lib/erl_interface/src/prog/erl_call.c
@@ -441,11 +441,11 @@ int main(int argc, char *argv[])
memcpy(&h_ipadr.s_addr, *hp->h_addr_list, sizeof(struct in_addr));
if (h_alivename) {
- if (strlen(h_alivename) + strlen(h_hostname) + 2 > sizeof(h_nodename_buf)) {
+ if (snprintf(h_nodename_buf, sizeof(h_nodename_buf), "%s@%s",
+ h_alivename, h_hostname) > sizeof(h_nodename_buf)) {;
fprintf(stderr,"erl_call: hostname too long: %s\n", h_hostname);
exit_free_flags_fields(1, &flags);
}
- sprintf(h_nodename, "%s@%s", h_alivename, h_hostname);
}
else {
/* dynamic node name */
@@ -490,11 +490,11 @@ int main(int argc, char *argv[])
}
if (flags.port == -1) {
- if (strlen(flags.node) + strlen(host_name) + 2 > sizeof(nodename)) {
+ if (snprintf(nodename, sizeof(nodename),
+ "%s@%s", flags.node, host_name) > sizeof(nodename)) {
fprintf(stderr,"erl_call: nodename too long: %s\n", flags.node);
exit_free_flags_fields(1, &flags);
}
- sprintf(nodename, "%s@%s", flags.node, host_name);
}
/*
* Try to connect. Start an Erlang system if the
diff --git a/lib/erl_interface/vsn.mk b/lib/erl_interface/vsn.mk
index badd74c6da..683e6643c7 100644
--- a/lib/erl_interface/vsn.mk
+++ b/lib/erl_interface/vsn.mk
@@ -1,2 +1,2 @@
-EI_VSN = 5.3.1
+EI_VSN = 5.3.2
ERL_INTERFACE_VSN = $(EI_VSN)
diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml
index 9b234c37f1..a10ee19030 100644
--- a/lib/inets/doc/src/notes.xml
+++ b/lib/inets/doc/src/notes.xml
@@ -33,7 +33,33 @@
<file>notes.xml</file>
</header>
- <section><title>Inets 8.3</title>
+ <section><title>Inets 8.3.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Correct timing related pipelining/keepalive queue bug,
+ that could result in unexpected "socket_remotly_closed"
+ errors.</p>
+ <p>
+ Own Id: OTP-18509 Aux Id: OTP-18476 </p>
+ </item>
+ <item>
+ <p>
+ With this change, upon remote socket closure current
+ request is added to a retried queue (either pipeline or
+ keep_alive, but not both).</p>
+ <p>
+ Own Id: OTP-18545 Aux Id: OTP-18509, ERIERL-937,
+ ERIERL-928 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Inets 8.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/inets/src/http_client/httpc.erl b/lib/inets/src/http_client/httpc.erl
index b4315f4ba3..8f62b8906c 100644
--- a/lib/inets/src/http_client/httpc.erl
+++ b/lib/inets/src/http_client/httpc.erl
@@ -1009,12 +1009,14 @@ http_options_default() ->
error
end,
+ SslOpts = ssl_verify_host_options(true),
+
UrlDecodePost = boolfun(),
[
{version, {value, "HTTP/1.1"}, #http_options.version, VersionPost},
{timeout, {value, ?HTTP_REQUEST_TIMEOUT}, #http_options.timeout, TimeoutPost},
{autoredirect, {value, true}, #http_options.autoredirect, AutoRedirectPost},
- {ssl, {value, {ssl, []}}, #http_options.ssl, SslPost},
+ {ssl, {value, {ssl, SslOpts}}, #http_options.ssl, SslPost},
{proxy_auth, {value, undefined}, #http_options.proxy_auth, ProxyAuthPost},
{relaxed, {value, false}, #http_options.relaxed, RelaxedPost},
{url_encode, {value, false}, #http_options.url_encode, UrlDecodePost},
diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk
index 99285501c5..78efa696d8 100644
--- a/lib/inets/vsn.mk
+++ b/lib/inets/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = inets
-INETS_VSN = 8.3
+INETS_VSN = 8.3.1
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)"
diff --git a/lib/kernel/Makefile b/lib/kernel/Makefile
index 534b564c2c..7586a4c981 100644
--- a/lib/kernel/Makefile
+++ b/lib/kernel/Makefile
@@ -35,7 +35,7 @@ SPECIAL_TARGETS =
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_subdir.mk
-DIA_PLT_APPS=crypto
+DIA_PLT_APPS=crypto compiler
TEST_NEEDS_RELEASE=true
include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/kernel/doc/src/code.xml b/lib/kernel/doc/src/code.xml
index 1bc90f1e8b..ffa77641de 100644
--- a/lib/kernel/doc/src/code.xml
+++ b/lib/kernel/doc/src/code.xml
@@ -427,7 +427,7 @@ zip:create("mnesia-4.4.7.ez",
</func>
<func>
<name name="add_pathsa" arity="1" since=""/>
- <name name="add_pathsa" arity="2" since=""/>
+ <name name="add_pathsa" arity="2" since="OTP @OTP-18466@"/>
<fsummary>Add directories to the beginning of the code path.</fsummary>
<desc>
<p>Traverses <c><anno>Dirs</anno></c> and adds
diff --git a/lib/kernel/doc/src/disk_log.xml b/lib/kernel/doc/src/disk_log.xml
index dc774538fb..dc6f9ba5bf 100644
--- a/lib/kernel/doc/src/disk_log.xml
+++ b/lib/kernel/doc/src/disk_log.xml
@@ -713,7 +713,7 @@
</desc>
</func>
<func>
- <name name="next_file" arity="1" since=""/>
+ <name name="next_file" arity="1" since="OTP @OTP-18331@"/>
<fsummary>Change to the next log file of a disk log.</fsummary>
<type name="next_file_error_rsn"/>
<type name="invalid_header"/>
diff --git a/lib/kernel/doc/src/socket.xml b/lib/kernel/doc/src/socket.xml
index e8e77b8295..2861108da6 100644
--- a/lib/kernel/doc/src/socket.xml
+++ b/lib/kernel/doc/src/socket.xml
@@ -148,69 +148,8 @@
<c>Info</c> will be <c>{SelectHandle, closed}</c>. </p>
</note>
- <marker id="asynchronous-call"/>
<note>
- <p>OLD NOTE</p>
- <p>Select-based <i>asynchronous</i> message interface. </p>
- <p>
- Some functions allow for an <i>asynchronous</i> call.
- This is achieved by setting the <c>Timeout</c> argument to
- <c>nowait</c>.
- For instance, if calling the
- <seeerl marker="#recv-nowait"><c>recv/3</c></seeerl>
- function with Timeout set to <c>nowait</c>
- (<c>recv(Sock, 0, nowait)</c>)
- when there is actually nothing to read, it will return with
- <c>{select, </c>
- <seetype marker="#select_info"><c>SelectInfo</c></seetype><c>}</c>
- (<c>SelectInfo</c> contains the
- <seetype marker="socket#select_handle">SelectHandle</seetype>).
- When data eventually arrives a 'select' message
- will be sent to the caller:
- </p>
- <taglist>
- <!-- NOTE THAT THE EMPTY TAG IS INTENTIONAL -->
- <tag></tag>
- <item><c>{'$socket', socket(), select, SelectHandle}</c></item>
- </taglist>
- <p>
- The caller can now call the <c>recv</c> function again
- and probably expect data
- (it is really up to the OS network protocol implementation).
- </p>
- <p>
- Note that all other users are <em>locked out</em> until the
- 'current user' has called the function (<c>recv</c> in this case)
- and its return value shows that the operation has completed.
- An operation can also be cancelled with
- <seemfa marker="#cancel/2"><c>cancel/2</c></seemfa>.
- </p>
- <p>
- Instead of <c>Timeout = nowait</c> it is equivalent to create a
- <seetype marker="socket#select_handle"><c>SelectHandle</c></seetype>)
- with
- <seemfa marker="erts:erlang#make_ref/0"><c>make_ref()</c></seemfa>
- and give as <c>Timeout</c>.
- This will then be the <c>SelectHandle</c> in the 'select' message,
- which enables a compiler optimization for receiving
- a message containing a newly created <c>reference()</c>
- (ignore the part of the message queue that had arrived
- before the the <c>reference()</c> was created).
- </p>
- <p>Another message the user must be prepared for (when making asynchronous
- calls) is the <c>abort</c> message:</p>
- <taglist>
- <!-- NOTE THAT THE EMPTY TAG IS INTENTIONAL -->
- <tag></tag>
- <item><c>{'$socket', socket(), abort, Info}</c></item>
- </taglist>
- <p>This message indicates
- that the (asynchronous) operation has been aborted.
- If, for instance, the socket has been closed (by another process),
- <c>Info</c> will be <c>{SelectHandle, closed}</c>. </p>
- </note>
- <note>
- <p>There is currently <em>no</em> support for Windows. </p>
+ <p>The Windows support has currently <em>pre-release</em> status. </p>
<p>Support for IPv6 has been implemented but not <em>fully</em>
tested. </p>
<p>SCTP has only been partly implemented (and not tested). </p>
@@ -1652,7 +1591,72 @@
<func>
<name name="cancel" arity="2" clause_i="1" since="OTP 22.1"/>
- <name name="cancel" arity="2" clause_i="2" since="OTP-18029"/>
+ <fsummary>Cancel an asynchronous request.</fsummary>
+ <desc>
+ <p>Cancel an asynchronous (select) request.</p>
+ <p>
+ Call this function in order to cancel a previous
+ asynchronous call to, e.g.
+ <seemfa marker="#recv/3"><c>recv/3</c></seemfa>.
+ </p>
+ <p>
+ An ongoing asynchronous operation blocks the socket
+ until the operation has been finished in good order,
+ or until it has been cancelled by this function.
+ </p>
+ <p>
+ Any other process that tries an operation
+ of the same basic type (accept / send / recv) will be
+ enqueued and notified with the regular <c>select</c>
+ mechanism for asynchronous operations
+ when the current operation and all enqueued before it
+ has been completed.
+ </p>
+ <p>
+ If <c><anno>SelectInfo</anno></c> does not match an
+ operation in progress for the calling process,
+ this function returns
+ <c>{error,&nbsp;{invalid,&nbsp;SelectInfo}}</c>.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="cancel" arity="2" clause_i="2" since="OTP 26.0"/>
+ <fsummary>Cancel an asynchronous request.</fsummary>
+ <desc>
+ <p>Cancel an asynchronous (completion) request.</p>
+ <p>
+ Call this function in order to cancel a previous
+ asynchronous call to, e.g.
+ <seemfa marker="#recv/3"><c>recv/3</c></seemfa>.
+ </p>
+ <p>
+ An ongoing asynchronous operation blocks the socket
+ until the operation has been finished in good order,
+ or until it has been cancelled by this function.
+ </p>
+ <p>
+ Any other process that tries an operation
+ of the same basic type (accept / send / recv) will be
+ enqueued and notified with the regular <c>select</c>
+ mechanism for asynchronous operations
+ when the current operation and all enqueued before it
+ has been completed.
+ </p>
+ <p>
+ If <c><anno>CompletionInfo</anno></c> does not match an
+ operation in progress for the calling process,
+ this function returns
+ <c>{error,&nbsp;{invalid,&nbsp;CompletionInfo}}</c>.
+ </p>
+ </desc>
+ </func>
+
+ <!--
+ <func>
+ <name name="cancel" arity="2" clause_i="1" since="OTP 22.1"/>
+ <name name="cancel" arity="2" clause_i="2" since="OTP 26.0"/>
<fsummary>Cancel an asynchronous request.</fsummary>
<desc>
<p>Cancel an asynchronous request.</p>
@@ -1683,6 +1687,7 @@
</p>
</desc>
</func>
+ -->
<func>
<name name="close" arity="1" since="OTP 22.0"/>
diff --git a/lib/kernel/src/gen_tcp_socket.erl b/lib/kernel/src/gen_tcp_socket.erl
index 84ea036c3f..fb0f807cc1 100644
--- a/lib/kernel/src/gen_tcp_socket.erl
+++ b/lib/kernel/src/gen_tcp_socket.erl
@@ -729,8 +729,10 @@ socket_close(Socket) ->
-compile({inline, [socket_cancel/2]}).
socket_cancel(Socket, SelectInfo) ->
case socket:cancel(Socket, SelectInfo) of
- ok -> ok;
- {error, closed} -> ok
+ ok -> ok;
+ {error, closed} -> ok;
+ {error, _} = ERROR -> ERROR
+
end.
%%% ========================================================================
@@ -1575,7 +1577,7 @@ handle_event(
info = SelectInfo, from = From,
listen_socket = ListenSocket},
{P, D}) ->
- socket_cancel(ListenSocket, SelectInfo),
+ _ = socket_cancel(ListenSocket, SelectInfo),
{next_state, 'closed', {P, D},
[{reply, From, {error, timeout}}]};
handle_event(Type, Content, #accept{} = State, P_D) ->
@@ -1661,7 +1663,7 @@ handle_event(
{timeout, connect}, connect,
#connect{info = SelectInfo, from = From},
{#params{socket = Socket} = _P, _D} = P_D) ->
- socket_cancel(Socket, SelectInfo),
+ _ = socket_cancel(Socket, SelectInfo),
_ = socket_close(Socket),
{next_state, 'closed', P_D,
[{reply, From, {error, timeout}}]};
@@ -2268,11 +2270,11 @@ cleanup_close_read(P, D, State, Reason) ->
case State of
#accept{
info = SelectInfo, from = From, listen_socket = ListenSocket} ->
- socket_cancel(ListenSocket, SelectInfo),
+ _ = socket_cancel(ListenSocket, SelectInfo),
{D,
[{reply, From, {error, Reason}}]};
#connect{info = SelectInfo, from = From} ->
- socket_cancel(P#params.socket, SelectInfo),
+ _ = socket_cancel(P#params.socket, SelectInfo),
{D,
[{reply, From, {error, Reason}}]};
_ ->
@@ -2283,7 +2285,7 @@ cleanup_recv(P, D, State, Reason) ->
%% ?DBG({P#params.socket, State, Reason}),
case State of
#recv{info = SelectInfo} ->
- socket_cancel(P#params.socket, SelectInfo),
+ _ = socket_cancel(P#params.socket, SelectInfo),
cleanup_recv_reply(P, D, [], Reason);
_ ->
cleanup_recv_reply(P, D, [], Reason)
diff --git a/lib/kernel/src/socket.erl b/lib/kernel/src/socket.erl
index 25be82543f..02e199d088 100644
--- a/lib/kernel/src/socket.erl
+++ b/lib/kernel/src/socket.erl
@@ -4548,6 +4548,7 @@ ioctl(Socket, SetRequest, Arg1, Arg2) ->
Socket :: socket(),
SelectInfo :: select_info(),
Reason :: 'closed' | invalid();
+
(Socket, CompletionInfo) -> 'ok' | {'error', Reason} when
Socket :: socket(),
CompletionInfo :: completion_info(),
@@ -4590,14 +4591,10 @@ cancel(Socket, Info) ->
erlang:error(badarg, [Socket, Info]).
-%% What about completion? There is no way to cancel a
-%% I/O completion "request" once it has been issued.
-%% But we may still have "stuff" in our own queues,
-%% which needs to be cleared out.
cancel(SockRef, Op, Handle) ->
case prim_socket:cancel(SockRef, Op, Handle) of
select_sent ->
- flush_select_msg(SockRef, Handle),
+ _ = flush_select_msg(SockRef, Handle),
_ = flush_abort_msg(SockRef, Handle),
ok;
not_found ->
@@ -4605,6 +4602,9 @@ cancel(SockRef, Op, Handle) ->
_ = flush_abort_msg(SockRef, Handle),
invalid;
Result ->
+ %% Since we do not actually if we are using
+ %% select or completion here, so flush both...
+ _ = flush_select_msg(SockRef, Handle),
_ = flush_completion_msg(SockRef, Handle),
_ = flush_abort_msg(SockRef, Handle),
Result
diff --git a/lib/kernel/test/gen_udp_SUITE.erl b/lib/kernel/test/gen_udp_SUITE.erl
index 0aab4e4fa0..4aefee8a79 100644
--- a/lib/kernel/test/gen_udp_SUITE.erl
+++ b/lib/kernel/test/gen_udp_SUITE.erl
@@ -2639,6 +2639,10 @@ do_simple_sockaddr_send_recv(#{family := _Fam} = SockAddr, _) ->
?P("[server] send failed: ~p",
[Reason1]),
exit({skip, Reason1});
+ {error, enetunreach = Reason1} ->
+ ?P("[server] send failed: ~p",
+ [Reason1]),
+ exit({skip, Reason1});
{error, Reason1} ->
exit({send_failed, Reason1})
end
@@ -2664,6 +2668,10 @@ do_simple_sockaddr_send_recv(#{family := _Fam} = SockAddr, _) ->
?P("[server] send failed: ~p",
[Reason2]),
exit({skip, Reason2});
+ {error, enetunreach = Reason2} ->
+ ?P("[server] send failed: ~p",
+ [Reason2]),
+ exit({skip, Reason2});
{error, Reason2} ->
exit({send_failed, Reason2})
end
diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl
index 5f0fd15923..196af34521 100644
--- a/lib/kernel/test/socket_SUITE.erl
+++ b/lib/kernel/test/socket_SUITE.erl
@@ -744,8 +744,11 @@
-define(TPP_LARGE_NUM, 50).
-define(TPP_NUM(Config, Base), (Base) div lookup(kernel_factor, 1, Config)).
+-define(WINDOWS, {win32,nt}).
+
-define(TTEST_RUNTIME, ?SECS(1)).
-define(TTEST_MIN_FACTOR, 3).
+-define(TTEST_MIN_FACTOR_WIN, ?TTEST_MIN_FACTOR-1).
-define(TTEST_DEFAULT_SMALL_MAX_OUTSTANDING, 50).
-define(TTEST_DEFAULT_MEDIUM_MAX_OUTSTANDING,
?TTEST_MK_DEFAULT_MAX_OUTSTANDING(
@@ -1404,7 +1407,12 @@ traffic_pp_sendmsg_recvmsg_cases() ->
%% No point in running these cases unless the machine is
%% reasonably fast.
ttest_condition(Config) ->
+ OsType = os:type(),
case ?config(kernel_factor, Config) of
+ Factor when (OsType =:= ?WINDOWS) andalso
+ is_integer(Factor) andalso
+ (Factor =< ?TTEST_MIN_FACTOR_WIN) ->
+ ok;
Factor when is_integer(Factor) andalso (Factor =< ?TTEST_MIN_FACTOR) ->
ok;
Factor when is_integer(Factor) ->
@@ -3492,7 +3500,10 @@ api_b_send_and_recv_seqpL(_Config) when is_list(_Config) ->
api_b_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) ->
?TT(?SECS(10)),
tc_try(api_b_sendmsg_and_recvmsg_tcp4,
- fun() -> has_support_ipv4() end,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv4()
+ end,
fun() ->
Send = fun(Sock, Data) ->
Msg = #{iov => [Data]},
@@ -6926,7 +6937,8 @@ api_a_connect_tcp(InitState) ->
{ok, State#{asynch_tag => select,
connect_tag => ST}};
- {completion, {completion_info, CT, CompletionRef}}
+ {completion,
+ {completion_info, CT, CompletionRef}}
when SR =:= nowait ->
?SEV_IPRINT("completion nowait ->"
"~n tag: ~p"
@@ -6935,7 +6947,8 @@ api_a_connect_tcp(InitState) ->
{ok, State#{asynch_tag => completion,
connect_tag => CT,
connect_ref => CompletionRef}};
- {completion, {completion_info, CT, CR}}
+ {completion,
+ {completion_info, CT, CR}}
when is_reference(CR) ->
?SEV_IPRINT("completion ref ->"
"~n tag: ~p"
@@ -7988,7 +8001,10 @@ api_a_sendmsg_and_recvmsg_tcp4(Config) when is_list(Config) ->
?TT(?SECS(10)),
Nowait = nowait(Config),
tc_try(api_a_sendmsg_and_recvmsg_tcp4,
- fun() -> has_support_ipv4() end,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv4()
+ end,
fun() ->
Send = fun(Sock, Data) ->
Msg = #{iov => [Data]},
@@ -9488,6 +9504,7 @@ api_a_recv_cancel_tcp(InitState) ->
cmd => fun(#{tester := Tester} = _State) ->
?SEV_AWAIT_CONTINUE(Tester, tester, recv)
end},
+
#{desc => "try recv request (with nowait, expect select|completion)",
cmd => fun(#{csock := Sock,
recv := Recv,
@@ -9506,13 +9523,15 @@ api_a_recv_cancel_tcp(InitState) ->
"~n Ref: ~p", [T, Ref]),
{ok, State#{recv_select_info => SI}};
- {completion, {completion_info, T, R} = CI}
+ {completion,
+ {completion_info, T, R} = CI}
when Ref =:= nowait ->
?SEV_IPRINT("recv completion nowait: "
"~n Tag: ~p"
"~n Ref: ~p", [T, R]),
{ok, State#{recv_completion_info => CI}};
- {completion, {completion_info, T, Ref} = CI}
+ {completion,
+ {completion_info, T, Ref} = CI}
when is_reference(Ref) ->
?SEV_IPRINT("recv completion ref: "
"~n Tag: ~p"
@@ -10913,6 +10932,7 @@ api_a_mrecvmsg_cancel_tcp4(Config) when is_list(Config) ->
Nowait = nowait(Config),
tc_try(?FUNCTION_NAME,
fun() ->
+ is_not_windows(),
has_support_ipv4()
end,
fun() ->
@@ -10937,7 +10957,10 @@ api_a_mrecvmsg_cancel_tcp6(Config) when is_list(Config) ->
?TT(?SECS(20)),
Nowait = nowait(Config),
tc_try(?FUNCTION_NAME,
- fun() -> has_support_ipv6() end,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv6()
+ end,
fun() ->
Recv = fun(Sock) ->
socket:recvmsg(Sock, Nowait)
@@ -14034,6 +14057,10 @@ api_opt_sock_broadcast() ->
?SEV_IPRINT("Expected Success: "
"broadcast message sent"),
ok;
+ {error, eaddrnotavail = Reason} ->
+ ?SEV_EPRINT("Unexpected Failure: ~p => SKIP",
+ [Reason]),
+ {skip, Reason};
{error, eacces = Reason} ->
?SEV_EPRINT("Unexpected Failure: ~p => SKIP",
[Reason]),
@@ -21126,6 +21153,37 @@ api_opt_ip_recvttl_udp(InitState) ->
{skip,
?F("Cannot send with TTL: ~p", [Info])};
+ {error,
+ {completion_status,
+ #{file := File,
+ function := Function,
+ line := Line,
+ raw_info := RawInfo,
+ info := invalid_parameter = Info}}} ->
+ %% IF we can't send it the test will not work
+ ?SEV_EPRINT("Cannot send TTL: "
+ "~p => SKIP: "
+ "~n File: ~s"
+ "~n Function: ~s"
+ "~n Line: ~p"
+ "~n Raw Info: ~p",
+ [Info,
+ File, Function, Line,
+ RawInfo]),
+ (catch socket:close(SSock)),
+ (catch socket:close(DSock)),
+ {skip,
+ ?F("Cannot send with TTL: ~p", [Info])};
+ {error, {completion_status,
+ invalid_parameter = Info}} ->
+ %% IF we can't send it the test will not work
+ ?SEV_EPRINT("Cannot send TTL: "
+ "~p => SKIP", [Info]),
+ (catch socket:close(SSock)),
+ (catch socket:close(DSock)),
+ {skip,
+ ?F("Cannot send with TTL: ~p", [Info])};
+
{error, _Reason} = ERROR ->
ERROR
end
@@ -23420,7 +23478,7 @@ api_opt_ipv6_tclass_udp(InitState) ->
#{desc => "send req (to dst) (w explicit tc = 1)",
cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
case Send(Sock, ?BASIC_REQ, Dst, 1) of
- {error,
+ {error,
{get_overlapped_result,
#{file := File,
function := Function,
@@ -23449,6 +23507,35 @@ api_opt_ipv6_tclass_udp(InitState) ->
{skip,
?F("Cannot send with TClass: ~p", [Info])};
+ {error,
+ {completion_status,
+ #{file := File,
+ function := Function,
+ line := Line,
+ raw_info := RawInfo,
+ info := invalid_parameter = Info}}} ->
+ %% IF we can't send it the test will not work
+ ?SEV_EPRINT("Cannot send TClass: "
+ "~p => SKIP: "
+ "~n File: ~s"
+ "~n Function: ~s"
+ "~n Line: ~p"
+ "~n Raw Info: ~p",
+ [Info,
+ File, Function, Line,
+ RawInfo]),
+ (catch socket:close(Sock)),
+ {skip,
+ ?F("Cannot send with TClass: ~p", [Info])};
+ {error, {completion_status,
+ invalid_parameter = Info}} ->
+ %% IF we can't send it the test will not work
+ ?SEV_EPRINT("Cannot send TClass: "
+ "~p => SKIP", [Info]),
+ (catch socket:close(Sock)),
+ {skip,
+ ?F("Cannot send with TClass: ~p", [Info])};
+
Other ->
Other
end
@@ -32985,8 +33072,11 @@ sc_lc_receive_response_udp(InitState) ->
sc_lc_recvmsg_response_tcp4(_Config) when is_list(_Config) ->
?TT(?SECS(10)),
- tc_try(sc_lc_recvmsg_response_tcp4,
- fun() -> has_support_ipv4() end,
+ tc_try(?FUNCTION_NAME,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv4()
+ end,
fun() ->
Recv = fun(Sock) -> socket:recvmsg(Sock) end,
InitState = #{domain => inet,
@@ -33003,8 +33093,11 @@ sc_lc_recvmsg_response_tcp4(_Config) when is_list(_Config) ->
sc_lc_recvmsg_response_tcp6(_Config) when is_list(_Config) ->
?TT(?SECS(10)),
- tc_try(sc_recvmsg_response_tcp6,
- fun() -> has_support_ipv6() end,
+ tc_try(?FUNCTION_NAME,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv6()
+ end,
fun() ->
Recv = fun(Sock) -> socket:recvmsg(Sock) end,
InitState = #{domain => inet6,
@@ -36503,7 +36596,10 @@ traffic_send_and_recv_counters_tcpL(_Config) when is_list(_Config) ->
traffic_sendmsg_and_recvmsg_counters_tcp4(_Config) when is_list(_Config) ->
?TT(?SECS(15)),
tc_try(traffic_sendmsg_and_recvmsg_counters_tcp4,
- fun() -> has_support_ipv4() end,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv4()
+ end,
fun() ->
InitState = #{domain => inet,
proto => tcp,
@@ -36532,7 +36628,10 @@ traffic_sendmsg_and_recvmsg_counters_tcp4(_Config) when is_list(_Config) ->
traffic_sendmsg_and_recvmsg_counters_tcp6(_Config) when is_list(_Config) ->
?TT(?SECS(15)),
tc_try(traffic_sendmsg_and_recvmsg_counters_tcp6,
- fun() -> has_support_ipv6() end,
+ fun() ->
+ is_not_windows(),
+ has_support_ipv6()
+ end,
fun() ->
InitState = #{domain => inet6,
proto => tcp,
@@ -48842,7 +48941,7 @@ otp16359_maccept_tcp(InitState) ->
%% This test case is to verify that we do not leak monitors.
otp18240_accept_mon_leak_tcp4(Config) when is_list(Config) ->
- ?TT(?SECS(10)),
+ ?TT(?SECS(30)),
tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv4() end,
fun() ->
@@ -48857,7 +48956,7 @@ otp18240_accept_mon_leak_tcp4(Config) when is_list(Config) ->
%% This test case is to verify that we do not leak monitors.
otp18240_accept_mon_leak_tcp6(Config) when is_list(Config) ->
- ?TT(?SECS(10)),
+ ?TT(?SECS(30)),
tc_try(?FUNCTION_NAME,
fun() -> has_support_ipv6() end,
fun() ->
@@ -48883,26 +48982,42 @@ otp18240_accept_tcp(#{domain := Domain,
otp18240_await_acceptor(Pid, Mon) ->
receive
- {'DOWN', Mon, process, Pid, Info} ->
- i("acceptor terminated: "
- "~n ~p", [Info])
+ {'DOWN', Mon, process, Pid, ok} ->
+ i("acceptor successfully terminated"),
+ ok;
+ {'DOWN', Mon, process, Pid, {skip, _} = SKIP} ->
+ i("acceptor successfully terminated"),
+ exit(SKIP);
+ {'DOWN', Mon, process, Pid, Info} ->
+ i("acceptor unexpected termination: "
+ "~n ~p", [Info]),
+ exit({unexpected_result, Info})
after 5000 ->
- i("acceptor info"
+ i("acceptor process (~p) info"
"~n Refs: ~p"
"~n Info: ~p",
- [monitored_by(Pid), erlang:process_info(Pid)]),
+ [Pid, monitored_by(Pid), erlang:process_info(Pid)]),
otp18240_await_acceptor(Pid, Mon)
end.
otp18240_acceptor(Parent, Domain, Proto, NumSocks) ->
i("[acceptor] begin with: "
+ "~n Parent: ~p"
"~n Domain: ~p"
- "~n Protocol: ~p", [Domain, Proto]),
+ "~n Protocol: ~p", [Parent, Domain, Proto]),
MonitoredBy0 = monitored_by(),
- {ok, LSock} = socket:open(Domain, stream, Proto,
- #{use_registry => false}),
- ok = socket:bind(LSock, #{family => Domain, port => 0, addr => any}),
+ ?SLEEP(?SECS(5)),
+ Addr = case ?LIB:which_local_host_info(Domain) of
+ {ok, #{addr := A}} ->
+ A;
+ {error, Reason} ->
+ exit({skip, Reason})
+ end,
+ {ok, LSock} = socket:open(Domain, stream, Proto,
+ #{use_registry => false}),
+ ok = socket:bind(LSock, #{family => Domain, addr => Addr, port => 0}),
ok = socket:listen(LSock, NumSocks),
+ ?SLEEP(?SECS(5)),
MonitoredBy1 = monitored_by(),
i("[acceptor]: listen socket created"
"~n 'Montored By' before listen socket: ~p"
@@ -48922,7 +49037,7 @@ otp18240_acceptor(Parent, Domain, Proto, NumSocks) ->
_Clients = [spawn_link(fun() ->
otp18240_client(CID,
Domain, Proto,
- Port)
+ Addr, Port)
end) || CID <- lists:seq(1, NumSocks)],
i("[acceptor] accept ~w connections", [NumSocks]),
@@ -48956,26 +49071,49 @@ otp18240_acceptor(Parent, Domain, Proto, NumSocks) ->
end.
-otp18240_client(ID, Domain, Proto, PortNo) ->
+otp18240_client(ID, Domain, Proto, Addr, PortNo) ->
i("[connector ~w] try create connector socket", [ID]),
{ok, Sock} = socket:open(Domain, stream, Proto, #{use_registry => false}),
- ok = socket:bind(Sock, #{family => Domain, port => 0, addr => any}),
+ ok = socket:bind(Sock, #{family => Domain, addr => Addr, port => 0}),
%% ok = socket:setopt(Sock, otp, debug, true),
i("[connector ~w] try connect", [ID]),
- ok = socket:connect(Sock, #{family => Domain, addr => any, port => PortNo}),
- i("[connector ~w] connected - now try recv", [ID]),
- case socket:recv(Sock) of
- {ok, Data} ->
- i("[connector ~w] received unexpected data: "
- "~n ~p", [ID, Data]),
- (catch socket:close(Sock)),
- exit('unexpected data');
- {error, closed} ->
- i("[connector ~w] expected socket close", [ID]);
- {error, Reason} ->
- i("[connector ~w] unexpected error when reading: "
- "~n ~p", [ID, Reason]),
- (catch socket:close(Sock))
+ case socket:connect(Sock,
+ #{family => Domain, addr => Addr, port => PortNo}) of
+ ok ->
+ i("[connector ~w] connected - now try recv", [ID]),
+ case socket:recv(Sock) of
+ {ok, Data} ->
+ i("[connector ~w] received unexpected data: "
+ "~n ~p", [ID, Data]),
+ (catch socket:close(Sock)),
+ exit('unexpected data');
+ {error, closed} ->
+ i("[connector ~w] expected socket close", [ID]);
+ {error, Reason} ->
+ i("[connector ~w] unexpected error when reading: "
+ "~n ~p", [ID, Reason]),
+ (catch socket:close(Sock))
+ end;
+ {error, {completion_status, #{info := invalid_netname = R} = Reason}} ->
+ i("[connector ~w] failed connecting: "
+ "~n ~p", [ID, Reason]),
+ (catch socket:close(Sock)),
+ exit({skip, R});
+ {error, {completion_status, invalid_netname = Reason}} ->
+ i("[connector ~w] failed connecting: "
+ "~n ~p", [ID, Reason]),
+ (catch socket:close(Sock)),
+ exit({skip, Reason});
+ {error, enetunreach = Reason} ->
+ i("[connector ~w] failed connecting: "
+ "~n ~p", [ID, Reason]),
+ (catch socket:close(Sock)),
+ exit({skip, Reason});
+
+ {error, Reason} ->
+ i("[connector ~w] failed connecting: "
+ "~n ~p", [ID, Reason]),
+ (catch socket:close(Sock))
end,
i("[connector ~w] done", [ID]),
ok.
diff --git a/lib/os_mon/doc/src/notes.xml b/lib/os_mon/doc/src/notes.xml
index 1fac53d330..439c1e839a 100644
--- a/lib/os_mon/doc/src/notes.xml
+++ b/lib/os_mon/doc/src/notes.xml
@@ -31,6 +31,23 @@
</header>
<p>This document describes the changes made to the OS_Mon application.</p>
+<section><title>Os_Mon 2.8.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Avoid error report from failing <c>erlang:port_close</c>
+ at shutdown of <c>cpu_sup</c> and <c>memsup</c>. Bug
+ exists since OTP 25.3 (os_mon-2.8.1).</p>
+ <p>
+ Own Id: OTP-18559 Aux Id: ERIERL-942 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Os_Mon 2.8.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/os_mon/src/cpu_sup.erl b/lib/os_mon/src/cpu_sup.erl
index 522ffe5830..a82dfd9961 100644
--- a/lib/os_mon/src/cpu_sup.erl
+++ b/lib/os_mon/src/cpu_sup.erl
@@ -614,8 +614,8 @@ port_server_loop(Port, Timeout) ->
% Close port and this server
{Pid, ?quit} ->
- port_command(Port, ?quit),
- port_close(Port),
+ Port ! {self(), {command, ?quit}},
+ Port ! {self(), close},
Pid ! {self(), {data, quit}},
ok;
diff --git a/lib/os_mon/src/memsup.erl b/lib/os_mon/src/memsup.erl
index 1c93bfbae4..c467adc2e5 100644
--- a/lib/os_mon/src/memsup.erl
+++ b/lib/os_mon/src/memsup.erl
@@ -653,7 +653,7 @@ start_portprogram() ->
port_shutdown(Port) ->
Port ! {self(), {command, [?EXIT]}},
- port_close(Port).
+ Port ! {self(), close}.
%% The connected process loops are a bit awkward (several different
%% functions doing almost the same thing) as
diff --git a/lib/os_mon/vsn.mk b/lib/os_mon/vsn.mk
index 9e64a83612..d6878e6773 100644
--- a/lib/os_mon/vsn.mk
+++ b/lib/os_mon/vsn.mk
@@ -1 +1 @@
-OS_MON_VSN = 2.8.1
+OS_MON_VSN = 2.8.2
diff --git a/lib/snmp/doc/src/notes.xml b/lib/snmp/doc/src/notes.xml
index cb4eb5985f..01cff8fa4b 100644
--- a/lib/snmp/doc/src/notes.xml
+++ b/lib/snmp/doc/src/notes.xml
@@ -34,7 +34,23 @@
</header>
- <section><title>SNMP 5.13.4</title>
+ <section><title>SNMP 5.13.5</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Attempts to minimize the number of the error reports
+ during a failed agent init.</p>
+ <p>
+ Own Id: OTP-18422 Aux Id: ERIERL-873 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SNMP 5.13.4</title>
<section><title>Improvements and New Features</title>
<list>
diff --git a/lib/snmp/vsn.mk b/lib/snmp/vsn.mk
index 4c11344234..13c7e9790e 100644
--- a/lib/snmp/vsn.mk
+++ b/lib/snmp/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = snmp
-SNMP_VSN = 5.13.4
+SNMP_VSN = 5.13.5
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(SNMP_VSN)$(PRE_VSN)"
diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml
index 6ff15d429a..911055d742 100644
--- a/lib/ssl/doc/src/notes.xml
+++ b/lib/ssl/doc/src/notes.xml
@@ -27,6 +27,30 @@
</header>
<p>This document describes the changes made to the SSL application.</p>
+<section><title>SSL 10.9.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ With this change, ssl:connection_information/2 returns
+ correct keylog data after TLS1.3 key update.</p>
+ <p>
+ Own Id: OTP-18489</p>
+ </item>
+ <item>
+ <p>
+ Client signature algorithm list input order is now
+ honored again , it was accidently reversed by a previous
+ fix.</p>
+ <p>
+ Own Id: OTP-18550</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SSL 10.9</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml
index 3574ae91ac..700709ed7a 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -819,7 +819,7 @@ marker="public_key:public_key#pkix_path_validation/3">public_key:pkix_path_valid
<desc>
<code>
fun(Chain::[public_key:der_encoded()]) ->
- {trusted_ca, DerCert::public_key:der_encoded()} | unknown_ca}
+ {trusted_ca, DerCert::public_key:der_encoded()} | unknown_ca.
</code>
<p>Claim an intermediate CA in the chain as trusted. TLS then
performs <seemfa
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index a37a72efdc..899e7d3305 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -479,10 +479,7 @@ wait_cert_verify(info, Event, State) ->
wait_cert_verify(state_timeout, Event, State) ->
handle_state_timeout(Event, ?FUNCTION_NAME, State);
wait_cert_verify(Type, Event, State) ->
- try tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State)
- catch throw:#alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State)
- end.
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec cipher(gen_statem:event_type(), term(), #state{}) ->
@@ -506,7 +503,7 @@ cipher(internal = Type, #finished{} = Event, #state{connection_states = Connecti
cipher(state_timeout, Event, State) ->
handle_state_timeout(Event, ?FUNCTION_NAME, State);
cipher(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, Type, Event, State).
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec connection(gen_statem:event_type(),
@@ -761,6 +758,8 @@ alert_or_reset_connection(Alert, StateName, #state{connection_states = Cs} = Sta
{next_state, connection, NewState}
end.
+gen_handshake(_, {call, _From}, {application_data, _Data}, _State) ->
+ {keep_state_and_data, [postpone]};
gen_handshake(StateName, Type, Event, State) ->
try tls_dtls_connection:StateName(Type, Event, State)
catch
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 6e83e16e1e..c96173e98b 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -1703,18 +1703,22 @@ validate_versions(dtls, Vsns0) ->
opt_verification(UserOpts, Opts0, #{role := Role} = Env) ->
{Verify, Opts1} =
case get_opt_of(verify, [verify_none, verify_peer], default_verify(Role), UserOpts, Opts0) of
+ {old, Val} ->
+ {Val, Opts0};
{_, verify_none} ->
{verify_none, Opts0#{verify => verify_none, verify_fun => {none_verify_fun(), []}}};
{_, verify_peer} ->
%% If 'verify' is changed from verify_none to verify_peer, (via update_options/3)
%% the 'verify_fun' must also be changed to undefined.
%% i.e remove verify_none fun
- {verify_peer, Opts0#{verify => verify_peer, verify_fun => undefined}}
+ Temp = Opts0#{verify => verify_peer, verify_fun => undefined},
+ {verify_peer, maps:remove(fail_if_no_peer_cert, Temp)}
end,
Opts2 = opt_cacerts(UserOpts, Opts1, Env),
{_, PartialChain} = get_opt_fun(partial_chain, 1, fun(_) -> unknown_ca end, UserOpts, Opts2),
- {_, FailNoPeerCert} = get_opt_bool(fail_if_no_peer_cert, false, UserOpts, Opts2),
+ DefFailNoPeer = Role =:= server andalso Verify =:= verify_peer,
+ {_, FailNoPeerCert} = get_opt_bool(fail_if_no_peer_cert, DefFailNoPeer, UserOpts, Opts2),
assert_server_only(Role, FailNoPeerCert, fail_if_no_peer_cert),
option_incompatible(FailNoPeerCert andalso Verify =:= verify_none,
[{verify, verify_none}, {fail_if_no_peer_cert, true}]),
diff --git a/lib/ssl/test/openssl_alpn_SUITE.erl b/lib/ssl/test/openssl_alpn_SUITE.erl
index bb33165bcf..1d0bc82c4e 100644
--- a/lib/ssl/test/openssl_alpn_SUITE.erl
+++ b/lib/ssl/test/openssl_alpn_SUITE.erl
@@ -194,7 +194,7 @@ erlang_client_alpn_openssl_server_alpn(Config) when is_list(Config) ->
erlang_server_alpn_openssl_client_alpn(Config) when is_list(Config) ->
ClientOpts = proplists:get_value(client_rsa_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
Protocol = <<"spdy/2">>,
Server = ssl_test_lib:start_server(erlang, [{from, self()}],
[{server_opts, [{alpn_preferred_protocols,
diff --git a/lib/ssl/test/openssl_renegotiate_SUITE.erl b/lib/ssl/test/openssl_renegotiate_SUITE.erl
index 01b5a7a8a9..5102730396 100644
--- a/lib/ssl/test/openssl_renegotiate_SUITE.erl
+++ b/lib/ssl/test/openssl_renegotiate_SUITE.erl
@@ -230,7 +230,7 @@ erlang_server_openssl_client_nowrap_seqnum() ->
erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) ->
process_flag(trap_exit, true),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
{_, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl
index a3bb2f6bdc..215097bd4d 100644
--- a/lib/ssl/test/ssl_api_SUITE.erl
+++ b/lib/ssl/test/ssl_api_SUITE.erl
@@ -2699,18 +2699,18 @@ options_verify(Config) -> %% fail_if_no_peer_cert, verify, verify_fun, partial_
?OK(#{fail_if_no_peer_cert := true, verify := verify_peer, verify_fun := undefined, partial_chain := _},
[{fail_if_no_peer_cert, true}, {verify, verify_peer}, {cacerts, [Cert]}],
server),
- ?OK(#{fail_if_no_peer_cert := false, verify := verify_peer, verify_fun := undefined, partial_chain := _},
- [{verify, verify_peer}, {cacerts, [Cert]}], server),
+ ?OK(#{fail_if_no_peer_cert := true, verify := verify_peer, verify_fun := undefined, partial_chain := _},
+ [{verify, verify_peer}, {cacerts, [Cert]}], server),
NewF3 = fun(_,_,_) -> ok end,
NewF4 = fun(_,_,_,_) -> ok end,
?OK(#{}, [], client, [fail_if_no_peer_cert]),
?OK(#{verify := verify_none, verify_fun := {NewF3, foo}, partial_chain := _},
[{verify_fun, {NewF3, foo}}], client),
- ?OK(#{fail_if_no_peer_cert := false, verify := verify_peer, verify_fun := {NewF3, foo}, partial_chain := _},
+ ?OK(#{fail_if_no_peer_cert := true, verify := verify_peer, verify_fun := {NewF3, foo}, partial_chain := _},
[{verify_fun, {NewF3, foo}}, {verify, verify_peer}, {cacerts, [Cert]}],
server),
- ?OK(#{fail_if_no_peer_cert := false, verify := verify_peer, verify_fun := {NewF4, foo}, partial_chain := _},
+ ?OK(#{fail_if_no_peer_cert := true, verify := verify_peer, verify_fun := {NewF4, foo}, partial_chain := _},
[{verify_fun, {NewF4, foo}}, {verify, verify_peer}, {cacerts, [Cert]}],
server),
diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl
index 01235a1d74..64ee3c9d0d 100644
--- a/lib/ssl/test/ssl_test_lib.erl
+++ b/lib/ssl/test/ssl_test_lib.erl
@@ -973,15 +973,19 @@ openssl_server_loop(Pid, SslPort, Args) ->
start_openssl_client(Args0, Config) ->
{ClientNode, _, Hostname} = run_where(Config),
- ClientOpts = get_client_opts(Config),
+
+ %% io:format("~p:~p: ~p~n",[?MODULE, ?LINE, Args0]),
+ %% io:format("~p:~p: ~p~n",[?MODULE, ?LINE, Config]),
+
+ ClientOpts0 = get_client_opts(Config),
+ ClientOpts = proplists:get_value(options, Args0, []) ++ ClientOpts0,
DefaultVersions = default_tls_version(ClientOpts),
[Version | _] = proplists:get_value(versions, ClientOpts, DefaultVersions),
Node = proplists:get_value(node, Args0, ClientNode),
- Args = [{from, self()},
- {host, Hostname},
- {options, ClientOpts} | Args0],
+ Args = [{from, self()}, {host, Hostname} | ClientOpts ++ Args0],
- Result = spawn_link(Node, ?MODULE, init_openssl_client, [[{version, Version} | lists:delete(return_port, Args)]]),
+ Result = spawn_link(Node, ?MODULE, init_openssl_client,
+ [[{version, Version} | lists:delete(return_port, Args)]]),
receive
{connected, OpenSSLPort} ->
case lists:member(return_port, Args) of
@@ -1636,9 +1640,7 @@ make_ec_cert_chains(UserConf, ClientChainType, ServerChainType, Config, Curve) -
[{server_config, ServerConf},
{client_config, ClientConf}] =
x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
- {[{verify, verify_peer} | ClientConf],
- [{reuseaddr, true}, {verify, verify_peer} | ServerConf]
- }.
+ {ClientConf, [{reuseaddr, true} | ServerConf]}.
default_cert_chain_conf() ->
%% Use only default options
@@ -1654,8 +1656,8 @@ make_rsa_pss_pem(Alg, _UserConf, Config, Suffix) ->
Conf = x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
CConf = proplists:get_value(client_config, Conf),
SConf = proplists:get_value(server_config, Conf),
- #{server_config => [{verify, verify_peer} | SConf],
- client_config => [{verify, verify_peer} | CConf]}.
+ #{server_config => SConf,
+ client_config => CConf}.
make_rsa_sni_configs() ->
Sha = appropriate_sha(crypto:supports()),
@@ -2032,7 +2034,7 @@ make_rsa_ecdsa_cert(Config, Curve) ->
[{server_rsa_ecdsa_opts, [{reuseaddr, true} | ServerConf]},
{server_rsa_ecdsa_verify_opts, [{ssl_imp, new},{reuseaddr, true},
{verify, verify_peer} | ServerConf]},
- {client_rsa_ecdsa_opts, [{verify, cerify_none} | ClientConf]} | Config];
+ {client_rsa_ecdsa_opts, [{verify, verify_none} | ClientConf]} | Config];
_ ->
Config
end.
@@ -2449,7 +2451,7 @@ start_server(erlang, _, ServerOpts, Config) ->
{mfa, {ssl_test_lib,
check_key_exchange_send_active,
[KeyEx]}},
- {options, [{verify, verify_peer} | ServerOpts]}]),
+ {options, ServerOpts}]),
{Server, inet_port(Server)}.
sig_algs(undefined) ->
diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk
index 7b821e2bc8..db6de41e50 100644
--- a/lib/ssl/vsn.mk
+++ b/lib/ssl/vsn.mk
@@ -1 +1 @@
-SSL_VSN = 10.9
+SSL_VSN = 10.9.1
diff --git a/lib/stdlib/doc/src/gb_sets.xml b/lib/stdlib/doc/src/gb_sets.xml
index 3477c2c90e..283c3f9198 100644
--- a/lib/stdlib/doc/src/gb_sets.xml
+++ b/lib/stdlib/doc/src/gb_sets.xml
@@ -68,48 +68,10 @@
<section>
<title>Compatibility</title>
- <p>The following functions in this module also exist and provides
- the same functionality in the
- <seeerl marker="sets"><c>sets(3)</c></seeerl> and
- <seeerl marker="ordsets"><c>ordsets(3)</c></seeerl>
- modules. That is, by only changing the module name for each call,
- you can try out different set representations.</p>
- <list type="bulleted">
- <item><seemfa marker="#add_element/2"><c>add_element/2</c></seemfa>
- </item>
- <item><seemfa marker="#del_element/2"><c>del_element/2</c></seemfa>
- </item>
- <item><seemfa marker="#filter/2"><c>filter/2</c></seemfa>
- </item>
- <item><seemfa marker="#fold/3"><c>fold/3</c></seemfa>
- </item>
- <item><seemfa marker="#from_list/1"><c>from_list/1</c></seemfa>
- </item>
- <item><seemfa marker="#intersection/1"><c>intersection/1</c></seemfa>
- </item>
- <item><seemfa marker="#intersection/2"><c>intersection/2</c></seemfa>
- </item>
- <item><seemfa marker="#is_element/2"><c>is_element/2</c></seemfa>
- </item>
- <item><seemfa marker="#is_empty/1"><c>is_empty/1</c></seemfa>
- </item>
- <item><seemfa marker="#is_set/1"><c>is_set/1</c></seemfa>
- </item>
- <item><seemfa marker="#is_subset/2"><c>is_subset/2</c></seemfa>
- </item>
- <item><seemfa marker="#new/0"><c>new/0</c></seemfa>
- </item>
- <item><seemfa marker="#size/1"><c>size/1</c></seemfa>
- </item>
- <item><seemfa marker="#subtract/2"><c>subtract/2</c></seemfa>
- </item>
- <item><seemfa marker="#to_list/1"><c>to_list/1</c></seemfa>
- </item>
- <item><seemfa marker="#union/1"><c>union/1</c></seemfa>
- </item>
- <item><seemfa marker="#union/2"><c>union/2</c></seemfa>
- </item>
- </list>
+ <p>See the <seeerl marker="sets#compatibility">Compatibility Section
+ in the <c>sets(3)</c> module</seeerl> for information about
+ the compatibility of the different implementations of sets in the
+ Standard Library.</p>
</section>
<datatypes>
diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml
index 403abf2be8..0906db1359 100644
--- a/lib/stdlib/doc/src/notes.xml
+++ b/lib/stdlib/doc/src/notes.xml
@@ -31,6 +31,22 @@
</header>
<p>This document describes the changes made to the STDLIB application.</p>
+<section><title>STDLIB 4.3.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>The type specs in the <c>erl_parse</c> module has been
+ updated to include the <c>maybe</c> construct and the
+ <c>!</c> operator.</p>
+ <p>
+ Own Id: OTP-18506 Aux Id: GH-6956 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>STDLIB 4.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/stdlib/doc/src/ordsets.xml b/lib/stdlib/doc/src/ordsets.xml
index 35127dcf95..7b02d13ab3 100644
--- a/lib/stdlib/doc/src/ordsets.xml
+++ b/lib/stdlib/doc/src/ordsets.xml
@@ -48,6 +48,11 @@
that while <c>sets</c> considers two elements as different if they
do not match (<c>=:=</c>), this module considers two elements as
different if and only if they do not compare equal (<c>==</c>).</p>
+
+ <p>See the <seeerl marker="sets#compatibility">Compatibility Section
+ in the <c>sets(3)</c> module</seeerl> for more information about
+ the compatibility of the different implementations of sets in the
+ Standard Library.</p>
</description>
<datatypes>
diff --git a/lib/stdlib/doc/src/sets.xml b/lib/stdlib/doc/src/sets.xml
index 53b64a3ac0..b3899c440f 100644
--- a/lib/stdlib/doc/src/sets.xml
+++ b/lib/stdlib/doc/src/sets.xml
@@ -69,6 +69,71 @@
</description>
+ <section>
+ <title>Compatibility</title>
+ <p>The following functions in this module also exist and provide
+ the same functionality in the
+ <seeerl marker="gb_sets"><c>gb_sets(3)</c></seeerl> and
+ <seeerl marker="ordsets"><c>ordsets(3)</c></seeerl>
+ modules. That is, by only changing the module name for each call,
+ you can try out different set representations.</p>
+ <list type="bulleted">
+ <item><seemfa marker="#add_element/2"><c>add_element/2</c></seemfa>
+ </item>
+ <item><seemfa marker="#del_element/2"><c>del_element/2</c></seemfa>
+ </item>
+ <item><seemfa marker="#filter/2"><c>filter/2</c></seemfa>
+ </item>
+ <item><seemfa marker="#fold/3"><c>fold/3</c></seemfa>
+ </item>
+ <item><seemfa marker="#from_list/1"><c>from_list/1</c></seemfa>
+ </item>
+ <item><seemfa marker="#intersection/1"><c>intersection/1</c></seemfa>
+ </item>
+ <item><seemfa marker="#intersection/2"><c>intersection/2</c></seemfa>
+ </item>
+ <item><seemfa marker="#is_element/2"><c>is_element/2</c></seemfa>
+ </item>
+ <item><seemfa marker="#is_empty/1"><c>is_empty/1</c></seemfa>
+ </item>
+ <item><seemfa marker="#is_set/1"><c>is_set/1</c></seemfa>
+ </item>
+ <item><seemfa marker="#is_subset/2"><c>is_subset/2</c></seemfa>
+ </item>
+ <item><seemfa marker="#new/0"><c>new/0</c></seemfa>
+ </item>
+ <item><seemfa marker="#size/1"><c>size/1</c></seemfa>
+ </item>
+ <item><seemfa marker="#subtract/2"><c>subtract/2</c></seemfa>
+ </item>
+ <item><seemfa marker="#to_list/1"><c>to_list/1</c></seemfa>
+ </item>
+ <item><seemfa marker="#union/1"><c>union/1</c></seemfa>
+ </item>
+ <item><seemfa marker="#union/2"><c>union/2</c></seemfa>
+ </item>
+ </list>
+ <note>
+ <p>
+ While the three set implementations offer the same <em>functionality</em>
+ with respect to the aforementioned functions, their overall <em>behavior</em>
+ may differ. As mentioned, this module considers elements as different if
+ and only if they do not match (<c>=:=</c>), while both
+ <seeerl marker="ordsets"><c>ordsets</c></seeerl> and
+ <seeerl marker="gb_sets"><c>gb_sets</c></seeerl> consider elements as
+ different if and only if they do not compare equal (<c>==</c>).
+ </p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>sets:is_element(1.0, sets:from_list([1])).</input>
+false
+2> <input>ordsets:is_element(1.0, ordsets:from_list([1])).</input>
+true
+2> <input>gb_sets:is_element(1.0, gb_sets:from_list([1])).</input>
+true</pre>
+ </note>
+ </section>
+
<datatypes>
<datatype>
<name name="set" n_vars="1"/>
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index a4833f940b..55e53cad3d 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -669,6 +669,9 @@ start(File, Opts) ->
false, Opts)},
{redefined_builtin_type,
bool_option(warn_redefined_builtin_type, nowarn_redefined_builtin_type,
+ true, Opts)},
+ {singleton_typevar,
+ bool_option(warn_singleton_typevar, nowarn_singleton_typevar,
true, Opts)}
],
Enabled1 = [Category || {Category,true} <- Enabled0],
@@ -2931,6 +2934,16 @@ check_type(Types, St) ->
"_"++_ -> AccSt;
_ -> add_error(Anno, {singleton_typevar, Var}, AccSt)
end;
+ (Var, {seen_once_union, Anno}, AccSt) ->
+ case is_warn_enabled(singleton_typevar, AccSt) of
+ true ->
+ case atom_to_list(Var) of
+ "_"++_ -> AccSt;
+ _ -> add_warning(Anno, {singleton_typevar, Var}, AccSt)
+ end;
+ false ->
+ AccSt
+ end;
(_Var, seen_multiple, AccSt) ->
AccSt
end, St1, SeenVars).
@@ -2971,6 +2984,7 @@ check_type_2({var, A, Name}, SeenVars, St) ->
NewSeenVars =
case maps:find(Name, SeenVars) of
{ok, {seen_once, _}} -> maps:put(Name, seen_multiple, SeenVars);
+ {ok, {seen_once_union, _}} -> maps:put(Name, seen_multiple, SeenVars);
{ok, seen_multiple} -> SeenVars;
error -> maps:put(Name, {seen_once, A}, SeenVars)
end,
@@ -3022,16 +3036,30 @@ check_type_2({type, _A, Tag, Args}=_F, SeenVars, St) when Tag =:= product;
lists:foldl(fun(T, {AccSeenVars, AccSt}) ->
check_type_1(T, AccSeenVars, AccSt)
end, {SeenVars, St}, Args);
-check_type_2({type, _A, union, Args}=_F, SeenVars, St) ->
- lists:foldl(fun(T, {AccSeenVars, AccSt}) ->
- {SeenVars0, St0} = check_type_1(T, SeenVars, AccSt),
- UpdatedSeenVars = maps:merge_with(fun (_K, {seen_once, _}, {seen_once, _}=R) -> R;
- (_K, {seen_once, _}, Else) -> Else;
- (_K, Else, {seen_once, _}) -> Else;
- (_K, Else1, _Else2) -> Else1
- end, SeenVars0, AccSeenVars),
- {UpdatedSeenVars, St0}
- end, {SeenVars, St}, Args);
+check_type_2({type, _A, union, Args}=_F, SeenVars0, St) ->
+ lists:foldl(fun(T, {AccSeenVars0, AccSt}) ->
+ {SeenVars1, St0} = check_type_1(T, SeenVars0, AccSt),
+ AccSeenVars = maps:merge_with(
+ fun (K, {seen_once, Anno}, {seen_once, _}) ->
+ case SeenVars0 of
+ #{K := _} ->
+ %% Unused outside of this union.
+ {seen_once, Anno};
+ #{} ->
+ {seen_once_union, Anno}
+ end;
+ (_K, {seen_once, Anno}, {seen_once_union, _}) ->
+ {seen_once_union, Anno};
+ (_K, {seen_once_union, _}=R, {seen_once, _}) -> R;
+ (_K, {seen_once_union, _}=R, {seen_once_union, _}) -> R;
+ (_K, {seen_once_union, _}, Else) -> Else;
+ (_K, {seen_once, _}, Else) -> Else;
+ (_K, Else, {seen_once_union, _}) -> Else;
+ (_K, Else, {seen_once, _}) -> Else;
+ (_K, Else1, _Else2) -> Else1
+ end, AccSeenVars0, SeenVars1),
+ {AccSeenVars, St0}
+ end, {SeenVars0, St}, Args);
check_type_2({type, Anno, TypeName, Args}, SeenVars, St) ->
#lint{module = Module, types=Types} = St,
Arity = length(Args),
diff --git a/lib/stdlib/src/sets.erl b/lib/stdlib/src/sets.erl
index 816fb85a48..dccc6dcf3a 100644
--- a/lib/stdlib/src/sets.erl
+++ b/lib/stdlib/src/sets.erl
@@ -466,23 +466,12 @@ fold_1(Fun, Acc, Iter) ->
Set1 :: set(Element),
Set2 :: set(Element).
filter(F, #{}=D) when is_function(F, 1)->
- maps:from_keys(filter_1(F, maps:iterator(D)), ?VALUE);
+ %% For this purpose, it is more efficient to use
+ %% maps:from_keys than a map comprehension.
+ maps:from_keys([K || K := _ <- D, F(K)], ?VALUE);
filter(F, #set{}=D) when is_function(F, 1)->
filter_set(F, D).
-filter_1(Fun, Iter) ->
- case maps:next(Iter) of
- {K, _, NextIter} ->
- case Fun(K) of
- true ->
- [K | filter_1(Fun, NextIter)];
- false ->
- filter_1(Fun, NextIter)
- end;
- none ->
- []
- end.
-
%% get_slot(Hashdb, Key) -> Slot.
%% Get the slot. First hash on the new range, if we hit a bucket
%% which has not been split use the unsplit buddy bucket.
diff --git a/lib/stdlib/src/stdlib.appup.src b/lib/stdlib/src/stdlib.appup.src
index 0cb84fd3dc..13efc74d85 100644
--- a/lib/stdlib/src/stdlib.appup.src
+++ b/lib/stdlib/src/stdlib.appup.src
@@ -54,7 +54,9 @@
{<<"^4\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^4\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^4\\.2$">>,[restart_new_emulator]},
- {<<"^4\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}],
+ {<<"^4\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^4\\.3$">>,[restart_new_emulator]},
+ {<<"^4\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}],
[{<<"^3\\.13$">>,[restart_new_emulator]},
{<<"^3\\.13\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.13\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
@@ -81,4 +83,6 @@
{<<"^4\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^4\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^4\\.2$">>,[restart_new_emulator]},
- {<<"^4\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}]}.
+ {<<"^4\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^4\\.3$">>,[restart_new_emulator]},
+ {<<"^4\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}]}.
diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl
index 834eb23335..81bc3e9a0d 100644
--- a/lib/stdlib/test/erl_lint_SUITE.erl
+++ b/lib/stdlib/test/erl_lint_SUITE.erl
@@ -907,105 +907,121 @@ unused_import(Config) when is_list(Config) ->
%% Test singleton type variables
singleton_type_var_errors(Config) when is_list(Config) ->
- Ts = [ {singleton_error1
- , <<"-spec test_singleton_typevars_in_union(Opts) -> term() when
+ Ts = [{singleton_error1,
+ <<"-spec test_singleton_typevars_in_union(Opts) -> term() when
Opts :: {ok, Unknown} | {error, Unknown}.
test_singleton_typevars_in_union(_) ->
error.
- ">>
- , []
- , { errors
- , [{{2,36},erl_lint,{singleton_typevar,'Unknown'}}]
- , []
- }
- }
- , { singleton_error2
- , <<"-spec test_singleton_list_typevars_in_union([Opts]) -> term() when
+ ">>,
+ [],
+ {warnings,[{{2,36},erl_lint,{singleton_typevar,'Unknown'}}]}},
+
+ {singleton_error2,
+ <<"-spec test_singleton_list_typevars_in_union([Opts]) -> term() when
Opts :: {ok, Unknown} | {error, Unknown}.
test_singleton_list_typevars_in_union(_) ->
- error.">>
- , []
- , { errors
- , [{{2,36},erl_lint,{singleton_typevar,'Unknown'}}]
- , []
- }
- }
- , { singleton_error3
- , <<"-spec test_singleton_list_typevars_in_list([Opts]) -> term() when
+ error.">>,
+ [],
+ {warnings,[{{2,36},erl_lint,{singleton_typevar,'Unknown'}}]}},
+
+ {singleton_error3,
+ <<"-spec test_singleton_list_typevars_in_list([Opts]) -> term() when
Opts :: {ok, Unknown}.
test_singleton_list_typevars_in_list(_) ->
- error.">>
- , []
- , { errors
- , [{{2,36},erl_lint,{singleton_typevar,'Unknown'}}]
- , []
- }
- }
- , { singleton_error4
- , <<"-spec test_singleton_list_typevars_in_list_with_type_subst([{ok, Unknown}]) -> term().
+ error.">>,
+ [],
+ {errors,
+ [{{2,36},erl_lint,{singleton_typevar,'Unknown'}}],[]}},
+
+ {singleton_error4,
+ <<"-spec test_singleton_list_typevars_in_list_with_type_subst([{ok, Unknown}]) -> term().
test_singleton_list_typevars_in_list_with_type_subst(_) ->
- error.">>
- , []
- , { errors
- , [{{1,86},erl_lint,{singleton_typevar,'Unknown'}}]
- , []
- }
- }
- , { singleton_error5
- , <<"-spec test_singleton_buried_typevars_in_union(Opts) -> term() when
+ error.">>,
+ [],
+ {errors,[{{1,86},erl_lint,{singleton_typevar,'Unknown'}}],[]}},
+
+ {singleton_error5,
+ <<"-spec test_singleton_buried_typevars_in_union(Opts) -> term() when
Opts :: {ok, Foo} | {error, Foo},
Foo :: {true, X} | {false, X}.
test_singleton_buried_typevars_in_union(_) ->
- error.">>
- , []
- , { errors
- , [{{3,38},erl_lint,{singleton_typevar,'X'}}]
- , []
- }
- }
- , { singleton_error6
- , <<"-spec test_multiple_subtypes_to_same_typevar(Opts) -> term() when
+ error.">>,
+ [],
+ {warnings,[{{3,38},erl_lint,{singleton_typevar,'X'}}]}},
+
+ {singleton_error6,
+ <<"-spec test_multiple_subtypes_to_same_typevar(Opts) -> term() when
Opts :: {Foo, Bar} | Y,
Foo :: X,
Bar :: X,
Y :: Z.
test_multiple_subtypes_to_same_typevar(_) ->
- error.">>
- , []
- , { errors
- , [{{5,31},erl_lint,{singleton_typevar,'Z'}}]
- , []
- }
- }
- , { singleton_error7
- , <<"-spec test_duplicate_non_terminal_var_in_union(Opts) -> term() when
+ error.">>,
+ [],
+ {errors,[{{5,31},erl_lint,{singleton_typevar,'Z'}}],[]}},
+
+ {singleton_error7,
+ <<"-spec test_duplicate_non_terminal_var_in_union(Opts) -> term() when
Opts :: {ok, U, U} | {error, U, U},
U :: Foo.
test_duplicate_non_terminal_var_in_union(_) ->
- error.">>
- , []
- , { errors
- , [{{3,31},erl_lint,{singleton_typevar,'Foo'}}]
- , []
- }
- }
- , { singleton_ok1
- , <<"-spec test_multiple_occurrences_singleton(Opts) -> term() when
+ error.">>,
+ [],
+ {errors,[{{3,31},erl_lint,{singleton_typevar,'Foo'}}],[]}},
+
+ {singleton_error8,
+ <<"-spec test_unused_outside_union(Opts) -> term() when
+ Unused :: Unknown,
+ A :: Unknown,
+ Opts :: {Unknown | A}.
+ test_unused_outside_union(_) ->
+ error.">>,
+ [],
+ {errors,[{{2,21},erl_lint,{singleton_typevar,'Unused'}}],[]}},
+
+ {singleton_disabled_warning,
+ <<"-spec test_singleton_typevars_in_union(Opts) -> term() when
+ Opts :: {ok, Unknown} | {error, Unknown}.
+ test_singleton_typevars_in_union(_) ->
+ error.
+ ">>,
+ [nowarn_singleton_typevar],
+ []},
+
+ {singleton_ok1,
+ <<"-spec test_multiple_occurrences_singleton(Opts) -> term() when
Opts :: {Foo, Foo}.
test_multiple_occurrences_singleton(_) ->
- ok.">>
- , []
- , []
- }
- , { singleton_ok2
- , <<"-spec id(X) -> X.
+ ok.">>,
+ [],
+ []},
+
+ {singleton_ok2,
+ <<"-spec id(X) -> X.
id(X) ->
- X.">>
- , []
- , []
- }
+ X.">>,
+ [],
+ []},
+
+ {singleton_ok3,
+ <<"-spec ok(Opts) -> term() when
+ Opts :: {Unknown, {ok, Unknown} | {error, Unknown}}.
+ ok(_) ->
+ error.">>,
+ [],
+ []},
+
+ {singleton_ok4,
+ <<"-spec ok(Opts) -> term() when
+ Union :: {ok, Unknown} | {error, Unknown},
+ Opts :: {{tag, Unknown} | Union}.
+ ok(_) ->
+ error.">>,
+ [],
+ []}
+
+ ],
- ],
[] = run(Config, Ts),
ok.
diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk
index cd81d52182..d6706e69bf 100644
--- a/lib/stdlib/vsn.mk
+++ b/lib/stdlib/vsn.mk
@@ -1 +1 @@
-STDLIB_VSN = 4.3
+STDLIB_VSN = 4.3.1
diff --git a/lib/syntax_tools/src/epp_dodger.erl b/lib/syntax_tools/src/epp_dodger.erl
index 2e0694c2cf..40f67b5660 100644
--- a/lib/syntax_tools/src/epp_dodger.erl
+++ b/lib/syntax_tools/src/epp_dodger.erl
@@ -517,6 +517,8 @@ quickscan_form([{'-', _Anno}, {'else', AnnoA} | _Ts]) ->
kill_form(AnnoA);
quickscan_form([{'-', _Anno}, {atom, AnnoA, endif} | _Ts]) ->
kill_form(AnnoA);
+quickscan_form([{'-', _Anno}, {atom, AnnoA, feature} | _Ts]) ->
+ kill_form(AnnoA);
quickscan_form([{'-', Anno}, {'?', _}, {Type, _, _}=N | [{'(', _} | _]=Ts])
when Type =:= atom; Type =:= var ->
%% minus, macro and open parenthesis at start of form - assume that
diff --git a/lib/wx/api_gen/gl_gen.erl b/lib/wx/api_gen/gl_gen.erl
index 641f21eccd..eef715b83a 100644
--- a/lib/wx/api_gen/gl_gen.erl
+++ b/lib/wx/api_gen/gl_gen.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. 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.
diff --git a/lib/wx/api_gen/gl_gen_erl.erl b/lib/wx/api_gen/gl_gen_erl.erl
index 7c9f97867c..06fc6c9e58 100644
--- a/lib/wx/api_gen/gl_gen_erl.erl
+++ b/lib/wx/api_gen/gl_gen_erl.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. 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.
diff --git a/lib/wx/api_gen/gl_gen_nif.erl b/lib/wx/api_gen/gl_gen_nif.erl
index b8a6be3908..a84bf454a5 100644
--- a/lib/wx/api_gen/gl_gen_nif.erl
+++ b/lib/wx/api_gen/gl_gen_nif.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. 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.
diff --git a/lib/wx/api_gen/wx_gen_nif.erl b/lib/wx/api_gen/wx_gen_nif.erl
index fa2fd52fe4..e768f483fe 100644
--- a/lib/wx/api_gen/wx_gen_nif.erl
+++ b/lib/wx/api_gen/wx_gen_nif.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. 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.
diff --git a/lib/wx/api_gen/wxapi.conf b/lib/wx/api_gen/wxapi.conf
index 31bb522bc0..be9529d40c 100644
--- a/lib/wx/api_gen/wxapi.conf
+++ b/lib/wx/api_gen/wxapi.conf
@@ -2,7 +2,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. 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.
diff --git a/lib/wx/c_src/egl_impl.c b/lib/wx/c_src/egl_impl.c
index 84e1601a7f..868b32ab79 100644
--- a/lib/wx/c_src/egl_impl.c
+++ b/lib/wx/c_src/egl_impl.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2011-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2011-2023. 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.
diff --git a/lib/wx/c_src/wxe_gl.cpp b/lib/wx/c_src/wxe_gl.cpp
index 7ad6abb659..e2327cfb54 100644
--- a/lib/wx/c_src/wxe_gl.cpp
+++ b/lib/wx/c_src/wxe_gl.cpp
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2008-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2008-2023. 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.
diff --git a/lib/wx/c_src/wxe_gl.h b/lib/wx/c_src/wxe_gl.h
index 612432c22f..12aea9bb8c 100644
--- a/lib/wx/c_src/wxe_gl.h
+++ b/lib/wx/c_src/wxe_gl.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2008-2021. All Rights Reserved.
+ * Copyright Ericsson AB 2008-2023. 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.
diff --git a/lib/wx/c_src/wxe_impl.cpp b/lib/wx/c_src/wxe_impl.cpp
index 52e9757d2e..5ea1371524 100644
--- a/lib/wx/c_src/wxe_impl.cpp
+++ b/lib/wx/c_src/wxe_impl.cpp
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2008-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2008-2023. 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.
diff --git a/lib/wx/c_src/wxe_impl.h b/lib/wx/c_src/wxe_impl.h
index 45e80a77d1..a99c30a254 100644
--- a/lib/wx/c_src/wxe_impl.h
+++ b/lib/wx/c_src/wxe_impl.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2008-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2008-2023. 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.
diff --git a/lib/wx/c_src/wxe_nif.c b/lib/wx/c_src/wxe_nif.c
index 6d9ed300ac..a7d3fea885 100644
--- a/lib/wx/c_src/wxe_nif.c
+++ b/lib/wx/c_src/wxe_nif.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2017-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2017-2023. 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.
diff --git a/lib/wx/c_src/wxe_return.cpp b/lib/wx/c_src/wxe_return.cpp
index 621e39f16b..8728dafcf9 100644
--- a/lib/wx/c_src/wxe_return.cpp
+++ b/lib/wx/c_src/wxe_return.cpp
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2008-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2008-2023. 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.
diff --git a/lib/wx/doc/src/gl.xml b/lib/wx/doc/src/gl.xml
index 865ce91bb9..1f35f8fa89 100644
--- a/lib/wx/doc/src/gl.xml
+++ b/lib/wx/doc/src/gl.xml
@@ -7,7 +7,7 @@
<erlref>
<header>
<copyright>
- <year>2020</year>
+ <year>2020</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder></copyright>
<legalnotice>
Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/lib/wx/doc/src/notes.xml b/lib/wx/doc/src/notes.xml
index f155b7a551..08e339e097 100644
--- a/lib/wx/doc/src/notes.xml
+++ b/lib/wx/doc/src/notes.xml
@@ -32,6 +32,22 @@
<p>This document describes the changes made to the wxErlang
application.</p>
+<section><title>Wx 2.2.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Improve debug prints from the nifs. Some minor fixes for
+ wxWidgets-3.2. Fixed OpenGL debug functions.</p>
+ <p>
+ Own Id: OTP-18512</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Wx 2.2.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/wx/src/wxe_master.erl b/lib/wx/src/wxe_master.erl
index dd6eb4f159..f0bb1e64e7 100644
--- a/lib/wx/src/wxe_master.erl
+++ b/lib/wx/src/wxe_master.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. 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.
diff --git a/lib/wx/src/wxe_util.erl b/lib/wx/src/wxe_util.erl
index 4f123ffc09..217e7118ff 100644
--- a/lib/wx/src/wxe_util.erl
+++ b/lib/wx/src/wxe_util.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. 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.
diff --git a/lib/wx/test/wx_class_SUITE.erl b/lib/wx/test/wx_class_SUITE.erl
index 267ad4d336..5275defce6 100644
--- a/lib/wx/test/wx_class_SUITE.erl
+++ b/lib/wx/test/wx_class_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. 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.
diff --git a/lib/wx/test/wx_opengl_SUITE.erl b/lib/wx/test/wx_opengl_SUITE.erl
index fa4e456c42..fb3e5ba598 100644
--- a/lib/wx/test/wx_opengl_SUITE.erl
+++ b/lib/wx/test/wx_opengl_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. 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.
diff --git a/lib/wx/vsn.mk b/lib/wx/vsn.mk
index e7144bd6a2..78f0fd3395 100644
--- a/lib/wx/vsn.mk
+++ b/lib/wx/vsn.mk
@@ -1 +1 @@
-WX_VSN = 2.2.1
+WX_VSN = 2.2.2
diff --git a/otp_versions.table b/otp_versions.table
index 118b1462ed..9da37263b9 100644
--- a/otp_versions.table
+++ b/otp_versions.table
@@ -1,3 +1,5 @@
+OTP-25.3.2 : compiler-8.2.6 erts-13.2.2 os_mon-2.8.2 # asn1-5.0.21 common_test-1.24 crypto-5.1.4 debugger-5.3.1 dialyzer-5.0.5 diameter-2.2.7 edoc-1.2 eldap-1.2.11 erl_docgen-1.4 erl_interface-5.3.2 et-1.6.5 eunit-2.8.2 ftp-1.1.4 inets-8.3.1 jinterface-1.13.2 kernel-8.5.4 megaco-4.4.3 mnesia-4.21.4 observer-2.14 odbc-2.14 parsetools-2.4.1 public_key-1.13.3 reltool-0.9.1 runtime_tools-1.19 sasl-4.2 snmp-5.13.5 ssh-4.15.3 ssl-10.9.1 stdlib-4.3.1 syntax_tools-3.0.1 tftp-1.0.4 tools-3.5.3 wx-2.2.2 xmerl-1.3.31 :
+OTP-25.3.1 : compiler-8.2.5 crypto-5.1.4 eldap-1.2.11 erl_interface-5.3.2 erts-13.2.1 inets-8.3.1 snmp-5.13.5 ssl-10.9.1 stdlib-4.3.1 wx-2.2.2 # asn1-5.0.21 common_test-1.24 debugger-5.3.1 dialyzer-5.0.5 diameter-2.2.7 edoc-1.2 erl_docgen-1.4 et-1.6.5 eunit-2.8.2 ftp-1.1.4 jinterface-1.13.2 kernel-8.5.4 megaco-4.4.3 mnesia-4.21.4 observer-2.14 odbc-2.14 os_mon-2.8.1 parsetools-2.4.1 public_key-1.13.3 reltool-0.9.1 runtime_tools-1.19 sasl-4.2 ssh-4.15.3 syntax_tools-3.0.1 tftp-1.0.4 tools-3.5.3 xmerl-1.3.31 :
OTP-25.3 : common_test-1.24 compiler-8.2.4 crypto-5.1.3 debugger-5.3.1 dialyzer-5.0.5 erl_interface-5.3.1 erts-13.2 eunit-2.8.2 ftp-1.1.4 inets-8.3 jinterface-1.13.2 kernel-8.5.4 megaco-4.4.3 mnesia-4.21.4 os_mon-2.8.1 public_key-1.13.3 reltool-0.9.1 snmp-5.13.4 ssh-4.15.3 ssl-10.9 stdlib-4.3 syntax_tools-3.0.1 tftp-1.0.4 xmerl-1.3.31 # asn1-5.0.21 diameter-2.2.7 edoc-1.2 eldap-1.2.10 erl_docgen-1.4 et-1.6.5 observer-2.14 odbc-2.14 parsetools-2.4.1 runtime_tools-1.19 sasl-4.2 tools-3.5.3 wx-2.2.1 :
OTP-25.2.3 : erts-13.1.5 inets-8.2.2 ssh-4.15.2 ssl-10.8.7 # asn1-5.0.21 common_test-1.23.3 compiler-8.2.3 crypto-5.1.2 debugger-5.3 dialyzer-5.0.4 diameter-2.2.7 edoc-1.2 eldap-1.2.10 erl_docgen-1.4 erl_interface-5.3 et-1.6.5 eunit-2.8.1 ftp-1.1.3 jinterface-1.13.1 kernel-8.5.3 megaco-4.4.2 mnesia-4.21.3 observer-2.14 odbc-2.14 os_mon-2.8 parsetools-2.4.1 public_key-1.13.2 reltool-0.9 runtime_tools-1.19 sasl-4.2 snmp-5.13.3 stdlib-4.2 syntax_tools-3.0 tftp-1.0.3 tools-3.5.3 wx-2.2.1 xmerl-1.3.30 :
OTP-25.2.2 : ftp-1.1.3 # asn1-5.0.21 common_test-1.23.3 compiler-8.2.3 crypto-5.1.2 debugger-5.3 dialyzer-5.0.4 diameter-2.2.7 edoc-1.2 eldap-1.2.10 erl_docgen-1.4 erl_interface-5.3 erts-13.1.4 et-1.6.5 eunit-2.8.1 inets-8.2.1 jinterface-1.13.1 kernel-8.5.3 megaco-4.4.2 mnesia-4.21.3 observer-2.14 odbc-2.14 os_mon-2.8 parsetools-2.4.1 public_key-1.13.2 reltool-0.9 runtime_tools-1.19 sasl-4.2 snmp-5.13.3 ssh-4.15.1 ssl-10.8.6 stdlib-4.2 syntax_tools-3.0 tftp-1.0.3 tools-3.5.3 wx-2.2.1 xmerl-1.3.30 :
diff --git a/system/doc/general_info/upcoming_incompatibilities.xml b/system/doc/general_info/upcoming_incompatibilities.xml
index 04ab4459c0..8538067b57 100644
--- a/system/doc/general_info/upcoming_incompatibilities.xml
+++ b/system/doc/general_info/upcoming_incompatibilities.xml
@@ -53,6 +53,7 @@
</section>
<section>
+ <marker id="maybe_expr"/>
<title>Feature maybe_expr will be enabled by default</title>
<p>
As of OTP 27, the <c>maybe_expr</c> feature will be approved
@@ -119,6 +120,65 @@
the regular expression before matching with it.</p></item>
</list>
</section>
+
+ <section>
+ <marker id="float_matching"/>
+ <title>0.0 and -0.0 will no longer be exactly equal</title>
+
+ <p>Currently, the floating point numbers <c>0.0</c> and <c>-0.0</c>
+ have distinct internal representations. That can be seen if they are
+ converted to binaries:</p>
+ <pre>
+1&gt; <input>&lt;&lt;0.0/float&gt;&gt;.</input>
+&lt;&lt;0,0,0,0,0,0,0,0&gt;&gt;
+2&gt; <input>&lt;&lt;-0.0/float&gt;>.</input>
+&lt;&lt;128,0,0,0,0,0,0,0>></pre>
+
+ <p>However, when they are matched against each other or compared
+ using the <c>=:=</c> operator, they are considered to be
+ equal. Thus, <c>0.0 =:= -0.0</c> currently returns
+ <c>true</c>.</p>
+
+ <p>In Erlang/OTP 27, <c>0.0 =:= -0.0</c> will return <c>false</c>, and matching
+ <c>0.0</c> against <c>-0.0</c> will fail. When used as map keys, <c>0.0</c> and
+ <c>-0.0</c> will be considered to be distinct.</p>
+
+ <p>The <c>==</c> operator will continue to return <c>true</c>
+ for <c>0.0 == -0.0</c>.</p>
+
+ <p>To help to find code that might need to be revised, in OTP 27
+ there will be a new compiler warning when matching against
+ <c>0.0</c> or comparing to that value using the <c>=:=</c>
+ operator. The warning can be suppressed by matching against
+ <c>+0.0</c> instead of <c>0.0</c>.</p>
+
+ <p>We plan to introduce the same warning in OTP 26.1, but by default it
+ will be disabled.</p>
+ </section>
+
+ <section>
+ <marker id="singleton_typevars"/>
+ <title>Singleton type variables will become a compile-time error</title>
+
+ <p>Before Erlang/OTP 26, the compiler would silenty accept the
+ following spec:</p>
+
+ <pre>
+-spec f(Opts) -> term() when
+ Opts :: {ok, Unknown} | {error, Unknown}.
+f(_) -> error.</pre>
+
+ <p>In OTP 26, the compiler emits a warning pointing out that the type variable
+ <c>Unknown</c> is unbound:</p>
+
+ <pre>
+t.erl:6:18: Warning: type variable 'Unknown' is only used once (is unbound)
+% 6| Opts :: {ok, Unknown} | {error, Unknown}.
+% | ^</pre>
+
+ <p>In OTP 27, that warning will become an error.</p>
+ </section>
+
</section>
<section>