diff options
-rw-r--r-- | bootstrap/lib/stdlib/ebin/erl_internal.beam | bin | 6732 -> 6732 bytes | |||
-rw-r--r-- | erts/doc/src/erlang.xml | 85 | ||||
-rw-r--r-- | erts/emulator/beam/atom.names | 1 | ||||
-rw-r--r-- | erts/emulator/beam/bif.tab | 2 | ||||
-rw-r--r-- | erts/emulator/beam/dist.c | 498 | ||||
-rw-r--r-- | erts/emulator/beam/erl_map.c | 134 | ||||
-rw-r--r-- | erts/emulator/beam/erl_map.h | 4 | ||||
-rw-r--r-- | erts/emulator/beam/erl_node_tables.c | 6 | ||||
-rw-r--r-- | erts/emulator/test/distribution_SUITE.erl | 292 | ||||
-rw-r--r-- | erts/preloaded/ebin/erlang.beam | bin | 100436 -> 100708 bytes | |||
-rw-r--r-- | erts/preloaded/src/erlang.erl | 34 | ||||
-rw-r--r-- | erts/preloaded/src/erts.app.src | 2 | ||||
-rw-r--r-- | lib/kernel/doc/src/net_kernel.xml | 219 | ||||
-rw-r--r-- | lib/kernel/src/kernel.app.src | 2 | ||||
-rw-r--r-- | lib/kernel/src/net_kernel.erl | 77 | ||||
-rw-r--r-- | lib/kernel/test/erl_distribution_SUITE.erl | 99 | ||||
-rw-r--r-- | lib/stdlib/src/erl_internal.erl | 1 | ||||
-rw-r--r-- | lib/stdlib/src/stdlib.app.src | 2 |
18 files changed, 1200 insertions, 258 deletions
diff --git a/bootstrap/lib/stdlib/ebin/erl_internal.beam b/bootstrap/lib/stdlib/ebin/erl_internal.beam Binary files differindex 9015ad8936..7f2a331f00 100644 --- a/bootstrap/lib/stdlib/ebin/erl_internal.beam +++ b/bootstrap/lib/stdlib/ebin/erl_internal.beam diff --git a/erts/doc/src/erlang.xml b/erts/doc/src/erlang.xml index 613d382396..a94c10bf22 100644 --- a/erts/doc/src/erlang.xml +++ b/erts/doc/src/erlang.xml @@ -3806,6 +3806,91 @@ RealSystem = system + MissedSystem</code> </func> <func> + <name name="nodes" arity="2" since="OTP @OTP-17934@"/> + <fsummary>All nodes of a certain type in the system.</fsummary> + <desc> + <p> + Returns a list of <c><anno>NodeInfo</anno></c> tuples. The first + element is the node name. Nodes to be included in the list are + determined by the first argument <c><anno>Arg</anno></c> in the same + way as for + <seealso marker="#nodes/1"><c>nodes(<anno>Arg</anno>)</c></seealso>. + The second element of <c><anno>NodeInfo</anno></c> tuples is a map + containing further information about the node identified by the + first element. The information present in this map is determined by + the <c><anno>InfoOpts</anno></c> map passed as the second argument. + Currently the following associations are allowed in the + <c><anno>InfoOpts</anno></c> map:</p> + <taglist> + <tag><c>connection_id => boolean()</c></tag> + <item><p> + If the value of the association equals <c>true</c>, the <c>Info</c> + map in the returned result will contain the key <c>connection_id</c> + associated with the value <c><anno>ConnectionId</anno></c>. If + <c><anno>ConnectionId</anno></c> equals <c>undefined</c>, the node + is not connected to the node which the caller is executing on, or + is the node which the caller is executing on. If + <c><anno>ConnectionId</anno></c> is an integer, the node is + currently connected to the node which the caller is executing on. + </p> + <p> + <marker id="connection_id"/> + The integer connection identifier value together with a node name + identifies a specific connection instance to the node with that + node name. The connection identifier value is node local. That is, + on the other node the connection identifier will <i>not</i> be the + same value. If a connection is taken down and then taken up again, + the connection identifier value will change for the connection to + that node. The amount of values for connection identifiers are + limited, so it is possible to see the same value for different + instances, but quite unlikely. It is undefined how the value + change between two consecutive connection instances. + </p></item> + <tag><c>node_type => boolean()</c></tag> + <item><p> + If the value of the association equals <c>true</c>, the <c>Info</c> + map in the returned result will contain the key <c>node_type</c> + associated with the value <c><anno>NodeTypeInfo</anno></c>. + Currently the following node types exist:</p> + <taglist> + <tag><c>visible</c></tag> + <item><p> + The node is connected to the node of the calling process + through an ordinary visible connection. That is, the node + name would appear in the result returned by + <seealso marker="#nodes/0"><c>nodes/0</c></seealso>. + </p></item> + <tag><c>hidden</c></tag> + <item><p> + The node is connected to the node of the calling process + through a hidden connection. That is, the node + name would <i>not</i> appear in the result returned by + <seealso marker="#nodes/0"><c>nodes/0</c></seealso>. + </p></item> + <tag><c>this</c></tag> + <item><p> + This is the node of the calling process. + </p></item> + <tag><c>known</c></tag> + <item><p> + The node is not connected but known to the node of the + calling process. + </p></item> + </taglist> + </item> + </taglist> + <p>Example:</p> + <code type="erl"> +(a@localhost)1> nodes([this, connected], #{connection_id=>true, node_type=>true}). +[{c@localhost,#{connection_id => 13892108,node_type => hidden}}, + {b@localhost,#{connection_id => 3067553,node_type => visible}}, + {a@localhost,#{connection_id => undefined,node_type => this}}] +(a@localhost)2> + </code> + </desc> + </func> + + <func> <name name="now" arity="0" since=""/> <fsummary>Elapsed time since 00:00 GMT.</fsummary> <type name="timestamp"/> diff --git a/erts/emulator/beam/atom.names b/erts/emulator/beam/atom.names index 93960862b0..c2c00a6da1 100644 --- a/erts/emulator/beam/atom.names +++ b/erts/emulator/beam/atom.names @@ -178,6 +178,7 @@ atom convert_time_unit atom connect atom connected atom connection_closed +atom connection_id atom const atom context_switches atom control diff --git a/erts/emulator/beam/bif.tab b/erts/emulator/beam/bif.tab index 8d16ffd857..c330e329a2 100644 --- a/erts/emulator/beam/bif.tab +++ b/erts/emulator/beam/bif.tab @@ -110,7 +110,9 @@ bif erlang:monitor_node/2 bif erlang:monitor_node/3 ubif erlang:node/1 ubif erlang:node/0 +bif erlang:nodes/0 bif erlang:nodes/1 +bif erlang:nodes/2 bif erlang:now/0 bif erlang:monotonic_time/0 bif erlang:monotonic_time/1 diff --git a/erts/emulator/beam/dist.c b/erts/emulator/beam/dist.c index d37211c742..8be2a77ae3 100644 --- a/erts/emulator/beam/dist.c +++ b/erts/emulator/beam/dist.c @@ -46,6 +46,7 @@ #include "erl_thr_progress.h" #include "dtrace-wrapper.h" #include "erl_proc_sig_queue.h" +#include "erl_map.h" #define DIST_CTL_DEFAULT_SIZE 64 @@ -180,7 +181,7 @@ static Export *dist_ctrl_put_data_trap; static void erts_schedule_dist_command(Port *, DistEntry *); static int dsig_send_exit(ErtsDSigSendContext *ctx, Eterm ctl, Eterm msg); static int dsig_send_ctl(ErtsDSigSendContext *ctx, Eterm ctl); -static void send_nodes_mon_msgs(Process *, Eterm, Eterm, Eterm, Eterm); +static void send_nodes_mon_msgs(Process *, Eterm, Eterm, Uint32, Eterm, Eterm); static void init_nodes_monitors(void); static Sint abort_pending_connection(DistEntry* dep, Uint32 conn_id, int *was_connected_p); @@ -280,6 +281,7 @@ typedef enum { typedef struct { ErtsConMonLnkSeqCleanupState state; DistEntry* dep; + Uint32 connection_id; ErtsMonLnkDist *dist; DistSeqNode *seq; void *yield_state; @@ -348,6 +350,7 @@ con_monitor_link_seq_cleanup(void *vcmlcp) send_nodes_mon_msgs(NULL, am_nodedown, cmlcp->nodename, + cmlcp->connection_id, cmlcp->visability, cmlcp->reason); erts_de_rwlock(cmlcp->dep); @@ -413,10 +416,13 @@ schedule_con_monitor_link_seq_cleanup(DistEntry* dep, cmlcp->yield_state = NULL; cmlcp->dist = dist; - if (!dist) + if (!dist) { cmlcp->state = ERTS_CML_CLEANUP_STATE_NODE_MONITORS; + cmlcp->connection_id = 0; + } else { cmlcp->state = ERTS_CML_CLEANUP_STATE_LINKS; + cmlcp->connection_id = dist->connection_id; erts_mtx_lock(&dist->mtx); ASSERT(dist->alive); dist->alive = 0; @@ -514,7 +520,8 @@ set_node_not_alive(void *unused) erts_thr_progress_block(); erts_set_this_node(am_Noname, 0); erts_is_alive = 0; - send_nodes_mon_msgs(NULL, am_nodedown, nodename, am_visible, nodedown.reason); + send_nodes_mon_msgs(NULL, am_nodedown, nodename, ~((Uint32) 0), + am_visible, nodedown.reason); nodedown.reason = NIL; bp = nodedown.bp; nodedown.bp = NULL; @@ -3842,7 +3849,8 @@ BIF_RETTYPE setnode_2(BIF_ALIST_2) inc_no_nodes(); erts_set_this_node(BIF_ARG_1, (Uint32) creation); erts_is_alive = 1; - send_nodes_mon_msgs(NULL, am_nodeup, BIF_ARG_1, am_visible, NIL); + send_nodes_mon_msgs(NULL, am_nodeup, BIF_ARG_1, ~((Uint32) 0), + am_visible, NIL); erts_proc_lock(net_kernel, ERTS_PROC_LOCKS_ALL); /* By setting F_DISTRIBUTION on net_kernel, @@ -4221,6 +4229,7 @@ setup_connection_epiloge_rwunlock(Process *c_p, DistEntry *dep, send_nodes_mon_msgs(c_p, am_nodeup, dep->sysname, + dep->connection_id, flags & DFLAG_PUBLISHED ? am_visible : am_hidden, NIL); @@ -4497,38 +4506,54 @@ BIF_RETTYPE node_0(BIF_ALIST_0) BIF_RET(erts_this_dist_entry->sysname); } - /**********************************************************************/ /* nodes() -> [ Node ] */ -#if 0 /* Done in erlang.erl instead. */ +static BIF_RETTYPE nodes(Process *c_p, Eterm node_types, Eterm options); + BIF_RETTYPE nodes_0(BIF_ALIST_0) { - return nodes_1(BIF_P, am_visible); + return nodes(BIF_P, am_visible, THE_NON_VALUE); } -#endif - BIF_RETTYPE nodes_1(BIF_ALIST_1) { + return nodes(BIF_P, BIF_ARG_1, THE_NON_VALUE); +} + +BIF_RETTYPE nodes_2(BIF_ALIST_2) +{ + return nodes(BIF_P, BIF_ARG_1, BIF_ARG_2); +} + +typedef struct { + Eterm name; + Eterm type; + Uint32 cid; +} ErtsNodeInfo; + +static BIF_RETTYPE +nodes(Process *c_p, Eterm node_types, Eterm options) +{ + BIF_RETTYPE ret_val; + ErtsNodeInfo *eni, *eni_start = NULL, *eni_end; Eterm result; - int length; - Eterm* hp; + Uint length; int not_connected = 0; int visible = 0; int hidden = 0; int this = 0; - DeclareTmpHeap(buf,2,BIF_P); /* For one cons-cell */ + int node_type = 0; + int connection_id = 0; + int xinfo = 0; + Eterm tmp_heap[2]; /* For one cons-cell */ DistEntry *dep; - Eterm arg_list = BIF_ARG_1; -#ifdef DEBUG - Eterm* endp; -#endif - - UseTmpHeap(2,BIF_P); + Eterm arg_list; - if (is_atom(BIF_ARG_1)) - arg_list = CONS(buf, BIF_ARG_1, NIL); + if (is_atom(node_types)) + arg_list = CONS(&tmp_heap[0], node_types, NIL); + else + arg_list = node_types; while (is_list(arg_list)) { switch(CAR(list_val(arg_list))) { @@ -4537,13 +4562,43 @@ BIF_RETTYPE nodes_1(BIF_ALIST_1) case am_known: visible = hidden = not_connected = this = 1; break; case am_this: this = 1; break; case am_connected: visible = hidden = 1; break; - default: goto error; break; + default: goto badarg; break; } arg_list = CDR(list_val(arg_list)); } if (is_not_nil(arg_list)) { - goto error; + goto badarg; + } + + if (is_value(options)) { + if (is_not_map(options)) { + goto badarg; + } + else { + Sint no_opts = 0; + const Eterm *conn_idp = erts_maps_get(am_connection_id, options); + const Eterm *node_typep = erts_maps_get(am_node_type, options); + if (conn_idp) { + switch (*conn_idp) { + case am_true: connection_id = !0; break; + case am_false: connection_id = 0; break; + default: goto badarg; + } + no_opts++; + } + if (node_typep) { + switch (*node_typep) { + case am_true: node_type = !0; break; + case am_false: node_type = 0; break; + default: goto badarg; + } + no_opts++; + } + if (no_opts != erts_map_size(options)) + goto badarg; /* got invalid options... */ + xinfo = !0; + } } length = 0; @@ -4568,50 +4623,130 @@ BIF_RETTYPE nodes_1(BIF_ALIST_1) if (length == 0) { erts_rwmtx_runlock(&erts_dist_table_rwmtx); - goto done; + ERTS_BIF_PREP_RET(ret_val, NIL); + return ret_val; } - hp = HAlloc(BIF_P, 2*length); + eni_start = eni = erts_alloc(ERTS_ALC_T_TMP, sizeof(ErtsNodeInfo)*length); -#ifdef DEBUG - endp = hp + length*2; -#endif - if(not_connected) { - for(dep = erts_not_connected_dist_entries; dep; dep = dep->next) { - if (dep != erts_this_dist_entry) { - result = CONS(hp, dep->sysname, result); - hp += 2; - } + if (this) { + eni->name = erts_this_dist_entry->sysname; + eni->type = am_this; + eni->cid = ~((Uint32) 0); + eni++; + } + + if (visible) { + for (dep = erts_visible_dist_entries; dep; dep = dep->next) { + eni->name = dep->sysname; + eni->type = am_visible; + eni->cid = dep->connection_id; + ASSERT(eni->cid >= 0); + eni++; } - for(dep = erts_pending_dist_entries; dep; dep = dep->next) { - result = CONS(hp, dep->sysname, result); - hp += 2; - } } - if(hidden) - for(dep = erts_hidden_dist_entries; dep; dep = dep->next) { - result = CONS(hp, dep->sysname, result); - hp += 2; - } - if(visible) - for(dep = erts_visible_dist_entries; dep; dep = dep->next) { - result = CONS(hp, dep->sysname, result); - hp += 2; - } - if(this) { - result = CONS(hp, erts_this_dist_entry->sysname, result); - hp += 2; + + if (hidden) { + for (dep = erts_hidden_dist_entries; dep; dep = dep->next) { + eni->name = dep->sysname; + eni->type = am_hidden; + eni->cid = dep->connection_id; + eni++; + } + } + + if (not_connected) { + for (dep = erts_not_connected_dist_entries; dep; dep = dep->next) { + if (dep != erts_this_dist_entry) { + eni->name = dep->sysname; + eni->type = am_known; + eni->cid = ~((Uint32) 0); + eni++; + } + } + for (dep = erts_pending_dist_entries; dep; dep = dep->next) { + eni->name = dep->sysname; + eni->type = am_known; + eni->cid = ~((Uint32) 0); + eni++; + } } - ASSERT(endp == hp); + erts_rwmtx_runlock(&erts_dist_table_rwmtx); -done: - UnUseTmpHeap(2,BIF_P); - BIF_RET(result); + eni_end = eni; + + result = NIL; + if (!xinfo) { + Eterm *hp = HAlloc(c_p, 2*length); + for (eni = eni_start; eni < eni_end; eni++) { + result = CONS(hp, eni->name, result); + hp += 2; + } + } + else { + Eterm ks[2], *hp, keys_tuple = THE_NON_VALUE; + Uint map_size = 0, el_xtra, xtra; + ErtsHeapFactory hfact; + + erts_factory_proc_init(&hfact, c_p); + + if (connection_id) { + ks[map_size++] = am_connection_id; + } + if (node_type) { + ks[map_size++] = am_node_type; + } + + el_xtra = 3 + 2 + MAP_HEADER_FLATMAP_SZ + map_size; + xtra = length*el_xtra; + + for (eni = eni_start; eni < eni_end; eni++) { + Eterm vs[2], info_map, tuple; + map_size = 0; + if (connection_id) { + Eterm cid; + if (eni->cid == ~((Uint32) 0)) + cid = am_undefined; + else if (IS_USMALL(0, (Uint) eni->cid)) + cid = make_small((Uint) eni->cid); + else { + hp = erts_produce_heap(&hfact, BIG_UINT_HEAP_SIZE, xtra); + cid = uint_to_big((Uint) eni->cid, hp); + } + vs[map_size++] = cid; + } + if (node_type) { + vs[map_size++] = eni->type; + } + + info_map = erts_map_from_sorted_ks_and_vs(&hfact, ks, vs, + map_size, &keys_tuple); + + hp = erts_produce_heap(&hfact, 3+2, xtra); + + tuple = TUPLE2(hp, eni->name, info_map); + hp += 3; + result = CONS(hp, tuple, result); + xtra -= el_xtra; + } + + erts_factory_close(&hfact); + } + + erts_free(ERTS_ALC_T_TMP, (void *) eni_start); + + if (length > 10) { + Uint reds = length / 10; + BUMP_REDS(c_p, reds); + } + + ERTS_BIF_PREP_RET(ret_val, result); + return ret_val; -error: - UnUseTmpHeap(2,BIF_P); - BIF_ERROR(BIF_P,BADARG); +badarg: + ERTS_BIF_PREP_ERROR(ret_val, c_p, BADARG); + return ret_val; } /**********************************************************************/ @@ -4842,6 +4977,8 @@ BIF_RETTYPE net_kernel_dflag_unicode_io_1(BIF_ALIST_1) #define ERTS_NODES_MON_OPT_TYPE_VISIBLE (((Uint16) 1) << 0) #define ERTS_NODES_MON_OPT_TYPE_HIDDEN (((Uint16) 1) << 1) #define ERTS_NODES_MON_OPT_DOWN_REASON (((Uint16) 1) << 2) +#define ERTS_NODES_MON_OPT_INFO_MAP (((Uint16) 1) << 3) +#define ERTS_NODES_MON_OPT_CONN_ID (((Uint16) 1) << 4) #define ERTS_NODES_MON_OPT_TYPES \ (ERTS_NODES_MON_OPT_TYPE_VISIBLE|ERTS_NODES_MON_OPT_TYPE_HIDDEN) @@ -4871,10 +5008,10 @@ init_nodes_monitors(void) } Eterm -erts_monitor_nodes(Process *c_p, Eterm on, Eterm olist) +erts_monitor_nodes(Process *c_p, Eterm on, Eterm options) { - Eterm key, old_value, opts_list = olist; - Uint opts = (Uint) 0; + Eterm key, old_value; + Uint opts = (Uint) ERTS_NODES_MON_OPT_INFO_MAP; ASSERT(c_p); ERTS_LC_ASSERT(erts_proc_lc_my_proc_locks(c_p) == ERTS_PROC_LOCK_MAIN); @@ -4882,55 +5019,63 @@ erts_monitor_nodes(Process *c_p, Eterm on, Eterm olist) if (on != am_true && on != am_false) return THE_NON_VALUE; - if (is_not_nil(opts_list)) { - int all = 0, visible = 0, hidden = 0; - - while (is_list(opts_list)) { - Eterm *cp = list_val(opts_list); - Eterm opt = CAR(cp); - opts_list = CDR(cp); - if (opt == am_nodedown_reason) + if (is_nil(options)) { + opts &= ~ERTS_NODES_MON_OPT_INFO_MAP; + } + else if (is_not_map(options)) { + return THE_NON_VALUE; + } + else { + Sint no_opts = 0; + const Eterm *l = erts_maps_get(am_list, options); + const Eterm *cid = erts_maps_get(am_connection_id, options); + const Eterm *nt = erts_maps_get(am_node_type, options); + const Eterm *nr = erts_maps_get(am_nodedown_reason, options); + if (l) { + if (*l == am_true) { + opts &= ~ERTS_NODES_MON_OPT_INFO_MAP; + } + else { + return THE_NON_VALUE; + } + no_opts++; + } + if (cid) { + if (*cid == am_true) { + opts |= ERTS_NODES_MON_OPT_CONN_ID; + } + else if (*cid != am_false) { + return THE_NON_VALUE; + } + no_opts++; + } + if (nt) { + switch (*nt) { + case am_visible: + opts |= ERTS_NODES_MON_OPT_TYPE_VISIBLE; + break; + case am_hidden: + opts |= ERTS_NODES_MON_OPT_TYPE_HIDDEN; + break; + case am_all: + opts |= ERTS_NODES_MON_OPT_TYPES; + break; + default: + return THE_NON_VALUE; + } + no_opts++; + } + if (nr) { + if (*nr == am_true) { opts |= ERTS_NODES_MON_OPT_DOWN_REASON; - else if (is_tuple(opt)) { - Eterm* tp = tuple_val(opt); - if (arityval(tp[0]) != 2) - return THE_NON_VALUE; - switch (tp[1]) { - case am_node_type: - switch (tp[2]) { - case am_visible: - if (hidden || all) - return THE_NON_VALUE; - opts |= ERTS_NODES_MON_OPT_TYPE_VISIBLE; - visible = 1; - break; - case am_hidden: - if (visible || all) - return THE_NON_VALUE; - opts |= ERTS_NODES_MON_OPT_TYPE_HIDDEN; - hidden = 1; - break; - case am_all: - if (visible || hidden) - return THE_NON_VALUE; - opts |= ERTS_NODES_MON_OPT_TYPES; - all = 1; - break; - default: - return THE_NON_VALUE; - } - break; - default: - return THE_NON_VALUE; - } - } - else { - return THE_NON_VALUE; - } - } - - if (is_not_nil(opts_list)) - return THE_NON_VALUE; + } + else if (*nr != am_false) { + return THE_NON_VALUE; + } + no_opts++; + } + if (no_opts != erts_map_size(options)) + return THE_NON_VALUE; /* got invalid options... */ } key = make_small(opts); @@ -5023,8 +5168,24 @@ save_nodes_monitor(ErtsMonitor *mon, void *vctxt, Sint reds) return 1; } +#define ERTS_MON_NODES_MAX_INFO_LIST_SZ__(MAX_ELEMS) \ + ((MAX_ELEMS)*(3 /* key/value 2-tuple */ + 2/* cons cell */) \ + + BIG_UINT_HEAP_SIZE /* connection id value */ \ + + 4 /* top 3-tuple */) +#define ERTS_MON_NODES_MAX_INFO_MAP_SZ__(MAX_ELEMS) \ + ((MAX_ELEMS)*2 /* keys and values */ \ + + 1 /* key tuple header */ + MAP_HEADER_FLATMAP_SZ /* 3 */ \ + + BIG_UINT_HEAP_SIZE /* connection id value */ \ + + 4 /* top 3-tuple */) +#define ERTS_MON_NODES_MAX_INFO_SZ__(MAX_ELEMS) \ + ((ERTS_MON_NODES_MAX_INFO_MAP_SZ__((MAX_ELEMS)) \ + > ERTS_MON_NODES_MAX_INFO_LIST_SZ__((MAX_ELEMS))) \ + ? ERTS_MON_NODES_MAX_INFO_MAP_SZ__((MAX_ELEMS)) \ + : ERTS_MON_NODES_MAX_INFO_LIST_SZ__((MAX_ELEMS))) + static void -send_nodes_mon_msgs(Process *c_p, Eterm what, Eterm node, Eterm type, Eterm reason) +send_nodes_mon_msgs(Process *c_p, Eterm what, Eterm node, + Uint32 connection_id, Eterm type, Eterm reason) { Uint opts; Uint i, no, reason_size; @@ -5070,7 +5231,8 @@ send_nodes_mon_msgs(Process *c_p, Eterm what, Eterm node, Eterm type, Eterm reas erts_mtx_unlock(&nodes_monitors_mtx); for (i = 0; i < no; i++) { - Eterm tmp_heap[3+2+3+2+4 /* max need */]; + ErtsHeapFactory hfact; + Eterm tmp_heap[ERTS_MON_NODES_MAX_INFO_SZ__(3/* max info elements */)]; Eterm *hp, msg; Uint hsz; @@ -5096,42 +5258,114 @@ send_nodes_mon_msgs(Process *c_p, Eterm what, Eterm node, Eterm type, Eterm reas } } + /* + * tmp_heap[] is sized so there will be room for everything + * we need assuming no info, a two-tuple info list, or an info + * flat map is generated. In case there would be a greater heap + * need this will be taken care of by the heap factory... + */ + erts_factory_tmp_init(&hfact, + &tmp_heap[0], + sizeof(tmp_heap)/sizeof(Uint), + ERTS_ALC_T_TMP); hsz = 0; - hp = &tmp_heap[0]; if (!opts) { + hp = erts_produce_heap(&hfact, 3, 0); msg = TUPLE2(hp, what, node); - hp += 3; } - else { + else { /* Info list or map... */ Eterm tup; - Eterm info = NIL; + Eterm info; - if (opts & (ERTS_NODES_MON_OPT_TYPE_VISIBLE - | ERTS_NODES_MON_OPT_TYPE_HIDDEN)) { + if (opts & ERTS_NODES_MON_OPT_INFO_MAP) { /* Info map */ + Uint map_size = 0; + Eterm ks[3], vs[3]; - tup = TUPLE2(hp, am_node_type, type); - hp += 3; - info = CONS(hp, tup, info); - hp += 2; - } + if (opts & ERTS_NODES_MON_OPT_CONN_ID) { + Eterm cid; + if (connection_id == ~((Uint32) 0)) { + cid = am_undefined; + } + else if (IS_USMALL(0, (Uint) connection_id)) { + cid = make_small(connection_id); + } + else { + hp = erts_produce_heap(&hfact, BIG_UINT_HEAP_SIZE, 0); + cid = uint_to_big(connection_id, hp); + } + ks[map_size] = am_connection_id; + vs[map_size] = cid; + map_size++; + } + if (opts & (ERTS_NODES_MON_OPT_TYPE_VISIBLE + | ERTS_NODES_MON_OPT_TYPE_HIDDEN)) { + ks[map_size] = am_node_type; + vs[map_size] = type; + map_size++; + } + if (what == am_nodedown + && (opts & ERTS_NODES_MON_OPT_DOWN_REASON)) { + hsz += reason_size; + ks[map_size] = am_nodedown_reason; + vs[map_size] = reason; + map_size++; + } - if (what == am_nodedown - && (opts & ERTS_NODES_MON_OPT_DOWN_REASON)) { - hsz += reason_size; - tup = TUPLE2(hp, am_nodedown_reason, reason); - hp += 3; - info = CONS(hp, tup, info); - hp += 2; + info = erts_map_from_sorted_ks_and_vs(&hfact, ks, vs, + map_size, NULL); + ASSERT(is_value(info)); } + else { /* Info list */ + + info = NIL; + if (opts & (ERTS_NODES_MON_OPT_TYPE_VISIBLE + | ERTS_NODES_MON_OPT_TYPE_HIDDEN)) { + hp = erts_produce_heap(&hfact, 3 + 2, 0); + tup = TUPLE2(hp, am_node_type, type); + hp += 3; + info = CONS(hp, tup, info); + } + if (what == am_nodedown + && (opts & ERTS_NODES_MON_OPT_DOWN_REASON)) { + hp = erts_produce_heap(&hfact, 3 + 2, 0); + hsz += reason_size; + tup = TUPLE2(hp, am_nodedown_reason, reason); + hp += 3; + info = CONS(hp, tup, info); + } + + if (opts & ERTS_NODES_MON_OPT_CONN_ID) { + Eterm cid; + if (connection_id == ~((Uint32) 0)) { + cid = am_undefined; + } + else if (IS_USMALL(0, (Uint) connection_id)) { + cid = make_small(connection_id); + } + else { + hp = erts_produce_heap(&hfact, BIG_UINT_HEAP_SIZE, 0); + cid = uint_to_big(connection_id, hp); + } + hp = erts_produce_heap(&hfact, 3 + 2, 0); + tup = TUPLE2(hp, am_connection_id, cid); + hp += 3; + info = CONS(hp, tup, info); + } + } + + hp = erts_produce_heap(&hfact, 4, 0); msg = TUPLE3(hp, what, node, info); - hp += 4; } - ASSERT(hp - &tmp_heap[0] <= sizeof(tmp_heap)/sizeof(tmp_heap[0])); - hsz += hp - &tmp_heap[0]; + hsz += hfact.hp - hfact.hp_start; + if (hfact.heap_frags) { + ErlHeapFragment *bp; + for (bp = hfact.heap_frags; bp; bp = bp->next) + hsz += bp->used_size; + } erts_proc_sig_send_persistent_monitor_msg(ERTS_MON_TYPE_NODES, nmdp[i].options, @@ -5139,6 +5373,8 @@ send_nodes_mon_msgs(Process *c_p, Eterm what, Eterm node, Eterm type, Eterm reas nmdp[i].pid, msg, hsz); + + erts_factory_close(&hfact); } if (nmdp != &def_buf[0]) diff --git a/erts/emulator/beam/erl_map.c b/erts/emulator/beam/erl_map.c index d3e355183d..417f8b2a8b 100644 --- a/erts/emulator/beam/erl_map.c +++ b/erts/emulator/beam/erl_map.c @@ -121,28 +121,35 @@ void erts_init_map(void) { */ BIF_RETTYPE map_size_1(BIF_ALIST_1) { - if (is_flatmap(BIF_ARG_1)) { - flatmap_t *mp = (flatmap_t*)flatmap_val(BIF_ARG_1); - BIF_RET(make_small(flatmap_get_size(mp))); - } else if (is_hashmap(BIF_ARG_1)) { - Eterm *head; - Uint size; + Sint size = erts_map_size(BIF_ARG_1); + if (size < 0) { + BIF_P->fvalue = BIF_ARG_1; + BIF_ERROR(BIF_P, BADMAP); + } - head = hashmap_val(BIF_ARG_1); - size = head[1]; + /* + * As long as a small has 28 bits (on a 32-bit machine) for + * the integer itself, it is impossible to build a map whose + * size would not fit in a small. Add an assertion in case we + * ever decreases the number of bits in a small. + */ + ASSERT(IS_USMALL(0, size)); + BIF_RET(make_small(size)); +} - /* - * As long as a small has 28 bits (on a 32-bit machine) for - * the integer itself, it is impossible to build a map whose - * size would not fit in a small. Add an assertion in case we - * ever decreases the number of bits in a small. - */ - ASSERT(IS_USMALL(0, size)); - BIF_RET(make_small(size)); +Sint +erts_map_size(Eterm map) +{ + if (is_flatmap(map)) { + flatmap_t *mp = (flatmap_t*)flatmap_val(map); + return (Sint) flatmap_get_size(mp); + } + else if (is_hashmap(map)) { + Eterm *head = hashmap_val(map); + return (Sint) head[1]; } - BIF_P->fvalue = BIF_ARG_1; - BIF_ERROR(BIF_P, BADMAP); + return -1; } /* maps:find/2 @@ -478,38 +485,87 @@ Eterm erts_hashmap_from_array(ErtsHeapFactory* factory, Eterm *leafs, Uint n, return res; } -Eterm erts_map_from_ks_and_vs(ErtsHeapFactory *factory, Eterm *ks0, Eterm *vs0, Uint n) +static ERTS_INLINE Eterm +from_ks_and_vs(ErtsHeapFactory *factory, Eterm *ks, Eterm *vs, + Uint n, Eterm *key_tuple, flatmap_t **fmpp) { if (n <= MAP_SMALL_MAP_LIMIT) { - Eterm *ks, *vs, *hp; - flatmap_t *mp; + Eterm *hp; + flatmap_t *fmp; Eterm keys; - hp = erts_produce_heap(factory, 3 + 1 + (2 * n), 0); - keys = make_tuple(hp); - *hp++ = make_arityval(n); - ks = hp; - hp += n; - mp = (flatmap_t*)hp; - hp += MAP_HEADER_FLATMAP_SZ; - vs = hp; + 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(sys_memcmp((void *) (tuple_val(keys) + 1), + (void *) ks, + n * sizeof(Eterm)) == 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, + n * sizeof(Eterm)); + hp += n; + } - mp->thing_word = MAP_HEADER_FLATMAP; - mp->size = n; - mp->keys = keys; + fmp = (flatmap_t*)hp; + hp += MAP_HEADER_FLATMAP_SZ; + + fmp->thing_word = MAP_HEADER_FLATMAP; + fmp->size = n; + fmp->keys = keys; - sys_memcpy(ks, ks0, n * sizeof(Eterm)); - sys_memcpy(vs, vs0, n * sizeof(Eterm)); + sys_memcpy((void *) hp, (void *) vs, n * sizeof(Eterm)); - if (!erts_validate_and_sort_flatmap(mp)) { + if (fmpp) { + *fmpp = fmp; return THE_NON_VALUE; } - - return make_flatmap(mp); + return make_flatmap(fmp); } else { - return erts_hashmap_from_ks_and_vs(factory, ks0, vs0, n); + if (fmpp) { + *fmpp = NULL; + } + return erts_hashmap_from_ks_and_vs(factory, ks, vs, n); } - return THE_NON_VALUE; +} + +Eterm erts_map_from_ks_and_vs(ErtsHeapFactory *factory, Eterm *ks, Eterm *vs, Uint n) +{ + Eterm res; + flatmap_t *fmp; + + res = from_ks_and_vs(factory, ks, vs, n, NULL, &fmp); + if (fmp) { + if (erts_validate_and_sort_flatmap(fmp)) { + res = make_flatmap(fmp); + } + else { + res = THE_NON_VALUE; + } + } + return res; +} + +Eterm erts_map_from_sorted_ks_and_vs(ErtsHeapFactory *factory, Eterm *ks, Eterm *vs, + Uint n, Eterm *key_tuple) +{ +#ifdef DEBUG + Uint i; + /* verify that key array contains unique and sorted keys... */ + for (i = 1; i < n; i++) { + ASSERT(CMP_TERM(ks[i-1], ks[i]) < 0); + } +#endif + + return from_ks_and_vs(factory, ks, vs, n, key_tuple, NULL); } diff --git a/erts/emulator/beam/erl_map.h b/erts/emulator/beam/erl_map.h index 718d400e22..6236ac8e2f 100644 --- a/erts/emulator/beam/erl_map.h +++ b/erts/emulator/beam/erl_map.h @@ -101,6 +101,8 @@ Eterm erts_hashmap_from_array(ErtsHeapFactory*, Eterm *leafs, Uint n, int rejec erts_hashmap_from_ks_and_vs_extra((F), (KS), (VS), (N), THE_NON_VALUE, THE_NON_VALUE); Eterm erts_map_from_ks_and_vs(ErtsHeapFactory *factory, Eterm *ks, Eterm *vs, Uint n); +Eterm erts_map_from_sorted_ks_and_vs(ErtsHeapFactory *factory, Eterm *ks0, Eterm *vs0, + Uint n, Eterm *key_tuple); Eterm erts_hashmap_from_ks_and_vs_extra(ErtsHeapFactory *factory, Eterm *ks, Eterm *vs, Uint n, Eterm k, Eterm v); @@ -109,6 +111,8 @@ const Eterm *erts_maps_get(Eterm key, Eterm map); const Eterm *erts_hashmap_get(Uint32 hx, Eterm key, Eterm map); +Sint erts_map_size(Eterm map); + /* hamt nodes v2.0 * * node :: leaf | array | bitmap diff --git a/erts/emulator/beam/erl_node_tables.c b/erts/emulator/beam/erl_node_tables.c index eb2049f565..760d6d3cd7 100644 --- a/erts/emulator/beam/erl_node_tables.c +++ b/erts/emulator/beam/erl_node_tables.c @@ -152,9 +152,13 @@ dist_table_alloc(void *dep_tmpl) Eterm sysname; Binary *bin; DistEntry *dep; + Uint32 init_connection_id; erts_rwmtx_opt_t rwmtx_opt = ERTS_RWMTX_OPT_DEFAULT_INITER; rwmtx_opt.type = ERTS_RWMTX_TYPE_FREQUENT_READ; + init_connection_id = (Uint32) erts_get_monotonic_time(NULL); + init_connection_id &= ERTS_DIST_CON_ID_MASK; + sysname = ((DistEntry *) dep_tmpl)->sysname; bin = erts_create_magic_binary_x(sizeof(DistEntry), @@ -174,7 +178,7 @@ dist_table_alloc(void *dep_tmpl) dep->sysname = sysname; dep->cid = NIL; erts_atomic_init_nob(&dep->input_handler, (erts_aint_t) NIL); - dep->connection_id = 0; + dep->connection_id = init_connection_id; dep->state = ERTS_DE_STATE_IDLE; dep->pending_nodedown = 0; dep->suspended_nodeup = NULL; diff --git a/erts/emulator/test/distribution_SUITE.erl b/erts/emulator/test/distribution_SUITE.erl index d115626597..0fd8d29f4c 100644 --- a/erts/emulator/test/distribution_SUITE.erl +++ b/erts/emulator/test/distribution_SUITE.erl @@ -41,7 +41,7 @@ init_per_suite/1, end_per_suite/1, init_per_group/2, end_per_group/2, ping/1, bulk_send_small/1, - group_leader/1, + group_leader/1, nodes2/1, optimistic_dflags/1, bulk_send_big/1, bulk_send_bigbig/1, local_send_small/1, local_send_big/1, @@ -87,7 +87,7 @@ suite() -> all() -> [ping, {group, bulk_send}, {group, local_send}, - group_leader, + group_leader, nodes2, optimistic_dflags, link_to_busy, exit_to_busy, lost_exit, link_to_dead, link_to_dead_new_node, @@ -205,6 +205,294 @@ group_leader_1(Node2) -> ?Line {ExtPid, group_leader, GL2} = receive_one(), ok. +nodes2(Config) when is_list(Config) -> + + This = node(), + + ok = net_kernel:monitor_nodes(true, #{node_type => all, + connection_id => true}), + + AlreadyConnected = maps:from_list(lists:map(fun (N) -> + {N, true} + end, nodes(connected))), + AlreadyVisible = maps:from_list(lists:map(fun (N) -> + {N, true} + end, nodes(visible))), + AlreadyHidden = maps:from_list(lists:map(fun (N) -> + {N, true} + end, nodes(visible))), + AlreadyKnown = maps:from_list(lists:map(fun (N) -> + {N, true} + end, nodes(known))), + + {ok, V1} = start_node(visible1), + {ok, H1} = start_node(hidden1, "-hidden"), + {ok, V2} = start_node(visible2), + {ok, H2} = start_node(hidden2, "-hidden"), + + TestNodes = maps:from_list(lists:map(fun (N) -> + {N, true} + end, [This, V1, H1, V2, H2])), + + V1CId = receive {nodeup, V1, #{connection_id := C1, node_type := visible}} -> C1 end, + V2CId = receive {nodeup, V2, #{connection_id := C2, node_type := visible}} -> C2 end, + H1CId = receive {nodeup, H1, #{connection_id := C3, node_type := hidden}} -> C3 end, + H2CId = receive {nodeup, H2, #{connection_id := C4, node_type := hidden}} -> C4 end, + + lists:foreach(fun ({N, I}) when N == V1 -> + 2 = maps:size(I), + #{connection_id := V1CId, node_type := visible} = I; + ({N, I}) when N == V2 -> + 2 = maps:size(I), + #{connection_id := V2CId, node_type := visible} = I; + ({N, I}) when N == H1 -> + 2 = maps:size(I), + #{connection_id := H1CId, node_type := hidden} = I; + ({N, I}) when N == H2 -> + 2 = maps:size(I), + #{connection_id := H2CId, node_type := hidden} = I; + ({N, I}) -> + 2 = maps:size(I), + #{connection_id := _, node_type := _} = I, + false = maps:is_key(N, TestNodes), + true = maps:is_key(N, AlreadyConnected) + end, erlang:nodes(connected, #{connection_id => true, + node_type => true})), + lists:foreach(fun ({N, I}) when N == V1 -> + 2 = maps:size(I), + #{connection_id := V1CId, node_type := visible} = I; + ({N, I}) when N == V2 -> + 2 = maps:size(I), + #{connection_id := V2CId, node_type := visible} = I; + ({N, I}) when N == H1 -> + 2 = maps:size(I), + #{connection_id := H1CId, node_type := hidden} = I; + ({N, I}) when N == H2 -> + 2 = maps:size(I), + #{connection_id := H2CId, node_type := hidden} = I; + ({N, I}) when N == This -> + 2 = maps:size(I), + #{connection_id := undefined, node_type := this} = I; + ({N, I}) -> + 2 = maps:size(I), + #{connection_id := _, node_type := _} = I, + false = maps:is_key(N, TestNodes), + true = maps:is_key(N, AlreadyConnected) + end, erlang:nodes([this, connected], #{connection_id => true, + node_type => true})), + lists:foreach(fun ({N, I}) when N == V1 -> + 1 = maps:size(I), + #{connection_id := V1CId} = I; + ({N, I}) when N == V2 -> + 1 = maps:size(I), + #{connection_id := V2CId} = I; + ({N, I}) when N == H1 -> + 1 = maps:size(I), + #{connection_id := H1CId} = I; + ({N, I}) when N == H2 -> + 1 = maps:size(I), + #{connection_id := H2CId} = I; + ({N, I}) -> + 1 = maps:size(I), + #{connection_id := _} = I, + false = maps:is_key(N, TestNodes), + true = maps:is_key(N, AlreadyConnected) + end, erlang:nodes(connected, #{connection_id => true})), + lists:foreach(fun ({N, I}) when N == V1 -> + 1 = maps:size(I), + #{node_type := visible} = I; + ({N, I}) when N == V2 -> + 1 = maps:size(I), + #{node_type := visible} = I; + ({N, I}) when N == H1 -> + 1 = maps:size(I), + #{node_type := hidden} = I; + ({N, I}) when N == H2 -> + 1 = maps:size(I), + #{node_type := hidden} = I; + ({N, I}) -> + 1 = maps:size(I), + #{node_type := _} = I, + false = maps:is_key(N, TestNodes), + true = maps:is_key(N, AlreadyConnected) + end, erlang:nodes(connected, #{node_type => true})), + lists:foreach(fun ({N, I}) when N == V1 -> + 2 = maps:size(I), + #{connection_id := V1CId, node_type := visible} = I; + ({N, I}) when N == V2 -> + 2 = maps:size(I), + #{connection_id := V2CId, node_type := visible} = I; + ({N, I}) -> + 2 = maps:size(I), + #{connection_id := _, node_type := _} = I, + false = maps:is_key(N, TestNodes), + true = maps:is_key(N, AlreadyVisible) + end, erlang:nodes(visible, #{connection_id => true, + node_type => true})), + lists:foreach(fun ({N, I}) when N == V1 -> + 2 = maps:size(I), + #{connection_id := V1CId, node_type := visible} = I; + ({N, I}) when N == V2 -> + 2 = maps:size(I), + #{connection_id := V2CId, node_type := visible} = I; + ({N, I}) when N == This -> + 2 = maps:size(I), + #{connection_id := undefined, node_type := this} = I; + ({N, I}) -> + 2 = maps:size(I), + #{connection_id := _, node_type := _} = I, + false = maps:is_key(N, TestNodes), + true = maps:is_key(N, AlreadyVisible) + end, erlang:nodes([this, visible], #{connection_id => true, + node_type => true})), + lists:foreach(fun ({N, I}) when N == H1 -> + 2 = maps:size(I), + #{connection_id := H1CId, node_type := hidden} = I; + ({N, I}) when N == H2 -> + 2 = maps:size(I), + #{connection_id := H2CId, node_type := hidden} = I; + ({N, I}) -> + 2 = maps:size(I), + #{connection_id := _, node_type := _} = I, + false = maps:is_key(N, TestNodes), + true = maps:is_key(N, AlreadyHidden) + end, erlang:nodes(hidden, #{connection_id => true, + node_type => true})), + [{This, #{connection_id := undefined, + node_type := this}}] = erlang:nodes(this, #{connection_id => true, + node_type => true}), + [{This, #{connection_id := undefined}}] = erlang:nodes(this, #{connection_id => true}), + [{This, #{node_type := this}}] = erlang:nodes(this, #{node_type => true}), + + %% Ensure dist these dist entries are not GC:d yet... + NKV2 = rpc:call(V2, erlang, whereis, [net_kernel]), + true = is_pid(NKV2), + NKH2 = rpc:call(H2, erlang, whereis, [net_kernel]), + true = is_pid(NKH2), + + stop_node(V2), + stop_node(H2), + + receive {nodedown, V2, #{connection_id := V2CId, node_type := visible}} -> ok end, + receive {nodedown, H2, #{connection_id := H2CId, node_type := hidden}} -> ok end, + + lists:foreach(fun ({N, I}) when N == V1 -> + 2 = maps:size(I), + #{connection_id := V1CId, node_type := visible} = I; + ({N, I}) when N == V2 -> + 2 = maps:size(I), + #{connection_id := undefined, node_type := known} = I; + ({N, I}) when N == H1 -> + 2 = maps:size(I), + #{connection_id := H1CId, node_type := hidden} = I; + ({N, I}) when N == H2 -> + 2 = maps:size(I), + #{connection_id := undefined, node_type := known} = I; + ({N, I}) when N == This -> + 2 = maps:size(I), + #{connection_id := undefined, node_type := this} = I; + ({N, I}) -> + 2 = maps:size(I), + #{connection_id := _, node_type := _} = I, + false = maps:is_key(N, TestNodes), + true = maps:is_key(N, AlreadyKnown) + end, erlang:nodes(known, #{connection_id => true, + node_type => true})), + lists:foreach(fun ({N, I}) when N == V1 -> + 1 = maps:size(I), + #{node_type := visible} = I; + ({N, I}) when N == V2 -> + 1 = maps:size(I), + #{node_type := known} = I; + ({N, I}) when N == H1 -> + 1 = maps:size(I), + #{node_type := hidden} = I; + ({N, I}) when N == H2 -> + 1 = maps:size(I), + #{node_type := known} = I; + ({N, I}) when N == This -> + 1 = maps:size(I), + #{node_type := this} = I; + ({N, I}) -> + 1 = maps:size(I), + #{node_type := _} = I, + false = maps:is_key(N, TestNodes), + true = maps:is_key(N, AlreadyKnown) + end, erlang:nodes(known, #{node_type => true})), + lists:foreach(fun ({N, I}) when N == V1 -> + 1 = maps:size(I), + #{connection_id := V1CId} = I; + ({N, I}) when N == V2 -> + 1 = maps:size(I), + #{connection_id := undefined} = I; + ({N, I}) when N == H1 -> + 1 = maps:size(I), + #{connection_id := H1CId} = I; + ({N, I}) when N == H2 -> + 1 = maps:size(I), + #{connection_id := undefined} = I; + ({N, I}) when N == This -> + 1 = maps:size(I), + #{connection_id := undefined} = I; + ({N, I}) -> + 1 = maps:size(I), + #{connection_id := _} = I, + false = maps:is_key(N, TestNodes), + true = maps:is_key(N, AlreadyKnown) + end, erlang:nodes(known, #{connection_id => true})), + lists:foreach(fun ({N, I}) when N == V1 -> + 0 = maps:size(I), + #{} = I; + ({N, I}) when N == V2 -> + 0 = maps:size(I), + #{} = I; + ({N, I}) when N == H1 -> + 0 = maps:size(I), + #{} = I; + ({N, I}) when N == H2 -> + 0 = maps:size(I), + #{} = I; + ({N, I}) when N == This -> + 0 = maps:size(I), + #{} = I; + ({N, I}) -> + 0 = maps:size(I), + false = maps:is_key(N, TestNodes), + true = maps:is_key(N, AlreadyKnown) + end, erlang:nodes(known, #{})), + + stop_node(V1), + stop_node(H1), + + id(NKV2), + id(NKH2), + + try erlang:nodes("visible", #{connection_id => true}) + catch error:badarg -> ok + end, + try erlang:nodes([another], #{connection_id => true}) + catch error:badarg -> ok + end, + try erlang:nodes(visible, #{cid => true}) + catch error:badarg -> ok + end, + try erlang:nodes(visible, #{connection_id => yes}) + catch error:badarg -> ok + end, + try erlang:nodes(visible, #{node_type => yes}) + catch error:badarg -> ok + end, + try erlang:nodes(visible, [{connection_id, true}]) + catch error:badarg -> ok + end, + try erlang:nodes(visible, [{node_type, true}]) + catch error:badarg -> ok + end, + ok. + +id(X) -> + X. + %% Test optimistic distribution flags toward pending connections (DFLAG_DIST_HOPEFULLY) optimistic_dflags(Config) when is_list(Config) -> ?Line Sender = start_relay_node(optimistic_dflags_sender, []), diff --git a/erts/preloaded/ebin/erlang.beam b/erts/preloaded/ebin/erlang.beam Binary files differindex 050db2b8b2..9d4bc2a6c6 100644 --- a/erts/preloaded/ebin/erlang.beam +++ b/erts/preloaded/ebin/erlang.beam diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl index f78a79203c..5020cc6ff8 100644 --- a/erts/preloaded/src/erlang.erl +++ b/erts/preloaded/src/erlang.erl @@ -34,7 +34,7 @@ -export([dmonitor_node/3]). -export([delay_trap/2]). -export([set_cookie/2, get_cookie/0]). --export([nodes/0]). +-export([nodes/0, nodes/1, nodes/2]). -export([integer_to_list/2]). -export([integer_to_binary/2]). @@ -175,7 +175,7 @@ is_list/1, is_map/1, is_number/1, is_pid/1, is_port/1, is_record/2, is_record/3, is_reference/1, is_tuple/1, load_module/2, load_nif/2, localtime_to_universaltime/2, make_fun/3, - make_tuple/2, make_tuple/3, nodes/1, open_port/2, + make_tuple/2, make_tuple/3, open_port/2, port_call/2, port_call/3, port_info/1, port_info/2, process_flag/2, process_info/2, send/2, send/3, seq_trace_info/1, setelement/3, spawn_opt/1, @@ -2176,13 +2176,6 @@ make_tuple(_Arity,_InitialValue) -> make_tuple(_Arity,_DefaultValue,_InitList) -> erlang:nif_error(undefined). --spec nodes(Arg) -> Nodes when - Arg :: NodeType | [NodeType], - NodeType :: visible | hidden | connected | this | known, - Nodes :: [node()]. -nodes(_Arg) -> - erlang:nif_error(undefined). - -spec open_port(PortName, PortSettings) -> port() when PortName :: {spawn, Command :: string() | binary()} | {spawn_driver, Command :: string() | binary()} | @@ -3065,7 +3058,28 @@ yield() -> -spec nodes() -> Nodes when Nodes :: [node()]. nodes() -> - erlang:nodes(visible). + erlang:nif_error(undefined). + +-spec nodes(Arg) -> Nodes when + Arg :: NodeType | [NodeType], + NodeType :: visible | hidden | connected | this | known, + Nodes :: [node()]. +nodes(_Arg) -> + erlang:nif_error(undefined). + +-spec nodes(Arg, InfoOpts) -> [NodeInfo] when + NodeType :: visible | hidden | connected | this | known, + Arg :: NodeType | [NodeType], + InfoOpts :: #{connection_id => boolean(), + node_type => boolean()}, + NodeTypeInfo :: visible | hidden | this | known, + ConnectionId :: undefined | integer(), + Info :: #{connection_id => ConnectionId, + node_type => NodeTypeInfo}, + NodeInfo :: {node(), Info}. + +nodes(_Args, _Opts) -> + erlang:nif_error(undefined). -spec disconnect_node(Node) -> boolean() | ignored when Node :: node(). diff --git a/erts/preloaded/src/erts.app.src b/erts/preloaded/src/erts.app.src index 4f11613d0e..6cb889573a 100644 --- a/erts/preloaded/src/erts.app.src +++ b/erts/preloaded/src/erts.app.src @@ -42,7 +42,7 @@ {registered, []}, {applications, []}, {env, []}, - {runtime_dependencies, ["stdlib-3.5", "kernel-@OTP-17843@", "sasl-3.3"]} + {runtime_dependencies, ["stdlib-@OTP-17934@", "kernel-@OTP-17843:OTP-17934@", "sasl-3.3"]} ]}. %% vim: ft=erlang diff --git a/lib/kernel/doc/src/net_kernel.xml b/lib/kernel/doc/src/net_kernel.xml index 419d3cad84..096af5cb55 100644 --- a/lib/kernel/doc/src/net_kernel.xml +++ b/lib/kernel/doc/src/net_kernel.xml @@ -161,82 +161,121 @@ $ <input>erl -sname foobar</input></pre> are stopped. Two option lists are considered the same if they contain the same set of options.</p> - <p>As from Kernel version 2.11.4, and ERTS version - 5.5.4, the following is guaranteed:</p> - <list type="bulleted"> - <item><p><c>nodeup</c> messages are delivered before delivery - of any message from the remote node passed through the - newly established connection.</p></item> - <item><p><c>nodedown</c> messages are not delivered until all - messages from the remote node that have been passed - through the connection have been delivered.</p></item> - </list> - <p>Notice that this is <em>not</em> guaranteed for Kernel - versions before 2.11.4.</p> - <p>As from Kernel version 2.11.4, subscriptions can also be - made before the <c>net_kernel</c> server is started, that is, - <c>net_kernel:monitor_nodes/[1,2]</c> does not return - <c>ignored</c>.</p> - <p>As from Kernel version 2.13, and ERTS version - 5.7, the following is guaranteed:</p> - <list type="bulleted"> - <item><p><c>nodeup</c> messages are delivered after the + <p>Delivery guarantees of <c>nodeup</c>/<c>nodedown</c> messages:</p> + <list> + <item><p> + <c>nodeup</c> messages are delivered before delivery + of any signals from the remote node through the newly + established connection. + </p></item> + <item> + <p><c>nodedown</c> messages are delivered after all + the signals from the remote node over the connection + have been delivered. + </p></item> + <item><p> + <c>nodeup</c> messages are delivered after the corresponding node appears in results from - <c>erlang:nodes/X</c>.</p></item> - <item><p><c>nodedown</c> messages are delivered after the + <c>erlang:nodes()</c>. + </p></item> + <item> + <p><c>nodedown</c> messages are delivered after the corresponding node has disappeared in results from - <c>erlang:nodes/X</c>.</p></item> + <c>erlang:nodes()</c>. + </p></item> + <item><p> + As of OTP @OTP-16362@, a <c>nodedown</c> message for a + connection being taken down will be delivered before a + <c>nodeup</c> message due to a new connection to the + same node. Prior to OTP @OTP-16362@, this was not + guaranteed to be the case. + </p></item> </list> - <p>Notice that this is <em>not</em> guaranteed for Kernel - versions before 2.13.</p> <p>The format of the node status change messages depends on <c><anno>Options</anno></c>. If <c><anno>Options</anno></c> is - <c>[]</c>, which is the default, the format is as follows:</p> - <code type="none"> + the empty list or if <c>net_kernel:monitor_nodes/1</c> is called, + the format is as follows:</p> + <code type="erl"> {nodeup, Node} | {nodedown, Node} Node = node()</code> - <p>If <c><anno>Options</anno></c> is not <c>[]</c>, the format is - as follows:</p> - <code type="none"> -{nodeup, Node, InfoList} | {nodedown, Node, InfoList} + <p> + When <c><anno>Options</anno></c> is the empty map or empty + list, the caller will only subscribe for status change messages + for visible nodes. That is, only nodes that appear in the + result of + <seealso marker="erts:erlang#nodes/0"><c>erlang:nodes/0</c></seealso>. + </p> + <p> + If <c><anno>Options</anno></c> equals anything other than the + empty list, the format of the status change messages is as follows: + </p> + <code type="erl"> +{nodeup, Node, Info} | {nodedown, Node, Info} Node = node() - InfoList = [{Tag, Val}]</code> - <p><c>InfoList</c> is a list of tuples. Its contents depends on - <c><anno>Options</anno></c>, see below.</p> - <p>Also, when <c>OptionList == []</c>, only visible nodes, that - is, nodes that appear in the result of - <seealso marker="erts:erlang#nodes/0"><c>erlang:nodes/0</c></seealso>, - are monitored.</p> - <p><c><anno>Option</anno></c> can be any of the following:</p> + Info = #{Tag => Val} | [{Tag, Val}]</code> + <p> + <c>Info</c> is either a map or a list of 2-tuples. Its content + depends on <c><anno>Options</anno></c>. If <c><anno>Options</anno></c> + is a map, <c>Info</c> will also be a map. If <c><anno>Options</anno></c> + is a list, <c>Info</c> will also be a list. + </p> + <p> + When <c><anno>Options</anno></c> is a map, currently + the following associations are allowed: + </p> <taglist> - <tag><c>{node_type, NodeType}</c></tag> + <tag><c>connection_id => boolean()</c></tag> + <item> + <p> + If the value of the association equals <c>true</c>, a + <c>connection_id => ConnectionId</c> association will be + included in the <c>Info</c> map where <c>ConnectionId</c> + is the connection identifier of the connection coming up + or going down. For more info about this connection + identifier see the documentation of + <seealso marker="erts:erlang#connection_id">erlang:nodes/2</seealso>. + </p> + </item> + <tag><c>node_type => <anno>NodeType</anno></c></tag> <item> <p>Valid values for <c>NodeType</c>:</p> <taglist> <tag><c>visible</c></tag> <item><p>Subscribe to node status change messages for visible - nodes only. The tuple <c>{node_type, visible}</c> is - included in <c>InfoList</c>.</p></item> + nodes only. The association <c>node_type => visible</c> will + be included in the <c>Info</c> map.</p></item> <tag><c>hidden</c></tag> <item><p>Subscribe to node status change messages for hidden - nodes only. The tuple <c>{node_type, hidden}</c> is - included in <c>InfoList</c>.</p></item> + nodes only. The association <c>node_type => hidden</c> will + be included in the <c>Info</c> map.</p></item> <tag><c>all</c></tag> <item><p>Subscribe to node status change messages for both - visible and hidden nodes. The tuple - <c>{node_type, visible | hidden}</c> is included in - <c>InfoList</c>.</p></item> + visible and hidden nodes. The association + <c>node_type => visible | hidden</c> will be included in + the <c>Info</c> map.</p></item> </taglist> + <p> + If no <c>node_type => <anno>NodeType</anno></c> association + is included in the <c><anno>Options</anno></c> map, the + caller will subscribe for status change messages for visible + nodes only, but <i>no</i> <c>node_type => visible</c> + association will be included in the <c>Info</c> map. + </p> </item> - <tag><c>nodedown_reason</c></tag> + <tag><c>nodedown_reason => boolean()</c></tag> <item> - <p>The tuple <c>{nodedown_reason, Reason}</c> is included in - <c>InfoList</c> in <c>nodedown</c> messages.</p> + <p> + If the value of the association equals <c>true</c>, a + <c>nodedown_reason => Reason</c> association will be + included in the <c>Info</c> map for <c>nodedown</c> + messages. + </p> + <marker id="nodedown_reasons"/> <p> <c>Reason</c> can, depending on which - distribution module or process that is used be any term, + distribution module or process that is used, be any term, but for the standard TCP distribution module it is - any of the following: + one of the following: </p> <taglist> <tag><c>connection_setup_failed</c></tag> @@ -263,6 +302,82 @@ $ <input>erl -sname foobar</input></pre> </taglist> </item> </taglist> + <p> + When <c><anno>Options</anno></c> is a list, currently + <c><anno>ListOption</anno></c> can be one of the following: + </p> + <taglist> + <tag><c>connection_id</c></tag> + <item> + <p> + A <c>{connection_id, ConnectionId}</c> tuple will be + included in <c>Info</c> where <c>ConnectionId</c> is the + connection identifier of the connection coming up or + going down. For more info about this connection identifier + see the documentation of + <seealso marker="erts:erlang#connection_id">erlang:nodes/2</seealso>. + </p> + </item> + <tag><c>{node_type, <anno>NodeType</anno>}</c></tag> + <item> + <p>Valid values for <c><anno>NodeType</anno></c>:</p> + <taglist> + <tag><c>visible</c></tag> + <item><p>Subscribe to node status change messages for visible + nodes only. The tuple <c>{node_type, visible}</c> will be + included in the <c>Info</c> list.</p></item> + <tag><c>hidden</c></tag> + <item><p>Subscribe to node status change messages for hidden + nodes only. The tuple <c>{node_type, hidden}</c> will be + included in the <c>Info</c> list.</p></item> + <tag><c>all</c></tag> + <item><p>Subscribe to node status change messages for both + visible and hidden nodes. The tuple + <c>{node_type, visible | hidden}</c> will be included in + the <c>Info</c> list.</p></item> + </taglist> + <p> + If no <c>{node_type, <anno>NodeType</anno>}</c> option + has been given. The caller will subscribe for status + change messages for visible nodes only, but <i>no</i> + <c>{node_type, visible}</c> tuple will be included in the + <c>Info</c> list. + </p> + </item> + <tag><c>nodedown_reason</c></tag> + <item> + <p> + The tuple <c>{nodedown_reason, Reason}</c> will be included + in the <c>Info</c> list for <c>nodedown</c> messages. + </p> + <p> + See the documentation of the + <seealso marker="#nodedown_reasons"><c>nodedown_reason + => boolean()</c></seealso> association above for information + about possible <c>Reason</c> values. + </p> + </item> + </taglist> + <p>Example:</p> + <code type="erl"> +(a@localhost)1> net_kernel:monitor_nodes(true, #{connection_id=>true, node_type=>all, nodedown_reason=>true}). +ok +(a@localhost)2> flush(). +Shell got {nodeup,b@localhost, + #{connection_id => 3067552,node_type => visible}} +Shell got {nodeup,c@localhost, + #{connection_id => 13892107,node_type => hidden}} +Shell got {nodedown,b@localhost, + #{connection_id => 3067552,node_type => visible, + nodedown_reason => connection_closed}} +Shell got {nodedown,c@localhost, + #{connection_id => 13892107,node_type => hidden, + nodedown_reason => net_tick_timeout}} +Shell got {nodeup,b@localhost, + #{connection_id => 3067553,node_type => visible}} +ok +(a@localhost)3> + </code> </desc> </func> diff --git a/lib/kernel/src/kernel.app.src b/lib/kernel/src/kernel.app.src index 234d71f745..1e49d081fc 100644 --- a/lib/kernel/src/kernel.app.src +++ b/lib/kernel/src/kernel.app.src @@ -150,6 +150,6 @@ {prevent_overlapping_partitions, false} ]}, {mod, {kernel, []}}, - {runtime_dependencies, ["erts-@OTP-17843@", "stdlib-3.5", "sasl-3.0"]} + {runtime_dependencies, ["erts-@OTP-17843:OTP-17934@", "stdlib-3.5", "sasl-3.0"]} ] }. diff --git a/lib/kernel/src/net_kernel.erl b/lib/kernel/src/net_kernel.erl index b857000a80..e4e2ca6e49 100644 --- a/lib/kernel/src/net_kernel.erl +++ b/lib/kernel/src/net_kernel.erl @@ -256,15 +256,41 @@ monitor_nodes(Flag) -> -spec monitor_nodes(Flag, Options) -> ok | Error when Flag :: boolean(), - Options :: [Option], - Option :: {node_type, NodeType} - | nodedown_reason, + Options :: OptionsList | OptionsMap, + OptionsList :: [ListOption], + ListOption :: connection_id + | {node_type, NodeType} + | nodedown_reason, + OptionsMap :: #{connection_id => boolean(), + node_type => NodeType, + nodedown_reason => boolean()}, NodeType :: visible | hidden | all, Error :: error | {error, term()}. monitor_nodes(Flag, Opts) -> - case catch process_flag({monitor_nodes, Opts}, Flag) of - N when is_integer(N) -> ok; - _ -> mk_monitor_nodes_error(Flag, Opts) + try + MapOpts = if is_map(Opts) -> + error = maps:find(list, Opts), + Opts; + is_list(Opts) -> + lists:foldl(fun (nodedown_reason, Acc) -> + Acc#{nodedown_reason => true}; + (connection_id, Acc) -> + Acc#{connection_id => true}; + ({node_type, Val}, Acc) -> + case maps:find(node_type, Acc) of + error -> ok; + {ok, Val} -> ok + end, + Acc#{node_type => Val} + end, + #{list => true}, + Opts) + end, + true = is_integer(process_flag({monitor_nodes, MapOpts}, Flag)), + ok + catch + _:_ -> + mk_monitor_nodes_error(Flag, Opts) end. %% ... @@ -1194,8 +1220,45 @@ check_options(Opts) when is_list(Opts) -> _ -> {error, {unknown_options, RestOpts2}} end; +check_options(Opts) when is_map(Opts) -> + BadMap0 = case maps:find(connection_id, Opts) of + error -> + Opts; + {ok, CIdBool} when is_boolean(CIdBool) -> + maps:remove(connection_id, Opts); + {ok, BadCIdVal} -> + throw({error, + {bad_option_value, + #{connection_id => BadCIdVal}}}) + end, + BadMap1 = case maps:find(nodedown_reason, BadMap0) of + error -> + BadMap0; + {ok, NRBool} when is_boolean(NRBool) -> + maps:remove(nodedown_reason, BadMap0); + {ok, BadNRVal} -> + throw({error, + {bad_option_value, + #{nodedown_reason => BadNRVal}}}) + end, + BadMap2 = case maps:find(node_type, BadMap1) of + error -> + BadMap1; + {ok, NTVal} when NTVal == visible; NTVal == hidden; NTVal == all -> + maps:remove(node_type, BadMap1); + {ok, BadNTVal} -> + throw({error, + {bad_option_value, + #{node_type => BadNTVal}}}) + end, + if map_size(BadMap2) == 0 -> + {error, internal_error}; + true -> + throw({error, {unknown_options, BadMap2}}) + end; check_options(Opts) -> - {error, {options_not_a_list, Opts}}. + {error, {invalid_options, Opts}}. + mk_monitor_nodes_error(Flag, _Opts) when Flag =/= true, Flag =/= false -> error; diff --git a/lib/kernel/test/erl_distribution_SUITE.erl b/lib/kernel/test/erl_distribution_SUITE.erl index e4fe27c619..7d7108e44b 100644 --- a/lib/kernel/test/erl_distribution_SUITE.erl +++ b/lib/kernel/test/erl_distribution_SUITE.erl @@ -1108,7 +1108,9 @@ monitor_nodes_misc(DCfg, _Config) -> MonNodeState = monitor_node_state(), ok = net_kernel:monitor_nodes(true), ok = net_kernel:monitor_nodes(true, [{node_type, all}, nodedown_reason]), - ok = net_kernel:monitor_nodes(true, [nodedown_reason, {node_type, all}]), + ok = net_kernel:monitor_nodes(true, [nodedown_reason, {node_type, all}, connection_id]), + ok = net_kernel:monitor_nodes(true, #{node_type => all, nodedown_reason => true}), + ok = net_kernel:monitor_nodes(true, #{node_type => all, nodedown_reason => true, connection_id => true}), Names = get_numbered_nodenames(3, node), [NN1, NN2, NN3] = Names, @@ -1117,27 +1119,90 @@ monitor_nodes_misc(DCfg, _Config) -> receive {nodeup, N1} -> ok end, + receive {nodeup, N1, #{node_type := visible}} -> ok end, + receive {nodeup, N2, #{node_type := hidden}} -> ok end, receive {nodeup, N1, [{node_type, visible}]} -> ok end, - receive {nodeup, N1, [{node_type, visible}]} -> ok end, - receive {nodeup, N2, [{node_type, hidden}]} -> ok end, receive {nodeup, N2, [{node_type, hidden}]} -> ok end, + NodesInfo = erlang:nodes(connected, #{connection_id => true}), + + {N1, #{connection_id := N1CId}} = lists:keyfind(N1, 1, NodesInfo), + {N2, #{connection_id := N2CId}} = lists:keyfind(N2, 1, NodesInfo), + + ct:pal("N1: ~p ~p~n", [N1, N1CId]), + ct:pal("N2: ~p ~p~n", [N2, N2CId]), + + receive {nodeup, N1, #{node_type := visible, connection_id := N1CId}} -> ok end, + receive {nodeup, N2, #{node_type := hidden, connection_id := N2CId}} -> ok end, + + N1UpInfoSorted = lists:sort([{node_type, visible},{connection_id, N1CId}]), + N2UpInfoSorted = lists:sort([{node_type, hidden},{connection_id, N2CId}]), + + receive {nodeup, N1, UpN1Info} -> N1UpInfoSorted = lists:sort(UpN1Info) end, + receive {nodeup, N2, UpN2Info} -> N2UpInfoSorted = lists:sort(UpN2Info) end, + stop_node(N1), stop_node(N2), - VisbleDownInfo = lists:sort([{node_type, visible}, - {nodedown_reason, connection_closed}]), - HiddenDownInfo = lists:sort([{node_type, hidden}, - {nodedown_reason, connection_closed}]), - receive {nodedown, N1} -> ok end, - receive {nodedown, N1, Info1A} -> VisbleDownInfo = lists:sort(Info1A) end, - receive {nodedown, N1, Info1B} -> VisbleDownInfo = lists:sort(Info1B) end, - receive {nodedown, N2, Info2A} -> HiddenDownInfo = lists:sort(Info2A) end, - receive {nodedown, N2, Info2B} -> HiddenDownInfo = lists:sort(Info2B) end, + receive {nodedown, N1, #{node_type := visible, + nodedown_reason := connection_closed}} -> ok end, + receive {nodedown, N1, #{node_type := visible, + nodedown_reason := connection_closed, + connection_id := N1CId}} -> ok end, + receive {nodedown, N2, #{node_type := hidden, + nodedown_reason := connection_closed}} -> ok end, + receive {nodedown, N2, #{node_type := hidden, + nodedown_reason := connection_closed, + connection_id := N2CId}} -> ok end, + + N1ADownInfoSorted = lists:sort([{node_type, visible}, + {nodedown_reason, connection_closed}]), + N1BDownInfoSorted = lists:sort([{node_type, visible}, + {nodedown_reason, connection_closed}, + {connection_id, N1CId}]), + N2ADownInfoSorted = lists:sort([{node_type, hidden}, + {nodedown_reason, connection_closed}]), + N2BDownInfoSorted = lists:sort([{node_type, hidden}, + {nodedown_reason, connection_closed}, + {connection_id, N2CId}]), + + receive + {nodedown, N1, N1Info1} -> + case lists:sort(N1Info1) of + N1ADownInfoSorted -> + receive + {nodedown, N1, N1Info2} -> + N1BDownInfoSorted = lists:sort(N1Info2) + end; + N1BDownInfoSorted -> + receive + {nodedown, N1, N1Info2} -> + N1ADownInfoSorted = lists:sort(N1Info2) + end + end + end, + receive + {nodedown, N2, N2Info1} -> + case lists:sort(N2Info1) of + N2ADownInfoSorted -> + receive + {nodedown, N2, N2Info2} -> + N2BDownInfoSorted = lists:sort(N2Info2) + end; + N2BDownInfoSorted -> + receive + {nodedown, N2, N2Info2} -> + N2ADownInfoSorted = lists:sort(N2Info2) + end + end + end, ok = net_kernel:monitor_nodes(false, [{node_type, all}, nodedown_reason]), + ok = net_kernel:monitor_nodes(false, [nodedown_reason, {node_type, all}, connection_id]), + ok = net_kernel:monitor_nodes(false, #{node_type => all, nodedown_reason => true}), + ok = net_kernel:monitor_nodes(false, #{node_type => all, nodedown_reason => true, connection_id => true}), {ok, N3} = start_node(DCfg, NN3), receive {nodeup, N3} -> ok end, @@ -1273,7 +1338,11 @@ monitor_nodes_errors(Config) when is_list(Config) -> [gurka]}} = net_kernel:monitor_nodes(true, [gurka]), {error, - {options_not_a_list, + {unknown_options, + #{gurka := true}}} = net_kernel:monitor_nodes(true, + #{gurka => true}), + {error, + {invalid_options, gurka}} = net_kernel:monitor_nodes(true, gurka), {error, @@ -1295,6 +1364,10 @@ monitor_nodes_errors(Config) when is_list(Config) -> {node_type, blaha}}} = net_kernel:monitor_nodes(true, [{node_type, blaha}]), + {error, + {bad_option_value, + #{node_type := blaha}}} + = net_kernel:monitor_nodes(true, #{node_type => blaha}), MonNodeState = monitor_node_state(), ok. diff --git a/lib/stdlib/src/erl_internal.erl b/lib/stdlib/src/erl_internal.erl index 939abaff00..bf170fd7b4 100644 --- a/lib/stdlib/src/erl_internal.erl +++ b/lib/stdlib/src/erl_internal.erl @@ -351,6 +351,7 @@ bif(node, 0) -> true; bif(node, 1) -> true; bif(nodes, 0) -> true; bif(nodes, 1) -> true; +bif(nodes, 2) -> true; bif(now, 0) -> true; bif(open_port, 2) -> true; bif(pid_to_list, 1) -> true; diff --git a/lib/stdlib/src/stdlib.app.src b/lib/stdlib/src/stdlib.app.src index f1a1afaf72..a48dbf0fc5 100644 --- a/lib/stdlib/src/stdlib.app.src +++ b/lib/stdlib/src/stdlib.app.src @@ -108,6 +108,6 @@ dets]}, {applications, [kernel]}, {env, []}, - {runtime_dependencies, ["sasl-3.0","kernel-6.0","erts-10.7.1","crypto-3.3", + {runtime_dependencies, ["sasl-3.0","kernel-6.0","erts-@OTP-17934@","crypto-3.3", "compiler-5.0"]} ]}. |