diff options
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 Binary files differindex ba50f42a01..45f5ab71d9 100644 --- a/erts/preloaded/ebin/prim_socket.beam +++ b/erts/preloaded/ebin/prim_socket.beam 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, {invalid, 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, {invalid, 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> <input><<0.0/float>>.</input> +<<0,0,0,0,0,0,0,0>> +2> <input><<-0.0/float>>.</input> +<<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> |