summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--erts/doc/src/erl_nif.xml2
-rw-r--r--erts/doc/src/notes.xml36
-rw-r--r--erts/emulator/beam/erl_bif_info.c15
-rw-r--r--erts/emulator/beam/erl_db_hash.c6
-rw-r--r--erts/emulator/beam/erl_db_util.c12
-rw-r--r--erts/emulator/beam/erl_db_util.h30
-rw-r--r--erts/emulator/beam/erl_node_tables.c2
-rw-r--r--erts/emulator/beam/erl_process.c23
-rw-r--r--erts/emulator/beam/erl_process.h1
-rw-r--r--erts/emulator/beam/external.c4
-rw-r--r--erts/emulator/test/lttng_SUITE.erl168
-rw-r--r--erts/emulator/test/socket_SUITE.erl10
-rw-r--r--lib/common_test/doc/src/ct_netconfc.xml376
-rw-r--r--lib/common_test/src/ct_netconfc.erl1564
-rw-r--r--lib/common_test/test/ct_test_support.erl13
-rw-r--r--lib/erl_docgen/priv/xsl/db_pdf.xsl6
-rw-r--r--lib/ftp/src/ftp.erl7
-rw-r--r--lib/inets/doc/src/httpc.xml16
-rw-r--r--lib/inets/doc/src/notes.xml19
-rw-r--r--lib/inets/src/http_server/mod_esi.erl22
-rw-r--r--lib/inets/test/httpd_SUITE.erl28
-rw-r--r--lib/kernel/doc/src/notes.xml18
-rw-r--r--lib/kernel/src/group_history.erl14
-rw-r--r--lib/kernel/test/gen_tcp_misc_SUITE.erl53
-rw-r--r--lib/megaco/src/engine/megaco_monitor.erl2
-rw-r--r--lib/megaco/test/megaco_SUITE.erl14
-rw-r--r--lib/megaco/test/megaco_mess_test.erl343
-rw-r--r--lib/megaco/test/megaco_test_lib.erl83
-rw-r--r--lib/megaco/test/megaco_timer_test.erl15
-rw-r--r--lib/sasl/test/installer.erl2
-rw-r--r--lib/sasl/test/release_handler_SUITE.erl2
-rw-r--r--lib/sasl/test/rh_test_lib.erl2
-rw-r--r--lib/snmp/src/agent/snmp_generic.erl8
-rw-r--r--lib/snmp/src/agent/snmpa_general_db.erl12
-rw-r--r--lib/snmp/test/snmp_agent_test.erl71
-rw-r--r--lib/snmp/test/snmp_manager_test.erl1038
-rw-r--r--lib/snmp/test/snmp_test_lib.erl32
-rw-r--r--lib/snmp/test/snmp_test_mgr.erl320
-rw-r--r--lib/ssh/doc/src/notes.xml16
-rw-r--r--lib/ssh/doc/src/ssh.xml46
-rw-r--r--lib/ssh/src/ssh.erl135
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl52
-rw-r--r--lib/ssh/src/ssh_info.erl35
-rw-r--r--lib/ssh/src/ssh_options.erl282
-rw-r--r--lib/ssh/src/ssh_system_sup.erl18
-rw-r--r--lib/ssh/test/ssh_sup_SUITE.erl56
-rw-r--r--lib/ssl/src/ssl.erl4
-rw-r--r--lib/ssl/src/ssl_handshake.erl2
-rw-r--r--lib/stdlib/doc/src/gen_statem.xml183
-rw-r--r--lib/stdlib/doc/src/sys.xml28
-rw-r--r--lib/stdlib/src/gen_statem.erl410
-rw-r--r--lib/stdlib/src/sys.erl2
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl119
-rw-r--r--lib/stdlib/test/rand_SUITE.erl17
-rw-r--r--lib/stdlib/test/supervisor_SUITE.erl6
-rw-r--r--lib/syntax_tools/doc/src/notes.xml17
-rw-r--r--make/otp.mk.in2
-rw-r--r--otp_versions.table1
-rw-r--r--system/doc/design_principles/statem.xml158
59 files changed, 3302 insertions, 2676 deletions
diff --git a/erts/doc/src/erl_nif.xml b/erts/doc/src/erl_nif.xml
index 2283cee2b5..4eae346ead 100644
--- a/erts/doc/src/erl_nif.xml
+++ b/erts/doc/src/erl_nif.xml
@@ -52,7 +52,7 @@
VM will misbehave.</p>
<list type="bulleted">
<item>
- <p>A native function that crash will crash the whole VM.</p>
+ <p>A native function that crashes will crash the whole VM.</p>
</item>
<item>
<p>An erroneously implemented native function can cause a VM
diff --git a/erts/doc/src/notes.xml b/erts/doc/src/notes.xml
index 5ca387ffd8..1c065500e1 100644
--- a/erts/doc/src/notes.xml
+++ b/erts/doc/src/notes.xml
@@ -640,6 +640,42 @@
</section>
+<section><title>Erts 10.3.5.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p><c>process_info(P,binary)</c> would neglect to look
+ through heap fragments, potentially missing a few
+ binaries associated with the process.</p>
+ <p>
+ Own Id: OTP-15978 Aux Id: ERIERL-366 </p>
+ </item>
+ <item>
+ <p>
+ Fixed bug triggered if a process is killed during call to
+ <c>persistent_term:put</c> or
+ <c>persistent_term:erase</c>.</p>
+ <p>
+ Own Id: OTP-16041</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>Fixed rare emulator crash in
+ <c>instrument:allocations/0-1</c>.</p>
+ <p>
+ Own Id: OTP-15983</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erts 10.3.5.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/erts/emulator/beam/erl_bif_info.c b/erts/emulator/beam/erl_bif_info.c
index 21e8585e83..57fc5ec131 100644
--- a/erts/emulator/beam/erl_bif_info.c
+++ b/erts/emulator/beam/erl_bif_info.c
@@ -165,20 +165,21 @@ current_stacktrace(Process* p, ErtsHeapFactory *hfact, Process* rp,
Eterm
erts_bld_bin_list(Uint **hpp, Uint *szp, ErlOffHeap* oh, Eterm tail)
{
- struct erl_off_heap_header* ohh;
+ union erl_off_heap_ptr u;
Eterm res = tail;
Eterm tuple;
+ struct erts_tmp_aligned_offheap tmp;
- for (ohh = oh->first; ohh; ohh = ohh->next) {
- if (ohh->thing_word == HEADER_PROC_BIN) {
- ProcBin* pb = (ProcBin*) ohh;
- Eterm val = erts_bld_uword(hpp, szp, (UWord) pb->val);
- Eterm orig_size = erts_bld_uint(hpp, szp, pb->val->orig_size);
+ for (u.hdr = oh->first; u.hdr; u.hdr = u.hdr->next) {
+ erts_align_offheap(&u, &tmp);
+ if (u.hdr->thing_word == HEADER_PROC_BIN) {
+ Eterm val = erts_bld_uword(hpp, szp, (UWord) u.pb->val);
+ Eterm orig_size = erts_bld_uint(hpp, szp, u.pb->val->orig_size);
if (szp)
*szp += 4+2;
if (hpp) {
- Uint refc = (Uint) erts_refc_read(&pb->val->intern.refc, 1);
+ Uint refc = (Uint) erts_refc_read(&u.pb->val->intern.refc, 1);
tuple = TUPLE3(*hpp, val, orig_size, make_small(refc));
res = CONS(*hpp + 4, tuple, res);
*hpp += 4+2;
diff --git a/erts/emulator/beam/erl_db_hash.c b/erts/emulator/beam/erl_db_hash.c
index ac1e9beaa5..2bcdb47a54 100644
--- a/erts/emulator/beam/erl_db_hash.c
+++ b/erts/emulator/beam/erl_db_hash.c
@@ -3328,6 +3328,12 @@ void db_foreach_offheap_hash(DbTable *tbl,
int i;
int nactive = NACTIVE(tb);
+ if (nactive > tb->nslots) {
+ /* Table is being emptied by delete/1 or delete_all_objects/1 */
+ ASSERT(!(tb->common.status & (DB_PRIVATE|DB_PROTECTED|DB_PUBLIC)));
+ nactive = tb->nslots;
+ }
+
for (i = 0; i < nactive; i++) {
list = BUCKET(tb,i);
while(list != 0) {
diff --git a/erts/emulator/beam/erl_db_util.c b/erts/emulator/beam/erl_db_util.c
index 6a48f5c74e..096cb8a778 100644
--- a/erts/emulator/beam/erl_db_util.c
+++ b/erts/emulator/beam/erl_db_util.c
@@ -3288,22 +3288,15 @@ Eterm db_copy_element_from_ets(DbTableCommon* tb, Process* p,
void db_cleanup_offheap_comp(DbTerm* obj)
{
union erl_off_heap_ptr u;
- ProcBin tmp;
+ struct erts_tmp_aligned_offheap tmp;
for (u.hdr = obj->first_oh; u.hdr; u.hdr = u.hdr->next) {
- if ((UWord)u.voidp % sizeof(Uint) != 0) { /* unaligned ptr */
- sys_memcpy(&tmp, u.voidp, sizeof(tmp));
- /* Warning, must pass (void*)-variable to memcpy. Otherwise it will
- cause Bus error on Sparc due to false compile time assumptions
- about word aligned memory (type cast is not enough) */
- u.pb = &tmp;
- }
+ erts_align_offheap(&u, &tmp);
switch (thing_subtag(u.hdr->thing_word)) {
case REFC_BINARY_SUBTAG:
erts_bin_release(u.pb->val);
break;
case FUN_SUBTAG:
- ASSERT(u.pb != &tmp);
if (erts_refc_dectest(&u.fun->fe->refc, 0) == 0) {
erts_erase_fun_entry(u.fun->fe);
}
@@ -3314,7 +3307,6 @@ void db_cleanup_offheap_comp(DbTerm* obj)
break;
default:
ASSERT(is_external_header(u.hdr->thing_word));
- ASSERT(u.pb != &tmp);
erts_deref_node_entry(u.ext->node, make_boxed(u.ep));
break;
}
diff --git a/erts/emulator/beam/erl_db_util.h b/erts/emulator/beam/erl_db_util.h
index 7a21de9a6c..f038dba89a 100644
--- a/erts/emulator/beam/erl_db_util.h
+++ b/erts/emulator/beam/erl_db_util.h
@@ -543,6 +543,17 @@ ERTS_GLB_INLINE Eterm erts_db_make_match_prog_ref(Process *p, Binary *mp, Eterm
ERTS_GLB_INLINE Binary *erts_db_get_match_prog_binary(Eterm term);
ERTS_GLB_INLINE Binary *erts_db_get_match_prog_binary_unchecked(Eterm term);
+/* @brief Ensure off-heap header is word aligned, make a temporary copy if not.
+ * Needed when inspecting ETS off-heap lists that may contain unaligned
+ * ProcBins if table is 'compressed'.
+ */
+struct erts_tmp_aligned_offheap
+{
+ ProcBin proc_bin;
+};
+ERTS_GLB_INLINE void erts_align_offheap(union erl_off_heap_ptr*,
+ struct erts_tmp_aligned_offheap* tmp);
+
#if ERTS_GLB_INLINE_INCL_FUNC_DEF
/*
@@ -575,6 +586,25 @@ erts_db_get_match_prog_binary(Eterm term)
return bp;
}
+ERTS_GLB_INLINE void
+erts_align_offheap(union erl_off_heap_ptr* ohp,
+ struct erts_tmp_aligned_offheap* tmp)
+{
+ if ((UWord)ohp->voidp % sizeof(UWord) != 0) {
+ /*
+ * ETS store word unaligned ProcBins in its compressed format.
+ * Make a temporary aligned copy.
+ *
+ * Warning, must pass (void*)-variable to memcpy. Otherwise it will
+ * cause Bus error on Sparc due to false compile time assumptions
+ * about word aligned memory (type cast is not enough).
+ */
+ sys_memcpy(tmp, ohp->voidp, sizeof(*tmp));
+ ASSERT(tmp->proc_bin.thing_word == HEADER_PROC_BIN);
+ ohp->pb = &tmp->proc_bin;
+ }
+}
+
#endif
/*
diff --git a/erts/emulator/beam/erl_node_tables.c b/erts/emulator/beam/erl_node_tables.c
index 285252a53e..ae3193be81 100644
--- a/erts/emulator/beam/erl_node_tables.c
+++ b/erts/emulator/beam/erl_node_tables.c
@@ -1509,10 +1509,12 @@ static void
insert_offheap(ErlOffHeap *oh, int type, Eterm id)
{
union erl_off_heap_ptr u;
+ struct erts_tmp_aligned_offheap tmp;
struct insert_offheap2_arg a;
a.type = BIN_REF;
for (u.hdr = oh->first; u.hdr; u.hdr = u.hdr->next) {
+ erts_align_offheap(&u, &tmp);
switch (thing_subtag(u.hdr->thing_word)) {
case REF_SUBTAG:
if (ErtsIsDistEntryBinary(u.mref->mb))
diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c
index 15cc07ded1..b62ec77d65 100644
--- a/erts/emulator/beam/erl_process.c
+++ b/erts/emulator/beam/erl_process.c
@@ -12117,6 +12117,8 @@ erts_proc_exit_handle_dist_monitor(ErtsMonitor *mon, void *vctxt, Sint reds)
ASSERT(c_p->flags & F_DISABLE_GC);
ASSERT(erts_monitor_is_target(mon) && mon->type == ERTS_MON_TYPE_DIST_PROC);
+ ASSERT(ctxt->dist_state == NIL);
+ ASSERT(!ctxt->yield);
mdp = erts_monitor_to_data(mon);
@@ -12161,10 +12163,14 @@ erts_proc_exit_handle_dist_monitor(ErtsMonitor *mon, void *vctxt, Sint reds)
reason);
reds_consumed = reds - (ctx.reds / TERM_TO_BINARY_LOOP_FACTOR);
switch (code) {
- case ERTS_DSIG_SEND_CONTINUE:
case ERTS_DSIG_SEND_YIELD:
+ reds_consumed = reds; /* force yield */
+ ctxt->yield = 1;
+ break;
+ case ERTS_DSIG_SEND_CONTINUE:
ctxt->dist_state = erts_dsend_export_trap_context(c_p, &ctx);
reds_consumed = reds; /* force yield */
+ ctxt->yield = 1;
break;
case ERTS_DSIG_SEND_OK:
break;
@@ -12375,6 +12381,9 @@ erts_proc_exit_handle_dist_link(ErtsLink *lnk, void *vctxt, Sint reds)
ASSERT(c_p->flags & F_DISABLE_GC);
ASSERT(lnk->type == ERTS_LNK_TYPE_DIST_PROC);
+ ASSERT(ctxt->dist_state == NIL);
+ ASSERT(!ctxt->yield);
+
dlnk = erts_link_to_other(lnk, &ldp);
dist = ((ErtsLinkDataExtended *) ldp)->dist;
@@ -12412,9 +12421,13 @@ erts_proc_exit_handle_dist_link(ErtsLink *lnk, void *vctxt, Sint reds)
reds_consumed = reds - (ctx.reds / TERM_TO_BINARY_LOOP_FACTOR);
switch (code) {
case ERTS_DSIG_SEND_YIELD:
+ reds_consumed = reds; /* force yield */
+ ctxt->yield = 1;
+ break;
case ERTS_DSIG_SEND_CONTINUE:
ctxt->dist_state = erts_dsend_export_trap_context(c_p, &ctx);
reds_consumed = reds; /* force yield */
+ ctxt->yield = 1;
break;
case ERTS_DSIG_SEND_OK:
break;
@@ -12837,6 +12850,7 @@ restart:
trap_state->pectxt.dist_links = NULL;
trap_state->pectxt.dist_monitors = NULL;
trap_state->pectxt.dist_state = NIL;
+ trap_state->pectxt.yield = 0;
erts_proc_lock(p, ERTS_PROC_LOCK_MSGQ);
@@ -12920,6 +12934,8 @@ restart:
erts_kill_dist_connection(ctx->dep, ctx->connection_id);
break;
case ERTS_DSIG_SEND_YIELD: /*SEND_YIELD_RETURN*/
+ trap_state->pectxt.dist_state = NIL;
+ goto yield;
case ERTS_DSIG_SEND_CONTINUE: { /*SEND_YIELD_CONTINUE*/
goto yield;
}
@@ -12937,7 +12953,7 @@ restart:
(void *) &trap_state->pectxt,
&trap_state->yield_state,
reds);
- if (reds <= 0 || is_not_nil(trap_state->pectxt.dist_state))
+ if (reds <= 0 || trap_state->pectxt.yield)
goto yield;
trap_state->phase = ERTS_CONTINUE_EXIT_DIST_MONITORS;
}
@@ -12952,7 +12968,7 @@ restart:
(void *) &trap_state->pectxt,
&trap_state->yield_state,
reds);
- if (reds <= 0 || is_not_nil(trap_state->pectxt.dist_state))
+ if (reds <= 0 || trap_state->pectxt.yield)
goto yield;
trap_state->phase = ERTS_CONTINUE_EXIT_DONE;
@@ -13037,6 +13053,7 @@ restart:
sys_memcpy(trap_state, &static_state, sizeof(*trap_state));
p->u.terminate = trap_state;
}
+ trap_state->pectxt.yield = 0;
ASSERT(p->scheduler_data);
ASSERT(p->scheduler_data->current_process == p);
diff --git a/erts/emulator/beam/erl_process.h b/erts/emulator/beam/erl_process.h
index a965e975d4..09a6c0e961 100644
--- a/erts/emulator/beam/erl_process.h
+++ b/erts/emulator/beam/erl_process.h
@@ -1832,6 +1832,7 @@ typedef struct {
ErtsLink *dist_links;
ErtsMonitor *dist_monitors;
Eterm dist_state;
+ int yield;
} ErtsProcExitContext;
int erts_proc_exit_handle_monitor(ErtsMonitor *mon, void *vctxt, Sint reds);
int erts_proc_exit_handle_link(ErtsLink *lnk, void *vctxt, Sint reds);
diff --git a/erts/emulator/beam/external.c b/erts/emulator/beam/external.c
index 5cea253ebe..a575e1d743 100644
--- a/erts/emulator/beam/external.c
+++ b/erts/emulator/beam/external.c
@@ -1413,7 +1413,7 @@ enum B2TState { /* order is somewhat significant */
};
typedef struct {
- int heap_size;
+ Sint heap_size;
int terms;
byte* ep;
int atom_extra_skip;
@@ -4536,7 +4536,7 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
static Sint
decoded_size(byte *ep, byte* endp, int internal_tags, B2TContext* ctx)
{
- int heap_size;
+ Sint heap_size;
int terms;
int atom_extra_skip;
Uint n;
diff --git a/erts/emulator/test/lttng_SUITE.erl b/erts/emulator/test/lttng_SUITE.erl
index 19c3844c40..f19047ba71 100644
--- a/erts/emulator/test/lttng_SUITE.erl
+++ b/erts/emulator/test/lttng_SUITE.erl
@@ -27,8 +27,7 @@
-export([t_lttng_list/1,
t_carrier_pool/1,
t_memory_carrier/1,
- t_async_io_pool/1,
- t_driver_control_ready_async/1,
+ t_driver_control/1,
t_driver_start_stop/1,
t_driver_ready_input_output/1,
t_driver_timeout/1,
@@ -36,6 +35,8 @@
t_driver_flush/1,
t_scheduler_poll/1]).
+-export([ets_load/0]).
+
-include_lib("common_test/include/ct.hrl").
suite() ->
@@ -46,10 +47,9 @@ all() ->
[t_lttng_list,
t_memory_carrier,
t_carrier_pool,
- t_async_io_pool,
t_driver_start_stop,
t_driver_ready_input_output,
- t_driver_control_ready_async,
+ t_driver_control,
t_driver_timeout,
t_driver_caller,
t_driver_flush,
@@ -124,7 +124,7 @@ t_carrier_pool(Config) ->
true ->
ok = lttng_start_event("org_erlang_otp:carrier_pool*", Config),
- ok = ets_load(),
+ ok = ets_load(Config),
Res = lttng_stop_and_view(Config),
ok = check_tracepoint("org_erlang_otp:carrier_pool_get", Res),
@@ -141,7 +141,7 @@ t_memory_carrier(Config) ->
true ->
ok = lttng_start_event("org_erlang_otp:carrier_*", Config),
- ok = ets_load(),
+ ok = ets_load(Config),
Res = lttng_stop_and_view(Config),
ok = check_tracepoint("org_erlang_otp:carrier_destroy", Res),
@@ -149,70 +149,49 @@ t_memory_carrier(Config) ->
ok
end.
-%% org_erlang_otp:aio_pool_put
-%% org_erlang_otp:aio_pool_get
-t_async_io_pool(Config) ->
- case have_async_threads() of
- false ->
- {skip, "No Async Threads configured on system."};
- true ->
- ok = lttng_start_event("org_erlang_otp:aio_pool_*", Config),
-
- Path1 = proplists:get_value(priv_dir, Config),
- {ok, [[Path2]]} = init:get_argument(home),
- {ok, _} = file:list_dir(Path1),
- {ok, _} = file:list_dir(Path2),
- {ok, _} = file:list_dir(Path1),
- {ok, _} = file:list_dir(Path2),
-
- Res = lttng_stop_and_view(Config),
- ok = check_tracepoint("org_erlang_otp:aio_pool_put", Res),
- ok = check_tracepoint("org_erlang_otp:aio_pool_get", Res),
- ok
- end.
-
-
%% org_erlang_otp:driver_start
%% org_erlang_otp:driver_stop
t_driver_start_stop(Config) ->
ok = lttng_start_event("org_erlang_otp:driver_*", Config),
timer:sleep(500),
- Path = proplists:get_value(priv_dir, Config),
- Name = filename:join(Path, "sometext.txt"),
- Bin = txt(),
- ok = file:write_file(Name, Bin),
- {ok, Bin} = file:read_file(Name),
+ os:cmd("echo hello"),
timer:sleep(500),
Res = lttng_stop_and_view(Config),
ok = check_tracepoint("org_erlang_otp:driver_start", Res),
ok = check_tracepoint("org_erlang_otp:driver_stop", Res),
- ok = check_tracepoint("org_erlang_otp:driver_control", Res),
- ok = check_tracepoint("org_erlang_otp:driver_outputv", Res),
- ok = check_tracepoint("org_erlang_otp:driver_ready_async", Res),
+ ok = check_tracepoint("org_erlang_otp:driver_output", Res),
ok.
%% org_erlang_otp:driver_control
%% org_erlang_otp:driver_outputv
-%% org_erlang_otp:driver_ready_async
-t_driver_control_ready_async(Config) ->
+t_driver_control(Config) ->
ok = lttng_start_event("org_erlang_otp:driver_control", Config),
ok = lttng_start_event("org_erlang_otp:driver_outputv", Config),
- ok = lttng_start_event("org_erlang_otp:driver_ready_async", Config),
- Path = proplists:get_value(priv_dir, Config),
- Name = filename:join(Path, "sometext.txt"),
- Bin = txt(),
- ok = file:write_file(Name, Bin),
- {ok, Bin} = file:read_file(Name),
+
+ timer:sleep(500),
+ Me = self(),
+ Pid = spawn_link(fun() -> tcp_server(Me, active) end),
+ receive {Pid, accept} -> ok end,
+ Bin = txt(),
+ Sz = byte_size(Bin),
+
+ {ok, Sock} = gen_tcp:connect("localhost", 5679, [binary, {packet, 2}]),
+ ok = gen_tcp:send(Sock, <<Sz:16, Bin/binary>>),
+ ok = gen_tcp:send(Sock, <<Sz:16, Bin/binary>>),
+ ok = gen_tcp:close(Sock),
+ receive {Pid, done} -> ok end,
+
+ timer:sleep(500),
Res = lttng_stop_and_view(Config),
ok = check_tracepoint("org_erlang_otp:driver_control", Res),
ok = check_tracepoint("org_erlang_otp:driver_outputv", Res),
- ok = check_tracepoint("org_erlang_otp:driver_ready_async", Res),
ok.
%% org_erlang_otp:driver_ready_input
%% org_erlang_otp:driver_ready_output
t_driver_ready_input_output(Config) ->
ok = lttng_start_event("org_erlang_otp:driver_ready_*", Config),
+
timer:sleep(500),
Me = self(),
Pid = spawn_link(fun() -> tcp_server(Me, active) end),
@@ -290,8 +269,27 @@ t_driver_caller(Config) ->
t_scheduler_poll(Config) ->
ok = lttng_start_event("org_erlang_otp:scheduler_poll", Config),
+ N = 100,
+
+ Me = self(),
+ Pid = spawn_link(fun() -> tcp_server(Me, {active, N*2}) end),
+ receive {Pid, accept} -> ok end,
+
+ %% We want to create a scenario where the fd is moved into a scheduler
+ %% pollset, this means we have to send many small packages to the
+ %% same socket, but not fast enough for them to all arrive at the
+ %% same time.
+ {ok, Sock} = gen_tcp:connect("localhost", 5679, [binary, {packet, 2}]),
+ [begin gen_tcp:send(Sock,txt()), receive ok -> ok end end || _ <- lists:seq(1,N)],
+
ok = memory_load(),
+ [begin gen_tcp:send(Sock,txt()), receive ok -> ok end end || _ <- lists:seq(1,N)],
+
+ ok = gen_tcp:close(Sock),
+ Pid ! die,
+ receive {Pid, done} -> ok end,
+
Res = lttng_stop_and_view(Config),
ok = check_tracepoint("org_erlang_otp:scheduler_poll", Res),
ok.
@@ -335,8 +333,37 @@ chk_caller(Port, Callback, ExpectedCaller) ->
ExpectedCaller = Caller
end.
+memory_load() ->
+ Me = self(),
+ Pids0 = [spawn_link(fun() -> memory_loop(Me, 20, <<42>>) end) || _ <- lists:seq(1,30)],
+ timer:sleep(50),
+ Pids1 = [spawn_link(fun() -> memory_loop(Me, 20, <<42>>) end) || _ <- lists:seq(1,30)],
+ [receive {Pid, done} -> ok end || Pid <- Pids0 ++ Pids1],
+ timer:sleep(500),
+ ok.
+
+memory_loop(Parent, N, Bin) ->
+ memory_loop(Parent, N, Bin, []).
+
+memory_loop(Parent, 0, _Bin, _) ->
+ Parent ! {self(), done};
+memory_loop(Parent, N, Bin0, Ls) ->
+ Bin = binary:copy(<<Bin0/binary, Bin0/binary>>),
+ memory_loop(Parent, N - 1, Bin, [a,b,c|Ls]).
+
+ets_load(Config) ->
+
+ %% Have to do on a fresh node to guarantee that carriers are created
+ {ok,Node} = start_node(Config),
+
+ Res = rpc:call(Node, ?MODULE, ets_load, []),
+
+ stop_node(Node),
+
+ Res.
ets_load() ->
+
Tid = ets:new(ets_load, [public,set]),
N = erlang:system_info(schedulers_online),
Pids = [spawn_link(fun() -> ets_shuffle(Tid) end) || _ <- lists:seq(1,N)],
@@ -368,27 +395,6 @@ ets_shuffle(Tid, I, N, Data, Data0) ->
ets_shuffle(Tid, I - 1, N, Data1, Data0)
end.
-
-
-
-memory_load() ->
- Me = self(),
- Pids0 = [spawn_link(fun() -> memory_loop(Me, 20, <<42>>) end) || _ <- lists:seq(1,30)],
- timer:sleep(50),
- Pids1 = [spawn_link(fun() -> memory_loop(Me, 20, <<42>>) end) || _ <- lists:seq(1,30)],
- [receive {Pid, done} -> ok end || Pid <- Pids0 ++ Pids1],
- timer:sleep(500),
- ok.
-
-memory_loop(Parent, N, Bin) ->
- memory_loop(Parent, N, Bin, []).
-
-memory_loop(Parent, 0, _Bin, _) ->
- Parent ! {self(), done};
-memory_loop(Parent, N, Bin0, Ls) ->
- Bin = binary:copy(<<Bin0/binary, Bin0/binary>>),
- memory_loop(Parent, N - 1, Bin, [a,b,c|Ls]).
-
tcp_server(Pid, Type) ->
{ok, LSock} = gen_tcp:listen(5679, [binary,
{reuseaddr, true},
@@ -399,14 +405,19 @@ tcp_server(Pid, Type) ->
passive_no_read ->
receive die -> ok end;
active ->
- inet:setopts(Sock, [{active, once}, {packet,2}]),
+ inet:setopts(Sock, [{active, 2}, {packet,2}]),
receive Msg1 -> io:format("msg1: ~p~n", [Msg1]) end,
- inet:setopts(Sock, [{active, once}, {packet,2}]),
receive Msg2 -> io:format("msg2: ~p~n", [Msg2]) end,
ok = gen_tcp:close(Sock);
timeout ->
Res = gen_tcp:recv(Sock, 2000, 1000),
- io:format("res ~p~n", [Res])
+ io:format("res ~p~n", [Res]);
+ {active, Number} when is_number(Number) ->
+ inet:setopts(Sock, [{active, Number}, {packet,2}]),
+ [begin
+ receive _Msg1 -> Pid ! ok, io:format("msg ~p~n", [I]) end
+ end || I <- lists:seq(1,Number)],
+ ok = gen_tcp:close(Sock)
end,
Pid ! {self(), done},
ok.
@@ -497,3 +508,20 @@ cmd(Cmd) ->
Res = os:cmd(Cmd),
io:format(">> ~ts~n", [Res]),
{ok,Res}.
+
+start_node(Config) ->
+ start_node(Config, "").
+
+start_node(Config, Args) when is_list(Config) ->
+ Pa = filename:dirname(code:which(?MODULE)),
+ Name = list_to_atom(atom_to_list(?MODULE)
+ ++ "-"
+ ++ atom_to_list(proplists:get_value(testcase, Config))
+ ++ "-"
+ ++ integer_to_list(erlang:system_time(second))
+ ++ "-"
+ ++ integer_to_list(erlang:unique_integer([positive]))),
+ test_server:start_node(Name, slave, [{args, "-pa "++Pa++" "++Args}]).
+
+stop_node(Node) ->
+ test_server:stop_node(Node).
diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl
index 7e769a5686..124ae09951 100644
--- a/erts/emulator/test/socket_SUITE.erl
+++ b/erts/emulator/test/socket_SUITE.erl
@@ -19549,10 +19549,18 @@ traffic_ping_pong_large_host_cond() ->
traffic_ping_pong_large_host_cond({unix, sunos}, _) ->
skip("TC does not work on platform");
traffic_ping_pong_large_host_cond({unix, linux}, _) ->
- is_old_fedora16(string:trim(os:cmd("cat /etc/issue")));
+ traffic_ping_pong_large_host_cond2(string:trim(os:cmd("cat /etc/issue")));
traffic_ping_pong_large_host_cond(_, _) ->
ok.
+traffic_ping_pong_large_host_cond2("Welcome to SUSE Linux Enterprise Server 10 SP1 (i586)" ++ _) ->
+ skip("TC does not work on platform");
+traffic_ping_pong_large_host_cond2("Fedora release 16 " ++ _) ->
+ skip("Very slow VM");
+traffic_ping_pong_large_host_cond2(_) ->
+ ok.
+
+
is_old_fedora16() ->
is_old_fedora16(string:trim(os:cmd("cat /etc/issue"))).
diff --git a/lib/common_test/doc/src/ct_netconfc.xml b/lib/common_test/doc/src/ct_netconfc.xml
index 8fbe5f3df6..f4d98b611b 100644
--- a/lib/common_test/doc/src/ct_netconfc.xml
+++ b/lib/common_test/doc/src/ct_netconfc.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2010</year><year>2017</year>
+ <year>2010</year><year>2019</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -37,51 +37,64 @@
<description>
- <p>NETCONF client module.</p>
-
- <p>The NETCONF client is compliant with RFC 4741 NETCONF Configuration
- Protocol and RFC 4742 Using the NETCONF Configuration Protocol over
- Secure SHell (SSH).</p>
+ <p>NETCONF client module compliant with RFC 6241, NETCONF Configuration
+ Protocol, and RFC 6242, Using the NETCONF Configuration Protocol over
+ Secure SHell (SSH), and with support for RFC 5277, NETCONF Event
+ Notifications.</p>
<marker id="Connecting"/>
<p><em>Connecting to a NETCONF server</em></p>
- <p>NETCONF sessions can either be opened by a single call
- to <seealso marker="#open-1"><c>open/1,2</c></seealso> or by a call
- to <seealso marker="#connect-1"><c>connect/1,2</c></seealso> followed
- by one or more calls to
- <seealso marker="#session-1"><c>session/1,2,3</c></seealso>.</p>
-
- <p>The properties of the sessions will be exactly the same, except
- that when
- using <seealso marker="#connect-1"><c>connect/1,2</c></seealso>, you
- may start multiple sessions over the same SSH connection. Each
- session is implemented as an SSH channel.</p>
-
- <p><seealso marker="#open-1"><c>open/1,2</c></seealso> will establish one
- SSH connection with one SSH channel implementing one NETCONF
- session. You may start mutiple sessions by
- calling <seealso marker="#open-1"><c>open/1,2</c></seealso> multiple
- times, but then a new SSH connection will be established for each
- session.</p>
-
- <p>For each server to test against, the following entry can be added to a
- configuration file:</p>
+ <p>Call <seealso marker="#connect-1"><c>connect/1,2</c></seealso>
+ to establish a connection to a server, then pass the returned
+ handle to <seealso marker="#session-1"><c>session/1-3</c></seealso> to
+ establish a NETCONF session on a new SSH channel.
+ Each call to
+ <seealso marker="#session-1"><c>session/1-3</c></seealso> establishes a
+ new session on the same connection, and results in a hello message
+ to the server.</p>
+
+ <p>Alternately,
+ <seealso marker="#open-1"><c>open/1,2</c></seealso> can be used to
+ establish a single session on a dedicated connection.
+ (Or, equivalently,
+ <seealso marker="#only_open-1"><c>only_open/1,2</c></seealso>
+ followed by <seealso marker="#hello-1"><c>hello/1-3</c></seealso>.)</p>
+
+ <p>Connect/session options can be specified in a configuration
+ file with entries like the following.</p>
<pre>
- {server_id(),options()}.</pre>
+ {server_id(), [option()]}.</pre>
<p>The <seealso marker="#type-server_id"><c>server_id()</c></seealso>
- or an associated
- <seealso marker="ct#type-target_name"><c>ct:target_name()</c></seealso>
- must then be used in calls to
- <seealso marker="#connect-2"><c>connect/2</c></seealso>
- or <seealso marker="#open-2"><c>open/2</c></seealso>.</p>
-
- <p>If no configuration exists for a server,
- use <seealso marker="#connect-1"><c>connect/1</c></seealso>
- or <seealso marker="#open-1"><c>open/1</c></seealso> instead,
- and specify all necessary options in the <c>Options</c> parameter.</p>
+ or an associated
+ <seealso marker="ct#type-target_name"><c>ct:target_name()</c></seealso>
+ can then be passed to the aforementioned functions to use the
+ referenced configuration.</p>
+
+ <marker id="Signaling"/>
+ <p><em>Signaling</em></p>
+
+ <p>Protocol operations in the NETCONF protocol are realized as remote
+ procedure calls (RPCs) from client to server and a corresponding
+ reply from server to client.
+ RPCs are sent using like-named functions (eg.
+ <seealso marker="#edit_config-3"><c>edit_config/3-5</c></seealso>
+ to send an edit-config RPC), with the server reply
+ as return value.
+ There are functions for each RPC defined in RFC 6241 and
+ the create-subscription RPC from RFC 5277, all of which are
+ wrappers on <seealso marker="#send_rpc-2"><c>send_rpc/2,3</c></seealso>,
+ that can be used to send an arbitrary RPC
+ not defined in RFC 6241 or RFC 5277.</p>
+
+ <p>All of the signaling functions have one variant with a
+ <c>Timeout</c> argument and one without, corresponding to an
+ infinite timeout.
+ The latter is inappropriate in most cases since a non-response by
+ the server or a missing message-id causes the call to hang
+ indefinitely.</p>
<marker id="Logging"/>
<p><em>Logging</em></p>
@@ -93,7 +106,7 @@
<pre>
suite() -&gt;
- [{ct_hooks, [{cth_conn_log, [{<seealso marker="ct#type-conn_log_mod"><c>ct:conn_log_mod()</c></seealso>,<seealso marker="ct#type-conn_log_options"><c>ct:conn_log_options()</c></seealso>}]}]}].</pre>
+ [{ct_hooks, [{cth_conn_log, [{<seealso marker="ct#type-conn_log_mod"><c>ct:conn_log_mod()</c></seealso>, <seealso marker="ct#type-conn_log_options"><c>ct:conn_log_options()</c></seealso>}]}]}].</pre>
<p><c>conn_log_mod()</c> is the name of the <c>Common Test</c> module
implementing the connection protocol, for example, <c>ct_netconfc</c>.</p>
@@ -133,7 +146,7 @@
configuration variable <c>ct_conn_log</c>:</p>
<pre>
- {ct_conn_log,[{<seealso marker="ct#type-conn_log_mod"><c>ct:conn_log_mod()</c></seealso>,<seealso marker="ct#type-conn_log_options"><c>ct:conn_log_options()</c></seealso>}]}.</pre>
+ {ct_conn_log,[{<seealso marker="ct#type-conn_log_mod"><c>ct:conn_log_mod()</c></seealso>, <seealso marker="ct#type-conn_log_options"><c>ct:conn_log_options()</c></seealso>}]}.</pre>
<p>For example:</p>
@@ -185,100 +198,111 @@
would cause HTML logging of all NETCONF connections in to the test
case HTML log.</p>
- <marker id="Notifications"/>
- <p><em>Notifications</em></p>
-
- <p>The NETCONF client is also compliant with RFC 5277 NETCONF Event
- Notifications, which defines a mechanism for an asynchronous message
- notification delivery service for the NETCONF protocol.</p>
-
- <p>Specific functions to support this are
- <seealso marker="#create_subscription-1"><c>create_subscription/1-6</c></seealso>
- and
- <seealso marker="#get_event_streams-1"><c>get_event_streams/1-3</c></seealso>.</p>
-
- <marker id="Default_timeout"/>
- <p><em>Default Timeout</em></p>
-
- <p>Most of the functions in this module have one variant with
- a <c>Timeout</c> parameter, and one without. If nothing else is
- specified, the default value <c>infinity</c> is used when
- the <c>Timeout</c> parameter is not given.</p>
-
</description>
+ <!-- ====================================================================== -->
+
<datatypes>
<datatype>
<name name="client"/>
- </datatype>
- <datatype>
- <name name="error_reason"/>
- </datatype>
- <datatype>
- <name name="event_time"/>
+ <desc>
+ <p>Handle to a NETCONF session, as required by signaling
+ functions.</p>
+ </desc>
</datatype>
<datatype>
<name name="handle"/>
<desc>
- <p>Opaque reference for a connection to a NETCONF server or a
- NETCONF session.</p>
+ <p>Handle to a connection to a NETCONF server as
+ returned by
+ <seealso marker="#connect-1"><c>connect/1,2</c></seealso>,
+ or to a session as returned by
+ <seealso marker="#session-1"><c>session/1-3</c></seealso>,
+ <seealso marker="#open-1"><c>open/1,2</c></seealso>,
+ or <seealso marker="#only_open-1"><c>only_open/1,2</c></seealso>.</p>
</desc>
</datatype>
<datatype>
- <name name="host"/>
- </datatype>
- <datatype>
- <name name="netconf_db"/>
+ <name name="xs_datetime"/>
+ <desc>
+ <p>Date and time of a startTime/stopTime element in an RFC
+ 5277 create-subscription request. Of XML primitive type
+ <c>dateTime</c>, which has the (informal) form</p>
+ <pre>
+ [-]YYYY-MM-DDThh:mm:ss[.s][Z|(+|-)hh:mm]</pre>
+ <p>where <c>T</c> and <c>Z</c> are literal and <c>.s</c> is
+ one or more fractional seconds.</p>
+ </desc>
</datatype>
<datatype>
- <name name="notification"/>
+ <name name="event_time"/>
</datatype>
<datatype>
<name name="notification_content"/>
</datatype>
<datatype>
- <name name="option"/>
+ <name name="notification"/>
<desc>
- <p><c>SshConnectOption</c> is any valid option to
- <seealso marker="ssh:ssh#connect-3"><c>ssh:connect/3,4</c></seealso>.
- Common options used are <c>user</c>, <c>password</c>
- and <c>user_dir</c>. The <c>SshConnectOptions</c> are
- verfied by the SSH application.</p>
+ <p>Event notification messages sent as a result of calls to
+ <seealso marker="#create_subscription-2"><c>create_subscription/2,3</c></seealso>.</p>
</desc>
</datatype>
<datatype>
- <name name="options"/>
+ <name name="option"/>
<desc>
- <p>Options used for setting up an SSH connection to a NETCONF
- server.</p>
+ <p>Options <c>host</c> and <c>port</c> specify the
+ server endpoint to which to connect, and are passed directly
+ to <seealso
+ marker="ssh:ssh#connect-3"><c>ssh:connect/4</c></seealso>,
+ as are arbitrary ssh options. Common options are <c>user</c>,
+ <c>password</c> and <c>user_dir</c>.</p>
+
+ <p>Option <c>timeout</c> specifies the number of
+ milliseconds to allow for connection establishment and, if the
+ function in question results in an outgoing hello message,
+ reception of the server hello. The timeout applies to
+ connection and hello independently;
+ one timeout for connection establishment, another for hello
+ reception.</p>
+
+ <p>Option <c>capability</c> specifies the content of a
+ corresponding element in an outgoing hello message, each
+ option specifying the content of a single element.
+ If no base NETCONF capability is configured then the RFC 4741
+ 1.0 capability, "urn:ietf:params:netconf:base:1.0", is added,
+ otherwise not.
+ In particular, the RFC 6241 1.1 capability must be explicitly
+ configured.
+ NETCONF capabilities can be specified using the shorthand notation
+ defined in RFC 6241, any capability string starting with a
+ colon being prefixed by either "urn:ietf:params:netconf" or
+ "urn:ietf:params:netconf:capability", as appropriate.</p>
+
+ <p>Capability options are ignored by connect/1-3 and only_open/1-2,
+ which don't result in an outgoing hello message.</p>
</desc>
</datatype>
<datatype>
<name name="server_id"/>
<desc>
- <p>The identity of a server, specified in a configuration
- file.</p>
- </desc>
- </datatype>
- <datatype>
- <name name="simple_xml"/>
- <desc>
- <p>This type is further described in application
- <seealso marker="xmerl:index"><c>xmerl</c></seealso>.</p>
+ <p>Identity of connection or session configuration in a
+ configuration file.</p>
</desc>
</datatype>
<datatype>
<name name="stream_data"/>
- <desc>
- <p>For details about the data format for the string values, see
- "XML Schema for Event Notifications" in RFC 5277.</p>
- </desc>
</datatype>
<datatype>
<name name="stream_name"/>
</datatype>
<datatype>
<name name="streams"/>
+ <desc>
+ <p>Stream information as returned by
+ <seealso marker="#get_event_streams-1"><c>get_event_streams/1-3</c></seealso>.
+ See RFC 5277, "XML Schema for Event Notifications", for detail
+ on the format of the string values.</p>
+ </desc>
</datatype>
<datatype>
<name name="xml_attribute_tag"/>
@@ -296,20 +320,28 @@
<name name="xml_tag"/>
</datatype>
<datatype>
+ <name name="simple_xml"/>
+ <desc>
+ <p>Representation of XML, as described in application
+ <seealso marker="xmerl:index"><c>xmerl</c></seealso>.</p>
+ </desc>
+ </datatype>
+ <datatype>
<name name="xpath"/>
</datatype>
<datatype>
- <name name="xs_datetime"/>
- <desc>
- <p>This date and time identifier has the same format as the XML type
- <c>dateTime</c> and is compliant with RFC 3339 Date and Time on
- the Internet Timestamps. The format is as follows:</p>
- <pre>
- [-]CCYY-MM-DDThh:mm:ss[.s][Z|(+|-)hh:mm]</pre>
- </desc>
+ <name name="error_reason"/>
+ </datatype>
+ <datatype>
+ <name name="host"/>
+ </datatype>
+ <datatype>
+ <name name="netconf_db"/>
</datatype>
</datatypes>
+ <!-- ====================================================================== -->
+
<funcs>
<func>
<name name="action" arity="2" since="OTP R15B02"/>
@@ -352,11 +384,7 @@
reference returned from this
function is required as connection identifier when opening
sessions over this connection, see
- <seealso marker="#session-1"><c>session/1,2,3</c></seealso>.</p>
-
- <p>Option <c>timeout</c> (milliseconds) is used when setting up the
- SSH connection. It is not used for any other purposes during the
- lifetime of the connection.</p>
+ <seealso marker="#session-1"><c>session/1-3</c></seealso>.</p>
</desc>
</func>
@@ -371,10 +399,9 @@
<c>target_name()</c> associated with such an Id, then the options
for this server are fetched from the configuration file.</p>
- <p>Argument <c><anno>ExtraOptions</anno></c> is added to the
- options found in the configuration file. If the same options
- are specified, the values from the configuration file
- overwrite <c><anno>ExtraOptions</anno></c>.</p>
+ <p>The options list is added to those of the
+ configuration file. If an option is specified in both lists,
+ the configuration file takes precedence.</p>
<p>If the server is not specified in a configuration file, use
<seealso marker="#connect-1"><c>connect/1</c></seealso>
@@ -384,17 +411,13 @@
reference returned from this
function can be used as connection identifier when opening
sessions over this connection, see
- <seealso marker="#session-1"><c>session/1,2,3</c></seealso>.
+ <seealso marker="#session-1"><c>session/1-3</c></seealso>.
However, if <c><anno>KeyOrName</anno></c> is a
<c>target_name()</c>, that is, if the server is named through a
call to <seealso marker="ct#require-2"><c>ct:require/2</c></seealso>
or a <c>require</c> statement in the test suite, then this name can
be used instead of
<seealso marker="#type-handle"><c>handle()</c></seealso>.</p>
-
- <p>Option <c>timeout</c> (milliseconds) is used when setting up the
- SSH connection. It is not used for any other purposes during the
- lifetime of the connection.</p>
</desc>
</func>
@@ -412,80 +435,61 @@
</func>
<func>
- <name since="OTP R15B02">create_subscription(Client) -> Result</name>
- <name since="OTP R15B02">create_subscription(Client, Stream) -> Result</name>
- <name since="OTP R15B02">create_subscription(Client, Stream, Filter) -> Result</name>
- <name since="OTP R15B02">create_subscription(Client, Stream, Filter, Timeout) -> Result</name>
- <name name="create_subscription" arity="5" clause_i="2" since="OTP R15B02"/>
- <name name="create_subscription" arity="6" since="OTP R15B02"/>
+ <name name="create_subscription" arity="2" clause_i="1" since="OTP 22.1"/>
+ <name name="create_subscription" arity="3" clause_i="1" since="OTP 22.1"/>
<fsummary>Creates a subscription for event notifications.</fsummary>
<desc>
- <p>Creates a subscription for event notifications.</p>
-
- <p>This function sets up a subscription for NETCONF event
- notifications of the specified stream type, matching the specified
- filter. The calling process receives notifications as messages of
- type <seealso marker="#type-notification"><c>notification()</c></seealso>.</p>
-
- <p>Only a subset of the function clauses are show above. The
- full set of valid combinations of input parameters is as
- follows:</p>
-
-<pre>create_subscription(Client)
+ <p>Creates a subscription for event notifications by sending
+ an RFC 5277 create-subscription RPC to the server.
+ The calling process receives events as messages of
+ type <seealso marker="#type-notification"><c>notification()</c></seealso>.</p>
-create_subscription(Client, Timeout)
-create_subscription(Client, Stream)
-create_subscription(Client, Filter)
-
-create_subscription(Client, Stream, Timeout)
-create_subscription(Client, Filter, Timeout)
-create_subscription(Client, Stream, Filter)
-create_subscription(Client, StartTime, StopTime)
-
-create_subscription(Client, Stream, Filter, Timeout)
-create_subscription(Client, StartTime, StopTime, Timeout)
-create_subscription(Client, Stream, StartTime, StopTime)
-create_subscription(Client, Filter, StartTime, StopTime)
-
-create_subscription(Client, Stream, StartTime, StopTime, Timeout)
-create_subscription(Client, Stream, Filter, StartTime, StopTime)
-create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
+ <p>From RFC 5722, 2.1 Subscribing to Receive Event Notifications:</p>
<taglist>
<tag><c><anno>Stream</anno></c></tag>
- <item><p>Optional parameter that indicates which stream of event
+ <item><p>Indicates which stream of event
is of interest. If not present, events in the default NETCONF
stream are sent.</p></item>
<tag><c><anno>Filter</anno></c></tag>
- <item><p>Optional parameter that indicates which subset of all
+ <item><p>Indicates which subset of all
possible events is of interest. The parameter format is the
same as that of the filter parameter in the NETCONF protocol
operations. If not present, all events not precluded by other
parameters are sent.</p></item>
<tag><c><anno>StartTime</anno></c></tag>
- <item><p>Optional parameter used to trigger the replay feature and
+ <item><p>Used to trigger the replay feature and
indicate that the replay is to start at the time specified.
If <c><anno>StartTime</anno></c> is not present, this is not a
- replay subscription.</p>
- <p>It is not valid to specify start times that are later than
+ replay subscription.
+ It is not valid to specify start times that are later than
the current time. If <c><anno>StartTime</anno></c> is specified
earlier than the log can support, the replay begins with the
- earliest available notification.</p>
- <p>This parameter is of type <c>dateTime</c> and compliant to
+ earliest available notification.
+ This parameter is of type <c>dateTime</c> and compliant to
RFC 3339. Implementations must support time zones.</p></item>
<tag><c><anno>StopTime</anno></c></tag>
- <item><p>Optional parameter used with the optional replay feature
+ <item><p>Used with the optional replay feature
to indicate the newest notifications of interest. If
<c><anno>StopTime</anno></c> is not present, the notifications
- continues until the subscription is terminated.</p>
- <p>Must be used with and be later than <c>StartTime</c>. Values
+ continues until the subscription is terminated.
+ Must be used with and be later than <c>StartTime</c>. Values
of <c><anno>StopTime</anno></c> in the future are valid. This
parameter is of type <c>dateTime</c> and compliant to RFC 3339.
Implementations must support time zones.</p></item>
</taglist>
- <p>For more details about the event notification mechanism, see
- RFC 5277.</p>
+ <p>See RFC 5277 for more details. The requirement that
+ <c>StopTime</c> must only be used with <c>StartTime</c> is not
+ enforced, to allow an invalid request to be sent to the
+ server.</p>
+
+ <p>Prior to OTP 22.1, this function was documented as having
+ 15 variants in 6 arities. These are still exported for
+ backwards compatibility, but no longer documented.
+ The map-based variants documented above provide the same
+ functionality with simpler arguments.</p>
+
</desc>
</func>
@@ -561,23 +565,7 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
<name name="get_capabilities" arity="2" since="OTP R15B02"/>
<fsummary>Returns the server side capabilities.</fsummary>
<desc>
- <p>Returns the server side capabilities.</p>
-
- <p>The following capability identifiers, defined in RFC 4741 NETCONF
- Configuration Protocol, can be returned:</p>
-
- <list>
- <item><p><c>"urn:ietf:params:netconf:base:1.0"</c></p></item>
- <item><p><c>"urn:ietf:params:netconf:capability:writable-running:1.0"</c></p></item>
- <item><p><c>"urn:ietf:params:netconf:capability:candidate:1.0"</c></p></item>
- <item><p><c>"urn:ietf:params:netconf:capability:confirmed-commit:1.0"</c></p></item>
- <item><p><c>"urn:ietf:params:netconf:capability:rollback-on-error:1.0"</c></p></item>
- <item><p><c>"urn:ietf:params:netconf:capability:startup:1.0"</c></p></item>
- <item><p><c>"urn:ietf:params:netconf:capability:url:1.0"</c></p></item>
- <item><p><c>"urn:ietf:params:netconf:capability:xpath:1.0"</c></p></item>
- </list>
-
- <p>More identifiers can exist, for example, server-side namespace.</p>
+ <p>Returns the server capabilities as received in its hello message.</p>
</desc>
</func>
@@ -652,10 +640,12 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
<name name="hello" arity="3" since="OTP 17.5.3"/>
<fsummary>Exchanges hello messages with the server.</fsummary>
<desc>
- <p>Exchanges <c>hello</c> messages with the server.</p>
+ <p>Exchanges <c>hello</c> messages with the server. Returns
+ when the server hello has been received or after the
+ specified timeout.</p>
- <p>Adds optional capabilities and sends a <c>hello</c> message to the
- server and waits for the return.</p>
+ <p>Note that capabilities for an outgoing hello can be passed
+ directly to <seealso marker="#open-2"><c>open/2</c></seealso>.</p>
</desc>
</func>
@@ -740,11 +730,6 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
reference returned from this
function is required as client identifier when calling any other
function in this module.</p>
-
- <p>Option <c>timeout</c> (milliseconds) is used when setting up the
- SSH connection and when waiting for the <c>hello</c> message from
- the server. It is not used for any other purposes during the
- lifetime of the connection.</p>
</desc>
</func>
@@ -761,10 +746,9 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
<c>target_name()</c> associated with such an Id, then the options
for this server are fetched from the configuration file.</p>
- <p>Argument <c><anno>ExtraOptions</anno></c> is added to the
- options found in the configuration file. If the same
- options are specified, the values from the configuration
- file overwrite <c><anno>ExtraOptions</anno></c>.</p>
+ <p>The options list is added to those of the
+ configuration file. If an option is specified in both lists,
+ the configuration file take precedence.</p>
<p>If the server is not specified in a configuration file, use
<seealso marker="#open-1"><c>open/1</c></seealso>
@@ -780,11 +764,6 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
be used instead of
<seealso marker="#type-handle"><c>handle()</c></seealso>.</p>
- <p>Option <c>timeout</c> (milliseconds) is used when setting up the
- SSH connection and when waiting for the <c>hello</c> message from
- the server. It is not used for any other purposes during the
- lifetime of the connection.</p>
-
<p>See also
<seealso marker="ct#require-2"><c>ct:require/2</c></seealso>.</p>
</desc>
@@ -827,7 +806,6 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
<fsummary>Opens a NETCONF session as a channel on the given SSH
connection, and exchanges hello messages with the
server.</fsummary>
- <type name="session_options"/>
<type name="session_option"/>
<desc>
<p>Opens a NETCONF session as a channel on the given SSH
diff --git a/lib/common_test/src/ct_netconfc.erl b/lib/common_test/src/ct_netconfc.erl
index 6a758c4ea3..7ad6fa46e8 100644
--- a/lib/common_test/src/ct_netconfc.erl
+++ b/lib/common_test/src/ct_netconfc.erl
@@ -1,7 +1,7 @@
%%----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2012-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -26,7 +26,7 @@
%% Netconf servers can be configured by adding the following statement
%% to a configuration file:
%%
-%% {server_id(),options()}.
+%% {server_id(), [option()]}.
%%
%% The server_id() or an associated ct:target_name() shall then be
%% used in calls to open/2 connect/2.
@@ -55,13 +55,14 @@
%% The netconf client is also compliant with RFC5277 NETCONF Event
%% Notifications, which defines a mechanism for an asynchronous
%% message notification delivery service for the netconf protocol.
-%%
-%% Specific functions to support this are create_subscription/6
-%% get_event_streams/3. (The functions also exist with other arities.)
+%% Functions supporting this are create_subscription/3
+%% get_event_streams/3.
%%
%%----------------------------------------------------------------------
-module(ct_netconfc).
+-dialyzer(no_improper_lists).
+
-include("ct_netconfc.hrl").
-include("ct_util.hrl").
-include_lib("xmerl/include/xmerl.hrl").
@@ -107,12 +108,8 @@
copy_config/4,
action/2,
action/3,
- create_subscription/1,
create_subscription/2,
create_subscription/3,
- create_subscription/4,
- create_subscription/5,
- create_subscription/6,
get_event_streams/1,
get_event_streams/2,
get_event_streams/3,
@@ -121,6 +118,12 @@
get_session_id/1,
get_session_id/2]).
+%% historic, no longer documented
+-export([create_subscription/1,
+ create_subscription/4,
+ create_subscription/5,
+ create_subscription/6]).
+
%%----------------------------------------------------------------------
%% Exported types
%%----------------------------------------------------------------------
@@ -163,6 +166,9 @@
(is_atom(Xml) orelse (is_tuple(Xml) andalso is_atom(element(1,Xml))))).
-define(is_string(S), (is_list(S) andalso is_integer(hd(S)))).
+%% Keys into the process dictionary.
+-define(KEY(T), {?MODULE, T}).
+
%%----------------------------------------------------------------------
%% Records
%%----------------------------------------------------------------------
@@ -173,9 +179,9 @@
capabilities,
session_id,
msg_id = 1,
- hello_status,
- no_end_tag_buff = <<>>,
- buff = <<>>,
+ hello_status, % undefined | received | #pending{}
+ % string() | {error, Reason}
+ buf = false, % binary() | list() | boolean()
pending = [], % [#pending]
event_receiver}).% pid
@@ -195,10 +201,9 @@
type}).
%% Pending replies from server
--record(pending, {tref, % timer ref (returned from timer:xxx)
- ref, % pending ref
+-record(pending, {tref :: false | reference(), % timer reference
msg_id,
- op,
+ op,
caller}).% pid which sent the request
%%----------------------------------------------------------------------
@@ -207,13 +212,14 @@
-type client() :: handle() | server_id() | ct:target_name().
-opaque handle() :: pid().
--type options() :: [option()].
--type option() :: {ssh,host()} | {port,inet:port_number()} | {user,string()} |
- {password,string()} | {user_dir,string()} |
- {timeout,timeout()}.
+-type option() :: {host | ssh, host()}
+ | {port, inet:port_number()}
+ | {timeout, timeout()}
+ | {capability, string() | [string()]}
+ | ssh:client_option().
--type session_options() :: [session_option()].
--type session_option() :: {timeout,timeout()}.
+-type session_option() :: {timeout,timeout()}
+ | {capability, string() | [string()]}.
-type host() :: inet:hostname() | inet:ip_address().
@@ -258,33 +264,44 @@
%% Open an SSH connection to a Netconf server
%% If the server options are specified in a configuration file, use
%% open/2.
+
+%% connect/1
+
-spec connect(Options) -> Result when
- Options :: options(),
- Result :: {ok,handle()} | {error,error_reason()}.
+ Options :: [option()],
+ Result :: {ok, handle()} | {error, error_reason()}.
connect(Options) ->
- do_connect(Options, #options{type=connection},[]).
+ connect(Options, #options{type = connection}, []).
+
+%% connect/2
--spec connect(KeyOrName,ExtraOptions) -> Result when
+-spec connect(KeyOrName, ExtraOptions) -> Result when
KeyOrName :: ct:key_or_name(),
- ExtraOptions :: options(),
- Result :: {ok,handle()} | {error,error_reason()}.
+ ExtraOptions :: [option()],
+ Result :: {ok, handle()} | {error, error_reason()}.
+
connect(KeyOrName, ExtraOptions) ->
- SortedExtra = lists:keysort(1,ExtraOptions),
- SortedConfig = lists:keysort(1,ct:get_config(KeyOrName,[])),
- AllOpts = lists:ukeymerge(1,SortedConfig,SortedExtra),
- do_connect(AllOpts,#options{name=KeyOrName,type=connection},[{name,KeyOrName}]).
-
-do_connect(OptList,InitOptRec,NameOpt) ->
- case check_options(OptList,InitOptRec) of
- {Host,Port,Options} ->
- ct_gen_conn:start({Host,Port},Options,?MODULE,
- NameOpt ++ [{reconnect,false},
- {use_existing_connection,false},
- {forward_messages,false}]);
- Error ->
- Error
+ connect(make_opts(KeyOrName, ExtraOptions),
+ #options{name = KeyOrName, type = connection},
+ [{name, KeyOrName}]).
+
+%% connect/3
+
+connect(Opts, InitRec, NameOpt) ->
+ case make_options(Opts, InitRec) of
+ #options{} = Rec ->
+ start(Rec, NameOpt, false);
+ {error, _} = No ->
+ No
end.
+%% make_opts/2
+
+make_opts(KeyOrName, ExtraOptions) ->
+ SortedExtra = lists:keysort(1, ExtraOptions),
+ SortedConfig = lists:keysort(1, ct:get_config(KeyOrName, [])),
+ lists:ukeymerge(1, SortedConfig, SortedExtra).
+
%%----------------------------------------------------------------------
%% Close the given SSH connection.
-spec disconnect(Conn) -> ok | {error,error_reason()} when
@@ -300,146 +317,185 @@ disconnect(Conn) ->
%%----------------------------------------------------------------------
%% Open a netconf session as a channel on the given SSH connection,
%% and exchange `hello' messages.
+
+%% session/1
+
-spec session(Conn) -> Result when
Conn :: handle(),
- Result :: {ok,handle()} | {error,error_reason()}.
+ Result :: {ok, handle()} | {error, error_reason()}.
+
session(Conn) ->
- do_session(Conn,[],#options{type=channel},[]).
+ session(Conn, [], #options{type = channel}, []).
--spec session(Conn,Options) -> Result when
+%% session/2
+
+-spec session(Conn, Options) -> Result when
Conn :: handle(),
- Options :: session_options(),
- Result :: {ok,handle()} | {error,error_reason()};
- (KeyOrName,Conn) -> Result when
+ Options :: [session_option()],
+ Result :: {ok, handle()} | {error, error_reason()};
+ (KeyOrName, Conn) -> Result when
KeyOrName :: ct:key_or_name(),
Conn :: handle(),
- Result :: {ok,handle()} | {error,error_reason()}.
-session(Conn,Options) when is_list(Options) ->
- do_session(Conn,Options,#options{type=channel},[]);
-session(KeyOrName,Conn) ->
- do_session(Conn,[],#options{name=KeyOrName,type=channel},[{name,KeyOrName}]).
+ Result :: {ok, handle()} | {error, error_reason()}.
+
+session(Conn, Options) when is_list(Options) ->
+ session(Conn, Options, #options{type = channel}, []);
--spec session(KeyOrName,Conn,Options) -> Result when
+session(KeyOrName, Conn) ->
+ session(Conn,
+ [],
+ #options{name = KeyOrName, type = channel},
+ [{name, KeyOrName}]).
+
+%% session/3
+
+-spec session(KeyOrName, Conn, Options) -> Result when
Conn :: handle(),
- Options :: session_options(),
+ Options :: [session_option()],
KeyOrName :: ct:key_or_name(),
- Result :: {ok,handle()} | {error,error_reason()}.
-session(KeyOrName,Conn,ExtraOptions) ->
- SortedExtra = lists:keysort(1,ExtraOptions),
- SortedConfig = lists:keysort(1,ct:get_config(KeyOrName,[])),
- AllOpts = lists:ukeymerge(1,SortedConfig,SortedExtra),
- do_session(Conn,AllOpts,#options{name=KeyOrName,type=channel},
- [{name,KeyOrName}]).
-
-do_session(Conn,OptList,InitOptRec,NameOpt) ->
- case call(Conn,get_ssh_connection) of
- {ok,SshConn} ->
- case check_session_options(OptList,InitOptRec) of
- {ok,Options} ->
- case ct_gen_conn:start(SshConn,Options,?MODULE,
- NameOpt ++
- [{reconnect,false},
- {use_existing_connection,false},
- {forward_messages,true}]) of
- {ok,Client} ->
- case hello(Client,Options#options.timeout) of
- ok ->
- {ok,Client};
- Error ->
- Error
- end;
- Error ->
- Error
- end;
- Error ->
- Error
- end;
- Error ->
- Error
+ Result :: {ok, handle()} | {error, error_reason()}.
+
+session(KeyOrName, Conn, ExtraOptions) ->
+ session(Conn,
+ make_opts(KeyOrName, ExtraOptions),
+ #options{name = KeyOrName, type = channel},
+ [{name, KeyOrName}]).
+
+%% session/4
+
+session(Conn, Opts, InitRec, NameOpt) ->
+ T = make_ref(),
+ try
+ [_ | {ok, SshConn}] = [T | call(Conn, get_ssh_connection)],
+ [_ | #options{} = Rec] = [T | make_session_options(Opts, InitRec)],
+ [_ | {ok, Client} = Ok] = [T | start(SshConn, Rec, NameOpt, true)],
+ [_ | ok] = [T | hello(Client, caps(Opts), Rec#options.timeout)],
+ Ok
+ catch
+ error: {badmatch, [T | Error]} ->
+ Error
end.
+%% caps/1
+
+caps(Opts) ->
+ [T || {capability, _} = T <- Opts].
+
%%----------------------------------------------------------------------
%% Open a netconf session and exchange 'hello' messages.
%% If the server options are specified in a configuration file, use
%% open/2.
+
+%% open/1
+
-spec open(Options) -> Result when
- Options :: options(),
- Result :: {ok,handle()} | {error,error_reason()}.
+ Options :: [option()],
+ Result :: {ok, handle()} | {error, error_reason()}.
+
open(Options) ->
- open(Options,#options{type=connection_and_channel},[],true).
+ open(Options,
+ #options{type = connection_and_channel},
+ [],
+ true).
--spec open(KeyOrName, ExtraOptions) -> Result when
+-spec open(KeyOrName, ExtraOption) -> Result when
KeyOrName :: ct:key_or_name(),
- ExtraOptions :: options(),
- Result :: {ok,handle()} | {error,error_reason()}.
+ ExtraOption :: [option()],
+ Result :: {ok, handle()} | {error, error_reason()}.
+
open(KeyOrName, ExtraOpts) ->
open(KeyOrName, ExtraOpts, true).
-open(KeyOrName, ExtraOpts, Hello) ->
- SortedExtra = lists:keysort(1,ExtraOpts),
- SortedConfig = lists:keysort(1,ct:get_config(KeyOrName,[])),
- AllOpts = lists:ukeymerge(1,SortedConfig,SortedExtra),
- open(AllOpts,#options{name=KeyOrName,type=connection_and_channel},
- [{name,KeyOrName}],Hello).
-
-open(OptList,InitOptRec,NameOpt,Hello) ->
- case check_options(OptList,InitOptRec) of
- {Host,Port,Options} ->
- case ct_gen_conn:start({Host,Port},Options,?MODULE,
- NameOpt ++ [{reconnect,false},
- {use_existing_connection,false},
- {forward_messages,true}]) of
- {ok,Client} when Hello==true ->
- case hello(Client,Options#options.timeout) of
- ok ->
- {ok,Client};
- Error ->
- Error
- end;
- Other ->
- Other
- end;
- Error ->
- Error
+%% open/3
+
+open(KeyOrName, ExtraOptions, Hello) ->
+ open(make_opts(KeyOrName, ExtraOptions),
+ #options{name = KeyOrName, type = connection_and_channel},
+ [{name, KeyOrName}],
+ Hello).
+
+%% open/4
+
+open(Opts, InitRec, NameOpt, Hello) ->
+ T = make_ref(),
+ try
+ [_, #options{} = Rec] = [T, make_options(Opts, InitRec)],
+ [_, {ok, Client} = Ok | true] = [T, start(Rec, NameOpt, true) | Hello],
+ [_, ok] = [T, hello(Client, caps(Opts), Rec#options.timeout)],
+ Ok
+ catch
+ error: {badmatch, [T, Res | _]} ->
+ Res
end.
+%% start/3
+
+start(#options{host = undefined}, _, _) ->
+ {error, no_host_address};
+
+start(#options{port = undefined}, _, _) ->
+ {error, no_port};
+
+start(#options{host = Host, port = Port} = Opts, NameOpt, Fwd) ->
+ start({Host, Port}, Opts, NameOpt, Fwd).
+
+%% start/4
+
+start(Ep, Opts, NameOpt, Fwd) ->
+ ct_gen_conn:start(Ep, Opts, ?MODULE, [{reconnect, false},
+ {use_existing_connection, false},
+ {forward_messages, Fwd}
+ | NameOpt]).
%%----------------------------------------------------------------------
-%% As open/1,2, except no 'hello' message is sent.
+%% Like open/1,2, but no 'hello' message is sent.
+
-spec only_open(Options) -> Result when
- Options :: options(),
- Result :: {ok,handle()} | {error,error_reason()}.
+ Options :: [option()],
+ Result :: {ok, handle()} | {error, error_reason()}.
+
only_open(Options) ->
- open(Options,#options{type=connection_and_channel},[],false).
+ open(Options, #options{type = connection_and_channel}, [], false).
--spec only_open(KeyOrName,ExtraOptions) -> Result when
+-spec only_open(KeyOrName, ExtraOptions) -> Result when
KeyOrName :: ct:key_or_name(),
- ExtraOptions :: options(),
- Result :: {ok,handle()} | {error,error_reason()}.
+ ExtraOptions :: [option()],
+ Result :: {ok, handle()} | {error, error_reason()}.
+
only_open(KeyOrName, ExtraOpts) ->
open(KeyOrName, ExtraOpts, false).
%%----------------------------------------------------------------------
%% Send a 'hello' message.
+
+%% hello/1
+
-spec hello(Client) -> Result when
Client :: handle(),
- Result :: ok | {error,error_reason()}.
+ Result :: ok | {error, error_reason()}.
+
hello(Client) ->
- hello(Client,[],?DEFAULT_TIMEOUT).
+ hello(Client, [], ?DEFAULT_TIMEOUT).
+
+%% hello/2
--spec hello(Client,Timeout) -> Result when
+-spec hello(Client, Timeout) -> Result when
Client :: handle(),
Timeout :: timeout(),
- Result :: ok | {error,error_reason()}.
-hello(Client,Timeout) ->
- hello(Client,[],Timeout).
+ Result :: ok | {error, error_reason()}.
+
+hello(Client, Timeout) ->
+ hello(Client, [], Timeout).
--spec hello(Client,Options,Timeout) -> Result when
+%% hello/3
+
+-spec hello(Client, Options, Timeout) -> Result when
Client :: handle(),
Options :: [{capability, [string()]}],
Timeout :: timeout(),
- Result :: ok | {error,error_reason()}.
-hello(Client,Options,Timeout) ->
+ Result :: ok | {error, error_reason()}.
+
+hello(Client, Options, Timeout) ->
call(Client, {hello, Options, Timeout}).
@@ -675,117 +731,122 @@ action(Client,Action,Timeout) ->
%%----------------------------------------------------------------------
%% Send a 'create-subscription' request
%% See RFC5277, NETCONF Event Notifications
--spec create_subscription(Client) -> Result when
- Client :: client(),
- Result :: ok | {error,error_reason()}.
-create_subscription(Client) ->
- create_subscription(Client,?DEFAULT_STREAM,?DEFAULT_TIMEOUT).
--spec create_subscription(Client, Stream | Filter | Timeout) -> Result when
+%% create_subscription/2
+
+-spec create_subscription(Client, Values) -> Result when
Client :: client(),
+ Values :: #{stream => Stream,
+ filter => Filter,
+ start => StartTime,
+ stop => StopTime},
Stream :: stream_name(),
Filter :: simple_xml() | [simple_xml()],
- Timeout :: timeout(),
+ StartTime :: xs_datetime(),
+ StopTime :: xs_datetime(),
+ Result :: ok | {error,error_reason()};
+ %% historic, no longer documented
+ (Client, list() | timeout()) -> Result when
+ Client :: client(),
Result :: ok | {error,error_reason()}.
-create_subscription(Client,Timeout)
+
+create_subscription(Client, #{} = Values) ->
+ create_subscription(Client, Values, ?DEFAULT_TIMEOUT);
+
+%% historic clauses
+create_subscription(Client, Timeout)
when ?is_timeout(Timeout) ->
- create_subscription(Client,?DEFAULT_STREAM,Timeout);
-create_subscription(Client,Stream)
+ create_subscription(Client, #{}, Timeout);
+create_subscription(Client, Stream)
when ?is_string(Stream) ->
- create_subscription(Client,Stream,?DEFAULT_TIMEOUT);
-create_subscription(Client,Filter)
+ create_subscription(Client, #{stream => Stream});
+create_subscription(Client, Filter)
when ?is_filter(Filter) ->
- create_subscription(Client,?DEFAULT_STREAM,Filter,
- ?DEFAULT_TIMEOUT).
+ create_subscription(Client, #{filter => Filter}).
-create_subscription(Client,Stream,Timeout)
- when ?is_string(Stream) andalso
- ?is_timeout(Timeout) ->
- call(Client,{send_rpc_op,{create_subscription,self()},
- [Stream,undefined,undefined,undefined],
- Timeout});
-create_subscription(Client,StartTime,StopTime)
- when ?is_string(StartTime) andalso
- ?is_string(StopTime) ->
- create_subscription(Client,?DEFAULT_STREAM,StartTime,StopTime,
- ?DEFAULT_TIMEOUT);
-create_subscription(Client,Filter,Timeout)
- when ?is_filter(Filter) andalso
- ?is_timeout(Timeout) ->
- create_subscription(Client,?DEFAULT_STREAM,Filter,Timeout);
-create_subscription(Client,Stream,Filter)
- when ?is_string(Stream) andalso
- ?is_filter(Filter) ->
- create_subscription(Client,Stream,Filter,?DEFAULT_TIMEOUT).
-
-create_subscription(Client,StartTime,StopTime,Timeout)
- when ?is_string(StartTime) andalso
- ?is_string(StopTime) andalso
- ?is_timeout(Timeout) ->
- create_subscription(Client,?DEFAULT_STREAM,StartTime,StopTime,Timeout);
-create_subscription(Client,Stream,StartTime,StopTime)
- when ?is_string(Stream) andalso
- ?is_string(StartTime) andalso
- ?is_string(StopTime) ->
- create_subscription(Client,Stream,StartTime,StopTime,?DEFAULT_TIMEOUT);
-create_subscription(Client,Filter,StartTime,StopTime)
- when ?is_filter(Filter) andalso
- ?is_string(StartTime) andalso
- ?is_string(StopTime) ->
- create_subscription(Client,?DEFAULT_STREAM,Filter,
- StartTime,StopTime,?DEFAULT_TIMEOUT);
-create_subscription(Client,Stream,Filter,Timeout)
- when ?is_string(Stream) andalso
- ?is_filter(Filter) andalso
- ?is_timeout(Timeout) ->
- call(Client,{send_rpc_op,{create_subscription,self()},
- [Stream,Filter,undefined,undefined],
- Timeout}).
-
--spec create_subscription(Client, Stream, StartTime, StopTime, Timeout) ->
- Result when
+-spec create_subscription(Client, Values, Timeout) -> Result when
Client :: client(),
+ Values :: #{stream => Stream,
+ filter => Filter,
+ start => StartTime,
+ stop => StopTime},
Stream :: stream_name(),
+ Filter :: simple_xml() | [simple_xml()],
StartTime :: xs_datetime(),
StopTime :: xs_datetime(),
Timeout :: timeout(),
Result :: ok | {error,error_reason()};
- (Client, Stream, Filter,StartTime, StopTime) ->
- Result when
+ %% historic, no longer documented
+ (Client, list(), list() | timeout()) -> Result when
Client :: client(),
- Stream :: stream_name(),
- Filter :: simple_xml() | [simple_xml()],
- StartTime :: xs_datetime(),
- StopTime :: xs_datetime(),
Result :: ok | {error,error_reason()}.
-create_subscription(Client,Stream,StartTime,StopTime,Timeout)
- when ?is_string(Stream) andalso
- ?is_string(StartTime) andalso
- ?is_string(StopTime) andalso
+
+create_subscription(Client, #{} = Values, Timeout) ->
+ Keys = [{stream, ?DEFAULT_STREAM},
+ {filter, undefined},
+ {start, undefined},
+ {stop, undefined}],
+ call(Client, {send_rpc_op, {create_subscription, self()},
+ [maps:get(K, Values, D) || {K,D} <- Keys],
+ Timeout});
+
+%% historic clauses, arity 3
+create_subscription(Client, Stream, Timeout)
+ when ?is_string(Stream), ?is_timeout(Timeout) ->
+ create_subscription(Client, #{stream => Stream}, Timeout);
+create_subscription(Client, StartTime, StopTime)
+ when ?is_string(StartTime), ?is_string(StopTime) ->
+ create_subscription(Client, #{start => StartTime, stop => StopTime});
+create_subscription(Client, Filter, Timeout)
+ when ?is_filter(Filter), ?is_timeout(Timeout) ->
+ create_subscription(Client, #{filter => Filter}, Timeout);
+create_subscription(Client, Stream, Filter)
+ when ?is_string(Stream), ?is_filter(Filter) ->
+ create_subscription(Client, #{stream => Stream, filter => Filter}).
+
+%% historic clauses, arity 1,4-5
+create_subscription(Client) ->
+ create_subscription(Client, #{}).
+create_subscription(Client, StartTime, StopTime, Timeout)
+ when ?is_string(StartTime), ?is_string(StopTime), ?is_timeout(Timeout) ->
+ Values = #{start => StartTime,
+ stop => StopTime},
+ create_subscription(Client, Values, Timeout);
+create_subscription(Client, Stream, StartTime, StopTime)
+ when ?is_string(Stream), ?is_string(StartTime), ?is_string(StopTime) ->
+ create_subscription(Client, #{stream => Stream,
+ start => StartTime,
+ stop => StopTime});
+create_subscription(Client, Filter, StartTime, StopTime)
+ when ?is_filter(Filter), ?is_string(StartTime), ?is_string(StopTime) ->
+ create_subscription(Client, #{filter => Filter,
+ start => StartTime,
+ stop => StopTime});
+create_subscription(Client, Stream, Filter, Timeout)
+ when ?is_string(Stream), ?is_filter(Filter), ?is_timeout(Timeout) ->
+ Values = #{stream => Stream,
+ filter => Filter},
+ create_subscription(Client, Values, Timeout).
+create_subscription(Client, Stream, StartTime, StopTime, Timeout)
+ when ?is_string(Stream), ?is_string(StartTime), ?is_string(StopTime),
?is_timeout(Timeout) ->
- call(Client,{send_rpc_op,{create_subscription,self()},
- [Stream,undefined,StartTime,StopTime],
- Timeout});
-create_subscription(Client,Stream,Filter,StartTime,StopTime)
- when ?is_string(Stream) andalso
- ?is_filter(Filter) andalso
- ?is_string(StartTime) andalso
+ Values = #{stream => Stream,
+ start => StartTime,
+ stop => StopTime},
+ create_subscription(Client, Values, Timeout);
+create_subscription(Client, Stream, Filter, StartTime, StopTime)
+ when ?is_string(Stream), ?is_filter(Filter), ?is_string(StartTime),
?is_string(StopTime) ->
- create_subscription(Client,Stream,Filter,StartTime,StopTime,?DEFAULT_TIMEOUT).
-
--spec create_subscription(Client, Stream, Filter,StartTime, StopTime, Timeout) ->
- Result when
- Client :: client(),
- Stream :: stream_name(),
- Filter :: simple_xml() | [simple_xml()],
- StartTime :: xs_datetime(),
- StopTime :: xs_datetime(),
- Timeout :: timeout(),
- Result :: ok | {error,error_reason()}.
-create_subscription(Client,Stream,Filter,StartTime,StopTime,Timeout) ->
- call(Client,{send_rpc_op,{create_subscription, self()},
- [Stream,Filter,StartTime,StopTime],
- Timeout}).
+ create_subscription(Client, #{stream => Stream,
+ filter => Filter,
+ start => StartTime,
+ stop => StopTime}).
+create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout) ->
+ Values = #{stream => Stream,
+ filter => Filter,
+ start => StartTime,
+ stop => StopTime},
+ create_subscription(Client, Values, Timeout).
%%----------------------------------------------------------------------
%% Send a request to get the given event streams
@@ -859,6 +920,8 @@ kill_session(Client, SessionId, Timeout) ->
%% Callback functions
%%----------------------------------------------------------------------
+%% init/3
+
init(_KeyOrName,{CM,{Host,Port}},Options) ->
case ssh_channel(#connection{reference=CM,host=Host,port=Port},Options) of
{ok,Connection} ->
@@ -883,29 +946,32 @@ init(_KeyOrName,{_Host,_Port},Options) ->
{error,Reason}
end.
+%% terminate/2
terminate(_, #state{connection=Connection}) ->
ssh_close(Connection),
ok.
-handle_msg({hello, Options, Timeout}, From,
- #state{connection=Connection,hello_status=HelloStatus} = State) ->
+%% handle_msg/3
+
+%% Send hello and return to the caller only after reception of the
+%% server's hello.
+handle_msg({hello, Options, Timeout},
+ From,
+ #state{connection = Connection,
+ hello_status = HelloStatus}
+ = State) ->
case do_send(Connection, client_hello(Options)) of
- ok ->
- case HelloStatus of
- undefined ->
- {Ref,TRef} = set_request_timer(Timeout),
- {noreply, State#state{hello_status=#pending{tref=TRef,
- ref=Ref,
- caller=From}}};
- received ->
- {reply, ok, State#state{hello_status=done}};
- {error,Reason} ->
- {stop, {error,Reason}, State}
- end;
- Error ->
+ ok when HelloStatus == undefined -> %% server hello not yet received
+ TRef = set_request_timer(Timeout, hello),
+ {noreply, State#state{hello_status = #pending{tref = TRef,
+ caller = From}}};
+ ok -> %% or yes: negotiate version
+ handle_capx(State);
+ Error ->
{stop, Error, State}
end;
+
handle_msg(get_ssh_connection, _From, #state{connection=Connection}=State) ->
Reply =
case Connection#connection.reference of
@@ -914,29 +980,40 @@ handle_msg(get_ssh_connection, _From, #state{connection=Connection}=State) ->
Connection#connection.port}}}
end,
{reply, Reply, State};
-handle_msg(_, _From, #state{session_id=undefined} = State) ->
- %% Hello is not yet excanged - this shall never happen
- {reply,{error,waiting_for_hello},State};
+
+%% Request before server hello. Possible with only_open, since a
+%% handle is then returned without waiting for the server.
+handle_msg(_, _From, #state{session_id = undefined} = State) ->
+ {reply, {error, waiting_for_hello}, State};
+
handle_msg(get_capabilities, _From, #state{capabilities = Caps} = State) ->
{reply, Caps, State};
+
handle_msg(get_session_id, _From, #state{session_id = Id} = State) ->
{reply, Id, State};
-handle_msg({send, Timeout, SimpleXml}, From,
- #state{connection=Connection,pending=Pending} = State) ->
+
+handle_msg({send, Timeout, SimpleXml},
+ From,
+ #state{connection = Connection,
+ pending = Pending}
+ = State) ->
case do_send(Connection, SimpleXml) of
- ok ->
- {Ref,TRef} = set_request_timer(Timeout),
- {noreply, State#state{pending=[#pending{tref=TRef,
- ref=Ref,
- caller=From} | Pending]}};
- Error ->
- {reply, Error, State}
+ ok ->
+ TRef = set_request_timer(Timeout, send),
+ {noreply, State#state{pending = [#pending{tref = TRef,
+ caller = From}
+ | Pending]}};
+ Error ->
+ {reply, Error, State}
end;
+
handle_msg({send_rpc, SimpleXml, Timeout}, From, State) ->
do_send_rpc(undefined, SimpleXml, Timeout, From, State);
+
handle_msg({send_rpc_op, Op, Data, Timeout}, From, State) ->
SimpleXml = encode_rpc_operation(Op,Data),
do_send_rpc(Op, SimpleXml, Timeout, From, State);
+
handle_msg({get_event_streams=Op,Streams,Timeout}, From, State) ->
Filter = {netconf,?NETMOD_NOTIF_NAMESPACE_ATTR,
[{streams,[{stream,[{name,[Name]}]} || Name <- Streams]}]},
@@ -945,7 +1022,9 @@ handle_msg({get_event_streams=Op,Streams,Timeout}, From, State) ->
handle_msg({ssh_cm, CM, {data, Ch, _Type, Data}}, State) ->
ssh_connection:adjust_window(CM,Ch,size(Data)),
+ log(State#state.connection, recv, Data),
handle_data(Data, State);
+
handle_msg({ssh_cm, _CM, _SshCloseMsg}, State) ->
%% _SshCloseMsg can probably be one of
%% {eof,Ch}
@@ -962,21 +1041,29 @@ handle_msg({ssh_cm, _CM, _SshCloseMsg}, State) ->
%%! connection - due to terminate/2
{stop, State};
-handle_msg({Ref,timeout},
- #state{hello_status=#pending{ref=Ref,caller=Caller}} = State) ->
- ct_gen_conn:return(Caller,{error,{hello_session_failed,timeout}}),
- {stop,State#state{hello_status={error,timeout}}};
-handle_msg({Ref,timeout},#state{pending=Pending} = State) ->
- {value,#pending{op=Op,caller=Caller},Pending1} =
- lists:keytake(Ref,#pending.ref,Pending),
- ct_gen_conn:return(Caller,{error,timeout}),
- R = case Op of
- close_session -> stop;
- _ -> noreply
- end,
- %% Halfhearted try to get in correct state, this matches
- %% the implementation before this patch
- {R,State#state{pending=Pending1, no_end_tag_buff= <<>>, buff= <<>>}}.
+
+handle_msg({timeout, TRef, hello},
+ #state{hello_status = #pending{tref = TRef,
+ caller = From}}
+ = State) ->
+ ct_gen_conn:return(From, {error, {hello_session_failed, timeout}}),
+ {stop, State#state{hello_status = {error,timeout}}};
+
+handle_msg({timeout, TRef, Op}, #state{pending = Pending} = State) ->
+ case lists:keytake(TRef, #pending.tref, Pending) of
+ {value, #pending{caller = From}, Rest} ->
+ ct_gen_conn:return(From, {error, timeout}),
+ %% Discard received bytes in hope that the server has sent
+ %% an incomplete message. Otherwise this is doomed to
+ %% leave the connection in an unusable state.
+ {if Op == close_session -> stop; true -> noreply end,
+ State#state{pending = Rest,
+ buf = is_binary(State#state.buf)}};
+ false ->
+ {noreply, State}
+ end.
+
+%% close/1
%% Called by ct_util_server to close registered connections before terminate.
close(Client) ->
@@ -1048,63 +1135,163 @@ get_handle(Client) ->
Error
end.
-check_options(OptList,Options) ->
- check_options(OptList,undefined,undefined,Options).
+%% make_options/2
-check_options([], undefined, _Port, _Options) ->
- {error, no_host_address};
-check_options([], _Host, undefined, _Options) ->
- {error, no_port};
-check_options([], Host, Port, Options) ->
- {Host,Port,Options};
-check_options([{ssh, Host}|T], _, Port, Options) ->
- check_options(T, Host, Port, Options#options{host=Host});
-check_options([{port,Port}|T], Host, _, Options) ->
- check_options(T, Host, Port, Options#options{port=Port});
-check_options([{timeout, Timeout}|T], Host, Port, Options)
- when is_integer(Timeout); Timeout==infinity ->
- check_options(T, Host, Port, Options#options{timeout = Timeout});
-check_options([{timeout, _} = Opt|_T], _Host, _Port, _Options) ->
- {error, {invalid_option, Opt}};
-check_options([Opt|T], Host, Port, #options{ssh=SshOpts}=Options) ->
- %% Option verified by ssh
- check_options(T, Host, Port, Options#options{ssh=[Opt|SshOpts]}).
-
-check_session_options([],Options) ->
- {ok,Options};
-check_session_options([{timeout, Timeout}|T], Options)
- when is_integer(Timeout); Timeout==infinity ->
- check_session_options(T, Options#options{timeout = Timeout});
-check_session_options([Opt|_T], _Options) ->
- {error, {invalid_option, Opt}}.
+make_options(Opts, Rec) ->
+ make_options(Opts, Rec#options{port = undefined}, fun opt/2).
+
+opt({T, Host}, Rec)
+ when T == ssh;
+ T == host ->
+ Rec#options{host = Host};
+
+opt({port, Port}, Rec) ->
+ Rec#options{port = Port};
+
+opt({timeout, Tmo}, Rec)
+ when is_integer(Tmo);
+ Tmo == infinity ->
+ Rec#options{timeout = Tmo};
+
+opt({timeout, _} = T, _) ->
+ throw(T);
+
+opt({capability, _}, Rec) ->
+ Rec;
+
+opt(Opt, #options{ssh = Opts} = Rec) -> %% option verified by ssh
+ Rec#options{ssh = [Opt | Opts]}.
+
+%% make_session_options/2
+
+make_session_options(Opts, Rec) ->
+ make_options(Opts, Rec, fun session_opt/2).
+
+session_opt({capability, _}, Rec) ->
+ Rec;
+
+session_opt({timeout, Tmo}, Rec)
+ when is_integer(Tmo);
+ Tmo == infinity ->
+ Rec#options{timeout = Tmo};
+session_opt(T, _Rec) ->
+ throw(T).
+
+%% make_options/3
+
+make_options(Opts, Rec, F) ->
+ try
+ #options{} = lists:foldl(F, Rec, Opts)
+ catch
+ T ->
+ {error, {invalid_option, T}}
+ end.
%%%-----------------------------------------------------------------
-set_request_timer(infinity) ->
- {undefined,undefined};
-set_request_timer(T) ->
- Ref = make_ref(),
- {ok,TRef} = timer:send_after(T,{Ref,timeout}),
- {Ref,TRef}.
+
+set_request_timer(infinity, _) ->
+ false;
+
+set_request_timer(Tmo, Op) ->
+ erlang:start_timer(Tmo, self(), Op).
%%%-----------------------------------------------------------------
-cancel_request_timer(undefined,undefined) ->
+
+cancel_request_timer(false) ->
ok;
-cancel_request_timer(Ref,TRef) ->
- _ = timer:cancel(TRef),
- receive {Ref,timeout} -> ok
- after 0 -> ok
- end.
+
+cancel_request_timer(TRef) ->
+ erlang:cancel_timer(TRef).
%%%-----------------------------------------------------------------
-client_hello(Options) when is_list(Options) ->
- UserCaps = [{capability, UserCap} ||
- {capability, UserCap} <- Options,
- is_list(hd(UserCap))],
- {hello, ?NETCONF_NAMESPACE_ATTR,
- [{capabilities,
- [{capability,[?NETCONF_BASE_CAP++?NETCONF_BASE_CAP_VSN]}|
- UserCaps]}]}.
+
+%% client_hello/1
+%%
+%% Prepend the 1.0 base capability only if none is specified by the
+%% user. Store the versions in the process dictionary until they're
+%% examined upon reception of server capabilities in handle_capx/1.
+
+client_hello(Opts)
+ when is_list(Opts) ->
+ UserCaps = [{T, cap(lists:flatten(Cs))} || {capability = T, Cs} <- Opts],
+ Vsns = versions(UserCaps),
+ put(?KEY(protocol_vsn), Vsns),
+ {hello,
+ ?NETCONF_NAMESPACE_ATTR,
+ [{capabilities, [{capability, [?NETCONF_BASE_CAP, ?NETCONF_BASE_CAP_VSN]}
+ || [] == Vsns]
+ ++ UserCaps}]}.
+
+%% cap/1
+%%
+%% Let NETCONF capabilities be specified in the shorthand documented in
+%% RFC 6241.
+
+%% This shorthand is documented in RFC 6241 10.4 NETCONF Capabilities
+%% URNS, but not in 8 Capabilities.
+cap(":base:" ++ _ = Str) ->
+ ["urn:ietf:params:netconf", Str];
+
+cap([$:|_] = Str) ->
+ ["urn:ietf:params:netconf:capability", Str];
+
+cap(Str) ->
+ [Str].
+
+%% versions/1
+%%
+%% Extract base protocol versions from capability options.
+
+versions(Opts) ->
+ [V || {capability, L} <- Opts,
+ S <- L,
+ ?NETCONF_BASE_CAP ++ X <- [lists:flatten(S)],
+ V <- [lists:takewhile(fun(C) -> C /= $? end, X)]].
+
+%% handle_capx/1
+%%
+%% Ignore parameters as RFC 6241 (NETCONF 1.1) requires in 8.1
+%% Capabilities Exchange. Be overly lenient with whitespace since RFC
+%% 6241 gives examples with significant trailing whitespace.
+
+handle_capx(#state{hello_status = received, capabilities = Caps} = S) ->
+ Remote = [V || ?NETCONF_BASE_CAP ++ X <- Caps,
+ [V|_] <- [string:lexemes(X, "? \t\r\n")]],
+ Local = erase(?KEY(protocol_vsn)),
+ case protocol_vsn(Local, Remote) of
+ false when Remote == [] ->
+ Reason = {incorrect_hello, no_base_capability_found},
+ {stop, {error, Reason}, S};
+ false ->
+ Reason = {incompatible_base_capability_vsn, lists:min(Remote)},
+ {stop, {error, Reason}, S};
+ Vsn ->
+ put(?KEY(chunk), Vsn /= "1.0"),
+ {reply, ok, rebuf(Vsn, S#state{hello_status = Vsn})}
+ end;
+
+handle_capx(#state{hello_status = {error, _} = No} = S) ->
+ {stop, No, S}.
+
+%% rebuf/2
+%%
+%% Turn the message buffer into a list for 1.1 chunking if the
+%% negotiated protocol version is > 1.0.
+
+rebuf("1.0", S) ->
+ S;
+
+rebuf(_, #state{buf = Bin} = S) ->
+ S#state{buf = [Bin, 3]}.
+
+%% protocol_vsn/2
+
+protocol_vsn([], Vsns) ->
+ protocol_vsn(["1.0"], Vsns);
+
+protocol_vsn(Local, Remote) ->
+ lists:max([false | [V || V <- Remote, lists:member(V, Local)]]).
%%%-----------------------------------------------------------------
@@ -1150,111 +1337,130 @@ maybe_element(Tag,Value) ->
%%%-----------------------------------------------------------------
%%% Send XML data to server
-do_send_rpc(PendingOp,SimpleXml,Timeout,Caller,
- #state{connection=Connection,msg_id=MsgId,pending=Pending} = State) ->
- case do_send_rpc(Connection, MsgId, SimpleXml) of
- ok ->
- {Ref,TRef} = set_request_timer(Timeout),
- {noreply, State#state{msg_id=MsgId+1,
- pending=[#pending{tref=TRef,
- ref=Ref,
- msg_id=MsgId,
- op=PendingOp,
- caller=Caller} | Pending]}};
- Error ->
- {reply, Error, State#state{msg_id=MsgId+1}}
+do_send_rpc(Op, SimpleXml, Timeout, Caller, #state{connection = Connection,
+ msg_id = MsgId,
+ pending = Pending}
+ = State) ->
+ Msg = {rpc,
+ [{'message-id', MsgId} | ?NETCONF_NAMESPACE_ATTR],
+ [SimpleXml]},
+ Next = MsgId + 1,
+ case do_send(Connection, Msg) of
+ ok ->
+ TRef = set_request_timer(Timeout, Op),
+ Rec = #pending{tref = TRef,
+ msg_id = MsgId,
+ op = Op,
+ caller = Caller},
+ {noreply, State#state{msg_id = Next,
+ pending = [Rec | Pending]}};
+ Error ->
+ {reply, Error, State#state{msg_id = Next}}
end.
-do_send_rpc(Connection, MsgId, SimpleXml) ->
- do_send(Connection,
- {rpc,
- [{'message-id',MsgId} | ?NETCONF_NAMESPACE_ATTR],
- [SimpleXml]}).
+do_send(Connection, Simple) ->
+ ssh_send(Connection, frame(to_xml(Simple))).
-do_send(Connection, SimpleXml) ->
- Xml=to_xml_doc(SimpleXml),
- ssh_send(Connection, Xml).
-
-to_xml_doc(Simple) ->
+to_xml(Simple) ->
Prolog = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
- Xml = unicode:characters_to_binary(
- xmerl:export_simple([Simple],
- xmerl_xml,
- [#xmlAttribute{name=prolog,
- value=Prolog}])),
- <<Xml/binary,?END_TAG/binary>>.
+ Chars = xmerl:export_simple([Simple],
+ xmerl_xml,
+ [#xmlAttribute{name = prolog,
+ value = Prolog}]),
+ unicode:characters_to_binary(Chars).
+
+%% frame/1
+
+frame(Bin) ->
+ case get(?KEY(chunk)) of
+ true -> %% 1.1 chunking
+ [chunk(Bin) | "\n##\n"];
+ _ -> %% 1.0 framing
+ [Bin | ?END_TAG]
+ end.
+
+%% chunk/1
+%%
+%% Chunk randomly to exercise the server.
+
+chunk(<<>>) ->
+ [];
+
+chunk(Bin) ->
+ Sz = min(rand:uniform(1024), size(Bin)),
+ <<B:Sz/binary, Rest/binary>> = Bin,
+ ["\n#", integer_to_list(Sz), $\n, B | chunk(Rest)].
%%%-----------------------------------------------------------------
%%% Parse and handle received XML data
-%%% Two buffers are used:
-%%% * 'no_end_tag_buff' contains data that is checked and does not
-%%% contain any (part of an) end tag.
-%%% * 'buff' contains all other saved data - it may or may not
-%%% include (a part of) an end tag.
-%%% The reason for this is to avoid running binary:split/3 multiple
-%%% times on the same data when it does not contain an end tag. This
-%%% can be a considerable optimation in the case when a lot of data is
-%%% received (e.g. when fetching all data from a node) and the data is
-%%% sent in multiple ssh packages.
-handle_data(NewData,#state{connection=Connection} = State0) ->
- log(Connection,recv,NewData),
- NoEndTag0 = State0#state.no_end_tag_buff,
- Buff0 = State0#state.buff,
- Data = <<Buff0/binary, NewData/binary>>,
- case binary:split(Data,?END_TAG,[]) of
- [_NoEndTagFound] ->
- NoEndTagSize = case byte_size(Data) of
- Sz when Sz<5 -> 0;
- Sz -> Sz-5
- end,
- <<NoEndTag1:NoEndTagSize/binary,Buff/binary>> = Data,
- NoEndTag = <<NoEndTag0/binary,NoEndTag1/binary>>,
- {noreply, State0#state{no_end_tag_buff=NoEndTag, buff=Buff}};
- [FirstMsg0,Buff1] ->
- FirstMsg = remove_initial_nl(<<NoEndTag0/binary,FirstMsg0/binary>>),
- SaxArgs = [{event_fun,fun sax_event/3}, {event_state,[]}],
- case xmerl_sax_parser:stream(FirstMsg, SaxArgs) of
- {ok, Simple, _Thrash} ->
- case decode(Simple, State0#state{no_end_tag_buff= <<>>,
- buff=Buff1}) of
- {noreply, #state{buff=Buff} = State} when Buff =/= <<>> ->
- %% Recurse if we have more data in buffer
- handle_data(<<>>, State);
- Other ->
- Other
- end;
- {fatal_error,_Loc,Reason,_EndTags,_EventState} ->
- ?error(Connection#connection.name,
- [{parse_error,Reason},
- {buffer, Buff0},
- {new_data,NewData}]),
- handle_error(Reason, State0#state{no_end_tag_buff= <<>>,
- buff= <<>>})
- end
+
+handle_data(Bin, #state{buf = Head} = S) ->
+ case recv(Bin, Head) of
+ {error, Reason} ->
+ Conn = S#state.connection,
+ ?error(Conn#connection.name, [{receive_error, Reason},
+ {buffer, Head},
+ {bytes, Bin}]),
+ {stop, S};
+ {Bytes, Rest} ->
+ handle_more(Rest, handle_xml(Bytes, S));
+ Buf ->
+ {noreply, S#state{buf = Buf}}
end.
+%% handle_more/2
+
+handle_more(_, {stop, _} = No) ->
+ No;
+
+handle_more(Bin, {noreply, State}) ->
+ handle_data(Bin, State#state{buf = true == get(?KEY(chunk))}).
+
+%% handle_xml/2
+
+handle_xml(Bytes, State) ->
+ case parse(Bytes) of
+ {ok, Simple, _Rest} -> %% ignore trailing bytes
+ decode(Simple, State);
+ {fatal_error,_Loc,Reason,_EndTags,_EventState} ->
+ Conn = State#state.connection,
+ ?error(Conn#connection.name, [{parse_error, Reason},
+ {message, Bytes}]),
+ {noreply, handle_error(Reason, State)}
+ end.
+
+%% parse/1
+
+parse(Bytes) ->
+ xmerl_sax_parser:stream(<<>>, [{event_fun, fun sax_event/3},
+ {event_state, []},
+ {continuation_fun, fun cont/1},
+ {continuation_state, Bytes}]).
+
+%% cont/1
-%% xml does not accept a leading nl and some netconf server add a nl after
-%% each ?END_TAG, ignore them
-remove_initial_nl(<<"\n", Data/binary>>) ->
- remove_initial_nl(Data);
-remove_initial_nl(Data) ->
- Data.
-
-handle_error(Reason, State) ->
- Pending1 = case State#state.pending of
- [] -> [];
- Pending ->
- %% Assuming the first request gets the
- %% first answer
- P=#pending{tref=TRef,ref=Ref,caller=Caller} =
- lists:last(Pending),
- cancel_request_timer(Ref,TRef),
- Reason1 = {failed_to_parse_received_data,Reason},
- ct_gen_conn:return(Caller,{error,Reason1}),
- lists:delete(P,Pending)
- end,
- {noreply, State#state{pending=Pending1}}.
+cont([] = No) ->
+ {<<>>, No};
+
+cont([Bin | Rest]) ->
+ {Bin, Rest};
+
+cont(Bin) ->
+ {Bin, <<>>}.
+
+%% handle_error/2
+
+handle_error(_Reason, #state{pending = []} = State) ->
+ State;
+
+handle_error(Reason, #state{pending = Pending} = State) ->
+ %% Assuming the first request gets the first answer.
+ Rec = #pending{tref = TRef,
+ caller = Caller}
+ = lists:last(Pending),
+ cancel_request_timer(TRef),
+ ct_gen_conn:return(Caller,{error, {failed_to_parse_received_data, Reason}}),
+ State#state{pending = lists:delete(Rec, Pending)}.
%% Event function for the sax parser. It builds a simple XML structure.
%% Care is taken to keep namespace attributes and prefixes as in the original XML.
@@ -1305,136 +1511,180 @@ parse_attrs([]) ->
%%%-----------------------------------------------------------------
-%%% Decoding of parsed XML data
-decode({Tag,Attrs,_}=E, #state{connection=Connection,pending=Pending}=State) ->
- ConnName = Connection#connection.name,
- case get_local_name_atom(Tag) of
- 'rpc-reply' ->
- case get_msg_id(Attrs) of
- undefined ->
- case Pending of
- [#pending{msg_id=MsgId}] ->
- ?error(ConnName,[{warning,rpc_reply_missing_msg_id},
- {assuming,MsgId}]),
- decode_rpc_reply(MsgId,E,State);
- _ ->
- ?error(ConnName,[{error,rpc_reply_missing_msg_id}]),
- {noreply,State}
- end;
- MsgId ->
- decode_rpc_reply(MsgId,E,State)
- end;
- hello ->
- case State#state.hello_status of
- undefined ->
- case decode_hello(E) of
- {ok,SessionId,Capabilities} ->
- {noreply,State#state{session_id = SessionId,
- capabilities = Capabilities,
- hello_status = received}};
- {error,Reason} ->
- {noreply,State#state{hello_status = {error,Reason}}}
- end;
- #pending{tref=TRef,ref=Ref,caller=Caller} ->
- cancel_request_timer(Ref,TRef),
- case decode_hello(E) of
- {ok,SessionId,Capabilities} ->
- ct_gen_conn:return(Caller,ok),
- {noreply,State#state{session_id = SessionId,
- capabilities = Capabilities,
- hello_status = done}};
- {error,Reason} ->
- ct_gen_conn:return(Caller,{error,Reason}),
- {stop,State#state{hello_status={error,Reason}}}
- end;
- Other ->
- ?error(ConnName,[{got_unexpected_hello,E},
- {hello_status,Other}]),
- {noreply,State}
- end;
- notification ->
- EventReceiver = State#state.event_receiver,
- EventReceiver ! E,
- {noreply,State};
- Other ->
- %% Result of send/2, when not sending an rpc request - or
- %% if netconf server sends noise. Can handle this only if
- %% there is just one pending that matches (i.e. has
- %% undefined msg_id and op)
- case [P || P = #pending{msg_id=undefined,op=undefined} <- Pending] of
- [#pending{tref=TRef,ref=Ref,caller=Caller}] ->
- cancel_request_timer(Ref,TRef),
- ct_gen_conn:return(Caller,E),
- {noreply,State#state{pending=[]}};
- _ ->
- ?error(ConnName,[{got_unexpected_msg,Other},
- {expecting,Pending}]),
- {noreply,State}
- end
+%% decode/2
+%%
+%% Decode parsed (incoming) XML.
+
+decode({Tag, _, _} = E, #state{} = State) ->
+ case decode(get_local_name_atom(Tag), E, State) of
+ #state{} = S ->
+ {noreply, S};
+ {stop, #state{}} = T ->
+ T
+ end.
+
+%% decode/3
+
+decode('rpc-reply', {_, Attrs, _} = E, State) ->
+ decode_rpc_reply(get_msg_id(Attrs), E, State);
+
+%% Incoming hello, outgoing not yet sent.
+decode(hello, E, #state{hello_status = undefined} = State) ->
+ case decode_hello(E) of
+ {ok, SessionId, Capabilities} ->
+ State#state{session_id = SessionId,
+ capabilities = Capabilities,
+ hello_status = received};
+ {error, _Reason} = No ->
+ State#state{hello_status = No}
+ end;
+
+%% Incoming hello, outgoing already sent: negotiate protocol version.
+decode(hello, E, #state{hello_status = #pending{tref = TRef,
+ caller = From}}
+ = State) ->
+ cancel_request_timer(TRef),
+ case decode_hello(E) of
+ {ok, SessionId, Capabilities} ->
+ reply(From, handle_capx(State#state{session_id = SessionId,
+ capabilities = Capabilities,
+ hello_status = received}));
+ {error, _Reason} = No ->
+ ct_gen_conn:return(From, No),
+ {stop, State#state{hello_status = No}}
+ end;
+
+%% Duplicate hello: ignore.
+decode(hello, E, #state{hello_status = Other} = State) ->
+ ConnName = (State#state.connection)#connection.name,
+ ?error(ConnName, [{got_unexpected_hello, E},
+ {hello_status, Other}]),
+ State;
+
+decode(notification, E, State) ->
+ State#state.event_receiver ! E,
+ State;
+
+decode(Other, E, State) ->
+ decode_send({got_unexpected_msg, Other}, E, State).
+
+%% reply/2
+%%
+%% Explicitly send a reply that can't be returned.
+
+reply(From, {T, Res, State}) ->
+ ct_gen_conn:return(From, Res),
+ case T of
+ reply ->
+ State;
+ stop ->
+ {T, State}
end.
+%% get_msg_id/1
+
get_msg_id(Attrs) ->
- case lists:keyfind('message-id',1,Attrs) of
- {_,Str} ->
- list_to_integer(Str);
- false ->
- undefined
+ case find('message-id', Attrs) of
+ {_,Str} ->
+ list_to_integer(Str);
+ false ->
+ undefined
end.
-decode_rpc_reply(MsgId,{_,Attrs,Content0}=E,#state{pending=Pending} = State) ->
- case lists:keytake(MsgId,#pending.msg_id,Pending) of
- {value, #pending{tref=TRef,ref=Ref,op=Op,caller=Caller}, Pending1} ->
- cancel_request_timer(Ref,TRef),
- Content = forward_xmlns_attr(Attrs,Content0),
- {CallerReply,{ServerReply,State2}} =
- do_decode_rpc_reply(Op,Content,State#state{pending=Pending1}),
- ct_gen_conn:return(Caller,CallerReply),
- {ServerReply,State2};
- false ->
- %% Result of send/2, when receiving a correct
- %% rpc-reply. Can handle this only if there is just one
- %% pending that matches (i.e. has undefined msg_id and op)
- case [P || P = #pending{msg_id=undefined,op=undefined} <- Pending] of
- [#pending{tref=TRef,
- ref=Ref,
- msg_id=undefined,
- op=undefined,
- caller=Caller}] ->
- cancel_request_timer(Ref,TRef),
- ct_gen_conn:return(Caller,E),
- {noreply,State#state{pending=[]}};
- _ ->
- ConnName = (State#state.connection)#connection.name,
- ?error(ConnName,[{got_unexpected_msg_id,MsgId},
- {expecting,Pending}]),
- {noreply,State}
- end
+%% recode_rpc_reply/3
+
+decode_rpc_reply(undefined, E, #state{pending = [#pending{msg_id = MsgId}]}
+ = State)
+ when MsgId /= undefined ->
+ ConnName = (State#state.connection)#connection.name,
+ ?error(ConnName, [{warning, rpc_reply_missing_msg_id},
+ {assuming, MsgId}]),
+ decode_rpc_reply(MsgId, E, State);
+
+decode_rpc_reply(undefined, _, State) ->
+ ConnName = (State#state.connection)#connection.name,
+ ?error(ConnName, [{error, rpc_reply_missing_msg_id}]),
+ State;
+
+decode_rpc_reply(MsgId,
+ {_, Attrs, Content0}
+ = E,
+ #state{pending = Pending}
+ = State) ->
+ case lists:keytake(MsgId, #pending.msg_id, Pending) of
+ {value, Rec, Rest} ->
+ #pending{tref = TRef, op = Op, caller = From}
+ = Rec,
+ cancel_request_timer(TRef),
+ Content = forward_xmlns_attr(Attrs, Content0),
+ {Reply, T} = do_decode_rpc_reply(Op,
+ Content,
+ State#state{pending = Rest}),
+ ct_gen_conn:return(From, Reply),
+ T;
+ false -> %% not a send_rcp or server has sent wrong id
+ decode_send({got_unexpected_msg_id, MsgId}, E, State)
+ end.
+
+%% decode_send/2
+%%
+%% Result of send/2,3. Only handle one at a time there since all
+%% pendings have msg_id = undefined.
+
+decode_send(ErrorT, Elem, #state{pending = Pending} = State) ->
+ case [P || #pending{msg_id = undefined} = P <- Pending] of
+ [Rec] ->
+ #pending{tref = TRef,
+ caller = From}
+ = Rec,
+ cancel_request_timer(TRef),
+ ct_gen_conn:return(From, Elem),
+ State#state{pending = lists:delete(Rec, Pending)};
+ _ ->
+ Conn = State#state.connection,
+ ?error(Conn#connection.name, [ErrorT, {expecting, Pending}]),
+ State
end.
-do_decode_rpc_reply(Op,Result,State)
- when Op==lock; Op==unlock; Op==edit_config; Op==delete_config;
- Op==copy_config; Op==kill_session ->
- {decode_ok(Result),{noreply,State}};
-do_decode_rpc_reply(Op,Result,State)
- when Op==get; Op==get_config; Op==action ->
- {decode_data(Result),{noreply,State}};
-do_decode_rpc_reply(close_session,Result,State) ->
+%% do_decode_rpc_reply/3
+
+do_decode_rpc_reply(Op, Result, State)
+ when Op == lock;
+ Op == unlock;
+ Op == edit_config;
+ Op == delete_config;
+ Op == copy_config;
+ Op == kill_session ->
+ {decode_ok(Result), State};
+
+do_decode_rpc_reply(Op, Result, State)
+ when Op == get;
+ Op == get_config;
+ Op == action ->
+ {decode_data(Result), State};
+
+do_decode_rpc_reply(close_session, Result, State) ->
case decode_ok(Result) of
- ok -> {ok,{stop,State}};
- Other -> {Other,{noreply,State}}
+ ok ->
+ {ok, {stop, State}};
+ Other ->
+ {Other, State}
end;
-do_decode_rpc_reply({create_subscription,Caller},Result,State) ->
+
+do_decode_rpc_reply({create_subscription, From}, Result, State) ->
case decode_ok(Result) of
- ok ->
- {ok,{noreply,State#state{event_receiver=Caller}}};
- Other ->
- {Other,{noreply,State}}
+ ok ->
+ {ok, State#state{event_receiver = From}};
+ Other ->
+ {Other, State}
end;
-do_decode_rpc_reply(get_event_streams,Result,State) ->
- {decode_streams(decode_data(Result)),{noreply,State}};
-do_decode_rpc_reply(undefined,Result,State) ->
- {Result,{noreply,State}}.
+
+do_decode_rpc_reply(get_event_streams, Result, State) ->
+ {decode_streams(decode_data(Result)), State};
+
+do_decode_rpc_reply(undefined, Result, State) ->
+ {Result, State}.
@@ -1454,7 +1704,7 @@ decode_data([{Tag,Attrs,Content}]) ->
case get_local_name_atom(Tag) of
ok ->
%% when action has return type void
- ok;
+ ok;
data ->
%% Since content of data has nothing from the netconf
%% namespace, we remove the parent's xmlns attribute here
@@ -1525,41 +1775,43 @@ get_all_xmlns_attrs([{Key,_}=Attr|Attrs],XmlnsAttrs) ->
get_all_xmlns_attrs([],XmlnsAttrs) ->
XmlnsAttrs.
-
%% Decode server hello to pick out session id and capabilities
-decode_hello({hello,_Attrs,Hello}) ->
- case lists:keyfind('session-id',1,Hello) of
- {'session-id',_,[SessionId]} ->
- case lists:keyfind(capabilities,1,Hello) of
- {capabilities,_,Capabilities} ->
- case decode_caps(Capabilities,[],false) of
- {ok,Caps} ->
- {ok,list_to_integer(SessionId),Caps};
- Error ->
- Error
- end;
- false ->
- {error,{incorrect_hello,capabilities_not_found}}
- end;
- false ->
- {error,{incorrect_hello,no_session_id_found}}
+decode_hello({hello, _Attrs, Hello}) ->
+ U = make_ref(),
+ try
+ [{'session-id', _, [SessionId]}, _ | _]
+ = [find('session-id', Hello), no_session_id_found | U],
+ [{ok, Id}, _ | _]
+ = [catch {ok, list_to_integer(SessionId)}, invalid_session_id | U],
+ [true, _ | _]
+ = [0 < Id, invalid_session_id | U],
+ [{capabilities, _, Capabilities}, _ | _]
+ = [find(capabilities, Hello), capabilities_not_found | U],
+ [{ok, Caps}, _ | _]
+ = [decode_caps(Capabilities, [], false), false | U],
+ {ok, Id, Caps}
+ catch
+ error: {badmatch, [Error, false | U]} ->
+ Error;
+ error: {badmatch, [_, Reason | U]} ->
+ {error, {incorrect_hello, Reason}}
end.
-decode_caps([{capability,[],[?NETCONF_BASE_CAP++Vsn=Cap]} |Caps], Acc, _) ->
- case Vsn of
- ?NETCONF_BASE_CAP_VSN ->
- decode_caps(Caps, [Cap|Acc], true);
- _ ->
- {error,{incompatible_base_capability_vsn,Vsn}}
- end;
-decode_caps([{capability,[],[Cap]}|Caps],Acc,Base) ->
- decode_caps(Caps,[Cap|Acc],Base);
-decode_caps([H|_T],_,_) ->
- {error,{unexpected_capability_element,H}};
-decode_caps([],_,false) ->
- {error,{incorrect_hello,no_base_capability_found}};
-decode_caps([],Acc,true) ->
- {ok,lists:reverse(Acc)}.
+find(Key, List) ->
+ lists:keyfind(Key, 1, List).
+
+decode_caps([{capability, [], [?NETCONF_BASE_CAP ++ _ = Cap]} | Caps],
+ Acc,
+ _) ->
+ decode_caps(Caps, [Cap|Acc], true);
+decode_caps([{capability, [], [Cap]} | Caps], Acc, Base) ->
+ decode_caps(Caps, [Cap|Acc], Base);
+decode_caps([H|_], _, _) ->
+ {error, {unexpected_capability_element, H}};
+decode_caps([], _, false) ->
+ {error, {incorrect_hello, no_base_capability_found}};
+decode_caps([], Acc, true) ->
+ {ok, lists:reverse(Acc)}.
%% Return a list of {Name,Data}, where data is a {Tag,Value} list for each stream
@@ -1570,7 +1822,7 @@ decode_streams({ok,[{netconf,_,Streams}]}) ->
decode_streams([{streams,_,Streams}]) ->
decode_streams(Streams);
decode_streams([{stream,_,Stream} | Streams]) ->
- {name,_,[Name]} = lists:keyfind(name,1,Stream),
+ {name,_,[Name]} = find(name, Stream),
[{Name,[{Tag,Value} || {Tag,_,[Value]} <- Stream, Tag /= name]}
| decode_streams(Streams)];
decode_streams([]) ->
@@ -1814,6 +2066,190 @@ ssh_close(Connection=#connection{reference = CM}) ->
log(Connection,disconnect),
ok.
+%% ===========================================================================
+
+%% recv/1
+%%
+%% Extract incoming messages using either NETCONF 1.0 framing or
+%% NETCONF 1.1 chunking.
+
+recv(Bin, true) ->
+ recv(Bin, [<<>>, 3]);
+recv(Bin, false) ->
+ recv(Bin, <<>>);
+
+recv(Bin, [Head, Len | Chunks]) -> %% 1.1 chunking
+ chunk(<<Head/binary, Bin/binary>>, Chunks, Len);
+
+%% Start looking for the terminating end-of-message sequence ]]>]]>
+%% 5 characters from the end of the buffered head, since this binary
+%% has already been scanned.
+recv(Bin, Head) when is_binary(Head) -> %% 1.0 framing
+ frame(<<Head/binary, Bin/binary>>, max(0, size(Head) - 5)).
+
+%% frame/2
+%%
+%% Extract a message terminated by the ]]>]]> end-of-message sequence.
+%% Don't need to extract characters as UTF-8 since matching byte-wise
+%% is unambiguous: the high-order bit of every byte of a multi-byte
+%% UTF character is 1, while the end-of-message sequence is ASCII.
+
+frame(Bin, Start) ->
+ Sz = size(Bin),
+ Scope = {Start, Sz - Start},
+ case binary:match(Bin, pattern(), [{scope, Scope}]) of
+ {Len, 6} ->
+ <<Msg:Len/binary, _:6/binary, Rest/binary>> = Bin,
+ {trim(Msg), Rest};
+ nomatch ->
+ Bin
+ end.
+
+%% pattern/0
+
+pattern() ->
+ Key = ?KEY(pattern),
+ case get(Key) of
+ undefined ->
+ CP = binary:compile_pattern(<<"]]>]]>">>),
+ put(Key, CP),
+ CP;
+ CP ->
+ CP
+ end.
+
+%% trim/1
+%%
+%% Whitespace before an XML declaration is an error, but be somewhat
+%% lenient and strip line breaks since the RFC's are unclear on what's
+%% allowed following a ]]>]]> delimiter. Typical seems to be a single
+%% $\n, but strip any of " \t\r\n", and regardless of NETCONF version.
+
+trim(<<C, Bin/binary>>)
+ when C == $\n;
+ C == $\r;
+ C == $\t;
+ C == $ ->
+ trim(Bin);
+
+trim(Bin) ->
+ Bin.
+
+%% chunk/3
+%%
+%% The final argument is either 0 to indicate that a specified number
+%% of bytes of chunk data should be consumed, or at least 3 to
+%% indicate an offset at which to look for a newline following a chunk
+%% size.
+
+%% Accumulating chunk-data ...
+chunk(Bin, [Sz | Chunks] = L, 0) ->
+ case Bin of
+ <<Chunk:Sz/binary, Rest/binary>> ->
+ chunk(Rest, acc(Chunk, Chunks), 3); %% complete chunk ...
+ _ ->
+ [Bin, 0 | L] %% ... or not
+ end;
+
+%% ... or a header.
+
+chunk(Bin, Chunks, Len)
+ when size(Bin) < 4 ->
+ [Bin, 3 = Len | Chunks];
+
+%% End of chunks.
+chunk(<<"\n##\n", Rest/binary>>, Chunks, _) ->
+ case Chunks of
+ [] ->
+ {error, "end-of-chunks unexpected"}; %% must be at least one
+ Bins ->
+ {lists:reverse(Bins), Rest}
+ end;
+
+%% Matching each of the 10 newline possibilities is faster than
+%% searching.
+chunk(<<"\n#", Head:1/binary, $\n, Rest/binary>>, Chunks, _) ->
+ acc(Head, Rest, Chunks);
+chunk(<<"\n#", Head:2/binary, $\n, Rest/binary>>, Chunks, _) ->
+ acc(Head, Rest, Chunks);
+chunk(<<"\n#", Head:3/binary, $\n, Rest/binary>>, Chunks, _) ->
+ acc(Head, Rest, Chunks);
+chunk(<<"\n#", Head:4/binary, $\n, Rest/binary>>, Chunks, _) ->
+ acc(Head, Rest, Chunks);
+chunk(<<"\n#", Head:5/binary, $\n, Rest/binary>>, Chunks, _) ->
+ acc(Head, Rest, Chunks);
+chunk(<<"\n#", Head:6/binary, $\n, Rest/binary>>, Chunks, _) ->
+ acc(Head, Rest, Chunks);
+chunk(<<"\n#", Head:7/binary, $\n, Rest/binary>>, Chunks, _) ->
+ acc(Head, Rest, Chunks);
+chunk(<<"\n#", Head:8/binary, $\n, Rest/binary>>, Chunks, _) ->
+ acc(Head, Rest, Chunks);
+chunk(<<"\n#", Head:9/binary, $\n, Rest/binary>>, Chunks, _) ->
+ acc(Head, Rest, Chunks);
+chunk(<<"\n#", Head:10/binary, $\n, Rest/binary>>, Chunks, _) ->
+ acc(Head, Rest, Chunks);
+
+chunk(<<"\n#", Bin:11/binary, _/binary>>, _, _) ->
+ {error, {"chunk-size too long", Bin}}; %% 32-bits = max 10 digits
+
+chunk(<<"\n#", _/binary>> = Bin, Chunks, _) ->
+ [Bin, size(Bin) | Chunks];
+
+chunk(Bin, Chunks, 3 = Len) ->
+ case drop(Bin) of
+ <<>> ->
+ [Bin, Len | Chunks];
+ <<"\n#", _/binary>> = B ->
+ chunk(B, Chunks, Len);
+ _ ->
+ {error, {"not a chunk", Bin}}
+ end.
+
+%% drop/1
+
+drop(<<"\n#", _/binary>> = Bin) ->
+ Bin;
+
+drop(<<C, Bin/binary>>)
+ when C == $\n;
+ C == $\r;
+ C == $\t;
+ C == $ ->
+ drop(Bin);
+
+drop(Bin) ->
+ Bin.
+
+%% acc/2
+
+acc(Chunk, []) ->
+ [B || B <- [trim(Chunk)], <<>> /= B];
+
+acc(Chunk, Chunks) ->
+ [Chunk | Chunks].
+
+%% acc/3
+
+acc(Head, Rest, Chunks) ->
+ case chunk_size(Head) of
+ {error, _Reason} = No ->
+ No;
+ Sz ->
+ chunk(Rest, [Sz | Chunks], 0)
+ end.
+
+%% chunk_size/1
+
+chunk_size(<<C, _/binary>> = Bin) ->
+ try true = $0 < C, binary_to_integer(Bin) of
+ Sz when 0 < Sz bsr 32 ->
+ {error, {"chunk-size too large", Sz}};
+ Sz ->
+ Sz
+ catch
+ error: _ ->
+ {error, {"chunk-size invalid", Bin}}
+ end.
%%----------------------------------------------------------------------
%% END OF MODULE
diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl
index 388d5d46c6..1d50f4f97f 100644
--- a/lib/common_test/test/ct_test_support.erl
+++ b/lib/common_test/test/ct_test_support.erl
@@ -219,17 +219,8 @@ get_opts(Config) ->
end,
LogDir =
case os:getenv("CT_USE_TMP_DIR") of
- false ->
- case os:type() of
- {win32,_} ->
- if TempDir == undefined -> PrivDir;
- true -> TempDir
- end;
- _ ->
- PrivDir
- end;
- _ ->
- TempDir
+ false -> PrivDir;
+ _ -> TempDir
end,
%% Copy test variables to app environment on new node
diff --git a/lib/erl_docgen/priv/xsl/db_pdf.xsl b/lib/erl_docgen/priv/xsl/db_pdf.xsl
index 1b91d768e3..7080394298 100644
--- a/lib/erl_docgen/priv/xsl/db_pdf.xsl
+++ b/lib/erl_docgen/priv/xsl/db_pdf.xsl
@@ -662,7 +662,7 @@
<fo:flow flow-name="xsl-region-body">
<fo:block xsl:use-attribute-sets="cover.logo">
- <fo:external-graphic src="{$logo}"/>
+ <fo:external-graphic src="url('{$logo}')"/>
</fo:block>
<fo:block xsl:use-attribute-sets="cover.title" id="cover-page">
<xsl:apply-templates/>
@@ -1658,10 +1658,10 @@
<fo:block xsl:use-attribute-sets="image">
<xsl:choose>
<xsl:when test="@width">
- <fo:external-graphic content-width="scale-to-fit" width="{@width}" inline-progression-dimension.maximum="100%" src="{@file}"/>
+ <fo:external-graphic content-width="scale-to-fit" width="{@width}" inline-progression-dimension.maximum="100%" src="url('{@file}')"/>
</xsl:when>
<xsl:otherwise>
- <fo:external-graphic content-width="scale-down-to-fit" inline-progression-dimension.maximum="100%" src="{@file}"/>
+ <fo:external-graphic content-width="scale-down-to-fit" inline-progression-dimension.maximum="100%" src="url('{@file}')"/>
</xsl:otherwise>
</xsl:choose>
<xsl:apply-templates>
diff --git a/lib/ftp/src/ftp.erl b/lib/ftp/src/ftp.erl
index 18cd8c7524..e9be7b8ff7 100644
--- a/lib/ftp/src/ftp.erl
+++ b/lib/ftp/src/ftp.erl
@@ -1399,10 +1399,13 @@ handle_info({Transport, Socket, Data}, #state{csock = {Transport, Socket},
ctrl_data =
{NextMsgData, [], start}})
end;
- {continue, NewCtrlData} ->
+ {continue, NewCtrlData} when NewCtrlData =/= CtrlData ->
?DBG(' ...Continue... ctrl_data=~p~n',[NewCtrlData]),
State = activate_ctrl_connection(State0),
- {noreply, State#state{ctrl_data = NewCtrlData}}
+ {noreply, State#state{ctrl_data = NewCtrlData}};
+ {continue, NewCtrlData} when NewCtrlData == CtrlData ->
+ ?DBG(' ...Continue... ctrl_data=~p~n',[NewCtrlData]),
+ {noreply, State0}
end;
%% If the server closes the control channel it is
diff --git a/lib/inets/doc/src/httpc.xml b/lib/inets/doc/src/httpc.xml
index 7451b314ec..72ac79a0b0 100644
--- a/lib/inets/doc/src/httpc.xml
+++ b/lib/inets/doc/src/httpc.xml
@@ -701,15 +701,17 @@
<type>
<v>Profile = profile() | pid()</v>
<d>When started <c>stand_alone</c> only the pid can be used.</d>
- <v>session_info() = {GoodSessions, BadSessions, NonSessions}</v>
- <v>GoodSessions = session()</v>
- <v>BadSessions = tuple()</v>
- <v>NonSessions = term()</v>
+ <v>session_info() = {[session()], [term()], [term()]}</v>
+ <v>session() = term() - Internal representation of a session</v>
</type>
<desc>
- <p>Produces a slightly processed dump of the session
- database. It is intended for debugging.
- If no profile is specified, the default profile is used.</p>
+ <p> This function is intended for debugging only. It produces
+ a slightly processed dump of the session database. The first
+ list of the session information tuple will contain session
+ information on an internal format. The last two lists of the
+ session information tuple should always be empty if the code
+ is working as intended. If no profile is specified, the default
+ profile is used.</p>
</desc>
</func>
diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml
index 45533c4f4b..e3981265af 100644
--- a/lib/inets/doc/src/notes.xml
+++ b/lib/inets/doc/src/notes.xml
@@ -66,6 +66,23 @@
</section>
+ <section><title>Inets 7.0.7.1</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ mod_esi will now always propagate the actual HTTP status
+ code that it anwsered with, to later mod-modules, and not
+ in some cases hardcode 200.</p>
+ <p>
+ Own Id: OTP-16049 Aux Id: ERIERL-395 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Inets 7.0.7</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -3346,5 +3363,3 @@
-->
</chapter>
-
-
diff --git a/lib/inets/src/http_server/mod_esi.erl b/lib/inets/src/http_server/mod_esi.erl
index 8cbd9798e6..bcf392d55c 100644
--- a/lib/inets/src/http_server/mod_esi.erl
+++ b/lib/inets/src/http_server/mod_esi.erl
@@ -345,12 +345,12 @@ erl_scheme_webpage_whole(Mod, Func, Env, Input, ModData) ->
integer_to_list(Length)}| NewHeaders]),
case ModData#mod.method of
"HEAD" ->
- {proceed, [{response, {already_sent, 200, 0}} |
+ {proceed, [{response, {already_sent, StatusCode, 0}} |
ModData#mod.data]};
_ ->
httpd_response:send_body(ModData,
StatusCode, Body),
- {proceed, [{response, {already_sent, 200,
+ {proceed, [{response, {already_sent, StatusCode,
Length}} |
ModData#mod.data]}
end
@@ -415,12 +415,12 @@ deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid, Timeout) ->
[{"transfer-encoding",
"chunked"} | NewHeaders])
end,
- handle_body(Pid, ModData, Body, Timeout, length(Body),
+ handle_body(Pid, ModData, Body, Timeout, length(Body), StatusCode,
IsDisableChunkedSend);
timeout ->
send_headers(ModData, 504, [{"connection", "close"}]),
httpd_socket:close(ModData#mod.socket_type, ModData#mod.socket),
- {proceed,[{response, {already_sent, 200, 0}} | ModData#mod.data]}
+ {proceed,[{response, {already_sent, 504, 0}} | ModData#mod.data]}
end.
receive_headers(Timeout) ->
@@ -444,24 +444,24 @@ send_headers(ModData, StatusCode, HTTPHeaders) ->
httpd_response:send_header(ModData, StatusCode,
ExtraHeaders ++ HTTPHeaders).
-handle_body(_, #mod{method = "HEAD"} = ModData, _, _, Size, _) ->
- {proceed, [{response, {already_sent, 200, Size}} | ModData#mod.data]};
+handle_body(_, #mod{method = "HEAD"} = ModData, _, _, Size, StatusCode, _) ->
+ {proceed, [{response, {already_sent, StatusCode, Size}} | ModData#mod.data]};
-handle_body(Pid, ModData, Body, Timeout, Size, IsDisableChunkedSend) ->
+handle_body(Pid, ModData, Body, Timeout, Size, StatusCode, IsDisableChunkedSend) ->
httpd_response:send_chunk(ModData, Body, IsDisableChunkedSend),
receive
{esi_data, Data} when is_binary(Data) ->
- handle_body(Pid, ModData, Data, Timeout, Size + byte_size(Data),
+ handle_body(Pid, ModData, Data, Timeout, Size + byte_size(Data), StatusCode,
IsDisableChunkedSend);
{esi_data, Data} ->
- handle_body(Pid, ModData, Data, Timeout, Size + length(Data),
+ handle_body(Pid, ModData, Data, Timeout, Size + length(Data), StatusCode,
IsDisableChunkedSend);
{ok, Data} ->
- handle_body(Pid, ModData, Data, Timeout, Size + length(Data),
+ handle_body(Pid, ModData, Data, Timeout, Size + length(Data), StatusCode,
IsDisableChunkedSend);
{'EXIT', Pid, normal} when is_pid(Pid) ->
httpd_response:send_final_chunk(ModData, IsDisableChunkedSend),
- {proceed, [{response, {already_sent, 200, Size}} |
+ {proceed, [{response, {already_sent, StatusCode, Size}} |
ModData#mod.data]};
{'EXIT', Pid, Reason} when is_pid(Pid) ->
Error = lists:flatten(io_lib:format("mod_esi process failed with reason ~p", [Reason])),
diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl
index fc5ca14dcd..bf926ec9c1 100644
--- a/lib/inets/test/httpd_SUITE.erl
+++ b/lib/inets/test/httpd_SUITE.erl
@@ -28,6 +28,7 @@
-include_lib("kernel/include/file.hrl").
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+-include_lib("inets/include/httpd.hrl").
-include("inets_test_lib.hrl").
%% Note: This directive should only be used in test suites.
@@ -58,6 +59,7 @@ all() ->
{group, https_limit},
{group, http_custom},
{group, https_custom},
+ {group, https_custom},
{group, http_basic_auth},
{group, https_basic_auth},
{group, http_auth_api},
@@ -139,7 +141,7 @@ groups() ->
{http_1_1, [],
[host, chunked, expect, cgi, cgi_chunked_encoding_test,
trace, range, if_modified_since, mod_esi_chunk_timeout,
- esi_put, esi_post] ++ http_head() ++ http_get() ++ load()},
+ esi_put, esi_post, esi_proagate] ++ http_head() ++ http_get() ++ load()},
{http_1_0, [], [host, cgi, trace] ++ http_head() ++ http_get() ++ load()},
{http_0_9, [], http_head() ++ http_get() ++ load()},
{http_rel_path_script_alias, [], [cgi]},
@@ -1053,6 +1055,17 @@ mod_esi_chunk_timeout(Config) when is_list(Config) ->
proplists:get_value(port, Config),
proplists:get_value(host, Config),
proplists:get_value(node, Config)).
+%%-------------------------------------------------------------------------
+esi_proagate(Config) when is_list(Config) ->
+ register(propagate_test, self()),
+ ok = http_status("GET /cgi-bin/erl/httpd_example:new_status_and_location ",
+ Config, [{statuscode, 201}]),
+ receive
+ {status, 201} ->
+ ok;
+ Err ->
+ ct:fail(Err)
+ end.
%%-------------------------------------------------------------------------
cgi() ->
@@ -2246,8 +2259,17 @@ head_status(_) ->
basic_conf() ->
[{modules, [mod_alias, mod_range, mod_responsecontrol,
- mod_trace, mod_esi, mod_cgi, mod_get, mod_head]}].
-
+ mod_trace, mod_esi, ?MODULE, mod_cgi, mod_get, mod_head]}].
+do(ModData) ->
+ case whereis(propagate_test) of
+ undefined ->
+ ok;
+ _ ->
+ {already_sent, Status, _Size} = proplists:get_value(response, ModData#mod.data),
+ propagate_test ! {status, Status}
+ end,
+ {proceed, ModData#mod.data}.
+
not_sup_conf() ->
[{modules, [mod_get]}].
diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml
index 4d31eeea3d..3acac69ca5 100644
--- a/lib/kernel/doc/src/notes.xml
+++ b/lib/kernel/doc/src/notes.xml
@@ -147,6 +147,24 @@
</section>
+<section><title>Kernel 6.3.1.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix bug where the log file in <c>logger_std_h</c> would
+ not be closed when the inode of the file changed. This
+ would in turn cause a file descriptor leak when tools
+ like logrotate are used.</p>
+ <p>
+ Own Id: OTP-15997 Aux Id: PR-2331 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Kernel 6.3.1.2</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/kernel/src/group_history.erl b/lib/kernel/src/group_history.erl
index e69fa85492..1fab2ba14e 100644
--- a/lib/kernel/src/group_history.erl
+++ b/lib/kernel/src/group_history.erl
@@ -131,11 +131,15 @@ repair_log(Name) ->
%% Return whether the shell history is enabled or not
-spec history_status() -> enabled | disabled.
history_status() ->
- case is_user() orelse init_running() orelse application:get_env(kernel, shell_history) of
- true -> disabled; % don't run for user proc
- {ok, enabled} -> enabled;
- undefined -> ?DEFAULT_STATUS;
- _ -> disabled
+ %% Don't run for user proc or if the emulator's tearing down
+ Skip = is_user() orelse not init_running(),
+ case application:get_env(kernel, shell_history) of
+ {ok, enabled} when not Skip ->
+ enabled;
+ undefined when not Skip ->
+ ?DEFAULT_STATUS;
+ _ ->
+ disabled
end.
%% Return whether the user process is running this
diff --git a/lib/kernel/test/gen_tcp_misc_SUITE.erl b/lib/kernel/test/gen_tcp_misc_SUITE.erl
index de87bd9472..cd1bc2e0d1 100644
--- a/lib/kernel/test/gen_tcp_misc_SUITE.erl
+++ b/lib/kernel/test/gen_tcp_misc_SUITE.erl
@@ -3545,28 +3545,33 @@ wait(Mref) ->
%% OTP-15536
%% Test that send error works correctly for delay_send
delay_send_error(_Config) ->
- {ok, LS} = gen_tcp:listen(0, [{reuseaddr, true}, {packet, 1}, {active, false}]),
- {ok,{{0,0,0,0},PortNum}}=inet:sockname(LS),
- P = spawn_link(
- fun() ->
- {ok, S} = gen_tcp:accept(LS),
- receive die -> gen_tcp:close(S) end
- end),
- erlang:monitor(process, P),
- {ok, S} = gen_tcp:connect("localhost", PortNum,
- [{packet, 1}, {active, false}, {delay_send, true}]),
-
+ {ok, L} =
+ gen_tcp:listen(
+ 0, [{reuseaddr, true}, {packet, 1}, {active, false}]),
+ {ok,{{0,0,0,0},PortNum}}=inet:sockname(L),
+ {ok, C} =
+ gen_tcp:connect(
+ "localhost", PortNum,
+ [{packet, 1}, {active, false}, {delay_send, true}]),
+ {ok, S} = gen_tcp:accept(L),
%% Do a couple of sends first to see that it works
- ok = gen_tcp:send(S, "hello"),
- ok = gen_tcp:send(S, "hello"),
- ok = gen_tcp:send(S, "hello"),
-
- %% Make the receiver close
- P ! die,
- receive _Down -> ok end,
-
- ok = gen_tcp:send(S, "hello"),
- timer:sleep(500), %% Sleep in order for delay_send to have time to trigger
-
- %% This used to result in a double free
- {error, closed} = gen_tcp:send(S, "hello").
+ ok = gen_tcp:send(C, "hello"),
+ ok = gen_tcp:send(C, "hello"),
+ ok = gen_tcp:send(C, "hello"),
+ %% Close the receiver
+ ok = gen_tcp:close(S),
+ %%
+ case gen_tcp:send(C, "hello") of
+ ok ->
+ case gen_tcp:send(C, "hello") of
+ ok ->
+ timer:sleep(1000), %% Sleep in order for delay_send to have time to trigger
+ %% This used to result in a double free
+ {error, closed} = gen_tcp:send(C, "hello");
+ {error, closed} ->
+ ok
+ end;
+ {error, closed} ->
+ ok
+ end,
+ ok = gen_tcp:close(C).
diff --git a/lib/megaco/src/engine/megaco_monitor.erl b/lib/megaco/src/engine/megaco_monitor.erl
index 877509a776..8f4741b202 100644
--- a/lib/megaco/src/engine/megaco_monitor.erl
+++ b/lib/megaco/src/engine/megaco_monitor.erl
@@ -193,7 +193,7 @@ cancel_apply_after({apply_after, Ref}) ->
TimeLeft when is_integer(TimeLeft) ->
{ok, TimeLeft};
_ ->
- {ok, 0}
+ {error, {already_expired, Ref}}
end;
cancel_apply_after(apply_after_infinity) ->
ok;
diff --git a/lib/megaco/test/megaco_SUITE.erl b/lib/megaco/test/megaco_SUITE.erl
index f7b8ffe032..b55dc68143 100644
--- a/lib/megaco/test/megaco_SUITE.erl
+++ b/lib/megaco/test/megaco_SUITE.erl
@@ -130,7 +130,19 @@ end_per_suite(_Config) ->
ok.
init_per_group(_GroupName, Config) ->
- Config.
+ Skippable = [{unix, [{darwin, fun(V) when (V > {9, 8, 0}) ->
+ %% This version is OK: No Skip
+ false;
+ (_V) ->
+ %% This version is *not* ok: Skip
+ true
+ end}]}],
+ case ?OS_BASED_SKIP(Skippable) of
+ true ->
+ {skip, "***OLD*** Darwin"};
+ false ->
+ Config
+ end.
end_per_group(_GroupName, Config) ->
Config.
diff --git a/lib/megaco/test/megaco_mess_test.erl b/lib/megaco/test/megaco_mess_test.erl
index 593688eb13..0486c13e5f 100644
--- a/lib/megaco/test/megaco_mess_test.erl
+++ b/lib/megaco/test/megaco_mess_test.erl
@@ -790,8 +790,15 @@ request_and_reply_pending_ack_no_pending(Config) when is_list(Config) ->
d("[MGC] start the simulation"),
{ok, MgcId} = megaco_test_megaco_generator:exec(Mgc, MgcEvSeq),
- i("wait some time before starting the MG simulator"),
- sleep(1000),
+ %% i("wait some time before starting the MG simulator"),
+ %% sleep(1000),
+
+ i("await MGC ready announcement"),
+ receive
+ announce_mgc ->
+ i("received MGC ready announcement"),
+ ok
+ end,
d("[MG] start the simulator (generator)"),
{ok, Mg} = megaco_test_tcp_generator:start_link("MG", MgNode),
@@ -844,6 +851,7 @@ request_and_reply_pending_ack_no_pending(Config) when is_list(Config) ->
-endif.
rarpanp_mgc_event_sequence(text, tcp) ->
+ CTRL = self(),
Mid = {deviceName,"ctrl"},
RI = [
{port, 2944},
@@ -855,10 +863,6 @@ rarpanp_mgc_event_sequence(text, tcp) ->
ScrVerify = ?rarpanp_mgc_verify_service_change_req_fun(Mid),
NrVerify = ?rarpanp_mgc_verify_notify_req_fun(),
DiscoVerify = ?rarpanp_mgc_verify_handle_disconnect_fun(),
-%% ConnectVerify = fun rarpanp_mgc_verify_handle_connect/1,
-%% ScrVerify = rarpanp_mgc_verify_service_change_req_fun(Mid),
-%% NrVerify = rarpanp_mgc_verify_notify_request_fun(),
-%% DiscoVerify = fun rarpanp_mgc_verify_handle_disconnect/1,
EvSeq = [
{debug, true},
{megaco_trace, disable},
@@ -868,6 +872,10 @@ rarpanp_mgc_event_sequence(text, tcp) ->
{megaco_update_user_info, sent_pending_limit, 100},
start_transport,
listen,
+
+ %% ANNOUNCE READY
+ {trigger, fun() -> CTRL ! announce_mgc end},
+
{megaco_callback, handle_connect, ConnectVerify},
{megaco_conn_info, all},
{megaco_callback, handle_trans_request, ScrVerify},
@@ -1381,8 +1389,15 @@ request_and_reply_pending_ack_one_pending(Config) when is_list(Config) ->
d("[MGC] start the simulation"),
{ok, MgcId} = megaco_test_megaco_generator:exec(Mgc, MgcEvSeq),
- i("wait some time before starting the MG simulator"),
- sleep(1000),
+ %% i("wait some time before starting the MG simulator"),
+ %% sleep(1000),
+
+ i("await MGC ready announcement"),
+ receive
+ announce_mgc ->
+ i("received MGC ready announcement"),
+ ok
+ end,
d("[MG] start the simulator (generator)"),
{ok, Mg} = megaco_test_tcp_generator:start_link("MG", MgNode),
@@ -1453,6 +1468,7 @@ rarpaop_mgc_event_sequence(binary, tcp) ->
rarpaop_mgc_event_sequence(Port, TranspMod, EncMod, EncConf).
rarpaop_mgc_event_sequence(Port, TranspMod, EncMod, EncConf) ->
+ CTRL = self(),
Mid = {deviceName,"ctrl"},
RI = [
{port, Port},
@@ -1465,11 +1481,6 @@ rarpaop_mgc_event_sequence(Port, TranspMod, EncMod, EncConf) ->
NrVerify = ?rarpaop_mgc_verify_notify_req_fun(),
AckVerify = ?rarpaop_mgc_verify_reply_ack_fun(),
DiscoVerify = ?rarpaop_mgc_verify_handle_disconnect_fun(),
-%% ConnectVerify = fun rarpaop_mgc_verify_handle_connect/1,
-%% ScrVerify = rarpaop_mgc_verify_service_change_req_fun(Mid),
-%% NrVerify = rarpaop_mgc_verify_notify_request_fun(),
-%% AckVerify = rarpaop_mgc_verify_reply_ack_fun(),
-%% DiscoVerify = fun rarpaop_mgc_verify_handle_disconnect/1,
EvSeq = [
{debug, true},
{megaco_trace, disable},
@@ -1479,6 +1490,10 @@ rarpaop_mgc_event_sequence(Port, TranspMod, EncMod, EncConf) ->
{megaco_update_user_info, sent_pending_limit, 100},
start_transport,
listen,
+
+ %% ANNOUNCE READY
+ {trigger, fun() -> CTRL ! announce_mgc end},
+
{megaco_callback, handle_connect, ConnectVerify},
{megaco_conn_info, all},
{megaco_callback, handle_trans_request, ScrVerify},
@@ -2066,8 +2081,15 @@ single_trans_req_and_reply(Config) when is_list(Config) ->
d("[MGC] start the simulation"),
{ok, MgcId} = megaco_test_megaco_generator:exec(Mgc, MgcEvSeq),
- i("wait some time before starting the MG simulator"),
- sleep(1000),
+ %% i("wait some time before starting the MG simulator"),
+ %% sleep(1000),
+
+ i("await MGC ready announcement"),
+ receive
+ announce_mgc ->
+ i("received MGC ready announcement"),
+ ok
+ end,
d("[MG] start the simulator (generator)"),
{ok, Mg} = megaco_test_megaco_generator:start_link("MG", MgNode),
@@ -2120,6 +2142,7 @@ single_trans_req_and_reply(Config) when is_list(Config) ->
-endif.
strar_mgc_event_sequence(text, tcp) ->
+ CTRL = self(),
Mid = {deviceName,"ctrl"},
RI = [
{port, 2944},
@@ -2127,18 +2150,10 @@ strar_mgc_event_sequence(text, tcp) ->
{encoding_config, []},
{transport_module, megaco_tcp}
],
- %% Tid = #megaco_term_id{id = ["00000000","00000000","01101101"]},
-%% ReqTmr = #megaco_incr_timer{wait_for = 500,
-%% factor = 1,
-%% max_retries = 1},
ConnectVerify = ?strar_mgc_verify_handle_connect_fun(),
ServiceChangeReqVerify = ?strar_mgc_verify_service_change_req_fun(Mid),
NotifyReqVerify = ?strar_mgc_verify_notify_req_fun(),
DiscoVerify = ?strar_mgc_verify_handle_disconnect_fun(),
-%% ConnectVerify = fun strar_mgc_verify_handle_connect/1,
-%% ServiceChangeReqVerify = strar_mgc_verify_service_change_req_fun(Mid),
-%% NotifyReqVerify = strar_mgc_verify_notify_request_fun(),
-%% DiscoVerify = fun strar_mgc_verify_handle_disconnect/1,
EvSeq = [
{debug, true},
{megaco_trace, disable},
@@ -2146,6 +2161,10 @@ strar_mgc_event_sequence(text, tcp) ->
{megaco_start_user, Mid, RI, []},
start_transport,
listen,
+
+ %% ANNOUNCE READY
+ {trigger, fun() -> CTRL ! announce_mgc end},
+
{megaco_callback, handle_connect, ConnectVerify},
{megaco_callback, handle_trans_request, ServiceChangeReqVerify},
{megaco_callback, handle_trans_request, NotifyReqVerify},
@@ -2578,8 +2597,15 @@ single_trans_req_and_reply_sendopts(Config) when is_list(Config) ->
d("[MGC] start the simulation"),
{ok, MgcId} = megaco_test_megaco_generator:exec(Mgc, MgcEvSeq),
- i("wait some time before starting the MG simulator"),
- sleep(1000),
+ %% i("wait some time before starting the MG simulator"),
+ %% sleep(1000),
+
+ i("await MGC ready announcement"),
+ receive
+ announce_mgc ->
+ i("received MGC ready announcement"),
+ ok
+ end,
d("[MG] start the simulator (generator)"),
{ok, Mg} = megaco_test_megaco_generator:start_link("MG", MgNode),
@@ -2632,6 +2658,7 @@ single_trans_req_and_reply_sendopts(Config) when is_list(Config) ->
-endif.
straro_mgc_event_sequence(text, tcp) ->
+ CTRL = self(),
Mid = {deviceName,"ctrl"},
RI = [
{port, 2944},
@@ -2643,10 +2670,6 @@ straro_mgc_event_sequence(text, tcp) ->
ServiceChangeReqVerify = ?straro_mgc_verify_service_change_req_fun(Mid),
NotifyReqVerify = ?straro_mgc_verify_notify_req_fun(),
TransAckVerify = ?straro_mgc_verify_handle_trans_ack_fun(),
-%% ConnectVerify = fun straro_mgc_verify_handle_connect/1,
-%% ServiceChangeReqVerify = straro_mgc_verify_service_change_req_fun(Mid),
-%% NotifyReqVerify = straro_mgc_verify_notify_request_fun(),
-%% TransAckVerify = straro_mgc_verify_handle_trans_ack_fun(),
EvSeq = [
{debug, true},
{megaco_trace, disable},
@@ -2654,6 +2677,10 @@ straro_mgc_event_sequence(text, tcp) ->
{megaco_start_user, Mid, RI, []},
start_transport,
listen,
+
+ %% ANNOUNCE READY
+ {trigger, fun() -> CTRL ! announce_mgc end},
+
{megaco_callback, handle_connect, ConnectVerify},
{megaco_callback, handle_trans_request, ServiceChangeReqVerify},
{megaco_callback, handle_trans_request, NotifyReqVerify},
@@ -3125,8 +3152,15 @@ request_and_reply_and_ack(Config) when is_list(Config) ->
d("[MGC] start the simulation"),
{ok, MgcId} = megaco_test_megaco_generator:exec(Mgc, MgcEvSeq),
- i("wait some time before starting the MG simulator"),
- sleep(1000),
+ %% i("wait some time before starting the MG simulator"),
+ %% sleep(1000),
+
+ i("await MGC ready announcement"),
+ receive
+ announce_mgc ->
+ i("received MGC ready announcement"),
+ ok
+ end,
d("[MG] start the simulator (generator)"),
{ok, Mg} = megaco_test_tcp_generator:start_link("MG", MgNode),
@@ -3184,6 +3218,7 @@ request_and_reply_and_ack(Config) when is_list(Config) ->
-endif.
raraa_mgc_event_sequence(text, tcp) ->
+ CTRL = self(),
Mid = {deviceName,"ctrl"},
RI = [
{port, 2944},
@@ -3196,11 +3231,6 @@ raraa_mgc_event_sequence(text, tcp) ->
NrVerify = ?raraa_mgc_verify_notify_req_fun(),
AckVerify = ?raraa_mgc_verify_handle_trans_ack_fun(),
DiscoVerify = ?raraa_mgc_verify_handle_disconnect_fun(),
-%% ConnectVerify = fun raraa_mgc_verify_handle_connect/1,
-%% ScrVerify = raraa_mgc_verify_service_change_req_fun(Mid),
-%% NrVerify = raraa_mgc_verify_notify_request_fun(),
-%% AckVerify = raraa_mgc_verify_trans_ack_fun(),
-%% DiscoVerify = fun raraa_mgc_verify_handle_disconnect/1,
EvSeq = [
{debug, true},
{megaco_trace, disable},
@@ -3210,6 +3240,10 @@ raraa_mgc_event_sequence(text, tcp) ->
{megaco_update_user_info, sent_pending_limit, 100},
start_transport,
listen,
+
+ %% ANNOUNCE READY
+ {trigger, fun() -> CTRL ! announce_mgc end},
+
{megaco_callback, handle_connect, ConnectVerify},
{megaco_conn_info, all},
{megaco_callback, handle_trans_request, ScrVerify},
@@ -3739,8 +3773,15 @@ request_and_reply_and_no_ack(Config) when is_list(Config) ->
d("[MGC] start the simulation"),
{ok, MgcId} = megaco_test_megaco_generator:exec(Mgc, MgcEvSeq),
- i("wait some time before starting the MG simulator"),
- sleep(1000),
+ %% i("wait some time before starting the MG simulator"),
+ %% sleep(1000),
+
+ i("await MGC ready announcement"),
+ receive
+ announce_mgc ->
+ i("received MGC ready announcement"),
+ ok
+ end,
d("[MG] start the simulator (generator)"),
{ok, Mg} = megaco_test_tcp_generator:start_link("MG", MgNode),
@@ -3798,6 +3839,7 @@ request_and_reply_and_no_ack(Config) when is_list(Config) ->
-endif.
rarana_mgc_event_sequence(text, tcp) ->
+ CTRL = self(),
Mid = {deviceName,"ctrl"},
RI = [
{port, 2944},
@@ -3810,11 +3852,6 @@ rarana_mgc_event_sequence(text, tcp) ->
NrVerify = ?rarana_mgc_verify_notify_req_fun(),
AckVerify = ?rarana_mgc_verify_handle_trans_ack_fun(),
DiscoVerify = ?rarana_mgc_verify_handle_disconnect_fun(),
-%% ConnectVerify = fun rarana_mgc_verify_handle_connect/1,
-%% ScrVerify = rarana_mgc_verify_service_change_req_fun(Mid),
-%% NrVerify = rarana_mgc_verify_notify_request_fun(),
-%% AckVerify = rarana_mgc_verify_trans_ack_fun(),
-%% DiscoVerify = fun rarana_mgc_verify_handle_disconnect/1,
EvSeq = [
{debug, true},
{megaco_trace, disable},
@@ -3825,6 +3862,10 @@ rarana_mgc_event_sequence(text, tcp) ->
{megaco_update_user_info, reply_timer, 9000},
start_transport,
listen,
+
+ %% ANNOUNCE READY
+ {trigger, fun() -> CTRL ! announce_mgc end},
+
{megaco_callback, handle_connect, ConnectVerify},
{megaco_conn_info, all},
{megaco_callback, handle_trans_request, ScrVerify},
@@ -4339,8 +4380,15 @@ request_and_reply_and_late_ack(Config) when is_list(Config) ->
d("[MGC] start the simulation"),
{ok, MgcId} = megaco_test_megaco_generator:exec(Mgc, MgcEvSeq),
- i("wait some time before starting the MG simulator"),
- sleep(1000),
+ %% i("wait some time before starting the MG simulator"),
+ %% sleep(1000),
+
+ i("await MGC ready announcement"),
+ receive
+ announce_mgc ->
+ i("received MGC ready announcement"),
+ ok
+ end,
d("[MG] start the simulator (generator)"),
{ok, Mg} = megaco_test_tcp_generator:start_link("MG", MgNode),
@@ -4398,6 +4446,7 @@ request_and_reply_and_late_ack(Config) when is_list(Config) ->
-endif.
rarala_mgc_event_sequence(text, tcp) ->
+ CTRL = self(),
Mid = {deviceName,"ctrl"},
RI = [
{port, 2944},
@@ -4415,11 +4464,6 @@ rarala_mgc_event_sequence(text, tcp) ->
NrVerify = ?rarala_mgc_verify_notify_req_fun(),
AckVerify = ?rarala_mgc_verify_handle_trans_ack_fun(),
DiscoVerify = ?rarala_mgc_verify_handle_disconnect_fun(),
-%% ConnectVerify = fun rarala_mgc_verify_handle_connect/1,
-%% ScrVerify = rarala_mgc_verify_service_change_req_fun(Mid),
-%% NrVerify = rarala_mgc_verify_notify_request_fun(),
-%% AckVerify = rarala_mgc_verify_trans_ack_fun(),
-%% DiscoVerify = fun rarala_mgc_verify_handle_disconnect/1,
EvSeq = [
{debug, true},
{megaco_trace, disable},
@@ -4430,6 +4474,10 @@ rarala_mgc_event_sequence(text, tcp) ->
{megaco_update_user_info, reply_timer, RepTmr},
start_transport,
listen,
+
+ %% ANNOUNCE READY
+ {trigger, fun() -> CTRL ! announce_mgc end},
+
{megaco_callback, handle_connect, ConnectVerify},
{megaco_conn_info, all},
{megaco_callback, handle_trans_request, ScrVerify},
@@ -4970,8 +5018,15 @@ trans_req_and_reply_and_req(Config) when is_list(Config) ->
d("[MGC] start the simulation"),
{ok, MgcId} = megaco_test_megaco_generator:exec(Mgc, MgcEvSeq),
- i("wait some time before starting the MG simulator"),
- sleep(1000),
+ %% i("wait some time before starting the MG simulator"),
+ %% sleep(1000),
+
+ i("await MGC ready announcement"),
+ receive
+ announce_mgc ->
+ i("received MGC ready announcement"),
+ ok
+ end,
d("[MG] start the simulator (generator)"),
{ok, Mg} = megaco_test_tcp_generator:start_link("MG", MgNode),
@@ -5024,6 +5079,7 @@ trans_req_and_reply_and_req(Config) when is_list(Config) ->
-endif.
trarar_mgc_event_sequence(text, tcp) ->
+ CTRL = self(),
Mid = {deviceName,"ctrl"},
RI = [
{port, 2944},
@@ -5031,10 +5087,6 @@ trarar_mgc_event_sequence(text, tcp) ->
{encoding_config, []},
{transport_module, megaco_tcp}
],
-%% Tid = #megaco_term_id{id = ["00000000","00000000","01101101"]},
-%% ReqTmr = #megaco_incr_timer{wait_for = 500,
-%% factor = 1,
-%% max_retries = 1},
ConnectVerify = ?trarar_mgc_verify_handle_connect_fun(),
ServiceChangeReqVerify = ?trarar_mgc_verify_service_change_req_fun(Mid),
NotifyReqVerify1 = ?trarar_mgc_verify_notify_req_fun(1),
@@ -5042,13 +5094,6 @@ trarar_mgc_event_sequence(text, tcp) ->
NotifyReqVerify3 = ?trarar_mgc_verify_notify_req_fun(3),
NotifyReqVerify4 = ?trarar_mgc_verify_notify_req_fun(4),
DiscoVerify = ?trarar_mgc_verify_handle_disconnect_fun(),
-%% ConnectVerify = fun trarar_mgc_verify_handle_connect/1,
-%% ServiceChangeReqVerify = trarar_mgc_verify_service_change_req_fun(Mid),
-%% NotifyReqVerify1 = trarar_mgc_verify_notify_request_fun(1),
-%% NotifyReqVerify2 = trarar_mgc_verify_notify_request_fun(2),
-%% NotifyReqVerify3 = trarar_mgc_verify_notify_request_fun(3),
-%% NotifyReqVerify4 = trarar_mgc_verify_notify_request_fun(4),
-%% DiscoVerify = fun trarar_mgc_verify_handle_disconnect/1,
EvSeq = [
{debug, true},
{megaco_trace, disable},
@@ -5057,6 +5102,10 @@ trarar_mgc_event_sequence(text, tcp) ->
{megaco_update_user_info, reply_timer, 2000},
start_transport,
listen,
+
+ %% ANNOUNCE READY
+ {trigger, fun() -> CTRL ! announce_mgc end},
+
{megaco_callback, handle_connect, ConnectVerify},
{megaco_callback, handle_trans_request, ServiceChangeReqVerify},
{megaco_callback, handle_trans_request, NotifyReqVerify1},
@@ -5625,8 +5674,15 @@ pending_ack_plain(Config) when is_list(Config) ->
d("[MGC] start the simulation"),
{ok, MgcId} = megaco_test_megaco_generator:exec(Mgc, MgcEvSeq),
- i("wait some time before starting the MG simulator"),
- sleep(1000),
+ %% i("wait some time before starting the MG simulator"),
+ %% sleep(1000),
+
+ i("await MGC ready announcement"),
+ receive
+ announce_mgc ->
+ i("received MGC ready announcement"),
+ ok
+ end,
d("[MG] start the simulator (generator)"),
{ok, Mg} = megaco_test_tcp_generator:start_link("MG", MgNode),
@@ -5688,6 +5744,7 @@ pending_ack_plain(Config) when is_list(Config) ->
-endif.
pap_mgc_event_sequence(text, tcp) ->
+ CTRL = self(),
Mid = {deviceName,"ctrl"},
RI = [
{port, 2944},
@@ -5701,12 +5758,6 @@ pap_mgc_event_sequence(text, tcp) ->
NrVerify2 = ?pap_mgc_verify_notify_req_long_fun(),
AckVerify = ?pap_mgc_verify_handle_trans_ack_fun(),
DiscoVerify = ?pap_mgc_verify_handle_disconnect_fun(),
-%% ConnectVerify = fun pap_mgc_verify_handle_connect/1,
-%% ScrVerify = pap_mgc_verify_service_change_req_fun(Mid),
-%% NrVerify1 = pap_mgc_verify_notify_request_fun(),
-%% NrVerify2 = pap_mgc_verify_notify_request_long_fun(),
-%% AckVerify = pap_mgc_verify_trans_ack_fun(),
-%% DiscoVerify = fun pap_mgc_verify_handle_disconnect/1,
EvSeq = [
{debug, true},
{megaco_trace, disable},
@@ -5716,6 +5767,10 @@ pap_mgc_event_sequence(text, tcp) ->
{megaco_update_user_info, sent_pending_limit, 100},
start_transport,
listen,
+
+ %% ANNOUNCE READY
+ {trigger, fun() -> CTRL ! announce_mgc end},
+
{megaco_callback, handle_connect, ConnectVerify},
{megaco_conn_info, all},
{megaco_callback, handle_trans_request, ScrVerify},
@@ -6312,8 +6367,15 @@ request_and_pending_and_late_reply(Config) when is_list(Config) ->
d("[MGC] start the simulation"),
{ok, MgcId} = megaco_test_tcp_generator:exec(Mgc, MgcEvSeq),
- i("wait some time before starting the MG simulator"),
- sleep(1000),
+ %% i("wait some time before starting the MG simulator"),
+ %% sleep(1000),
+
+ i("await MGC ready announcement"),
+ receive
+ announce_mgc ->
+ i("received MGC ready announcement"),
+ ok
+ end,
d("[MG] start the simulator (generator)"),
{ok, Mg} = megaco_test_megaco_generator:start_link("MG", MgNode),
@@ -6371,6 +6433,7 @@ request_and_pending_and_late_reply(Config) when is_list(Config) ->
-endif.
rapalr_mgc_event_sequence(text, tcp) ->
+ CTRL = self(),
DecodeFun = ?rapalr_mgc_decode_msg_fun(megaco_pretty_text_encoder, []),
EncodeFun = ?rapalr_mgc_encode_msg_fun(megaco_pretty_text_encoder, []),
Mid = {deviceName,"mgc"},
@@ -6391,6 +6454,10 @@ rapalr_mgc_event_sequence(text, tcp) ->
{decode, DecodeFun},
{encode, EncodeFun},
{listen, 2944},
+
+ %% ANNOUNCE READY
+ {trigger, "announce ready", fun() -> CTRL ! announce_mgc end},
+
{expect_accept, any},
{expect_receive, "service-change-request", {ScrVerifyFun, 5000}},
{send, "service-change-reply", ServiceChangeRep},
@@ -7143,6 +7210,13 @@ otp_4836(Config) when is_list(Config) ->
d("start the MGC simulation"),
{ok, MgcId} = megaco_test_tcp_generator:exec(Mgc, MgcEvSeq),
+ i("await MGC ready announcement"),
+ receive
+ announce_mgc ->
+ i("received MGC ready announcement"),
+ ok
+ end,
+
i("[MG] start"),
MgMid = {deviceName, "mg"},
ReqTmr = #megaco_incr_timer{wait_for = 3000,
@@ -7207,6 +7281,7 @@ otp_4836(Config) when is_list(Config) ->
-endif.
otp_4836_mgc_event_sequence(text, tcp) ->
+ CTRL = self(),
DecodeFun = ?otp_4836_mgc_decode_msg_fun(megaco_pretty_text_encoder, []),
EncodeFun = ?otp_4836_mgc_encode_msg_fun(megaco_pretty_text_encoder, []),
Mid = {deviceName,"ctrl"},
@@ -7220,6 +7295,10 @@ otp_4836_mgc_event_sequence(text, tcp) ->
{decode, DecodeFun},
{encode, EncodeFun},
{listen, 2944},
+
+ %% ANNOUNCE READY
+ {trigger, "announce ready", fun() -> CTRL ! announce_mgc end},
+
{expect_accept, any},
{expect_receive, "service-change-request", {ServiceChangeVerifyFun, 10000}},
{send, "service-change-reply", ServiceChangeReply},
@@ -7388,8 +7467,15 @@ otp_5805(Config) when is_list(Config) ->
{ok, MgcId} =
megaco_test_megaco_generator:exec(Mgc, MgcEvSeq, timer:minutes(1)),
- i("wait some time before starting the MG simulator"),
- sleep(1000),
+ %% i("wait some time before starting the MG simulator"),
+ %% sleep(1000),
+
+ i("await MGC ready announcement"),
+ receive
+ announce_mgc ->
+ i("received MGC ready announcement"),
+ ok
+ end,
d("start the MG simulator (generator)"),
{ok, Mg} = megaco_test_tcp_generator:start_link("MG", MgNode),
@@ -7652,6 +7738,7 @@ Transaction = 2 {
-endif.
otp_5805_mgc_event_sequence(text, tcp) ->
+ CTRL = self(),
Mid = {deviceName,"ctrl"},
RI = [
{port, 2944},
@@ -7664,11 +7751,6 @@ otp_5805_mgc_event_sequence(text, tcp) ->
SyntaxErrorVerify1 = ?otp_5805_mgc_verify_handle_syntax_error_fun(),
SyntaxErrorVerify2 = ?otp_5805_mgc_verify_handle_syntax_error_fun(),
DiscoVerify = ?otp_5805_mgc_verify_handle_disconnect_fun(),
-%% ConnectVerify = fun otp_5805_mgc_verify_handle_connect/1,
-%% ServiceChangeReqVerify = otp_5805_mgc_verify_service_change_req_fun(Mid),
-%% SyntaxErrorVerify1 = fun otp_5805_mgc_verify_handle_syntax_error/1,
-%% SyntaxErrorVerify2 = fun otp_5805_mgc_verify_handle_syntax_error/1,
-%% DiscoVerify = fun otp_5805_mgc_verify_handle_disconnect/1,
EvSeq = [
{debug, true},
{megaco_trace, disable},
@@ -7677,6 +7759,10 @@ otp_5805_mgc_event_sequence(text, tcp) ->
{megaco_start_user, Mid, RI, []},
start_transport,
listen,
+
+ %% ANNOUNCE READY
+ {trigger, fun() -> CTRL ! announce_mgc end},
+
{megaco_callback, handle_connect, ConnectVerify},
{megaco_conn_info, all},
{megaco_callback, handle_trans_request_sc, ServiceChangeReqVerify},
@@ -7849,6 +7935,13 @@ otp_5881(Config) when is_list(Config) ->
d("start the MGC simulation"),
{ok, MgcId} = megaco_test_tcp_generator:exec(Mgc, MgcEvSeq),
+ i("await MGC ready announcement"),
+ receive
+ announce_mgc ->
+ i("received MGC ready announcement"),
+ ok
+ end,
+
i("[MG] start"),
MgMid = {deviceName, "mg"},
ReqTmr = #megaco_incr_timer{wait_for = 3000,
@@ -7930,6 +8023,7 @@ otp_5881_verify_trans_id(Mg, Expected) ->
-endif.
otp_5881_mgc_event_sequence(text, tcp) ->
+ CTRL = self(),
DecodeFun = ?otp_5881_mgc_decode_msg_fun(megaco_pretty_text_encoder, []),
EncodeFun = ?otp_5881_mgc_encode_msg_fun(megaco_pretty_text_encoder, []),
Mid = {deviceName,"ctrl"},
@@ -7943,6 +8037,10 @@ otp_5881_mgc_event_sequence(text, tcp) ->
{decode, DecodeFun},
{encode, EncodeFun},
{listen, 2944},
+
+ %% ANNOUNCE READY
+ {trigger, "announce ready", fun() -> CTRL ! announce_mgc end},
+
{expect_accept, any},
{expect_receive, "service-change-request", {ServiceChangeVerifyFun, 10000}},
{send, "service-change-reply", ServiceChangeReply},
@@ -8090,6 +8188,13 @@ otp_5887(Config) when is_list(Config) ->
d("start the MGC simulation"),
{ok, MgcId} = megaco_test_tcp_generator:exec(Mgc, MgcEvSeq),
+ i("await MGC ready announcement"),
+ receive
+ announce_mgc ->
+ i("received MGC ready announcement"),
+ ok
+ end,
+
i("[MG] start"),
MgMid = {deviceName, "mg"},
ReqTmr = #megaco_incr_timer{wait_for = 3000,
@@ -8201,6 +8306,7 @@ otp_5887_verify_trans_id(F, Expected) ->
-endif.
otp_5887_mgc_event_sequence(text, tcp) ->
+ CTRL = self(),
DecodeFun = ?otp_5887_mgc_decode_msg_fun(megaco_pretty_text_encoder, []),
EncodeFun = ?otp_5887_mgc_encode_msg_fun(megaco_pretty_text_encoder, []),
Mid = {deviceName,"ctrl"},
@@ -8213,6 +8319,10 @@ otp_5887_mgc_event_sequence(text, tcp) ->
{decode, DecodeFun},
{encode, EncodeFun},
{listen, 2944},
+
+ %% ANNOUNCE READY
+ {trigger, "announce ready", fun() -> CTRL ! announce_mgc end},
+
{expect_accept, any},
{expect_receive, "service-change-request", {ServiceChangeVerifyFun, 10000}},
{send, "service-change-reply", ServiceChangeReply},
@@ -8421,6 +8531,13 @@ otp_6275(Config) when is_list(Config) ->
d("start the MGC simulation"),
{ok, MgcId} = megaco_test_tcp_generator:exec(Mgc, MgcEvSeq),
+ i("await MGC ready announcement"),
+ receive
+ announce_mgc ->
+ i("received MGC ready announcement"),
+ ok
+ end,
+
d("[MG] start the simulator (generator)"),
{ok, Mg} = megaco_test_megaco_generator:start_link("MG", MgNode),
@@ -8483,9 +8600,6 @@ otp_6275_mg_event_sequence2(Mid, RI) ->
ConnectVerify = ?otp_6275_mg_verify_handle_connect_fun(),
NotifyReqVerify = ?otp_6275_mg_verify_notify_req_fun(),
TransReplyVerify = ?otp_6275_mg_verify_handle_trans_rep_fun(),
-%% ConnectVerify = otp_6275_mg_verify_handle_connect_fun(),
-%% NotifyReqVerify = otp_6275_mg_verify_notify_request_fun(),
-%% TransReplyVerify = otp_6275_mg_verify_trans_reply_fun(),
EvSeq = [
{debug, true},
megaco_start,
@@ -8657,6 +8771,7 @@ otp_6275_mg_service_change_request_ar(_Mid, Cid) ->
-endif.
otp_6275_mgc_event_sequence(text, tcp) ->
+ CTRL = self(),
DecodeFun = ?otp_6275_mgc_decode_msg_fun(megaco_pretty_text_encoder, []),
EncodeFun = ?otp_6275_mgc_encode_msg_fun(megaco_pretty_text_encoder, []),
Mid = {deviceName,"ctrl"},
@@ -8670,6 +8785,9 @@ otp_6275_mgc_event_sequence(text, tcp) ->
{encode, EncodeFun},
{listen, 2944},
+ %% ANNOUNCE READY
+ {trigger, "announce ready", fun() -> CTRL ! announce_mgc end},
+
{expect_accept, any},
{expect_receive, "service-change-request", {SCRVerifyFun, 5000}},
{sleep, 1000}, %% Do _not_ send SC reply
@@ -8912,8 +9030,15 @@ otp_6276(Config) when is_list(Config) ->
d("[MGC] start the tcp-simulation"),
{ok, MgcId} = megaco_test_tcp_generator:exec(Mgc, MgcEvSeq),
- i("wait some time before starting the MG simulator"),
- sleep(1000),
+ %% i("wait some time before starting the MG simulator"),
+ %% sleep(1000),
+
+ i("await MGC ready announcement"),
+ receive
+ announce_mgc ->
+ i("received MGC ready announcement"),
+ ok
+ end,
d("[MG] start the simulator (generator)"),
{ok, Mg} = megaco_test_megaco_generator:start_link("MG", MgNode),
@@ -9106,6 +9231,7 @@ otp_6276_mgc_event_sequence(text, tcp, Ctrl) ->
otp_6276_mgc_event_sequence2(Mid, EM, EC, Ctrl).
otp_6276_mgc_event_sequence2(Mid, EM, EC, Ctrl) ->
+ CTRL = self(),
DecodeFun = otp_6276_mgc_decode_msg_fun(EM, EC),
EncodeFun = otp_6276_mgc_encode_msg_fun(EM, EC),
AnnounceMe = otp_6276_mgc_announce_fun(Ctrl),
@@ -9125,6 +9251,10 @@ otp_6276_mgc_event_sequence2(Mid, EM, EC, Ctrl) ->
{decode, DecodeFun},
{encode, EncodeFun},
{listen, 2944},
+
+ %% ANNOUNCE READY
+ {trigger, fun() -> CTRL ! announce_mgc end},
+
{expect_accept, any},
{expect_receive, "service-change-req",
{ServiceChangeReqVerify, 10000}},
@@ -11127,8 +11257,12 @@ otp_6865_request_and_reply_plain_extra2(Config) when is_list(Config) ->
d("[MGC] start the simulation"),
{ok, MgcId} = megaco_test_megaco_generator:exec(Mgc, MgcEvSeq),
- i("wait some time before starting the MG simulator"),
- sleep(1000),
+ i("await MGC ready announcement"),
+ receive
+ announce_mgc ->
+ i("received MGC ready announcement"),
+ ok
+ end,
d("[MG] start the simulator (generator)"),
{ok, Mg} = megaco_test_tcp_generator:start_link("MG", MgNode),
@@ -11192,13 +11326,14 @@ otp_6865_request_and_reply_plain_extra2(Config) when is_list(Config) ->
-endif.
otp6865e2_mgc_event_sequence(ExtraInfo, text, tcp) ->
- Mid = {deviceName,"ctrl"},
- RI = [
- {port, 2944},
- {encoding_module, megaco_pretty_text_encoder},
- {encoding_config, []},
- {transport_module, megaco_tcp}
- ],
+ Mid = {deviceName, "ctrl"},
+ CTRL = self(),
+ RI = [
+ {port, 2944},
+ {encoding_module, megaco_pretty_text_encoder},
+ {encoding_config, []},
+ {transport_module, megaco_tcp}
+ ],
ConnectVerify =
?otp6865e2_mgc_verify_handle_connect_fun(ExtraInfo),
ServiceChangeReqVerify =
@@ -11220,6 +11355,10 @@ otp6865e2_mgc_event_sequence(ExtraInfo, text, tcp) ->
{megaco_start_user, Mid, RI, []},
start_transport,
listen,
+
+ %% ANNOUNCE READY
+ {trigger, fun() -> CTRL ! announce_mgc end},
+
{megaco_callback, handle_connect, ConnectVerify},
{megaco_callback, handle_trans_request, ServiceChangeReqVerify},
{megaco_callback, handle_trans_request, NotifyReqVerify1},
@@ -11992,8 +12131,15 @@ otp_7189(Config) when is_list(Config) ->
d("[MGC] start the simulation"),
{ok, MgcId} = megaco_test_megaco_generator:exec(Mgc, MgcEvSeq),
- i("wait some time before starting the MG simulator"),
- sleep(1000),
+ %% i("wait some time before starting the MG simulator"),
+ %% sleep(1000),
+
+ i("await MGC ready announcement"),
+ receive
+ announce_mgc ->
+ i("received MGC ready announcement"),
+ ok
+ end,
d("[MG] start the simulator (generator)"),
{ok, Mg} = megaco_test_tcp_generator:start_link("MG", MgNode),
@@ -12046,6 +12192,7 @@ otp_7189(Config) when is_list(Config) ->
-endif.
otp_7189_mgc_event_sequence(text, tcp) ->
+ CTRL = self(),
Mid = {deviceName,"ctrl"},
RI = [
{port, 2944},
@@ -12099,6 +12246,10 @@ otp_7189_mgc_event_sequence(text, tcp) ->
{megaco_user_info, all},
start_transport,
listen,
+
+ %% ANNOUNCE READY
+ {trigger, fun() -> CTRL ! announce_mgc end},
+
{megaco_callback, handle_connect, ConnectVerify},
{megaco_conn_info, all},
{megaco_callback, handle_trans_request, ScrVerify},
diff --git a/lib/megaco/test/megaco_test_lib.erl b/lib/megaco/test/megaco_test_lib.erl
index 7c8c287e7c..3d26a8585f 100644
--- a/lib/megaco/test/megaco_test_lib.erl
+++ b/lib/megaco/test/megaco_test_lib.erl
@@ -127,31 +127,72 @@ non_pc_tc_maybe_skip(Config, Condition, File, Line)
end.
+%% The type and spec'ing is just to increase readability
+-type os_family() :: win32 | unix.
+-type os_name() :: atom().
+-type os_version() :: string() | {non_neg_integer(),
+ non_neg_integer(),
+ non_neg_integer()}.
+-type os_skip_check() :: fun(() -> boolean()) |
+ fun((os_version()) -> boolean()).
+-type skippable() :: any | [os_family() |
+ {os_family(), os_name() |
+ [os_name() | {os_name(),
+ os_skip_check()}]}].
+
+-spec os_based_skip(skippable()) -> boolean().
+
os_based_skip(any) ->
true;
os_based_skip(Skippable) when is_list(Skippable) ->
- {OsFam, OsName} =
- case os:type() of
- {_Fam, _Name} = FamAndName ->
- FamAndName;
- Fam ->
- {Fam, undefined}
- end,
- case lists:member(OsFam, Skippable) of
- true ->
- true;
- false ->
- case lists:keysearch(OsFam, 1, Skippable) of
- {value, {OsFam, OsName}} ->
- true;
- {value, {OsFam, OsNames}} when is_list(OsNames) ->
- lists:member(OsName, OsNames);
- _ ->
- false
- end
- end;
-os_based_skip(_) ->
+ os_base_skip(Skippable, os:type());
+os_based_skip(_Crap) ->
false.
+
+os_base_skip(Skippable, {OsFam, OsName}) ->
+ os_base_skip(Skippable, OsFam, OsName);
+os_base_skip(Skippable, OsFam) ->
+ os_base_skip(Skippable, OsFam, undefined).
+
+os_base_skip(Skippable, OsFam, OsName) ->
+ %% Check if the entire family is to be skipped
+ %% Example: [win32, unix]
+ case lists:member(OsFam, Skippable) of
+ true ->
+ true;
+ false ->
+ %% Example: [{unix, freebsd}] | [{unix, [freebsd, darwin]}]
+ case lists:keysearch(OsFam, 1, Skippable) of
+ {value, {OsFam, OsName}} ->
+ true;
+ {value, {OsFam, OsNames}} when is_list(OsNames) ->
+ %% OsNames is a list of:
+ %% [atom()|{atom(), function/0 | function/1}]
+ case lists:member(OsName, OsNames) of
+ true ->
+ true;
+ false ->
+ os_based_skip_check(OsName, OsNames)
+ end;
+ _ ->
+ false
+ end
+ end.
+
+
+
+%% Performs a check via a provided fun with arity 0 or 1.
+%% The argument is the result of os:version().
+os_based_skip_check(OsName, OsNames) ->
+ case lists:keysearch(OsName, 1, OsNames) of
+ {value, {OsName, Check}} when is_function(Check, 0) ->
+ Check();
+ {value, {OsName, Check}} when is_function(Check, 1) ->
+ Check(os:version());
+ _ ->
+ false
+ end.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/megaco/test/megaco_timer_test.erl b/lib/megaco/test/megaco_timer_test.erl
index 84c314d8ed..87aa51b8de 100644
--- a/lib/megaco/test/megaco_timer_test.erl
+++ b/lib/megaco/test/megaco_timer_test.erl
@@ -354,18 +354,23 @@ integer_timer_start_and_stop(Config) when is_list(Config) ->
i("starting"),
Timeout = 5000,
- Ref = tmr_start(Timeout),
+ i("try start (~w msec) timer", [Timeout]),
+ Ref = tmr_start(Timeout),
+ i("timer started "),
receive
{timeout, Timeout} ->
+ i("unexpected premature timer expire"),
error(bad_timeout)
after Timeout - 100 ->
+ i("try stop timer"),
case tmr_stop(Ref) of
- ok ->
- ok;
- {ok, _} ->
+ {ok, Rem} ->
+ i("timer stopped with ~w msec remaining", [Rem]),
ok;
CancelRes ->
- ?SKIP({cancel_failed, CancelRes})
+ i("failed stop timer: "
+ "~n ~p", [CancelRes]),
+ ?SKIP({cancel_failed, CancelRes}) % Race - not our problem
end
end,
diff --git a/lib/sasl/test/installer.erl b/lib/sasl/test/installer.erl
index 5429008a5f..d763fad11b 100644
--- a/lib/sasl/test/installer.erl
+++ b/lib/sasl/test/installer.erl
@@ -903,7 +903,7 @@ start_client(TestNode,Client,Sname) ->
receive
{nodeup, Node} ->
wait_started(TestNode,Node)
- after 30000 ->
+ after 60000 ->
?print([{start_client,failed,Node},net_adm:ping(Node)]),
?fail({"cannot start", Node})
end.
diff --git a/lib/sasl/test/release_handler_SUITE.erl b/lib/sasl/test/release_handler_SUITE.erl
index 2ff2f29591..98f96b7170 100644
--- a/lib/sasl/test/release_handler_SUITE.erl
+++ b/lib/sasl/test/release_handler_SUITE.erl
@@ -2331,7 +2331,7 @@ reg_print_proc() ->
rh_print() ->
receive
{print, {Module,Line}, [H|T]} ->
- ?t:format("=== ~p:~p - ~p",[Module,Line,H]),
+ ?t:format("=== ~p:~p - ~tp",[Module,Line,H]),
lists:foreach(fun(Term) -> ?t:format(" ~tp",[Term]) end, T),
?t:format("",[]),
rh_print();
diff --git a/lib/sasl/test/rh_test_lib.erl b/lib/sasl/test/rh_test_lib.erl
index dacd8b6b9f..6425fce7a7 100644
--- a/lib/sasl/test/rh_test_lib.erl
+++ b/lib/sasl/test/rh_test_lib.erl
@@ -29,7 +29,7 @@ erlsrv(Erlsrv,Action,Name) ->
erlsrv(Erlsrv,Action,Name,Rest) ->
Cmd = "\"" ++ Erlsrv ++ "\" " ++ atom_to_list(Action) ++ " " ++
Name ++ " " ++ Rest,
- io:format("erlsrv cmd: ~p~n",[Cmd]),
+ io:format("erlsrv cmd: ~tp~n",[Cmd]),
Port = open_port({spawn, Cmd}, [stream, {line, 100}, eof, in]),
Res = recv_prog_output(Port),
case Res of
diff --git a/lib/snmp/src/agent/snmp_generic.erl b/lib/snmp/src/agent/snmp_generic.erl
index 26a0dd0648..429f78abd0 100644
--- a/lib/snmp/src/agent/snmp_generic.erl
+++ b/lib/snmp/src/agent/snmp_generic.erl
@@ -424,8 +424,8 @@ table_check_status(NameDb, Col, ?'RowStatus_createAndGo', RowIndex, Cols) ->
_:_E:_S ->
?vtrace(
"failed construct row (createAndGo): "
- " n Error: ~p"
- " n Stack: ~p",
+ "~n Error: ~p"
+ "~n Stack: ~p",
[_E, _S]),
{noCreation, Col} % Bad RowIndex
end;
@@ -444,8 +444,8 @@ table_check_status(NameDb, Col, ?'RowStatus_createAndWait', RowIndex, Cols) ->
_:_E:_S ->
?vtrace(
"failed construct row (createAndWait): "
- " n Error: ~p"
- " n Stack: ~p",
+ "~n Error: ~p"
+ "~n Stack: ~p",
[_E, _S]),
{noCreation, Col} % Bad RowIndex
end;
diff --git a/lib/snmp/src/agent/snmpa_general_db.erl b/lib/snmp/src/agent/snmpa_general_db.erl
index 431302b6cc..0d4e39f56d 100644
--- a/lib/snmp/src/agent/snmpa_general_db.erl
+++ b/lib/snmp/src/agent/snmpa_general_db.erl
@@ -534,16 +534,16 @@ dets_backup(close, _Cont, _Name, B) ->
ok;
dets_backup(read, Cont1, Name, B) ->
case dets:bchunk(Name, Cont1) of
+ {error, _} = ERROR ->
+ ERROR;
+ '$end_of_table' ->
+ dets:close(B),
+ end_of_input;
{Cont2, Data} ->
F = fun(Arg) ->
dets_backup(Arg, Cont2, Name, B)
end,
- {Data, F};
- '$end_of_table' ->
- dets:close(B),
- end_of_input;
- Error ->
- Error
+ {Data, F}
end.
diff --git a/lib/snmp/test/snmp_agent_test.erl b/lib/snmp/test/snmp_agent_test.erl
index a45cfa9e98..f6416ee2f9 100644
--- a/lib/snmp/test/snmp_agent_test.erl
+++ b/lib/snmp/test/snmp_agent_test.erl
@@ -3550,7 +3550,8 @@ table_test() ->
?line ?expect1([{NewKeyc5, ?destroy}]),
s([{NewKeyc3, 3}]),
?line ?expect3(?v1_2(noSuchName, inconsistentName), 1, [{NewKeyc3, 3}]),
- otp_1128_test().
+ otp_1128_test(),
+ ok.
%% Req. system group
simple_standard_test() ->
@@ -5030,7 +5031,8 @@ snmpv2_mib_2(Config) when is_list(Config) ->
"then disable auth traps",[]),
try_test(snmpv2_mib_test_finish, [], [{community, "bad community"}]),
- ?LOG("snmpv2_mib_2 -> done",[]).
+ ?LOG("snmpv2_mib_2 -> done", []),
+ ok.
standard_mibs3_cases() ->
@@ -5126,7 +5128,8 @@ snmpv2_mib_a() ->
?line ?expect3(inconsistentValue, 2,
[{[sysLocation, 0], "val3"},
{[snmpSetSerialNo,0], SetSerial}]),
- ?line ["val2"] = get_req(5, [[sysLocation,0]]).
+ ?line ["val2"] = get_req(5, [[sysLocation,0]]),
+ ok.
%%-----------------------------------------------------------------
@@ -5149,6 +5152,7 @@ snmp_community_mib_test() ->
?INF("NOT YET IMPLEMENTED", []),
nyi.
+
%%-----------------------------------------------------------------
%% o Test engine boots / time
%%-----------------------------------------------------------------
@@ -5277,7 +5281,8 @@ snmp_mpd_mib_b() ->
snmp_mpd_mib_c(UnknownPDUHs) ->
?line [UnknownPDUHs2] = get_req(1, [[snmpUnknownPDUHandlers, 0]]),
- ?line UnknownPDUHs2 = UnknownPDUHs + 1.
+ ?line UnknownPDUHs2 = UnknownPDUHs + 1,
+ ok.
snmp_target_mib(suite) -> [];
@@ -5314,6 +5319,7 @@ snmp_notification_mib_test() ->
?INF("NOT YET IMPLEMENTED", []),
nyi.
+
%%-----------------------------------------------------------------
%% o add/delete views and try them
%% o try boundaries
@@ -5661,7 +5667,8 @@ usm_key_change3(OldShaKey, OldDesKey, ShaKey, DesKey) ->
Vbs3 = [{[usmUserAuthKeyChange, NewRowIndex], ShaKeyChange},
{[usmUserPrivKeyChange, NewRowIndex], DesKeyChange}],
s(Vbs3),
- ?line ?expect1(Vbs3).
+ ?line ?expect1(Vbs3),
+ ok.
usm_read() ->
NewRowIndex = [11,"agentEngine", 7, "newUser"],
@@ -5779,7 +5786,8 @@ loop_mib_1(Config) when is_list(Config) ->
?line unload_master("SNMP-VIEW-BASED-ACM-MIB"),
%% snmpa:verbosity(master_agent,log),
%% snmpa:verbosity(mib_server,silence),
- ?LOG("loop_mib_1 -> done",[]).
+ ?LOG("loop_mib_1 -> done",[]),
+ ok.
loop_mib_2(suite) -> [];
@@ -5808,7 +5816,8 @@ loop_mib_2(Config) when is_list(Config) ->
?line unload_master("SNMP-NOTIFICATION-MIB"),
?line unload_master("SNMP-FRAMEWORK-MIB"),
?line unload_master("SNMP-VIEW-BASED-ACM-MIB"),
- ?LOG("loop_mib_2 -> done",[]).
+ ?LOG("loop_mib_2 -> done",[]),
+ ok.
loop_mib_3(suite) -> [];
@@ -5833,7 +5842,8 @@ loop_mib_3(Config) when is_list(Config) ->
?line unload_master("SNMP-NOTIFICATION-MIB"),
?line unload_master("SNMP-VIEW-BASED-ACM-MIB"),
?line unload_master("SNMP-USER-BASED-SM-MIB"),
- ?LOG("loop_mib_3 -> done",[]).
+ ?LOG("loop_mib_3 -> done",[]),
+ ok.
%% Req. As many mibs all possible
@@ -6043,7 +6053,8 @@ otp_1128(Config) when is_list(Config) ->
?line load_master("OLD-SNMPEA-MIB"),
?line init_old(),
try_test(otp_1128_test),
- ?line unload_master("OLD-SNMPEA-MIB").
+ ?line unload_master("OLD-SNMPEA-MIB"),
+ ok.
otp_1128_2(X) -> ?P(otp_1128_2), otp_1128(X).
@@ -6065,7 +6076,8 @@ otp_1128_test() ->
g([NewKeyc5]),
?line ?expect1([{NewKeyc5, ?active}]),
s([{NewKeyc5, ?destroy}]),
- ?line ?expect1([{NewKeyc5, ?destroy}]).
+ ?line ?expect1([{NewKeyc5, ?destroy}]),
+ ok.
%%-----------------------------------------------------------------
@@ -6078,7 +6090,8 @@ otp_1129(Config) when is_list(Config) ->
init_case(Config),
?line load_master("Klas3"),
try_test(otp_1129_i, [node()]),
- ?line unload_master("Klas3").
+ ?line unload_master("Klas3"),
+ ok.
otp_1129_2(X) -> ?P(otp_1129_2), otp_1129(X).
@@ -6149,7 +6162,8 @@ otp_1131_test() ->
io:format("Testing bug reported in ticket OTP-1131...~n"),
s([{[friendsEntry, [2, 3, 1]], s, "kompis3"},
{[friendsEntry, [3, 3, 1]], i, ?createAndGo}]),
- ?line ?expect3(?v1_2(noSuchName, noCreation), 2, any).
+ ?line ?expect3(?v1_2(noSuchName, noCreation), 2, any),
+ ok.
%%-----------------------------------------------------------------
@@ -6170,7 +6184,8 @@ otp_1162_3(X) -> ?P(otp_1162_3), otp_1162(X).
otp_1162_test() ->
s([{[sa, [2,0]], 6}]), % wrongValue (i is_set_ok)
- ?line ?expect3(?v1_2(badValue, wrongValue), 1, any).
+ ?line ?expect3(?v1_2(badValue, wrongValue), 1, any),
+ ok.
%%-----------------------------------------------------------------
@@ -6185,7 +6200,8 @@ otp_1222(Config) when is_list(Config) ->
?line load_master("Klas4"),
try_test(otp_1222_test),
?line unload_master("Klas3"),
- ?line unload_master("Klas4").
+ ?line unload_master("Klas4"),
+ ok.
otp_1222_2(X) -> ?P(otp_1222_2), otp_1222(X).
@@ -6196,7 +6212,8 @@ otp_1222_test() ->
s([{[fStatus4,1], 4}, {[fName4,1], 1}]),
?line ?expect3(genErr, 0, any),
s([{[fStatus4,2], 4}, {[fName4,2], 1}]),
- ?line ?expect3(genErr, 0, any).
+ ?line ?expect3(genErr, 0, any),
+ ok.
%%-----------------------------------------------------------------
@@ -6210,7 +6227,8 @@ otp_1298(Config) when is_list(Config) ->
?line load_master("Klas2"),
try_test(otp_1298_test),
- ?line unload_master("Klas2").
+ ?line unload_master("Klas2"),
+ ok.
otp_1298_2(X) -> ?P(otp_1298_2), otp_1298(X).
@@ -6219,7 +6237,8 @@ otp_1298_3(X) -> ?P(otp_1298_3), otp_1298(X).
otp_1298_test() ->
io:format("Testing bug reported in ticket OTP-1298...~n"),
s([{[fint,0], -1}]),
- ?line ?expect1([{[fint,0], -1}]).
+ ?line ?expect1([{[fint,0], -1}]),
+ ok.
%%-----------------------------------------------------------------
@@ -6233,7 +6252,8 @@ otp_1331(Config) when is_list(Config) ->
?line load_master("OLD-SNMPEA-MIB"),
?line init_old(),
try_test(otp_1331_test),
- ?line unload_master("OLD-SNMPEA-MIB").
+ ?line unload_master("OLD-SNMPEA-MIB"),
+ ok.
otp_1331_2(X) -> ?P(otp_1331_2), otp_1331(X).
@@ -6242,7 +6262,8 @@ otp_1331_3(X) -> ?P(otp_1331_3), otp_1331(X).
otp_1331_test() ->
NewKeyc5 = [intCommunityStatus,[127,32,0,0],is("test")],
s([{NewKeyc5, ?destroy}]),
- ?line ?expect1([{NewKeyc5, ?destroy}]).
+ ?line ?expect1([{NewKeyc5, ?destroy}]),
+ ok.
%%-----------------------------------------------------------------
@@ -6280,7 +6301,8 @@ otp_1342(Config) when is_list(Config) ->
init_case(Config),
?line load_master("Klas4"),
try_test(otp_1342_test),
- ?line unload_master("Klas4").
+ ?line unload_master("Klas4"),
+ ok.
otp_1342_2(X) -> ?P(otp_1342_2), otp_1342(X).
@@ -6290,7 +6312,8 @@ otp_1342_test() ->
s([{[fIndex5, 1], i, 1},
{[fName5, 1], i, 3},
{[fStatus5, 1], i, ?createAndGo}]),
- ?line ?expect3(?v1_2(noSuchName, noCreation), 3, any).
+ ?line ?expect3(?v1_2(noSuchName, noCreation), 3, any),
+ ok.
%%-----------------------------------------------------------------
@@ -6306,7 +6329,8 @@ otp_1366(Config) when is_list(Config) ->
?line load_master("OLD-SNMPEA-MIB"),
?line init_old(),
try_test(otp_1366_test),
- ?line unload_master("OLD-SNMPEA-MIB").
+ ?line unload_master("OLD-SNMPEA-MIB"),
+ ok.
otp_1366_2(X) -> ?P(otp_1366_2), otp_1366(X).
@@ -6404,7 +6428,8 @@ otp_2979_3(X) -> ?P(otp_2979_3), otp_2979(X).
otp_2979_test() ->
gn([[sparseDescr], [sparseStatus]]),
?line ?expect1([{[sparseStr,0], "slut"},
- {[sparseStr,0], "slut"}]).
+ {[sparseStr,0], "slut"}]),
+ ok.
%%-----------------------------------------------------------------
diff --git a/lib/snmp/test/snmp_manager_test.erl b/lib/snmp/test/snmp_manager_test.erl
index c31bb92e1f..e2356bd70a 100644
--- a/lib/snmp/test/snmp_manager_test.erl
+++ b/lib/snmp/test/snmp_manager_test.erl
@@ -68,35 +68,26 @@
info/1,
- simple_sync_get1/1,
simple_sync_get2/1,
simple_sync_get3/1,
- simple_async_get1/1,
simple_async_get2/1,
simple_async_get3/1,
- simple_sync_get_next1/1,
simple_sync_get_next2/1,
simple_sync_get_next3/1,
- simple_async_get_next1/1,
simple_async_get_next2/1,
simple_async_get_next3/1,
- simple_sync_set1/1,
simple_sync_set2/1,
simple_sync_set3/1,
- simple_async_set1/1,
simple_async_set2/1,
simple_async_set3/1,
- simple_sync_get_bulk1/1,
simple_sync_get_bulk2/1,
simple_sync_get_bulk3/1,
- simple_async_get_bulk1/1,
simple_async_get_bulk2/1,
simple_async_get_bulk3/1,
- misc_async1/1,
misc_async2/1,
discovery/1,
@@ -163,6 +154,10 @@ init_per_suite(Config0) when is_list(Config0) ->
%% until we have a more important reason to fix this.
case snmp_test_lib:crypto_start() of
ok ->
+
+ snmp_test_global_sys_monitor:start(),
+ snmp_test_sys_monitor:start(), % We need one on this node also
+
Config1 = snmp_test_lib:init_suite_top_dir(?MODULE, Config0),
Config2 = snmp_test_lib:fix_data_dir(Config1),
%% Mib-dirs
@@ -181,6 +176,9 @@ end_per_suite(Config) when is_list(Config) ->
?DBG("end_per_suite -> entry with"
"~n Config: ~p", [Config]),
+ snmp_test_sys_monitor:stop(),
+ snmp_test_global_sys_monitor:stop(),
+
Config.
@@ -190,15 +188,6 @@ init_per_testcase(Case, Config) when is_list(Config) ->
%% This version of the API, based on Addr and Port, has been deprecated
DeprecatedApiCases =
[
- simple_sync_get1,
- simple_async_get1,
- simple_sync_get_next1,
- simple_async_get_next1,
- simple_sync_set1,
- simple_async_set1,
- simple_sync_get_bulk1,
- simple_async_get_bulk1,
- misc_async1
],
Result =
case lists:member(Case, DeprecatedApiCases) of
@@ -505,47 +494,38 @@ groups() ->
},
{get_tests, [],
[
- simple_sync_get1,
simple_sync_get2,
simple_sync_get3,
- simple_async_get1,
simple_async_get2,
simple_async_get3
]
},
{get_next_tests, [],
[
- simple_sync_get_next1,
simple_sync_get_next2,
simple_sync_get_next3,
- simple_async_get_next1,
simple_async_get_next2,
simple_async_get_next3
]
},
{set_tests, [],
[
- simple_sync_set1,
simple_sync_set2,
simple_sync_set3,
- simple_async_set1,
simple_async_set2,
simple_async_set3
]
},
{bulk_tests, [],
[
- simple_sync_get_bulk1,
simple_sync_get_bulk2,
simple_sync_get_bulk3,
- simple_async_get_bulk1,
simple_async_get_bulk2,
simple_async_get_bulk3
]
},
{misc_request_tests, [],
[
- misc_async1,
misc_async2
]
},
@@ -1806,68 +1786,6 @@ do_register_agent3(Config) ->
%%======================================================================
-simple_sync_get1(doc) -> ["Simple sync get-request - Old style (Addr & Port)"];
-simple_sync_get1(suite) -> [];
-simple_sync_get1(Config) when is_list(Config) ->
- ?TC_TRY(simple_sync_get1,
- fun() -> {skip, "API no longer supported"} end,
- fun() -> do_simple_sync_get1(Config) end).
-
-do_simple_sync_get1(Config) ->
- p("starting with Config: ~p~n", [Config]),
-
- Node = ?config(manager_node, Config),
- Addr = ?config(manager_agent_target_name, Config),
- Port = ?AGENT_PORT,
-
- p("issue get-request without loading the mib"),
- Oids1 = [?sysObjectID_instance, ?sysDescr_instance, ?sysUpTime_instance],
- ?line ok = do_simple_sync_get(Node, Addr, Port, Oids1),
-
- p("issue get-request after first loading the mibs"),
- ?line ok = mgr_user_load_mib(Node, std_mib()),
- Oids2 = [[sysObjectID, 0], [sysDescr, 0], [sysUpTime, 0]],
- ?line ok = do_simple_sync_get(Node, Addr, Port, Oids2),
-
- p("Display log"),
- display_log(Config),
-
- p("done"),
- ok.
-
-do_simple_sync_get(Node, Addr, Port, Oids) ->
- ?line {ok, Reply, _Rem} = mgr_user_sync_get(Node, Addr, Port, Oids),
-
- ?DBG("~n Reply: ~p"
- "~n Rem: ~w", [Reply, _Rem]),
-
- %% verify that the operation actually worked:
- %% The order should be the same, so no need to seach
- ?line ok = case Reply of
- {noError, 0, [#varbind{oid = ?sysObjectID_instance,
- value = SysObjectID},
- #varbind{oid = ?sysDescr_instance,
- value = SysDescr},
- #varbind{oid = ?sysUpTime_instance,
- value = SysUpTime}]} ->
- p("expected result from get: "
- "~n SysObjectID: ~p"
- "~n SysDescr: ~s"
- "~n SysUpTime: ~w",
- [SysObjectID, SysDescr, SysUpTime]),
- ok;
- {noError, 0, Vbs} ->
- p("unexpected varbinds: ~n~p", [Vbs]),
- {error, {unexpected_vbs, Vbs}};
- Else ->
- p("unexpected reply: ~n~p", [Else]),
- {error, {unexpected_response, Else}}
- end,
- ok.
-
-
-%%======================================================================
-
simple_sync_get2(doc) ->
["Simple sync get-request - Version 2 API (TargetName)"];
simple_sync_get2(suite) -> [];
@@ -1967,79 +1885,9 @@ do_simple_sync_get3(Config) ->
ok.
-%%======================================================================
-
-simple_async_get1(doc) ->
- ["Simple (async) get-request - Old style (Addr & Port)"];
-simple_async_get1(suite) -> [];
-simple_async_get1(Config) when is_list(Config) ->
- ?TC_TRY(simple_async_get1,
- fun() -> {skip, "API no longer supported"} end,
- fun() -> do_simple_async_get1(Config) end).
-
-do_simple_async_get1(Config) ->
- p("starting with Config: ~p~n", [Config]),
-
- MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
- Addr = ?config(ip, Config),
- Port = ?AGENT_PORT,
-
- ?line ok = mgr_user_load_mib(MgrNode, std_mib()),
- Test2Mib = test2_mib(Config),
- ?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
- ?line ok = agent_load_mib(AgentNode, Test2Mib),
-
- Exec = fun(Data) ->
- async_g_exec1(MgrNode, Addr, Port, Data)
- end,
-
- Requests =
- [
- { 1,
- [?sysObjectID_instance],
- Exec,
- fun(X) -> sag_verify(X, [?sysObjectID_instance]) end },
- { 2,
- [?sysDescr_instance, ?sysUpTime_instance],
- Exec,
- fun(X) ->
- sag_verify(X, [?sysObjectID_instance,
- ?sysUpTime_instance])
- end },
- { 3,
- [[sysObjectID, 0], [sysDescr, 0], [sysUpTime, 0]],
- Exec,
- fun(X) ->
- sag_verify(X, [?sysObjectID_instance,
- ?sysDescr_instance,
- ?sysUpTime_instance])
- end },
- { 4,
- [?sysObjectID_instance,
- ?sysDescr_instance,
- ?sysUpTime_instance],
- Exec,
- fun(X) ->
- sag_verify(X, [?sysObjectID_instance,
- ?sysDescr_instance,
- ?sysUpTime_instance])
- end }
- ],
-
- p("manager info when starting test: ~n~p", [mgr_info(MgrNode)]),
- p("agent info when starting test: ~n~p", [agent_info(AgentNode)]),
-
- ?line ok = async_exec(Requests, []),
-
- p("manager info when ending test: ~n~p", [mgr_info(MgrNode)]),
- p("agent info when ending test: ~n~p", [agent_info(AgentNode)]),
- display_log(Config),
- ok.
-async_g_exec1(Node, Addr, Port, Oids) ->
- mgr_user_async_get(Node, Addr, Port, Oids).
+%%======================================================================
sag_verify({noError, 0, _Vbs}, any) ->
p("verified [any]"),
@@ -2191,114 +2039,6 @@ async_g_exec3(Node, TargetName, Oids, SendOpts) ->
%%======================================================================
-simple_sync_get_next1(doc) -> ["Simple (sync) get_next-request - "
- "Old style (Addr & Port)"];
-simple_sync_get_next1(suite) -> [];
-simple_sync_get_next1(Config) when is_list(Config) ->
- ?TC_TRY(simple_sync_get_next1,
- fun() -> {skip, "API no longer supported"} end,
- fun() -> do_simple_sync_get_next1(Config) end).
-
-do_simple_sync_get_next1(Config) ->
- p("starting with Config: ~p~n", [Config]),
-
- MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
- Addr = ?config(ip, Config),
- Port = ?AGENT_PORT,
-
- %% -- 1 --
- Oids01 = [[1,3,7,1]],
- VF01 = fun(X) -> verify_ssgn_reply1(X, [{[1,3,7,1],endOfMibView}]) end,
- ?line ok = do_simple_get_next(1,
- MgrNode, Addr, Port, Oids01, VF01),
-
- ?line ok = mgr_user_load_mib(MgrNode, std_mib()),
-
- %% -- 2 --
- Oids02 = [[sysDescr], [1,3,7,1]],
- VF02 = fun(X) ->
- verify_ssgn_reply1(X, [?sysDescr_instance, endOfMibView])
- end,
- ?line ok = do_simple_get_next(2,
- MgrNode, Addr, Port, Oids02, VF02),
-
- Test2Mib = test2_mib(Config),
- ?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
- ?line ok = agent_load_mib(AgentNode, Test2Mib),
-
- %% -- 3 --
- ?line {ok, [TCnt2|_]} = mgr_user_name_to_oid(MgrNode, tCnt2),
- Oids03 = [[TCnt2, 1]],
- VF03 = fun(X) ->
- verify_ssgn_reply1(X, [{fl([TCnt2,2]), 100}])
- end,
- ?line ok = do_simple_get_next(3,
- MgrNode, Addr, Port, Oids03, VF03),
-
- %% -- 4 --
- Oids04 = [[TCnt2, 2]],
- VF04 = fun(X) ->
- verify_ssgn_reply1(X, [{fl([TCnt2,2]), endOfMibView}])
- end,
- ?line ok = do_simple_get_next(4,
- MgrNode, Addr, Port, Oids04, VF04),
-
- %% -- 5 --
- ?line {ok, [TGenErr1|_]} = mgr_user_name_to_oid(MgrNode, tGenErr1),
- Oids05 = [TGenErr1],
- VF05 = fun(X) ->
- verify_ssgn_reply2(X, {genErr, 1, [TGenErr1]})
- end,
- ?line ok = do_simple_get_next(5,
- MgrNode, Addr, Port, Oids05, VF05),
-
- %% -- 6 --
- ?line {ok, [TGenErr2|_]} = mgr_user_name_to_oid(MgrNode, tGenErr2),
- Oids06 = [TGenErr2],
- VF06 = fun(X) ->
- verify_ssgn_reply2(X, {genErr, 1, [TGenErr2]})
- end,
- ?line ok = do_simple_get_next(6,
- MgrNode, Addr, Port, Oids06, VF06),
-
- %% -- 7 --
- ?line {ok, [TGenErr3|_]} = mgr_user_name_to_oid(MgrNode, tGenErr3),
- Oids07 = [[sysDescr], TGenErr3],
- VF07 = fun(X) ->
- verify_ssgn_reply2(X, {genErr, 2,
- [?sysDescr, TGenErr3]})
- end,
- ?line ok = do_simple_get_next(7,
- MgrNode, Addr, Port, Oids07, VF07),
-
- %% -- 8 --
- ?line {ok, [TTooBig|_]} = mgr_user_name_to_oid(MgrNode, tTooBig),
- Oids08 = [TTooBig],
- VF08 = fun(X) ->
- verify_ssgn_reply2(X, {tooBig, 0, []})
- end,
- ?line ok = do_simple_get_next(8,
- MgrNode, Addr, Port, Oids08, VF08),
-
- display_log(Config),
- ok.
-
-
-do_simple_get_next(N, Node, Addr, Port, Oids, Verify) ->
- p("issue get-next command ~w", [N]),
- case mgr_user_sync_get_next(Node, Addr, Port, Oids) of
- {ok, Reply, _Rem} ->
- ?DBG("get-next ok:"
- "~n Reply: ~p"
- "~n Rem: ~w", [Reply, _Rem]),
- Verify(Reply);
-
- Error ->
- {error, {unexpected_reply, Error}}
- end.
-
-
verify_ssgn_reply1({noError, 0, _Vbs}, any) ->
ok;
verify_ssgn_reply1({noError, 0, Vbs}, Expected) ->
@@ -2488,107 +2228,6 @@ simple_sync_get_next3(Config) when is_list(Config) ->
%%======================================================================
-simple_async_get_next1(doc) -> ["Simple (async) get_next-request - "
- "Old style (Addr & Port)"];
-simple_async_get_next1(suite) -> [];
-simple_async_get_next1(Config) when is_list(Config) ->
- ?TC_TRY(simple_async_get_next1,
- fun() -> {skip, "API no longer supported"} end,
- fun() -> do_simple_async_get_next1(Config) end).
-
-do_simple_async_get_next1(Config) ->
- p("starting with Config: ~p~n", [Config]),
-
- MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
- Addr = ?config(ip, Config),
- Port = ?AGENT_PORT,
-
- ?line ok = mgr_user_load_mib(MgrNode, std_mib()),
- Test2Mib = test2_mib(Config),
- ?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
- ?line ok = agent_load_mib(AgentNode, Test2Mib),
-
- Exec = fun(X) ->
- async_gn_exec1(MgrNode, Addr, Port, X)
- end,
-
- ?line {ok, [TCnt2|_]} = mgr_user_name_to_oid(MgrNode, tCnt2),
- ?line {ok, [TGenErr1|_]} = mgr_user_name_to_oid(MgrNode, tGenErr1),
- ?line {ok, [TGenErr2|_]} = mgr_user_name_to_oid(MgrNode, tGenErr2),
- ?line {ok, [TGenErr3|_]} = mgr_user_name_to_oid(MgrNode, tGenErr3),
- ?line {ok, [TTooBig|_]} = mgr_user_name_to_oid(MgrNode, tTooBig),
-
- Requests =
- [
- {1,
- [[1,3,7,1]],
- Exec,
- fun(X) ->
- verify_ssgn_reply1(X, [{[1,3,7,1], endOfMibView}])
- end},
- {2,
- [[sysDescr], [1,3,7,1]],
- Exec,
- fun(X) ->
- verify_ssgn_reply1(X, [?sysDescr_instance, endOfMibView])
- end},
- {3,
- [[TCnt2, 1]],
- Exec,
- fun(X) ->
- verify_ssgn_reply1(X, [{fl([TCnt2,2]), 100}])
- end},
- {4,
- [[TCnt2, 2]],
- Exec,
- fun(X) ->
- verify_ssgn_reply1(X, [{fl([TCnt2,2]), endOfMibView}])
- end},
- {5,
- [TGenErr1],
- Exec,
- fun(X) ->
- verify_ssgn_reply2(X, {genErr, 1, [TGenErr1]})
- end},
- {6,
- [TGenErr2],
- Exec,
- fun(X) ->
- verify_ssgn_reply2(X, {genErr, 1, [TGenErr2]})
- end},
- {7,
- [[sysDescr], TGenErr3],
- Exec,
- fun(X) ->
- verify_ssgn_reply2(X, {genErr, 2, [TGenErr3]})
- end},
- {8,
- [TTooBig],
- Exec,
- fun(X) ->
- verify_ssgn_reply2(X, {tooBig, 0, []})
- end}
- ],
-
- p("manager info when starting test: ~n~p", [mgr_info(MgrNode)]),
- p("agent info when starting test: ~n~p", [agent_info(AgentNode)]),
-
- ?line ok = async_exec(Requests, []),
-
- p("manager info when ending test: ~n~p", [mgr_info(MgrNode)]),
- p("agent info when ending test: ~n~p", [agent_info(AgentNode)]),
-
- display_log(Config),
- ok.
-
-
-async_gn_exec1(Node, Addr, Port, Oids) ->
- mgr_user_async_get_next(Node, Addr, Port, Oids).
-
-
-%%======================================================================
-
simple_async_get_next2(doc) ->
["Simple (async) get_next-request - Version 2 API (TargetName)"];
simple_async_get_next2(suite) -> [];
@@ -2747,67 +2386,6 @@ async_gn_exec3(Node, TargetName, Oids, SendOpts) ->
%%======================================================================
-simple_sync_set1(doc) -> ["Simple (sync) set-request - "
- "Old style (Addr & Port)"];
-simple_sync_set1(suite) -> [];
-simple_sync_set1(Config) when is_list(Config) ->
- ?TC_TRY(simple_sync_set1,
- fun() -> {skip, "API no longer supported"} end,
- fun() -> do_simple_sync_set1(Config) end).
-
-do_simple_sync_set1(Config) ->
- p("starting with Config: ~p~n", [Config]),
-
- Node = ?config(manager_node, Config),
- Addr = ?config(ip, Config),
- Port = ?AGENT_PORT,
-
- p("issue set-request without loading the mib"),
- Val11 = "Arne Anka",
- Val12 = "Stockholm",
- VAVs1 = [
- {?sysName_instance, s, Val11},
- {?sysLocation_instance, s, Val12}
- ],
- ?line ok = do_simple_set1(Node, Addr, Port, VAVs1),
-
- p("issue set-request after first loading the mibs"),
- ?line ok = mgr_user_load_mib(Node, std_mib()),
- Val21 = "Sune Anka",
- Val22 = "Gothenburg",
- VAVs2 = [
- {[sysName, 0], Val21},
- {[sysLocation, 0], Val22}
- ],
- ?line ok = do_simple_set1(Node, Addr, Port, VAVs2),
-
- display_log(Config),
- ok.
-
-do_simple_set1(Node, Addr, Port, VAVs) ->
- [SysName, SysLoc] = value_of_vavs(VAVs),
- ?line {ok, Reply, _Rem} = mgr_user_sync_set(Node, Addr, Port, VAVs),
-
- ?DBG("~n Reply: ~p"
- "~n Rem: ~w", [Reply, _Rem]),
-
- %% verify that the operation actually worked:
- %% The order should be the same, so no need to seach
- %% The value we get should be exactly the same as we sent
- ?line ok = case Reply of
- {noError, 0, [#varbind{oid = ?sysName_instance,
- value = SysName},
- #varbind{oid = ?sysLocation_instance,
- value = SysLoc}]} ->
- ok;
- {noError, 0, Vbs} ->
- {error, {unexpected_vbs, Vbs}};
- Else ->
- p("unexpected reply: ~n~p", [Else]),
- {error, {unexpected_response, Else}}
- end,
- ok.
-
value_of_vavs(VAVs) ->
value_of_vavs(VAVs, []).
@@ -2924,70 +2502,6 @@ do_simple_sync_set3(Config) ->
%%======================================================================
-simple_async_set1(doc) -> ["Simple (async) set-request - "
- "Old style (Addr & Port)"];
-simple_async_set1(suite) -> [];
-simple_async_set1(Config) when is_list(Config) ->
- ?TC_TRY(simple_async_set1,
- fun() -> {skip, "API no longer supported"} end,
- fun() -> do_simple_async_set1(Config) end).
-
-do_simple_async_set1(Config) ->
- p("starting with Config: ~p~n", [Config]),
-
- MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
- Addr = ?config(ip, Config),
- Port = ?AGENT_PORT,
-
- ?line ok = mgr_user_load_mib(MgrNode, std_mib()),
- Test2Mib = test2_mib(Config),
- ?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
- ?line ok = agent_load_mib(AgentNode, Test2Mib),
-
- Exec = fun(X) ->
- async_s_exec1(MgrNode, Addr, Port, X)
- end,
-
- Requests =
- [
- {1,
- [{?sysName_instance, s, "Arne Anka"}],
- Exec,
- fun(X) ->
- sas_verify(X, [?sysName_instance])
- end},
- {2,
- [{?sysLocation_instance, s, "Stockholm"},
- {?sysName_instance, s, "Arne Anka"}],
- Exec,
- fun(X) ->
- sas_verify(X, [?sysLocation_instance, ?sysName_instance])
- end},
- {3,
- [{[sysName, 0], "Gothenburg"},
- {[sysLocation, 0], "Sune Anka"}],
- Exec,
- fun(X) ->
- sas_verify(X, [?sysName_instance, ?sysLocation_instance])
- end}
- ],
-
- p("manager info when starting test: ~n~p", [mgr_info(MgrNode)]),
- p("agent info when starting test: ~n~p", [agent_info(AgentNode)]),
-
- ?line ok = async_exec(Requests, []),
-
- p("manager info when ending test: ~n~p", [mgr_info(MgrNode)]),
- p("agent info when ending test: ~n~p", [agent_info(AgentNode)]),
-
- display_log(Config),
- ok.
-
-
-async_s_exec1(Node, Addr, Port, VAVs) ->
- mgr_user_async_set(Node, Addr, Port, VAVs).
-
sas_verify({noError, 0, _Vbs}, any) ->
p("verified [any]"),
ok;
@@ -3141,146 +2655,9 @@ async_s_exec3(Node, TargetName, VAVs, SendOpts) ->
%%======================================================================
-simple_sync_get_bulk1(doc) -> ["Simple (sync) get_bulk-request - "
- "Old style (Addr & Port)"];
-simple_sync_get_bulk1(suite) -> [];
-simple_sync_get_bulk1(Config) when is_list(Config) ->
- ?TC_TRY(simple_sync_get_bulk1,
- fun() -> {skip, "API no longer supported"} end,
- fun() -> do_simple_sync_get_bulk1(Config) end).
-
-do_simple_sync_get_bulk1(Config) ->
- p("starting with Config: ~p~n", [Config]),
-
- MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
- Addr = ?config(ip, Config),
- Port = ?AGENT_PORT,
-
- %% -- 1 --
- ?line ok = do_simple_get_bulk1(1,
- MgrNode, Addr, Port, 1, 1, [],
- fun verify_ssgb_reply1/1),
-
- %% -- 2 --
- ?line ok = do_simple_get_bulk1(2,
- MgrNode, Addr, Port, -1, 1, [],
- fun verify_ssgb_reply1/1),
-
- %% -- 3 --
- ?line ok = do_simple_get_bulk1(3,
- MgrNode, Addr, Port, -1, -1, [],
- fun verify_ssgb_reply1/1),
-
- ?line ok = mgr_user_load_mib(MgrNode, std_mib()),
- %% -- 4 --
- VF04 = fun(X) ->
- verify_ssgb_reply2(X, [?sysDescr_instance, endOfMibView])
- end,
- ?line ok = do_simple_get_bulk1(4,
- MgrNode, Addr, Port,
- 2, 0, [[sysDescr],[1,3,7,1]], VF04),
-
- %% -- 5 --
- ?line ok = do_simple_get_bulk1(5,
- MgrNode, Addr, Port,
- 1, 2, [[sysDescr],[1,3,7,1]], VF04),
-
- %% -- 6 --
- VF06 = fun(X) ->
- verify_ssgb_reply2(X,
- [?sysDescr_instance, endOfMibView,
- ?sysObjectID_instance, endOfMibView])
- end,
- ?line ok = do_simple_get_bulk1(6,
- MgrNode, Addr, Port,
- 0, 2, [[sysDescr],[1,3,7,1]], VF06),
-
- %% -- 7 --
- VF07 = fun(X) ->
- verify_ssgb_reply2(X,
- [?sysDescr_instance, endOfMibView,
- ?sysDescr_instance, endOfMibView,
- ?sysObjectID_instance, endOfMibView])
- end,
- ?line ok = do_simple_get_bulk1(7,
- MgrNode, Addr, Port,
- 2, 2,
- [[sysDescr],[1,3,7,1],[sysDescr],[1,3,7,1]],
- VF07),
-
- Test2Mib = test2_mib(Config),
- ?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
- ?line ok = agent_load_mib(AgentNode, Test2Mib),
-
- %% -- 8 --
- VF08 = fun(X) ->
- verify_ssgb_reply2(X,
- [?sysDescr_instance,
- ?sysDescr_instance])
- end,
- ?line ok = do_simple_get_bulk1(8,
- MgrNode, Addr, Port,
- 1, 2,
- [[sysDescr],[sysDescr],[tTooBig]],
- VF08),
-
- %% -- 9 --
- ?line ok = do_simple_get_bulk1(9,
- MgrNode, Addr, Port,
- 1, 12,
- [[tDescr2], [sysDescr]],
- fun verify_ssgb_reply1/1),
-
- %% -- 10 --
- VF10 = fun(X) ->
- verify_ssgb_reply3(X,
- [{?sysDescr, 'NULL'},
- {?sysObjectID, 'NULL'},
- {?tGenErr1, 'NULL'},
- {?sysDescr, 'NULL'}])
- end,
- ?line ok = do_simple_get_bulk1(10,
- MgrNode, Addr, Port,
- 2, 2,
- [[sysDescr],
- [sysObjectID],
- [tGenErr1],
- [sysDescr]],
- VF10),
-
- %% -- 11 --
- ?line {ok, [TCnt2|_]} = mgr_user_name_to_oid(MgrNode, tCnt2),
- p("TCnt2: ~p", [TCnt2]),
- VF11 = fun(X) ->
- verify_ssgb_reply2(X,
- [{fl([TCnt2,2]), 100},
- {fl([TCnt2,2]), endOfMibView}])
- end,
- ?line ok = do_simple_get_bulk1(11,
- MgrNode, Addr, Port,
- 0, 2,
- [[TCnt2, 1]], VF11),
-
- display_log(Config),
- ok.
-
fl(L) ->
lists:flatten(L).
-do_simple_get_bulk1(N, Node, Addr, Port, NonRep, MaxRep, Oids, Verify) ->
- p("issue get-bulk command ~w", [N]),
- case mgr_user_sync_get_bulk(Node, Addr, Port, NonRep, MaxRep, Oids) of
- {ok, Reply, _Rem} ->
- ?DBG("get-bulk ok:"
- "~n Reply: ~p"
- "~n Rem: ~w", [Reply, _Rem]),
- Verify(Reply);
-
- Error ->
- {error, {unexpected_reply, Error}}
- end.
-
verify_ssgb_reply1({noError, 0, []}) ->
ok;
verify_ssgb_reply1(X) ->
@@ -3313,7 +2690,6 @@ check_ssgb_vbs([#varbind{oid = Oid, value = Value}|R],
check_ssgb_vbs([R|_], [E|_]) ->
{error, {unexpected_vb, R, E}}.
-
%%======================================================================
simple_sync_get_bulk2(doc) ->
@@ -3513,153 +2889,6 @@ do_simple_sync_get_bulk3(Config) ->
%%======================================================================
-simple_async_get_bulk1(doc) -> ["Simple (async) get_bulk-request - "
- "Old style (Addr & Port)"];
-simple_async_get_bulk1(suite) -> [];
-simple_async_get_bulk1(Config) when is_list(Config) ->
- ?TC_TRY(simple_async_get_bulk1,
- fun() -> {skip, "API no longer supported"} end,
- fun() -> do_simple_async_get_bulk1(Config) end).
-
-do_simple_async_get_bulk1(Config) ->
- p("starting with Config: ~p~n", [Config]),
-
- MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
- Addr = ?config(ip, Config),
- Port = ?AGENT_PORT,
-
- ?line ok = mgr_user_load_mib(MgrNode, std_mib()),
- Test2Mib = test2_mib(Config),
- ?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
- ?line ok = agent_load_mib(AgentNode, Test2Mib),
-
- Exec = fun(Data) ->
- async_gb_exec1(MgrNode, Addr, Port, Data)
- end,
-
- %% We re-use the verification functions from the ssgb test-case
- VF04 = fun(X) ->
- verify_ssgb_reply2(X, [?sysDescr_instance, endOfMibView])
- end,
- VF06 = fun(X) ->
- verify_ssgb_reply2(X,
- [?sysDescr_instance, endOfMibView,
- ?sysObjectID_instance, endOfMibView])
- end,
- VF07 = fun(X) ->
- verify_ssgb_reply2(X,
- [?sysDescr_instance, endOfMibView,
- ?sysDescr_instance, endOfMibView,
- ?sysObjectID_instance, endOfMibView])
- end,
- VF08 = fun(X) ->
- verify_ssgb_reply2(X,
- [?sysDescr_instance,
- ?sysDescr_instance])
- end,
- VF10 = fun(X) ->
- verify_ssgb_reply3(X,
- [{?sysDescr, 'NULL'},
- {?sysObjectID, 'NULL'},
- {?tGenErr1, 'NULL'},
- {?sysDescr, 'NULL'}])
- end,
- ?line {ok, [TCnt2|_]} = mgr_user_name_to_oid(MgrNode, tCnt2),
- VF11 = fun(X) ->
- verify_ssgb_reply2(X,
- [{fl([TCnt2,2]), 100},
- {fl([TCnt2,2]), endOfMibView}])
- end,
- Requests = [
- { 1,
- {1, 1, []},
- Exec,
- fun verify_ssgb_reply1/1},
- { 2,
- {-1, 1, []},
- Exec,
- fun verify_ssgb_reply1/1},
- { 3,
- {-1, -1, []},
- Exec,
- fun verify_ssgb_reply1/1},
- { 4,
- {2, 0, [[sysDescr],[1,3,7,1]]},
- Exec,
- VF04},
- { 5,
- {1, 2, [[sysDescr],[1,3,7,1]]},
- Exec,
- VF04},
- { 6,
- {0, 2, [[sysDescr],[1,3,7,1]]},
- Exec,
- VF06},
- { 7,
- {2, 2, [[sysDescr],[1,3,7,1],[sysDescr],[1,3,7,1]]},
- Exec,
- VF07},
- { 8,
- {1, 2, [[sysDescr],[sysDescr],[tTooBig]]},
- Exec,
- VF08},
- { 9,
- {1, 12, [[tDescr2], [sysDescr]]},
- Exec,
- fun verify_ssgb_reply1/1},
- {10,
- {2, 2, [[sysDescr],[sysObjectID], [tGenErr1],[sysDescr]]},
- Exec,
- VF10},
- {11,
- {0, 2, [[TCnt2, 1]]},
- Exec,
- VF11},
- {12,
- {2, 0, [[sysDescr],[1,3,7,1]]},
- Exec,
- VF04},
- {13,
- {1, 12, [[tDescr2], [sysDescr]]},
- Exec,
- fun verify_ssgb_reply1/1},
- {14,
- {2, 2, [[sysDescr],[sysObjectID],[tGenErr1],[sysDescr]]},
- Exec,
- VF10},
- {15,
- {0, 2, [[TCnt2, 1]]},
- Exec,
- VF11},
- {16,
- {2, 2, [[sysDescr],[1,3,7,1],[sysDescr],[1,3,7,1]]},
- Exec,
- VF07},
- {17,
- {2, 2, [[sysDescr],[sysObjectID], [tGenErr1],[sysDescr]]},
- Exec,
- VF10}
- ],
-
- p("manager info when starting test: ~n~p", [mgr_info(MgrNode)]),
- p("agent info when starting test: ~n~p", [agent_info(AgentNode)]),
-
- ?line ok = async_exec(Requests, []),
-
- p("manager info when ending test: ~n~p", [mgr_info(MgrNode)]),
- p("agent info when ending test: ~n~p", [agent_info(AgentNode)]),
-
- display_log(Config),
- ok.
-
-
-async_gb_exec1(Node, Addr, Port, {NR, MR, Oids}) ->
- mgr_user_async_get_bulk(Node, Addr, Port, NR, MR, Oids).
-
-
-%%======================================================================
-
simple_async_get_bulk2(doc) ->
["Simple (async) get_bulk-request - Version 2 API (TargetName)"];
simple_async_get_bulk2(suite) -> [];
@@ -3864,199 +3093,6 @@ async_gb_exec3(Node, TargetName, {NR, MR, Oids}, SendOpts) ->
%%======================================================================
-misc_async1(doc) -> ["Misc (async) request(s) - "
- "Old style (Addr & Port)"];
-misc_async1(suite) -> [];
-misc_async1(Config) when is_list(Config) ->
- ?TC_TRY(misc_async1,
- fun() -> {skip, "API no longer supported"} end,
- fun() -> do_misc_async1(Config) end).
-
-do_misc_async1(Config) ->
- p("starting with Config: ~p~n", [Config]),
-
- MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
- Addr = ?config(ip, Config),
- Port = ?AGENT_PORT,
-
- ?line ok = mgr_user_load_mib(MgrNode, std_mib()),
- Test2Mib = test2_mib(Config),
- ?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
- ?line ok = agent_load_mib(AgentNode, Test2Mib),
-
- ExecG = fun(Data) ->
- async_g_exec1(MgrNode, Addr, Port, Data)
- end,
-
- ExecGN = fun(Data) ->
- async_gn_exec1(MgrNode, Addr, Port, Data)
- end,
-
- ExecS = fun(Data) ->
- async_s_exec1(MgrNode, Addr, Port, Data)
- end,
-
- ExecGB = fun(Data) ->
- async_gb_exec1(MgrNode, Addr, Port, Data)
- end,
-
- ?line {ok, [TCnt2|_]} = mgr_user_name_to_oid(MgrNode, tCnt2),
- ?line {ok, [TGenErr1|_]} = mgr_user_name_to_oid(MgrNode, tGenErr1),
- ?line {ok, [TGenErr2|_]} = mgr_user_name_to_oid(MgrNode, tGenErr2),
- ?line {ok, [TGenErr3|_]} = mgr_user_name_to_oid(MgrNode, tGenErr3),
- ?line {ok, [TTooBig|_]} = mgr_user_name_to_oid(MgrNode, tTooBig),
-
- Requests =
- [
- { 1,
- [?sysObjectID_instance],
- ExecG,
- fun(X) ->
- sag_verify(X, [?sysObjectID_instance])
- end
- },
- { 2,
- {1, 1, []},
- ExecGB,
- fun verify_ssgb_reply1/1},
- { 3,
- {-1, 1, []},
- ExecGB,
- fun verify_ssgb_reply1/1},
- { 4,
- [{?sysLocation_instance, s, "Stockholm"},
- {?sysName_instance, s, "Arne Anka"}],
- ExecS,
- fun(X) ->
- sas_verify(X, [?sysLocation_instance, ?sysName_instance])
- end},
- { 5,
- [[sysDescr], [1,3,7,1]],
- ExecGN,
- fun(X) ->
- verify_ssgn_reply1(X, [?sysDescr_instance, endOfMibView])
- end},
- { 6,
- [[sysObjectID, 0], [sysDescr, 0], [sysUpTime, 0]],
- ExecG,
- fun(X) ->
- sag_verify(X, [?sysObjectID_instance,
- ?sysDescr_instance,
- ?sysUpTime_instance])
- end},
- { 7,
- [TGenErr2],
- ExecGN,
- fun(X) ->
- verify_ssgn_reply2(X, {genErr, 1, [TGenErr2]})
- end},
- { 8,
- {2, 0, [[sysDescr],[1,3,7,1]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply2(X, [?sysDescr_instance, endOfMibView])
- end},
- { 9,
- {1, 2, [[sysDescr],[1,3,7,1]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply2(X, [?sysDescr_instance, endOfMibView])
- end},
- {10,
- [TGenErr1],
- ExecGN,
- fun(X) ->
- verify_ssgn_reply2(X, {genErr, 1, [TGenErr1]})
- end},
- {11,
- {0, 2, [[sysDescr],[1,3,7,1]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply2(X,
- [?sysDescr_instance, endOfMibView,
- ?sysObjectID_instance, endOfMibView])
- end},
- {12,
- [{[sysName, 0], "Gothenburg"},
- {[sysLocation, 0], "Sune Anka"}],
- ExecS,
- fun(X) ->
- sas_verify(X, [?sysName_instance, ?sysLocation_instance])
- end},
- {13,
- {2, 2, [[sysDescr],[1,3,7,1],[sysDescr],[1,3,7,1]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply2(X,
- [?sysDescr_instance, endOfMibView,
- ?sysDescr_instance, endOfMibView,
- ?sysObjectID_instance, endOfMibView])
- end},
- {14,
- {1, 2, [[sysDescr],[sysDescr],[tTooBig]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply2(X,
- [?sysDescr_instance,
- ?sysDescr_instance])
- end},
- {15,
- {1, 12, [[tDescr2], [sysDescr]]},
- ExecGB,
- fun verify_ssgb_reply1/1},
- {16,
- {2, 2, [[sysDescr],[sysObjectID], [tGenErr1],[sysDescr]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply3(X,
- [{?sysDescr, 'NULL'},
- {?sysObjectID, 'NULL'},
- {?tGenErr1, 'NULL'},
- {?sysDescr, 'NULL'}])
- end},
- {17,
- [[sysDescr], TGenErr3],
- ExecGN,
- fun(X) ->
- verify_ssgn_reply2(X, {genErr, 2, [TGenErr3]})
- end},
- {18,
- {0, 2, [[TCnt2, 1]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply2(X,
- [{fl([TCnt2,2]), 100},
- {fl([TCnt2,2]), endOfMibView}])
- end},
- {19,
- [TTooBig],
- ExecGN,
- fun(X) ->
- verify_ssgn_reply2(X, {tooBig, 0, []})
- end},
- {20,
- [TTooBig],
- ExecGN,
- fun(X) ->
- verify_ssgn_reply2(X, {tooBig, 0, []})
- end}
- ],
-
- p("manager info when starting test: ~n~p", [mgr_info(MgrNode)]),
- p("agent info when starting test: ~n~p", [agent_info(AgentNode)]),
-
- ?line ok = async_exec(Requests, []),
-
- p("manager info when ending test: ~n~p", [mgr_info(MgrNode)]),
- p("agent info when ending test: ~n~p", [agent_info(AgentNode)]),
-
- display_log(Config),
- ok.
-
-
-%%======================================================================
-
misc_async2(doc) ->
["Misc (async) request(s) - Version 2 API (TargetName)"];
misc_async2(suite) -> [];
@@ -6114,40 +5150,21 @@ mgr_user_stop(Node) ->
mgr_user_register_agent(Node, TargetName, Conf)
when is_list(TargetName) andalso is_list(Conf) ->
rcall(Node, snmp_manager_user, register_agent, [TargetName, Conf]).
-%% <REMOVED-IN-R16B>
-%% mgr_user_register_agent(Node, Addr, Port) ->
-%% mgr_user_register_agent(Node, Addr, Port, []).
-%% mgr_user_register_agent(Node, Addr, Port, Conf) ->
-%% rcall(Node, snmp_manager_user, register_agent, [Addr, Port, Conf]).
-%% </REMOVED-IN-R16B>
%% mgr_user_unregister_agent(Node) ->
%% mgr_user_unregister_agent(Node, ?LOCALHOST(), ?AGENT_PORT).
mgr_user_unregister_agent(Node, TargetName) when is_list(TargetName) ->
rcall(Node, snmp_manager_user, unregister_agent, [TargetName]).
-%% <REMOVED-IN-R16B>
-%% mgr_user_unregister_agent(Node, Addr, Port) ->
-%% rcall(Node, snmp_manager_user, unregister_agent, [Addr, Port]).
-%% </REMOVED-IN-R16B>
mgr_user_agent_info(Node, TargetName, Item)
when is_list(TargetName) andalso is_atom(Item) ->
rcall(Node, snmp_manager_user, agent_info, [TargetName, Item]).
-%% <REMOVED-IN-R16B>
-%% mgr_user_agent_info(Node, Addr, Port, Item) when is_atom(Item) ->
-%% rcall(Node, snmp_manager_user, agent_info, [Addr, Port, Item]).
-%% </REMOVED-IN-R16B>
%% mgr_user_update_agent_info(Node, Item, Val) when atom(Item) ->
%% mgr_user_update_agent_info(Node, ?LOCALHOST(), ?AGENT_PORT, Item, Val).
mgr_user_update_agent_info(Node, TargetName, Item, Val)
when is_list(TargetName) andalso is_atom(Item) ->
rcall(Node, snmp_manager_user, update_agent_info, [TargetName, Item, Val]).
-%% <REMOVED-IN-R16B>
-%% mgr_user_update_agent_info(Node, Addr, Port, Item, Val) when is_atom(Item) ->
-%% rcall(Node, snmp_manager_user, update_agent_info,
-%% [Addr, Port, Item, Val]).
-%% </REMOVED-IN-R16B>
%% mgr_user_which_all_agents(Node) ->
%% rcall(Node, snmp_manager_user, which_all_agents, []).
@@ -6162,10 +5179,6 @@ mgr_user_load_mib(Node, Mib) ->
%% mgr_user_sync_get(Node, ?LOCALHOST(), ?AGENT_PORT, Oids).
mgr_user_sync_get(Node, TargetName, Oids) when is_list(TargetName) ->
rcall(Node, snmp_manager_user, sync_get, [TargetName, Oids]).
-%% <REMOVED-IN-R16B>
-mgr_user_sync_get(Node, Addr, Port, Oids) ->
- rcall(Node, snmp_manager_user, sync_get, [Addr, Port, Oids]).
-%% </REMOVED-IN-R16B>
mgr_user_sync_get2(Node, TargetName, Oids, SendOpts) when is_list(TargetName) ->
rcall(Node, snmp_manager_user, sync_get2, [TargetName, Oids, SendOpts]).
@@ -6174,10 +5187,6 @@ mgr_user_sync_get2(Node, TargetName, Oids, SendOpts) when is_list(TargetName) ->
%% mgr_user_async_get(Node, ?LOCALHOST(), ?AGENT_PORT, Oids).
mgr_user_async_get(Node, TargetName, Oids) when is_list(TargetName) ->
rcall(Node, snmp_manager_user, async_get, [TargetName, Oids]).
-%% <REMOVED-IN-R16B>
-mgr_user_async_get(Node, Addr, Port, Oids) ->
- rcall(Node, snmp_manager_user, async_get, [Addr, Port, Oids]).
-%% </REMOVED-IN-R16B>
mgr_user_async_get2(Node, TargetName, Oids, SendOpts)
when is_list(TargetName) ->
@@ -6187,10 +5196,6 @@ mgr_user_async_get2(Node, TargetName, Oids, SendOpts)
%% mgr_user_sync_get_next(Node, ?LOCALHOST(), ?AGENT_PORT, Oids).
mgr_user_sync_get_next(Node, TargetName, Oids) when is_list(TargetName) ->
rcall(Node, snmp_manager_user, sync_get_next, [TargetName, Oids]).
-%% <REMOVED-IN-R16B>
-mgr_user_sync_get_next(Node, Addr, Port, Oids) ->
- rcall(Node, snmp_manager_user, sync_get_next, [Addr, Port, Oids]).
-%% </REMOVED-IN-R16B>
mgr_user_sync_get_next2(Node, TargetName, Oids, SendOpts)
when is_list(TargetName) ->
@@ -6200,10 +5205,6 @@ mgr_user_sync_get_next2(Node, TargetName, Oids, SendOpts)
%% mgr_user_async_get_next(Node, ?LOCALHOST(), ?AGENT_PORT, Oids).
mgr_user_async_get_next(Node, TargetName, Oids) when is_list(TargetName) ->
rcall(Node, snmp_manager_user, async_get_next, [TargetName, Oids]).
-%% <REMOVED-IN-R16B>
-mgr_user_async_get_next(Node, Addr, Port, Oids) ->
- rcall(Node, snmp_manager_user, async_get_next, [Addr, Port, Oids]).
-%% </REMOVED-IN-R16B>
mgr_user_async_get_next2(Node, TargetName, Oids, SendOpts)
when is_list(TargetName) ->
@@ -6213,10 +5214,6 @@ mgr_user_async_get_next2(Node, TargetName, Oids, SendOpts)
%% mgr_user_sync_set(Node, ?LOCALHOST(), ?AGENT_PORT, VAV).
mgr_user_sync_set(Node, TargetName, VAV) when is_list(TargetName) ->
rcall(Node, snmp_manager_user, sync_set, [TargetName, VAV]).
-%% <REMOVED-IN-R16B>
-mgr_user_sync_set(Node, Addr, Port, VAV) ->
- rcall(Node, snmp_manager_user, sync_set, [Addr, Port, VAV]).
-%% </REMOVED-IN-R16B>
mgr_user_sync_set2(Node, TargetName, VAV, SendOpts) when is_list(TargetName) ->
rcall(Node, snmp_manager_user, sync_set2, [TargetName, VAV, SendOpts]).
@@ -6225,10 +5222,6 @@ mgr_user_sync_set2(Node, TargetName, VAV, SendOpts) when is_list(TargetName) ->
%% mgr_user_async_set(Node, ?LOCALHOST(), ?AGENT_PORT, VAV).
mgr_user_async_set(Node, TargetName, VAV) when is_list(TargetName) ->
rcall(Node, snmp_manager_user, async_set, [TargetName, VAV]).
-%% <REMOVED-IN-R16B>
-mgr_user_async_set(Node, Addr, Port, VAV) ->
- rcall(Node, snmp_manager_user, async_set, [Addr, Port, VAV]).
-%% </REMOVED-IN-R16B>
mgr_user_async_set2(Node, TargetName, VAV, SendOpts) when is_list(TargetName) ->
rcall(Node, snmp_manager_user, async_set2, [TargetName, VAV, SendOpts]).
@@ -6240,11 +5233,6 @@ mgr_user_sync_get_bulk(Node, TargetName, NonRep, MaxRep, Oids)
when is_list(TargetName) ->
rcall(Node, snmp_manager_user, sync_get_bulk,
[TargetName, NonRep, MaxRep, Oids]).
-%% <REMOVED-IN-R16B>
-mgr_user_sync_get_bulk(Node, Addr, Port, NonRep, MaxRep, Oids) ->
- rcall(Node, snmp_manager_user, sync_get_bulk,
- [Addr, Port, NonRep, MaxRep, Oids]).
-%% </REMOVED-IN-R16B>
mgr_user_sync_get_bulk2(Node, TargetName, NonRep, MaxRep, Oids, SendOpts)
when is_list(TargetName) ->
@@ -6258,11 +5246,6 @@ mgr_user_async_get_bulk(Node, TargetName, NonRep, MaxRep, Oids)
when is_list(TargetName) ->
rcall(Node, snmp_manager_user, async_get_bulk,
[TargetName, NonRep, MaxRep, Oids]).
-%% <REMOVED-IN-R16B>
-mgr_user_async_get_bulk(Node, Addr, Port, NonRep, MaxRep, Oids) ->
- rcall(Node, snmp_manager_user, async_get_bulk,
- [Addr, Port, NonRep, MaxRep, Oids]).
-%% </REMOVED-IN-R16B>
mgr_user_async_get_bulk2(Node, TargetName, NonRep, MaxRep, Oids, SendOpts)
when is_list(TargetName) ->
@@ -6436,9 +5419,12 @@ start_node(Name, Retry) ->
error ->
""
end,
- A = Args ++ " -pa " ++ Pa,
+ A = Args ++ " -pa " ++ Pa ++
+ " -s " ++ atom_to_list(snmp_test_sys_monitor) ++ " start" ++
+ " -s global sync",
try ?START_NODE(Name, A) of
{ok, Node} ->
+ global:sync(),
Node;
{error, timeout} ->
e("Failed starting node ~p: timeout", [Name]),
diff --git a/lib/snmp/test/snmp_test_lib.erl b/lib/snmp/test/snmp_test_lib.erl
index 26b68501e2..a040c4f39c 100644
--- a/lib/snmp/test/snmp_test_lib.erl
+++ b/lib/snmp/test/snmp_test_lib.erl
@@ -80,22 +80,42 @@ tc_try(Case, TCCondFun, TCFun)
tc_end( f("skipping(catched,~w,tc)", [C]) ),
SKIP;
C:E:S ->
- tc_end( f("failed(catched,~w,tc)", [C]) ),
- erlang:raise(C, E, S)
+ %% We always check the system events before we accept a failure
+ case snmp_test_global_sys_monitor:events() of
+ [] ->
+ tc_end( f("failed(catched,~w,tc)", [C]) ),
+ erlang:raise(C, E, S);
+ SysEvs ->
+ tc_print("System Events received: "
+ "~n ~p", [SysEvs], "", ""),
+ tc_end( f("skipping(catched-sysevs,~w,tc)", [C]) ),
+ SKIP = {skip, "TC failure with system events"},
+ SKIP
+ end
end;
{skip, _} = SKIP ->
- tc_end("skipping(tc)"),
+ tc_end("skipping(cond)"),
SKIP;
{error, Reason} ->
- tc_end("failed(tc)"),
+ tc_end("failed(cond)"),
exit({tc_cond_failed, Reason})
catch
C:{skip, _} = SKIP when ((C =:= throw) orelse (C =:= exit)) ->
tc_end( f("skipping(catched,~w,cond)", [C]) ),
SKIP;
C:E:S ->
- tc_end( f("failed(catched,~w,cond)", [C]) ),
- erlang:raise(C, E, S)
+ %% We always check the system events before we accept a failure
+ case snmp_test_global_sys_monitor:events() of
+ [] ->
+ tc_end( f("failed(catched,~w,cond)", [C]) ),
+ erlang:raise(C, E, S);
+ SysEvs ->
+ tc_print("System Events received: "
+ "~n ~p", [SysEvs], "", ""),
+ tc_end( f("skipping(catched-sysevs,~w,cond)", [C]) ),
+ SKIP = {skip, "TC cond failure with system events"},
+ SKIP
+ end
end.
diff --git a/lib/snmp/test/snmp_test_mgr.erl b/lib/snmp/test/snmp_test_mgr.erl
index 9d6be65088..f50147a852 100644
--- a/lib/snmp/test/snmp_test_mgr.erl
+++ b/lib/snmp/test/snmp_test_mgr.erl
@@ -700,18 +700,30 @@ echo_errors({error, Id, {ExpectedFormat, ExpectedData}, {Format, Data}})->
echo_errors(ok) -> ok;
echo_errors({ok, Val}) -> {ok, Val}.
-get_response_impl(Id, Vars) ->
+get_response_impl(Id, ExpVars) ->
+ ?PRINT2("await response ~w with"
+ "~n Expected Varbinds: ~p",
+ [Id, ExpVars]),
+ PureVars = find_pure_oids2(ExpVars),
case receive_response() of
#pdu{type = 'get-response',
error_status = noError,
error_index = 0,
varbinds = VBs} ->
- match_vars(Id, find_pure_oids2(Vars), VBs, []);
+ ?PRINT2("received expected response pdu (~w) - match vars"
+ "~n Expected VBs: ~p"
+ "~n Received VBs: ~p",
+ [Id, PureVars, VBs]),
+ match_vars(Id, PureVars, VBs, []);
#pdu{type = Type2,
request_id = ReqId,
error_status = Err2,
error_index = Index2} ->
+ ?EPRINT2("received unexpected response pdu: ~w, ~w, ~w"
+ "~n Received Error: ~p"
+ "~n Received Index: ~p",
+ [Type2, Id, ReqId, Err2, Index2]),
{error,
Id,
{"Type: ~w, ErrStat: ~w, Idx: ~w, RequestId: ~w",
@@ -720,6 +732,8 @@ get_response_impl(Id, Vars) ->
[Type2, Err2, Index2]}};
{error, Reason} ->
+ ?EPRINT2("unexpected receive pdu error: ~w"
+ "~n ~p", [Id, Reason]),
format_reason(Id, Reason)
end.
@@ -729,171 +743,208 @@ get_response_impl(Id, Vars) ->
%% Returns: ok | {error, Id, {ExpectedFormat, ExpectedData}, {Format, Data}}
%%----------------------------------------------------------------------
expect_impl(Id, any) ->
- io:format("expect_impl(~w, any) -> entry ~n", [Id]),
+ ?PRINT2("await ~w pdu (any)", [Id]),
case receive_response() of
- PDU when is_record(PDU, pdu) -> ok;
- {error, Reason} -> format_reason(Id, Reason)
+ PDU when is_record(PDU, pdu) ->
+ ?PRINT2("received expected pdu (~w)", [Id]),
+ ok;
+ {error, Reason} ->
+ ?EPRINT1("unexpected receive error: ~w"
+ "~n ~p", [Id, Reason]),
+ format_reason(Id, Reason)
end;
expect_impl(Id, return) ->
- io:format("expect_impl(~w, return) -> entry ~n", [Id]),
+ ?PRINT2("await ~w pdu", [Id]),
case receive_response() of
- PDU when is_record(PDU, pdu) -> {ok, PDU};
- {error, Reason} -> format_reason(Id, Reason)
+ PDU when is_record(PDU, pdu) ->
+ ?PRINT2("received expected pdu (~w)", [Id]),
+ {ok, PDU};
+ {error, Reason} ->
+ ?EPRINT1("unexpected receive error: ~w"
+ "~n ~p", [Id, Reason]),
+ format_reason(Id, Reason)
end;
expect_impl(Id, trap) ->
- io:format("expect_impl(~w, trap) -> entry ~n", [Id]),
+ ?PRINT2("await ~w trap", [Id]),
case receive_trap(3500) of
- PDU when is_record(PDU, trappdu) -> ok;
- {error, Reason} -> format_reason(Id, Reason)
+ PDU when is_record(PDU, trappdu) ->
+ ?PRINT2("received expected trap (~w)", [Id]),
+ ok;
+ {error, Reason} ->
+ ?EPRINT1("unexpected receive error: ~w"
+ "~n ~p", [Id, Reason]),
+ format_reason(Id, Reason)
end;
expect_impl(Id, timeout) ->
- io:format("expect_impl(~w, timeout) -> entry ~n", [Id]),
+ ?PRINT2("await ~w nothing", [Id]),
receive
X ->
- io:format("expect_impl(~w, timeout) -> "
- "received unexpected message: ~n~p~n", [Id, X]),
+ ?EPRINT1("received unexpected message: ~w"
+ "~n ~p",
+ [Id, X]),
{error, Id, {"Timeout", []}, {"Message ~w", [X]}}
after 3500 ->
ok
end;
expect_impl(Id, Err) when is_atom(Err) ->
- io:format("expect_impl(~w, ~w) -> entry ~n", [Id, Err]),
+ ?PRINT2("await ~w with"
+ "~n Err: ~p",
+ [Id, Err]),
case receive_response() of
#pdu{error_status = Err} ->
+ ?PRINT2("received pdu with expected error status (~w, ~w)",
+ [Id, Err]),
ok;
- #pdu{request_id = ReqId,
- error_status = OtherErr} ->
- io:format("expect_impl(~w, ~w) -> "
- "received pdu (~w) with unexpected error-status: "
- "~n~p~n", [Id, Err, ReqId, OtherErr]),
+ #pdu{type = Type2,
+ request_id = ReqId,
+ error_status = Err2} ->
+ ?EPRINT1("received pdu with unexpected error status: ~w, ~w, ~w"
+ "~n Expected Error: ~p"
+ "~n Received Error: ~p",
+ [Type2, Id, ReqId, Err, Err2]),
{error, Id, {"ErrorStatus: ~w, RequestId: ~w", [Err,ReqId]},
- {"ErrorStatus: ~w", [OtherErr]}};
+ {"ErrorStatus: ~w", [Err2]}};
{error, Reason} ->
+ ?EPRINT1("unexpected receive error: ~w"
+ "~n ~p", [Id, Reason]),
format_reason(Id, Reason)
end;
expect_impl(Id, ExpectedVarbinds) when is_list(ExpectedVarbinds) ->
- io:format("expect_impl(~w) -> entry with"
- "~n ExpectedVarbinds: ~p~n", [Id, ExpectedVarbinds]),
+ ?PRINT2("await ~w with"
+ "~n ExpectedVarbinds: ~p",
+ [Id, ExpectedVarbinds]),
+ PureVars = find_pure_oids(ExpectedVarbinds),
case receive_response() of
#pdu{type = 'get-response',
error_status = noError,
error_index = 0,
varbinds = VBs} ->
- io:format("expect_impl(~w) -> received pdu with"
- "~n VBs: ~p~n", [Id, VBs]),
- check_vars(Id, find_pure_oids(ExpectedVarbinds), VBs);
+ ?PRINT2("received expected response pdu (~w) - check varbinds"
+ "~n Expected VBs: ~p"
+ "~n Received VBs: ~p",
+ [Id, PureVars, VBs]),
+ check_vars(Id, PureVars, VBs);
#pdu{type = Type2,
request_id = ReqId,
error_status = Err2,
error_index = Index2} ->
- io:format("expect_impl(~w) -> received unexpected pdu with"
- "~n Type2: ~p"
- "~n ReqId: ~p"
- "~n Err2: ~p"
- "~n Index2: ~p"
- "~n", [Id, Type2, ReqId, Err2, Index2]),
+ ?EPRINT1("received unexpected pdu: ~w, ~w, ~w"
+ "~n Received Error: ~p"
+ "~n Received Index: ~p",
+ [Type2, Id, ReqId, Err2, Index2]),
{error, Id, {"Type: ~w, ErrStat: ~w, Idx: ~w, RequestId: ~w",
['get-response', noError, 0, ReqId]},
{"Type: ~w, ErrStat: ~w, Idx: ~w", [Type2, Err2, Index2]}};
{error, Reason} ->
+ ?EPRINT1("unexpected receive error: ~w"
+ "~n ~p", [Id, Reason]),
format_reason(Id, Reason)
end.
expect_impl(Id, v2trap, ExpectedVarbinds) when is_list(ExpectedVarbinds) ->
- io:format("expect_impl(~w, v2trap) -> entry with"
- "~n ExpectedVarbinds: ~p~n", [Id, ExpectedVarbinds]),
+ ?PRINT2("await v2 trap ~w with"
+ "~n ExpectedVarbinds: ~p",
+ [Id, ExpectedVarbinds]),
+ PureVars = find_pure_oids(ExpectedVarbinds),
case receive_response() of
#pdu{type = 'snmpv2-trap',
error_status = noError,
error_index = 0,
varbinds = VBs} ->
- io:format("expect_impl(~w, v2trap) -> received pdu with"
- "~n VBs: ~p~n", [Id, VBs]),
- check_vars(Id, find_pure_oids(ExpectedVarbinds), VBs);
+ ?PRINT2("received expected v2 trap (~w) - check varbinds"
+ "~n Expected VBs: ~p"
+ "~n Received VBs: ~p",
+ [Id, PureVars, VBs]),
+ check_vars(Id, PureVars, VBs);
#pdu{type = Type2,
request_id = ReqId,
error_status = Err2,
error_index = Index2} ->
- io:format("expect_impl(~w, v2trap) -> received unexpected pdu with"
- "~n Type2: ~p"
- "~n ReqId: ~p"
- "~n Err2: ~p"
- "~n Index2: ~p"
- "~n", [Id, Type2, ReqId, Err2, Index2]),
+ ?EPRINT1("received unexpected pdu: ~w, ~w, ~w"
+ "~n Received Error: ~p"
+ "~n Received Index: ~p",
+ [Type2, Id, ReqId, Err2, Index2]),
{error, Id, {"Type: ~w, ErrStat: ~w, Idx: ~w, RequestId: ~w",
['snmpv2-trap', noError, 0, ReqId]},
{"Type: ~w, ErrStat: ~w, Idx: ~w", [Type2, Err2, Index2]}};
{error, Reason} ->
+ ?EPRINT1("unexpected receive error: ~w"
+ "~n ~p", [Id, Reason]),
format_reason(Id, Reason)
end;
expect_impl(Id, report, ExpectedVarbinds) when is_list(ExpectedVarbinds) ->
- io:format("expect_impl(~w, report) -> entry with"
- "~n ExpectedVarbinds: ~p~n", [Id, ExpectedVarbinds]),
+ ?PRINT2("await report ~w with"
+ "~n ExpectedVarbinds: ~p",
+ [Id, ExpectedVarbinds]),
+ PureVBs = find_pure_oids(ExpectedVarbinds),
case receive_response() of
#pdu{type = 'report',
error_status = noError,
error_index = 0,
varbinds = VBs} ->
- io:format("expect_impl(~w, report) -> received pdu with"
- "~n VBs: ~p~n", [Id, VBs]),
- check_vars(Id, find_pure_oids(ExpectedVarbinds), VBs);
+ ?PRINT2("received expected report (~w) - check varbinds"
+ "~n Expected VBs: ~p"
+ "~n Received VBs: ~p",
+ [Id, PureVBs, VBs]),
+ check_vars(Id, PureVBs, VBs);
#pdu{type = Type2,
request_id = ReqId,
error_status = Err2,
error_index = Index2} ->
- io:format("expect_impl(~w, report) -> received unexpected pdu with"
- "~n Type2: ~p"
- "~n ReqId: ~p"
- "~n Err2: ~p"
- "~n Index2: ~p"
- "~n", [Id, Type2, ReqId, Err2, Index2]),
+ ?EPRINT1("received unexpected pdu: ~w, ~w, ~w"
+ "~n Received Error: ~p"
+ "~n Received Index: ~p",
+ [Type2, Id, ReqId, Err2, Index2]),
{error, Id, {"Type: ~w, ErrStat: ~w, Idx: ~w, RequestId: ~w",
[report, noError, 0, ReqId]},
{"Type: ~w, ErrStat: ~w, Idx: ~w", [Type2, Err2, Index2]}};
{error, Reason} ->
+ ?EPRINT1("unexpected receive error: ~w"
+ "~n ~p", [Id, Reason]),
format_reason(Id, Reason)
end;
expect_impl(Id, {inform, Reply}, ExpectedVarbinds)
when is_list(ExpectedVarbinds) ->
- io:format("expect_impl(~w, inform) -> entry with"
- "~n Reply: ~p"
- "~n ExpectedVarbinds: ~p"
- "~n", [Id, Reply, ExpectedVarbinds]),
- Resp = receive_response(),
+ ?PRINT2("await inform ~w with"
+ "~n Reply: ~p"
+ "~n ExpectedVarbinds: ~p",
+ [Id, Reply, ExpectedVarbinds]),
+ PureVBs = find_pure_oids(ExpectedVarbinds),
+ Resp = receive_response(),
case Resp of
#pdu{type = 'inform-request',
error_status = noError,
error_index = 0,
varbinds = VBs} ->
- io:format("expect_impl(~w, inform) -> received pdu with"
- "~n VBs: ~p~n", [Id, VBs]),
- case check_vars(Id, find_pure_oids(ExpectedVarbinds), VBs) of
+ ?PRINT2("received inform (~w) - check varbinds"
+ "~n Expected VBs: ~p"
+ "~n Received VBs: ~p",
+ [Id, PureVBs, VBs]),
+ case check_vars(Id, PureVBs, VBs) of
ok when (Reply == true) ->
- io:format("expect_impl(~w, inform) -> send ok response"
- "~n", [Id]),
+ ?PRINT2("varbinds ok (~w) - send ok inform response", [Id]),
RespPDU = Resp#pdu{type = 'get-response',
error_status = noError,
error_index = 0},
?MODULE:rpl(RespPDU),
ok;
ok when (element(1, Reply) == error) ->
- io:format("expect_impl(~w, inform) -> send error response"
- "~n", [Id]),
+ ?PRINT2("varbinds ok (~w) - send error inform response", [Id]),
{error, Status, Index} = Reply,
RespPDU = Resp#pdu{type = 'get-response',
error_status = Status,
@@ -901,10 +952,10 @@ expect_impl(Id, {inform, Reply}, ExpectedVarbinds)
?MODULE:rpl(RespPDU),
ok;
ok when (Reply == false) ->
- io:format("expect_impl(~w, inform) -> no response sent"
- "~n", [Id]),
+ ?PRINT2("varbinds ok (~w) - don't send inform response", [Id]),
ok;
Else ->
+ ?EPRINT1("unexpected varbinds (~w)", [Id]),
io:format("expect_impl(~w, inform) -> "
"~n Else: ~p"
"~n", [Id, Else]),
@@ -915,54 +966,54 @@ expect_impl(Id, {inform, Reply}, ExpectedVarbinds)
request_id = ReqId,
error_status = Err2,
error_index = Index2} ->
- io:format("expect_impl(~w, inform) -> received unexpected pdu with"
- "~n Type2: ~p"
- "~n ReqId: ~p"
- "~n Err2: ~p"
- "~n Index2: ~p"
- "~n", [Id, Type2, ReqId, Err2, Index2]),
+ ?EPRINT1("received unexpected pdu: ~w, ~w, ~w"
+ "~n Received Error: ~p"
+ "~n Received Index: ~p",
+ [Type2, Id, ReqId, Err2, Index2]),
{error, Id, {"Type: ~w, ErrStat: ~w, Idx: ~w, RequestId: ~w",
['inform-request', noError, 0, ReqId]},
{"Type: ~w, ErrStat: ~w, Idx: ~w", [Type2, Err2, Index2]}};
{error, Reason} ->
- io:format("expect_impl(~w, inform) -> receive failed"
- "~n Reason: ~p"
- "~n", [Id, Reason]),
+ ?EPRINT1("unexpected receive error: ~w"
+ "~n ~p", [Id, Reason]),
format_reason(Id, Reason)
end.
-expect_impl(Id, Err, Index, any) ->
- io:format("expect_impl(~w, any) -> entry with"
- "~n Err: ~p"
- "~n Index: ~p"
- "~n", [Id, Err, Index]),
+expect_impl(Id, Err, Index, any = _ExpectedVarbinds) ->
+ ?PRINT2("await response ~w with"
+ "~n Err: ~p"
+ "~n Index: ~p"
+ "~n ExpectedVarbinds: ~p",
+ [Id, Err, Index, _ExpectedVarbinds]),
case receive_response() of
#pdu{type = 'get-response',
error_status = Err,
error_index = Index} ->
- io:format("expect_impl(~w, any) -> received expected pdu"
- "~n", [Id]),
+ ?PRINT2("received expected response pdu (~w, ~w, ~w)",
+ [Id, Err, Index]),
ok;
- #pdu{type = 'get-response', error_status = Err} when (Index == any) ->
- io:format("expect_impl(~w, any) -> received expected pdu (any)"
- "~n", [Id]),
+ #pdu{type = 'get-response',
+ error_status = Err} when (Index == any) ->
+ ?PRINT2("received expected response pdu (~w, ~w)",
+ [Id, Err]),
ok;
#pdu{type = 'get-response',
request_id = ReqId,
error_status = Err,
error_index = Idx} when is_list(Index) ->
- io:format("expect_impl(~w, any) -> received pdu: "
- "~n ReqId: ~p"
- "~n Err: ~p"
- "~n Idx: ~p"
- "~n", [Id, ReqId, Err, Idx]),
case lists:member(Idx, Index) of
true ->
+ ?PRINT2("received expected response pdu (~w, ~w, ~w)",
+ [Id, Err, Idx]),
ok;
false ->
+ ?EPRINT1("received response pdu with unexpected index (~w, ~w):"
+ "~n Expected Index: ~p"
+ "~n Received Index: ~p",
+ [Id, Err, Index, Idx]),
{error, Id, {"ErrStat: ~w, Idx: ~w, RequestId: ~w",
[Err, Index, ReqId]},
{"ErrStat: ~w, Idx: ~w", [Err, Idx]}}
@@ -972,12 +1023,12 @@ expect_impl(Id, Err, Index, any) ->
request_id = ReqId,
error_status = Err2,
error_index = Index2} ->
- io:format("expect_impl(~w, any) -> received unexpected pdu: "
- "~n Type2: ~p"
- "~n ReqId: ~p"
- "~n Err2: ~p"
- "~n Index2: ~p"
- "~n", [Id, Type2, ReqId, Err2, Index2]),
+ ?EPRINT1("received unexpected response pdu: ~w, ~w, ~w"
+ "~n Expected Error: ~p"
+ "~n Received Error: ~p"
+ "~n Expected Index: ~p"
+ "~n Received Index: ~p",
+ [Type2, Id, ReqId, Err, Err2, Index, Index2]),
{error, Id, {"Type: ~w, ErrStat: ~w, Idx: ~w, RequestId: ~w",
['get-response', Err, Index, ReqId]},
{"Type: ~w, ErrStat: ~w, Idx: ~w", [Type2, Err2, Index2]}};
@@ -987,22 +1038,30 @@ expect_impl(Id, Err, Index, any) ->
end;
expect_impl(Id, Err, Index, ExpectedVarbinds) ->
- io:format("expect_impl(~w) -> entry with"
- "~n Err: ~p"
- "~n Index: ~p"
- "~n ExpectedVarbinds: ~p"
- "~n", [Id, Err, Index, ExpectedVarbinds]),
+ ?PRINT2("await response ~w with"
+ "~n Err: ~p"
+ "~n Index: ~p"
+ "~n ExpectedVarbinds: ~p",
+ [Id, Err, Index, ExpectedVarbinds]),
PureVBs = find_pure_oids(ExpectedVarbinds),
case receive_response() of
#pdu{type = 'get-response',
error_status = Err,
error_index = Index,
varbinds = VBs} ->
+ ?PRINT2("received expected response pdu (~w, ~w, ~w) - check varbinds"
+ "~n Expected VBs: ~p"
+ "~n Received VBs: ~p",
+ [Id, Err, Index, PureVBs, VBs]),
check_vars(Id, PureVBs, VBs);
#pdu{type = 'get-response',
error_status = Err,
varbinds = VBs} when (Index == any) ->
+ ?PRINT2("received expected response pdu (~w, ~w) - check varbinds"
+ "~n Expected VBs: ~p"
+ "~n Received VBs: ~p",
+ [Id, Err, PureVBs, VBs]),
check_vars(Id, PureVBs, VBs);
#pdu{type = 'get-response',
@@ -1012,8 +1071,18 @@ expect_impl(Id, Err, Index, ExpectedVarbinds) ->
varbinds = VBs} when is_list(Index) ->
case lists:member(Idx, Index) of
true ->
+ ?PRINT2("received expected pdu (~w, ~w, ~w) - check varbinds"
+ "~n Expected VBs: ~p"
+ "~n Received VBs: ~p",
+ [Id, Err, Idx, PureVBs, VBs]),
check_vars(Id, PureVBs, VBs);
false ->
+ ?EPRINT1("received response pdu with unexpected index (~w, ~w):"
+ "~n Expected Index: ~p"
+ "~n Received Index: ~p"
+ "~n Expected VBs: ~p"
+ "~n Received VBs: ~p",
+ [Id, Err, Index, Idx, PureVBs, VBs]),
{error,Id,
{"ErrStat: ~w, Idx: ~w, Varbinds: ~w, RequestId: ~w",
[Err,Index,PureVBs,ReqId]},
@@ -1026,29 +1095,65 @@ expect_impl(Id, Err, Index, ExpectedVarbinds) ->
error_status = Err2,
error_index = Index2,
varbinds = VBs} ->
+ ?EPRINT1("received unexpected response pdu: ~w, ~w, ~w"
+ "~n Expected Error: ~p"
+ "~n Received Error: ~p"
+ "~n Expected Index: ~p"
+ "~n Received Index: ~p"
+ "~n Expected VBs: ~p"
+ "~n Received VBs: ~p",
+ [Type2, Id, ReqId,
+ Err, Err2, Index, Index2, PureVBs, VBs]),
{error,Id,
{"Type: ~w, ErrStat: ~w, Idx: ~w, Varbinds: ~w, RequestId: ~w",
['get-response',Err,Index,PureVBs,ReqId]},
{"Type: ~w, ErrStat: ~w Idx: ~w Varbinds: ~w",
[Type2,Err2,Index2,VBs]}};
- {error, Reason} ->
+ {error, Reason} ->
+ ?EPRINT1("unexpected receive pdu error: ~w"
+ "~n ~p", [Id, Reason]),
format_reason(Id, Reason)
end.
expect_impl(Id, trap, Enterp, Generic, Specific, ExpectedVarbinds) ->
- PureE = find_pure_oid(Enterp),
+ ?PRINT2("await trap pdu ~w with"
+ "~n Enterprise: ~p"
+ "~n Generic: ~p"
+ "~n Specific: ~p"
+ "~n ExpectedVarbinds: ~p",
+ [Id, Enterp, Generic, Specific, ExpectedVarbinds]),
+ PureE = find_pure_oid(Enterp),
+ PureVBs = find_pure_oids(ExpectedVarbinds),
case receive_trap(3500) of
#trappdu{enterprise = PureE,
generic_trap = Generic,
specific_trap = Specific,
varbinds = VBs} ->
- check_vars(Id, find_pure_oids(ExpectedVarbinds), VBs);
+ ?PRINT2("received expected trap pdu - check varbinds"
+ "~n Expected VBs: ~p"
+ "~n Received VBs: ~p",
+ [PureVBs, VBs]),
+ check_vars(Id, PureVBs, VBs);
#trappdu{enterprise = Ent2,
generic_trap = G2,
specific_trap = Spec2,
varbinds = VBs} ->
+ ?EPRINT1("received unexpected trap pdu: ~w"
+ "~n Expected Enterprise: ~p"
+ "~n Received Enterprise: ~p"
+ "~n Expected Generic: ~p"
+ "~n Received Generic: ~p"
+ "~n Expected Specific: ~p"
+ "~n Received Specific: ~p"
+ "~n Expected VBs: ~p"
+ "~n Received VBs: ~p",
+ [Id,
+ PureE, Ent2,
+ Generic, G2,
+ Specific, Spec2,
+ PureVBs, VBs]),
{error, Id,
{"Enterprise: ~w, Generic: ~w, Specific: ~w, Varbinds: ~w",
[PureE, Generic, Specific, ExpectedVarbinds]},
@@ -1056,12 +1161,15 @@ expect_impl(Id, trap, Enterp, Generic, Specific, ExpectedVarbinds) ->
[Ent2, G2, Spec2, VBs]}};
{error, Reason} ->
+ ?EPRINT1("unexpected receive trap pdu error: ~w"
+ "~n ~p", [Id, Reason]),
format_reason(Id, Reason)
end.
format_reason(Id, Reason) ->
{error, Id, {"?", []}, {"~w", [Reason]}}.
+
%%----------------------------------------------------------------------
%% Args: Id, ExpectedVarbinds, GotVarbinds
%% Returns: ok
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml
index 60f20c7c3f..dd21f97c44 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -45,6 +45,22 @@
</section>
+<section><title>Ssh 4.7.6.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed a possible SSH logging crash if there was a problem
+ in an early stage of session setup.</p>
+ <p>
+ Own Id: OTP-15962 Aux Id: ERL-990 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.7.6</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml
index 8b7cb4dcd4..afcae71418 100644
--- a/lib/ssh/doc/src/ssh.xml
+++ b/lib/ssh/doc/src/ssh.xml
@@ -1036,6 +1036,34 @@
</desc>
</datatype>
+ <datatype>
+ <name name="connection_info_tuple"/>
+ <name name="version"/>
+ <name name="protocol_version"/>
+ <name name="software_version"/>
+ <name name="conn_info_algs"/>
+ <name name="conn_info_channels"/>
+ <desc>
+ <p>Return values from the
+ <seealso marker="#connection_info/1">connection_info/1</seealso> and
+ <seealso marker="#connection_info/2">connection_info/2</seealso> functions.
+ </p>
+ <p>In the <c>option</c> info tuple are only the options included that differs from the default values.
+ </p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="daemon_info_tuple"/>
+ <desc>
+ <p>Return values from the
+ <seealso marker="#daemon_info/1">daemon_info/1</seealso> and
+ <seealso marker="#daemon_info/2">daemon_info/2</seealso> functions.
+ </p>
+ <p>In the <c>option</c> info tuple are only the options included that differs from the default values.
+ </p>
+ </desc>
+ </datatype>
<datatype>
<name>opaque_client_options</name>
@@ -1099,12 +1127,15 @@
<!-- CONNECTION_INFO/1, CONNECTION_INFO/2 -->
<func>
+ <name name="connection_info" arity="1" since=""/>
<name name="connection_info" arity="2" since=""/>
<fsummary>Retrieves information about a connection.</fsummary>
<desc>
- <p>Retrieves information about a connection. The list <c>Keys</c> defines which information that
- is returned.</p>
- </desc>
+ <p>Returns information about a connection intended for e.g debugging or logging.
+ </p>
+ <p>When the <c>Key</c> is a single <c>Item</c>, the result is a single <c>InfoTuple</c>
+ </p>
+ </desc>
</func>
<!-- DEAMON/1,2,3 -->
@@ -1156,9 +1187,16 @@
<!-- DAEMON_INFO/1 -->
<func>
<name name="daemon_info" arity="1" since="OTP 19.0"/>
+ <name name="daemon_info" arity="2" since=""/>
<fsummary>Get info about a daemon</fsummary>
<desc>
- <p>Returns a key-value list with information about the daemon.</p>
+ <p>Returns information about a daemon intended for e.g debugging or logging.
+ </p>
+ <p>When the <c>Key</c> is a single <c>Item</c>, the result is a single <c>InfoTuple</c>
+ </p>
+ <p>Note that <c>daemon_info/1</c> and <c>daemon_info/2</c> returns different types
+ due to compatibility reasons.
+ </p>
</desc>
</func>
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl
index bf4b0433d0..874c545e61 100644
--- a/lib/ssh/src/ssh.erl
+++ b/lib/ssh/src/ssh.erl
@@ -31,9 +31,10 @@
-export([start/0, start/1, stop/0,
connect/2, connect/3, connect/4,
close/1, connection_info/2,
+ connection_info/1,
channel_info/3,
daemon/1, daemon/2, daemon/3,
- daemon_info/1,
+ daemon_info/1, daemon_info/2,
default_algorithms/0,
chk_algos_opts/1,
stop_listener/1, stop_listener/2, stop_listener/3,
@@ -198,21 +199,50 @@ close(ConnectionRef) ->
%%--------------------------------------------------------------------
%% Description: Retrieves information about a connection.
-%%--------------------------------------------------------------------
--spec connection_info(ConnectionRef, Keys) -> ConnectionInfo when
+%%---------------------------------------------------------------------
+-type version() :: {protocol_version(), software_version()}.
+-type protocol_version() :: {Major::pos_integer(), Minor::non_neg_integer()}.
+-type software_version() :: string().
+-type conn_info_algs() :: [{kex, kex_alg()}
+ | {hkey, pubkey_alg()}
+ | {encrypt, cipher_alg()}
+ | {decrypt, cipher_alg()}
+ | {send_mac, mac_alg()}
+ | {recv_mac, mac_alg()}
+ | {compress, compression_alg()}
+ | {decompress, compression_alg()}
+ | {send_ext_info, boolean()}
+ | {recv_ext_info, boolean()}
+ ].
+-type conn_info_channels() :: [proplists:proplist()].
+
+-type connection_info_tuple() ::
+ {client_version, version()}
+ | {server_version, version()}
+ | {user, string()}
+ | {peer, {inet:hostname(), ip_port()}}
+ | {sockname, ip_port()}
+ | {options, client_options()}
+ | {algorithms, conn_info_algs()}
+ | {channels, conn_info_channels()}.
+
+-spec connection_info(ConnectionRef) -> InfoTupleList when
+ ConnectionRef :: connection_ref(),
+ InfoTupleList :: [InfoTuple],
+ InfoTuple :: connection_info_tuple().
+
+connection_info(ConnectionRef) ->
+ connection_info(ConnectionRef, []).
+
+-spec connection_info(ConnectionRef, ItemList|Item) -> InfoTupleList|InfoTuple when
ConnectionRef :: connection_ref(),
- Keys :: [client_version | server_version | user | peer | sockname],
- ConnectionInfo :: [{client_version, Version}
- | {server_version, Version}
- | {user,string()}
- | {peer, {inet:hostname(), ip_port()}}
- | {sockname, ip_port()}
- ],
- Version :: {ProtocolVersion, VersionString::string()},
- ProtocolVersion :: {Major::pos_integer(), Minor::non_neg_integer()} .
-
-connection_info(Connection, Options) ->
- ssh_connection_handler:connection_info(Connection, Options).
+ ItemList :: [Item],
+ Item :: client_version | server_version | user | peer | sockname | options | algorithms | sockname,
+ InfoTupleList :: [InfoTuple],
+ InfoTuple :: connection_info_tuple().
+
+connection_info(ConnectionRef, Key) ->
+ ssh_connection_handler:connection_info(ConnectionRef, Key).
%%--------------------------------------------------------------------
-spec channel_info(connection_ref(), channel_id(), [atom()]) -> proplists:proplist().
@@ -334,33 +364,72 @@ daemon(_, _, _) ->
{error, badarg}.
%%--------------------------------------------------------------------
--spec daemon_info(Daemon) -> {ok, DaemonInfo} | {error,term()} when
- Daemon :: daemon_ref(),
- DaemonInfo :: [ {ip, inet:ip_address()}
- | {port, inet:port_number()}
- | {profile, term()}
- ].
-
-daemon_info(Pid) ->
- case catch ssh_system_sup:acceptor_supervisor(Pid) of
+-type daemon_info_tuple() ::
+ {port, inet:port_number()}
+ | {ip, inet:ip_address()}
+ | {profile, atom()}
+ | {options, daemon_options()}.
+
+-spec daemon_info(DaemonRef) -> {ok,InfoTupleList} | {error,bad_daemon_ref} when
+ DaemonRef :: daemon_ref(),
+ InfoTupleList :: [InfoTuple],
+ InfoTuple :: daemon_info_tuple().
+
+daemon_info(DaemonRef) ->
+ case catch ssh_system_sup:acceptor_supervisor(DaemonRef) of
AsupPid when is_pid(AsupPid) ->
- [{IP,Port,Profile}] =
- [{IP,Prt,Prf}
+ [{Host,Port,Profile}] =
+ [{Hst,Prt,Prf}
|| {{ssh_acceptor_sup,Hst,Prt,Prf},_Pid,worker,[ssh_acceptor]}
- <- supervisor:which_children(AsupPid),
- IP <- [case inet:parse_strict_address(Hst) of
- {ok,IP} -> IP;
- _ -> Hst
- end]
- ],
+ <- supervisor:which_children(AsupPid)],
+ IP =
+ case inet:parse_strict_address(Host) of
+ {ok,IP0} -> IP0;
+ _ -> Host
+ end,
+
+ Opts =
+ case ssh_system_sup:get_options(DaemonRef, Host, Port, Profile) of
+ {ok, OptMap} ->
+ lists:sort(
+ maps:to_list(
+ ssh_options:keep_set_options(
+ server,
+ ssh_options:keep_user_options(server,OptMap))));
+ _ ->
+ []
+ end,
+
{ok, [{port,Port},
{ip,IP},
- {profile,Profile}
+ {profile,Profile},
+ {options,Opts}
]};
_ ->
{error,bad_daemon_ref}
end.
+-spec daemon_info(DaemonRef, ItemList|Item) -> InfoTupleList|InfoTuple | {error,bad_daemon_ref} when
+ DaemonRef :: daemon_ref(),
+ ItemList :: [Item],
+ Item :: ip | port | profile | options,
+ InfoTupleList :: [InfoTuple],
+ InfoTuple :: daemon_info_tuple().
+
+daemon_info(DaemonRef, Key) when is_atom(Key) ->
+ case daemon_info(DaemonRef, [Key]) of
+ [{Key,Val}] -> {Key,Val};
+ Other -> Other
+ end;
+daemon_info(DaemonRef, Keys) ->
+ case daemon_info(DaemonRef) of
+ {ok,KVs} ->
+ [{Key,proplists:get_value(Key,KVs)} || Key <- Keys,
+ lists:keymember(Key,1,KVs)];
+ _ ->
+ []
+ end.
+
%%--------------------------------------------------------------------
%% Description: Stops the listener, but leaves
%% existing connections started by the listener up and running.
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl
index cf8cde443f..69d3c3b18f 100644
--- a/lib/ssh/src/ssh_connection_handler.erl
+++ b/lib/ssh/src/ssh_connection_handler.erl
@@ -288,10 +288,13 @@ get_print_info(ConnectionHandler) ->
call(ConnectionHandler, get_print_info, 1000).
%%--------------------------------------------------------------------
--spec connection_info(connection_ref(),
- [atom()]
- ) -> proplists:proplist().
-%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+connection_info(ConnectionHandler, []) ->
+ connection_info(ConnectionHandler, conn_info_keys());
+connection_info(ConnectionHandler, Key) when is_atom(Key) ->
+ case connection_info(ConnectionHandler, [Key]) of
+ [{Key,Val}] -> {Key,Val};
+ Other -> Other
+ end;
connection_info(ConnectionHandler, Options) ->
call(ConnectionHandler, {connection_info, Options}).
@@ -2043,18 +2046,57 @@ counterpart_versions(NumVsn, StrVsn, #ssh{role = client} = Ssh) ->
Ssh#ssh{s_vsn = NumVsn , s_version = StrVsn}.
%%%----------------------------------------------------------------
+conn_info_keys() ->
+ [client_version,
+ server_version,
+ peer,
+ user,
+ sockname,
+ options,
+ algorithms,
+ channels
+ ].
+
conn_info(client_version, #data{ssh_params=S}) -> {S#ssh.c_vsn, S#ssh.c_version};
conn_info(server_version, #data{ssh_params=S}) -> {S#ssh.s_vsn, S#ssh.s_version};
conn_info(peer, #data{ssh_params=S}) -> S#ssh.peer;
conn_info(user, D) -> D#data.auth_user;
conn_info(sockname, #data{ssh_params=S}) -> S#ssh.local;
+conn_info(options, #data{ssh_params=#ssh{opts=Opts}}) -> lists:sort(
+ maps:to_list(
+ ssh_options:keep_set_options(
+ client,
+ ssh_options:keep_user_options(client,Opts))));
+conn_info(algorithms, #data{ssh_params=#ssh{algorithms=A}}) -> conn_info_alg(A);
+conn_info(channels, D) -> try conn_info_chans(ets:tab2list(cache(D)))
+ catch _:_ -> undefined
+ end;
%% dbg options ( = not documented):
-conn_info(socket, D) -> D#data.socket;
+conn_info(socket, D) -> D#data.socket;
conn_info(chan_ids, D) ->
ssh_client_channel:cache_foldl(fun(#channel{local_id=Id}, Acc) ->
[Id | Acc]
end, [], cache(D)).
+conn_info_chans(Chs) ->
+ Fs = record_info(fields, channel),
+ [lists:zip(Fs, tl(tuple_to_list(Ch))) || Ch=#channel{} <- Chs].
+
+conn_info_alg(AlgTup) ->
+ [alg|Vs] = tuple_to_list(AlgTup),
+ Fs = record_info(fields, alg),
+ [{K,V} || {K,V} <- lists:zip(Fs,Vs),
+ lists:member(K,[kex,
+ hkey,
+ encrypt,
+ decrypt,
+ send_mac,
+ recv_mac,
+ compress,
+ decompress,
+ send_ext_info,
+ recv_ext_info])].
+
%%%----------------------------------------------------------------
chann_info(recv_window, C) ->
{{win_size, C#channel.recv_window_size},
diff --git a/lib/ssh/src/ssh_info.erl b/lib/ssh/src/ssh_info.erl
index 11f4bc51e6..91365205aa 100644
--- a/lib/ssh/src/ssh_info.erl
+++ b/lib/ssh/src/ssh_info.erl
@@ -79,8 +79,8 @@ print_clients() ->
lists:map(fun print_client/1,
supervisor:which_children(sshc_sup))
catch
- C:E ->
- io_lib:format('***print_clients FAILED: ~p:~p~n',[C,E])
+ C:E:S ->
+ io_lib:format('***print_clients FAILED: ~p:~p,~n ~p~n',[C,E,S])
end.
print_client({undefined,Pid,supervisor,[ssh_connection_handler]}) ->
@@ -94,9 +94,9 @@ print_client({undefined,Pid,supervisor,[ssh_connection_handler]}) ->
io_lib:format(?INDENT?INDENT?INDENT"No channels~n",[])
end];
-print_client(Other) ->
- io_lib:format(" [[Other 1: ~p]]~n",[Other]).
-
+print_client({{client,ssh_system_sup,_,_,_},Pid,supervisor,[ssh_system_sup]}) when is_pid(Pid) ->
+ lists:map(fun print_system_sup/1,
+ supervisor:which_children(Pid)).
%%%================================================================
print_servers() ->
@@ -104,8 +104,8 @@ print_servers() ->
lists:map(fun print_server/1,
supervisor:which_children(sshd_sup))
catch
- C:E ->
- io_lib:format('***print_servers FAILED: ~p:~p~n',[C,E])
+ C:E:S ->
+ io_lib:format('***print_servers FAILED: ~p:~p,~n ~p~n',[C,E,S])
end.
@@ -140,22 +140,33 @@ print_system_sup({{ssh_acceptor_sup,_LocalHost,_LocalPort,_Profile}, Pid, superv
-print_channels({{server,ssh_channel_sup,_,_},Pid,supervisor,[ssh_channel_sup]}) when is_pid(Pid) ->
+print_channels({{Role,ssh_channel_sup,_,_},Pid,supervisor,[ssh_channel_sup]}) when is_pid(Pid) ->
+ ChanBehaviour =
+ case Role of
+ server -> ssh_server_channel;
+ client -> ssh_client_channel
+ end,
Children = supervisor:which_children(Pid),
- ChannelPids = [P || {R,P,worker,[ssh_server_channel]} <- Children,
+ ChannelPids = [P || {R,P,worker,[Mod]} <- Children,
+ ChanBehaviour == Mod,
is_pid(P),
is_reference(R)],
case ChannelPids of
[] -> io_lib:format(?INDENT?INDENT"No channels~n",[]);
[Ch1Pid|_] ->
- {{ConnManager,_}, _Str} = ssh_server_channel:get_print_info(Ch1Pid),
+ {{ConnManager,_}, _Str} = ChanBehaviour:get_print_info(Ch1Pid),
{{_,Remote},_} = ssh_connection_handler:get_print_info(ConnManager),
[io_lib:format(?INDENT?INDENT"Remote: ~s ConnectionRef = ~p~n",[fmt_host_port(Remote),ConnManager]),
lists:map(fun print_ch/1, ChannelPids)
]
end;
-print_channels({{server,ssh_connection_sup,_,_},Pid,supervisor,[ssh_connection_sup]}) when is_pid(Pid) ->
- []. % The supervisor of the connections socket owning process
+print_channels({{_Role,ssh_connection_sup,_,_},Pid,supervisor,[ssh_connection_sup]}) when is_pid(Pid) ->
+ []; % The supervisor of the connections socket owning process
+
+print_channels({Ref,Pid,supervisor,[ssh_tcpip_forward_acceptor_sup]}) when is_pid(Pid),
+ is_reference(Ref) ->
+ []. % The supervisor of the forward_acceptor process
+
print_ch(Pid) ->
try
diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl
index 1010c9be55..39f23a8b8a 100644
--- a/lib/ssh/src/ssh_options.erl
+++ b/lib/ssh/src/ssh_options.erl
@@ -29,7 +29,9 @@
get_value/5, get_value/6,
put_value/5,
delete_key/5,
- handle_options/2
+ handle_options/2,
+ keep_user_options/2,
+ keep_set_options/2
]).
-export_type([private_options/0
@@ -42,14 +44,14 @@
-type option_class() :: internal_options | socket_options | user_options .
--type option_declaration() :: #{class := user_options,
- chk := fun((any) -> boolean() | {true,any()}),
+-type option_declaration() :: #{class := user_option | undoc_user_option,
+ chk := fun((any()) -> boolean() | {true,any()}),
default => any()
}.
-type option_key() :: atom().
--type option_declarations() :: #{ {option_key(),def} := option_declaration() }.
+-type option_declarations() :: #{ option_key() := option_declaration() }.
-type error() :: {error,{eoptions,any()}} .
@@ -166,7 +168,7 @@ handle_options(Role, PropList0, Opts0) when is_map(Opts0),
OptionDefinitions = default(Role),
InitialMap =
maps:fold(
- fun({K,def}, #{default:=V}, M) -> M#{K=>V};
+ fun(K, #{default:=V}, M) -> M#{K=>V};
(_,_,M) -> M
end,
Opts0#{user_options =>
@@ -192,7 +194,7 @@ handle_options(Role, PropList0, Opts0) when is_map(Opts0),
check_fun(Key, Defs) ->
- #{chk := Fun} = maps:get({Key,def}, Defs),
+ #{chk := Fun} = maps:get(Key, Defs),
Fun.
%%%================================================================
@@ -232,10 +234,10 @@ save({Key,Value}, Defs, OptMap) when is_map(OptMap) ->
catch
%% An unknown Key (= not in the definition map) is
%% regarded as an inet option:
- error:{badkey,{inet,def}} ->
+ error:{badkey,inet} ->
%% atomic (= non-tuple) options 'inet' and 'inet6':
OptMap#{socket_options := [Value | maps:get(socket_options,OptMap)]};
- error:{badkey,{Key,def}} ->
+ error:{badkey,Key} ->
OptMap#{socket_options := [{Key,Value} | maps:get(socket_options,OptMap)]};
%% But a Key that is known but the value does not validate
@@ -249,6 +251,35 @@ save(Opt, _Defs, OptMap) when is_map(OptMap) ->
%%%================================================================
%%%
+-spec keep_user_options(client|server, #{}) -> #{}.
+
+keep_user_options(Type, Opts) ->
+ Defs = default(Type),
+ maps:filter(fun(Key, _Value) ->
+ try
+ #{class := Class} = maps:get(Key,Defs),
+ Class == user_option
+ catch
+ _:_ -> false
+ end
+ end, Opts).
+
+
+-spec keep_set_options(client|server, #{}) -> #{}.
+
+keep_set_options(Type, Opts) ->
+ Defs = default(Type),
+ maps:filter(fun(Key, Value) ->
+ try
+ #{default := DefVal} = maps:get(Key,Defs),
+ DefVal =/= Value
+ catch
+ _:_ -> false
+ end
+ end, Opts).
+
+%%%================================================================
+%%%
%%% Default options
%%%
@@ -257,7 +288,7 @@ save(Opt, _Defs, OptMap) when is_map(OptMap) ->
default(server) ->
(default(common))
#{
- {subsystems, def} =>
+ subsystems =>
#{default => [ssh_sftpd:subsystem_spec([])],
chk => fun(L) ->
is_list(L) andalso
@@ -269,42 +300,42 @@ default(server) ->
false
end, L)
end,
- class => user_options
+ class => user_option
},
- {shell, def} =>
+ shell =>
#{default => ?DEFAULT_SHELL,
chk => fun({M,F,A}) -> is_atom(M) andalso is_atom(F) andalso is_list(A);
(V) -> check_function1(V) orelse check_function2(V)
end,
- class => user_options
+ class => user_option
},
- {exec, def} =>
+ exec =>
#{default => undefined,
chk => fun({direct, V}) -> check_function1(V) orelse check_function2(V) orelse check_function3(V);
%% Compatibility (undocumented):
({M,F,A}) -> is_atom(M) andalso is_atom(F) andalso is_list(A);
(V) -> check_function1(V) orelse check_function2(V) orelse check_function3(V)
end,
- class => user_options
+ class => user_option
},
- {ssh_cli, def} =>
+ ssh_cli =>
#{default => undefined,
chk => fun({Cb, As}) -> is_atom(Cb) andalso is_list(As);
(V) -> V == no_cli
end,
- class => user_options
+ class => user_option
},
- {system_dir, def} =>
+ system_dir =>
#{default => "/etc/ssh",
chk => fun(V) -> check_string(V) andalso check_dir(V) end,
- class => user_options
+ class => user_option
},
- {auth_method_kb_interactive_data, def} =>
+ auth_method_kb_interactive_data =>
#{default => undefined, % Default value can be constructed when User is known
chk => fun({S1,S2,S3,B}) ->
check_string(S1) andalso
@@ -314,10 +345,10 @@ default(server) ->
(F) ->
check_function3(F)
end,
- class => user_options
+ class => user_option
},
- {user_passwords, def} =>
+ user_passwords =>
#{default => [],
chk => fun(V) ->
is_list(V) andalso
@@ -326,22 +357,22 @@ default(server) ->
check_string(S2)
end, V)
end,
- class => user_options
+ class => user_option
},
- {password, def} =>
+ password =>
#{default => undefined,
chk => fun check_string/1,
- class => user_options
+ class => user_option
},
- {dh_gex_groups, def} =>
+ dh_gex_groups =>
#{default => undefined,
chk => fun check_dh_gex_groups/1,
- class => user_options
+ class => user_option
},
- {dh_gex_limits, def} =>
+ dh_gex_limits =>
#{default => {0, infinity},
chk => fun({I1,I2}) ->
check_pos_integer(I1) andalso
@@ -350,137 +381,137 @@ default(server) ->
(_) ->
false
end,
- class => user_options
+ class => user_option
},
- {pwdfun, def} =>
+ pwdfun =>
#{default => undefined,
chk => fun(V) -> check_function4(V) orelse check_function2(V) end,
- class => user_options
+ class => user_option
},
- {negotiation_timeout, def} =>
+ negotiation_timeout =>
#{default => 2*60*1000,
chk => fun check_timeout/1,
- class => user_options
+ class => user_option
},
- {max_sessions, def} =>
+ max_sessions =>
#{default => infinity,
chk => fun check_pos_integer/1,
- class => user_options
+ class => user_option
},
- {max_channels, def} =>
+ max_channels =>
#{default => infinity,
chk => fun check_pos_integer/1,
- class => user_options
+ class => user_option
},
- {parallel_login, def} =>
+ parallel_login =>
#{default => false,
chk => fun erlang:is_boolean/1,
- class => user_options
+ class => user_option
},
- {minimal_remote_max_packet_size, def} =>
+ minimal_remote_max_packet_size =>
#{default => 0,
chk => fun check_pos_integer/1,
- class => user_options
+ class => user_option
},
- {failfun, def} =>
+ failfun =>
#{default => fun(_,_,_) -> void end,
chk => fun(V) -> check_function3(V) orelse
check_function2(V) % Backwards compatibility
end,
- class => user_options
+ class => user_option
},
- {connectfun, def} =>
+ connectfun =>
#{default => fun(_,_,_) -> void end,
chk => fun check_function3/1,
- class => user_options
+ class => user_option
},
%%%%% Undocumented
- {infofun, def} =>
+ infofun =>
#{default => fun(_,_,_) -> void end,
chk => fun(V) -> check_function3(V) orelse
check_function2(V) % Backwards compatibility
end,
- class => user_options
+ class => undoc_user_option
}
};
default(client) ->
(default(common))
#{
- {dsa_pass_phrase, def} =>
+ dsa_pass_phrase =>
#{default => undefined,
chk => fun check_string/1,
- class => user_options
+ class => user_option
},
- {rsa_pass_phrase, def} =>
+ rsa_pass_phrase =>
#{default => undefined,
chk => fun check_string/1,
- class => user_options
+ class => user_option
},
- {ecdsa_pass_phrase, def} =>
+ ecdsa_pass_phrase =>
#{default => undefined,
chk => fun check_string/1,
- class => user_options
+ class => user_option
},
-%%% Not yet implemented {ed25519_pass_phrase, def} =>
+%%% Not yet implemented ed25519_pass_phrase =>
%%% Not yet implemented #{default => undefined,
%%% Not yet implemented chk => fun check_string/1,
-%%% Not yet implemented class => user_options
+%%% Not yet implemented class => user_option
%%% Not yet implemented },
%%% Not yet implemented
-%%% Not yet implemented {ed448_pass_phrase, def} =>
+%%% Not yet implemented ed448_pass_phrase =>
%%% Not yet implemented #{default => undefined,
%%% Not yet implemented chk => fun check_string/1,
-%%% Not yet implemented class => user_options
+%%% Not yet implemented class => user_option
%%% Not yet implemented },
%%% Not yet implemented
- {silently_accept_hosts, def} =>
+ silently_accept_hosts =>
#{default => false,
chk => fun check_silently_accept_hosts/1,
- class => user_options
+ class => user_option
},
- {user_interaction, def} =>
+ user_interaction =>
#{default => true,
chk => fun erlang:is_boolean/1,
- class => user_options
+ class => user_option
},
- {save_accepted_host, def} =>
+ save_accepted_host =>
#{default => true,
chk => fun erlang:is_boolean/1,
- class => user_options
+ class => user_option
},
- {dh_gex_limits, def} =>
+ dh_gex_limits =>
#{default => {1024, 6144, 8192}, % FIXME: Is this true nowadays?
chk => fun({Min,I,Max}) ->
lists:all(fun check_pos_integer/1,
[Min,I,Max]);
(_) -> false
end,
- class => user_options
+ class => user_option
},
- {connect_timeout, def} =>
+ connect_timeout =>
#{default => infinity,
chk => fun check_timeout/1,
- class => user_options
+ class => user_option
},
- {user, def} =>
+ user =>
#{default =>
begin
Env = case os:type() of
@@ -498,59 +529,59 @@ default(client) ->
end
end,
chk => fun check_string/1,
- class => user_options
+ class => user_option
},
- {password, def} =>
+ password =>
#{default => undefined,
chk => fun check_string/1,
- class => user_options
+ class => user_option
},
- {quiet_mode, def} =>
+ quiet_mode =>
#{default => false,
chk => fun erlang:is_boolean/1,
- class => user_options
+ class => user_option
},
%%%%% Undocumented
- {keyboard_interact_fun, def} =>
+ keyboard_interact_fun =>
#{default => undefined,
chk => fun check_function3/1,
- class => user_options
+ class => undoc_user_option
}
};
default(common) ->
#{
- {user_dir, def} =>
+ user_dir =>
#{default => false, % FIXME: TBD ~/.ssh at time of call when user is known
chk => fun(V) -> check_string(V) andalso check_dir(V) end,
- class => user_options
+ class => user_option
},
- {pref_public_key_algs, def} =>
+ pref_public_key_algs =>
#{default => ssh_transport:default_algorithms(public_key),
chk => fun check_pref_public_key_algs/1,
- class => user_options
+ class => user_option
},
- {preferred_algorithms, def} =>
+ preferred_algorithms =>
#{default => ssh:default_algorithms(),
chk => fun check_preferred_algorithms/1,
- class => user_options
+ class => user_option
},
%% NOTE: This option is supposed to be used only in this very module (?MODULE). There is
%% a final stage in handle_options that "merges" the preferred_algorithms option and this one.
%% The preferred_algorithms is the one to use in the rest of the ssh application!
- {modify_algorithms, def} =>
+ modify_algorithms =>
#{default => undefined, % signals error if unsupported algo in preferred_algorithms :(
chk => fun check_modify_algorithms/1,
- class => user_options
+ class => user_option
},
- {id_string, def} =>
+ id_string =>
#{default => undefined, % FIXME: see ssh_transport:ssh_vsn/0
chk => fun(random) ->
{true, {random,2,5}}; % 2 - 5 random characters
@@ -562,56 +593,49 @@ default(common) ->
(V) ->
check_string(V)
end,
- class => user_options
+ class => user_option
},
- {key_cb, def} =>
+ key_cb =>
#{default => {ssh_file, []},
chk => fun({Mod,Opts}) -> is_atom(Mod) andalso is_list(Opts);
(Mod) when is_atom(Mod) -> {true, {Mod,[]}};
(_) -> false
end,
- class => user_options
+ class => user_option
},
- {profile, def} =>
+ profile =>
#{default => ?DEFAULT_PROFILE,
chk => fun erlang:is_atom/1,
- class => user_options
+ class => user_option
},
- {idle_time, def} =>
+ idle_time =>
#{default => infinity,
chk => fun check_timeout/1,
- class => user_options
+ class => user_option
},
- %% This is a "SocketOption"...
- %% {fd, def} =>
- %% #{default => undefined,
- %% chk => fun erlang:is_integer/1,
- %% class => user_options
- %% },
-
- {disconnectfun, def} =>
+ disconnectfun =>
#{default => fun(_) -> void end,
chk => fun check_function1/1,
- class => user_options
+ class => user_option
},
- {unexpectedfun, def} =>
+ unexpectedfun =>
#{default => fun(_,_) -> report end,
chk => fun check_function2/1,
- class => user_options
+ class => user_option
},
- {ssh_msg_debug_fun, def} =>
+ ssh_msg_debug_fun =>
#{default => fun(_,_,_,_) -> void end,
chk => fun check_function4/1,
- class => user_options
+ class => user_option
},
- {rekey_limit, def} =>
+ rekey_limit =>
#{default => {3600000, 1024000000}, % {1 hour, 1 GB}
chk => fun({infinity, infinity}) ->
true;
@@ -629,10 +653,10 @@ default(common) ->
(_) ->
false
end,
- class => user_options
+ class => user_option
},
- {auth_methods, def} =>
+ auth_methods =>
#{default => ?SUPPORTED_AUTH_METHODS,
chk => fun(As) ->
try
@@ -644,54 +668,54 @@ default(common) ->
_:_ -> false
end
end,
- class => user_options
+ class => user_option
},
+ send_ext_info =>
+ #{default => true,
+ chk => fun erlang:is_boolean/1,
+ class => user_option
+ },
+
+ recv_ext_info =>
+ #{default => true,
+ chk => fun erlang:is_boolean/1,
+ class => user_option
+ },
+
%%%%% Undocumented
- {transport, def} =>
+ transport =>
#{default => ?DEFAULT_TRANSPORT,
chk => fun({A,B,C}) ->
is_atom(A) andalso is_atom(B) andalso is_atom(C)
end,
- class => user_options
+ class => undoc_user_option
},
- {vsn, def} =>
+ vsn =>
#{default => {2,0},
chk => fun({Maj,Min}) -> check_non_neg_integer(Maj) andalso check_non_neg_integer(Min);
(_) -> false
end,
- class => user_options
+ class => undoc_user_option
},
- {tstflg, def} =>
+ tstflg =>
#{default => [],
chk => fun erlang:is_list/1,
- class => user_options
+ class => undoc_user_option
},
- {user_dir_fun, def} =>
+ user_dir_fun =>
#{default => undefined,
chk => fun check_function1/1,
- class => user_options
+ class => undoc_user_option
},
- {max_random_length_padding, def} =>
+ max_random_length_padding =>
#{default => ?MAX_RND_PADDING_LEN,
chk => fun check_non_neg_integer/1,
- class => user_options
- },
-
- {send_ext_info, def} =>
- #{default => true,
- chk => fun erlang:is_boolean/1,
- class => user_options
- },
-
- {recv_ext_info, def} =>
- #{default => true,
- chk => fun erlang:is_boolean/1,
- class => user_options
+ class => undoc_user_option
}
}.
diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl
index 3298fd1158..704c17c4e7 100644
--- a/lib/ssh/src/ssh_system_sup.erl
+++ b/lib/ssh/src/ssh_system_sup.erl
@@ -37,11 +37,16 @@
subsystem_supervisor/1, channel_supervisor/1,
connection_supervisor/1,
acceptor_supervisor/1, start_subsystem/6,
- stop_subsystem/2]).
+ stop_subsystem/2,
+ get_options/4
+ ]).
%% Supervisor callback
-export([init/1]).
+-define(START(Address, Port, Profile, Options),
+ {ssh_acceptor_sup, start_link, [Address, Port, Profile, Options]}).
+
%%%=========================================================================
%%% API
%%%=========================================================================
@@ -61,7 +66,7 @@ init([server, Address, Port, Profile, Options]) ->
case ?GET_INTERNAL_OPT(connected_socket,Options,undefined) of
undefined ->
[#{id => id(ssh_acceptor_sup, Address, Port, Profile),
- start => {ssh_acceptor_sup, start_link, [Address, Port, Profile, Options]},
+ start => ?START(Address,Port,Profile,Options),
restart => transient,
type => supervisor
}];
@@ -103,6 +108,15 @@ stop_system(server, Address, Port, Profile) ->
ok.
+get_options(Sup, Address, Port, Profile) ->
+ try
+ {ok, #{start:=?START(Address,Port,Profile,Options)}} =
+ supervisor:get_childspec(Sup, id(ssh_acceptor_sup,Address,Port,Profile)),
+ {ok, Options}
+ catch
+ _:_ -> {error,not_found}
+ end.
+
system_supervisor(Address, Port, Profile) ->
Name = make_name(Address, Port, Profile),
whereis(Name).
diff --git a/lib/ssh/test/ssh_sup_SUITE.erl b/lib/ssh/test/ssh_sup_SUITE.erl
index 3b5e858c53..0bf4d87f0a 100644
--- a/lib/ssh/test/ssh_sup_SUITE.erl
+++ b/lib/ssh/test/ssh_sup_SUITE.erl
@@ -116,8 +116,11 @@ sshc_subtree(Config) when is_list(Config) ->
{user_interaction, false},
{user, ?USER}, {password, ?PASSWD},{user_dir, UserDir}]),
- ?wait_match([{_, _,supervisor,[ssh_system_sup]}],
- supervisor:which_children(sshc_sup)),
+ ?wait_match([{{client,ssh_system_sup, LocalIP, LocalPort, ?DEFAULT_PROFILE},
+ SysSup, supervisor,[ssh_system_sup]}],
+ supervisor:which_children(sshc_sup),
+ [SysSup, LocalIP, LocalPort]),
+ check_sshc_system_tree(SysSup, Pid1, LocalIP, LocalPort, Config),
{ok, Pid2} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
{user_interaction, false},
@@ -389,12 +392,55 @@ check_sshd_system_tree(Daemon, Config) ->
?wait_match([], supervisor:which_children(ChannelSup)),
- ssh_sftp:start_channel(Client),
+ {ok,PidC} = ssh_sftp:start_channel(Client),
+
+ ?wait_match([{_, PidS,worker,[ssh_server_channel]}],
+ supervisor:which_children(ChannelSup),
+ [PidS]),
+ true = (PidS =/= PidC),
- ?wait_match([{_, _,worker,[ssh_server_channel]}],
- supervisor:which_children(ChannelSup)),
ssh:close(Client).
+
+check_sshc_system_tree(SysSup, Connection, LocalIP, LocalPort, _Config) ->
+ ?wait_match([{_,SubSysSup,supervisor,[ssh_subsystem_sup]}],
+ supervisor:which_children(SysSup),
+ [SubSysSup]),
+ ?wait_match([{{client,ssh_connection_sup, LocalIP, LocalPort},
+ ConnectionSup, supervisor,
+ [ssh_connection_sup]},
+ {{client,ssh_channel_sup, LocalIP, LocalPort},
+ ChannelSup,supervisor,
+ [ssh_channel_sup]}],
+ supervisor:which_children(SubSysSup),
+ [ConnectionSup,ChannelSup]),
+ ?wait_match([{_, Connection, worker,[ssh_connection_handler]}],
+ supervisor:which_children(ConnectionSup)),
+ ?wait_match([], supervisor:which_children(ChannelSup)),
+
+ {ok,ChPid1} = ssh_sftp:start_channel(Connection),
+ ?wait_match([{_,ChPid1,worker,[ssh_client_channel]}],
+ supervisor:which_children(ChannelSup)),
+
+ {ok,ChPid2} = ssh_sftp:start_channel(Connection),
+ ?wait_match([{_,ChPidA,worker,[ssh_client_channel]},
+ {_,ChPidB,worker,[ssh_client_channel]}],
+ supervisor:which_children(ChannelSup),
+ [ChPidA, ChPidB]),
+ lists:sort([ChPidA, ChPidB]) == lists:sort([ChPid1, ChPid2]),
+
+ ct:pal("Expect a SUPERVISOR REPORT with offender {pid,~p}....~n", [ChPid1]),
+ exit(ChPid1, kill),
+ ?wait_match([{_,ChPid2,worker,[ssh_client_channel]}],
+ supervisor:which_children(ChannelSup)),
+
+ ct:pal("Expect a SUPERVISOR REPORT with offender {pid,~p}....~n", [ChPid2]),
+ exit(ChPid2, kill),
+ ?wait_match([], supervisor:which_children(ChannelSup)),
+ ct:pal("... now there should not be any SUPERVISOR REPORT.~n", []).
+
+
+
acceptor_pid(DaemonPid) ->
Parent = self(),
Pid = spawn(fun() ->
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index c6c94ee853..0d6501b5ee 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -1609,7 +1609,9 @@ handle_options(Opts0, Role, Host) ->
proplists:delete(Key, PropList)
end, Opts, ?SSL_OPTIONS ++
[ssl_imp, %% TODO: remove ssl_imp
- client_preferred_next_protocols]), %% next_protocol_selector
+ client_preferred_next_protocols, %% next_protocol_selector
+ log_alert,
+ cb_info]),
{Sock, Emulated} = emulated_options(Protocol, SockOpts),
ConnetionCb = connection_cb(Opts),
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index 1c8a2ca452..a407694617 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -724,7 +724,7 @@ encode_extensions([#psk_key_exchange_modes{ke_modes = KEModes0} | Rest], Acc) ->
encode_extensions([#pre_shared_key_client_hello{
offered_psks = #offered_psks{
identities = Identities0,
- binders = Binders0} = PSKs} | Rest], Acc) ->
+ binders = Binders0}} | Rest], Acc) ->
Identities = encode_psk_identities(Identities0),
Binders = encode_psk_binders(Binders0),
Len = byte_size(Identities) + byte_size(Binders),
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index ef548ad643..fc2f6a3d20 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -33,43 +33,73 @@
<description>
<p>
<c>gen_statem</c> provides a generic state machine behaviour
- and replaces its predecessor
+ that for new code replaces its predecessor
<seealso marker="gen_fsm"><c>gen_fsm</c></seealso>
- since Erlang/OTP 20.0.
+ since Erlang/OTP 20.0. The <c>gen_fsm</c> behaviour remains
+ in OTP "as is".
</p>
+ <note>
+ <p>
+ If you are new to <c>gen_statem</c> and want an overview
+ of concepts and operation the section
+ <seealso marker="doc/design_principles:statem">
+ <c>gen_statem</c>&nbsp;Behaviour
+ </seealso>
+ located in the User's Guide
+ <seealso marker="doc/design_principles:users_guide">
+ OTP Design Principles
+ </seealso>
+ is recommended to read before this reference manual,
+ possibly after the Description section you are reading here.
+ </p>
+ </note>
<p>
- This reference manual describes types generated from the types
- in the <c>gen_statem</c> source code, so they are correct.
+ This reference manual contains type descriptions generated from
+ types in the <c>gen_statem</c> source code, so they are correct.
However, the generated descriptions also reflect the type hierarchy,
- which makes them kind of hard to read.
- </p>
- <p>
- To get an overview of the concepts and operation of <c>gen_statem</c>,
- do read the
+ which sometimes makes it hard to get a good overview.
+ If so, see the section
<seealso marker="doc/design_principles:statem">
<c>gen_statem</c>&nbsp;Behaviour
</seealso>
- in
+ in the
<seealso marker="doc/design_principles:users_guide">
OTP Design Principles
</seealso>
- which frequently links back to this reference manual to avoid
- containing detailed facts that may rot by age.
+ User's Guide.
</p>
<note>
- <p>
- This behavior appeared in Erlang/OTP 19.0.
- In OTP 19.1 a backwards incompatible change of
- the return tuple from
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
- was made and the mandatory callback function
- <seealso marker="#Module:callback_mode/0">
- <c>Module:callback_mode/0</c>
- </seealso>
- was introduced. In OTP 20.0 the
- <seealso marker="#type-generic_timeout"><c>generic timeouts</c></seealso>
- were added.
- </p>
+ <list type="bulleted">
+ <item>This behavior appeared in Erlang/OTP 19.0.</item>
+ <item>
+ In OTP 19.1 a backwards incompatible change of
+ the return tuple from
+ <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ was made and the mandatory callback function
+ <seealso marker="#Module:callback_mode/0">
+ <c>Module:callback_mode/0</c>
+ </seealso>
+ was introduced.
+ </item>
+ <item>
+ In OTP 20.0
+ <seealso marker="#type-generic_timeout">
+ generic time-outs
+ </seealso>
+ were added.
+ </item>
+ <item>
+ In OTP 22.1 time-out content
+ <seealso marker="#type-timeout_update_action">
+ <c>update</c>
+ </seealso>
+ and explicit time-out
+ <seealso marker="#type-timeout_cancel_action">
+ <c>cancel</c>
+ </seealso>
+ were added.
+ </item>
+ </list>
</note>
<p>
<c>gen_statem</c> has got the same features that
@@ -653,7 +683,7 @@ handle_event(_, _, State, Data) ->
<name name="timeout_event_type"/>
<desc>
<p>
- There are 3 types of timeout events that the state machine
+ There are 3 types of time-out events that the state machine
can generate for itself with the corresponding
<seealso marker="#type-timeout_action">timeout_action()</seealso>s.
</p>
@@ -720,7 +750,9 @@ handle_event(_, _, State, Data) ->
the <c>gen_statem</c> engine will, at every <em>state change</em>,
call the
<seealso marker="#state callback">state callback</seealso>
- with arguments <c>(enter, OldState, Data)</c>.
+ with arguments <c>(enter, OldState, Data)</c> or
+ <c>(enter, OldState, State, Data)</c>, depending on the
+ <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>.
This may look like an event but is really a call
performed after the previous
<seealso marker="#state callback"><em>state callback</em></seealso>
@@ -776,44 +808,53 @@ handle_event(_, _, State, Data) ->
</p>
<list type="ordered">
<item>
+ <p>
+ All returned
+ <seealso marker="#type-action">actions</seealso>
+ are processed in order of appearance.
+ In this step all replies generated by any
+ <seealso marker="#type-reply_action"><c>reply_action()</c></seealso>
+ are sent. Other actions set <c>transition_option()</c>s
+ that come into play in subsequent steps.
+ </p>
+ </item>
+ <item>
<p>
If
<seealso marker="#type-state_enter">
<em>state enter calls</em>
</seealso>
- are used, and either:
- the state changes, it is the initial state,
- or one of the callback results
+ are used, and either
+ it is the initial state or one of the callback results
<seealso marker="#type-state_callback_result">
- <c>repeat_state</c>
+ <c>repeat_state_and_data</c>
</seealso>
or
<seealso marker="#type-state_callback_result">
<c>repeat_state_and_data</c>
</seealso>
- is used; the <c>gen_statem</c> calls
- the new state callback with arguments
- <seealso marker="#type-state_enter"><c>(enter, OldState, Data)</c></seealso>.
+ is used the <c>gen_statem</c> engine calls
+ the current state callback with arguments
+ <seealso marker="#type-state_enter"><c>(enter, State, Data)</c></seealso>
+ or
+ <seealso marker="#type-state_enter"><c>(enter, State, State, Data)</c></seealso>
+ (depending on <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>)
+ and when it returns starts again from the top of this sequence.
</p>
- <p>
- Any
- <seealso marker="#type-enter_action">actions</seealso>
- returned from this call are handled as if they were
- appended to the actions
- returned by the state callback that caused the state entry.
+ <p>
+ If
+ <seealso marker="#type-state_enter">
+ <em>state enter calls</em>
+ </seealso>
+ are used, and the state changes
+ the <c>gen_statem</c> engine calls
+ the new state callback with arguments
+ <seealso marker="#type-state_enter"><c>(enter, OldState, Data)</c></seealso>
+ or
+ <seealso marker="#type-state_enter"><c>(enter, OldState, State, Data)</c></seealso>
+ (depending on <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>)
+ and when it returns starts again from the top of this sequence.
</p>
- <p>
- Should this <em>state enter call</em> return any of
- the mentioned <c>repeat_*</c> callback results
- it is repeated again, with the updated <c>Data</c>.
- </p>
- </item>
- <item>
- <p>
- All
- <seealso marker="#type-action">actions</seealso>
- are processed in order of appearance.
- </p>
</item>
<item>
<p>
@@ -863,7 +904,9 @@ handle_event(_, _, State, Data) ->
A <em>state change</em> cancels a
<seealso marker="#type-state_timeout"><c>state_timeout()</c></seealso>
and any new transition option of this type
- belongs to the new state.
+ belongs to the new state, that is; a
+ <seealso marker="#type-state_timeout"><c>state_timeout()</c></seealso>
+ applies to the state the state machine enters.
</p>
</item>
<item>
@@ -872,7 +915,7 @@ handle_event(_, _, State, Data) ->
<seealso marker="#state callback"><em>state callback</em></seealso>
for the possibly new state
is called with the oldest enqueued event,
- and we start again from the top of this list.
+ and we start again from the top of this sequence.
</p>
</item>
<item>
@@ -1176,7 +1219,7 @@ handle_event(_, _, State, Data) ->
<seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>.
</p>
<p>
- These timeout actions sets timeout
+ These time-out actions sets time-out
<seealso marker="#type-transition_option">transition options</seealso>.
</p>
<taglist>
@@ -1229,6 +1272,36 @@ handle_event(_, _, State, Data) ->
</desc>
</datatype>
<datatype>
+ <name name="timeout_cancel_action" since="OTP @OTP-15510@"/>
+ <desc>
+ <p>
+ This is a shorter and clearer form of
+ <seealso marker="#type-timeout_action">
+ timeout_action()
+ </seealso>
+ with <c>Time = infinity</c> which cancels a time-out.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="timeout_update_action" since="OTP @OTP-15510@"/>
+ <desc>
+ <p>
+ Updates a time-out with a new <c>EventContent</c>.
+ See
+ <seealso marker="#type-timeout_action">
+ timeout_action()
+ </seealso>
+ for how to start a time-out.
+ </p>
+ <p>
+ If no time-out of the same type is active instead
+ insert the time-out event just like when starting
+ a time-out with relative <c>Time = 0</c>.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
<name name="reply_action"/>
<desc>
<p>
diff --git a/lib/stdlib/doc/src/sys.xml b/lib/stdlib/doc/src/sys.xml
index e22ca89ef5..ebea054fff 100644
--- a/lib/stdlib/doc/src/sys.xml
+++ b/lib/stdlib/doc/src/sys.xml
@@ -239,6 +239,34 @@
</item>
<tag>
<c>
+ {start_timer,<anno>Action</anno>,<anno>State</anno>}
+ </c>
+ </tag>
+ <item>
+ <p>
+ Is produced by <c>gen_statem</c>
+ when the action <c><anno>Action</anno></c>
+ starts a timer in state <c><anno>State</anno></c>.
+ </p>
+ </item>
+ <tag>
+ <c>
+ {insert_timeout,<anno>Event</anno>,<anno>State</anno>}
+ </c>
+ </tag>
+ <item>
+ <p>
+ Is produced by <c>gen_statem</c> when a timeout zero action
+ inserts event <c><anno>Event</anno></c>
+ in state <c><anno>State</anno></c>.
+ </p>
+ <p>
+ <c><anno>Event</anno></c> is
+ an <c>{EventType,EventContent}</c> tuple.
+ </p>
+ </item>
+ <tag>
+ <c>
{enter,<anno>State</anno>}
</c>
</tag>
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index 49911eac2c..105b2a4577 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -176,7 +176,17 @@
{'state_timeout', % Set the state_timeout option
Time :: state_timeout(),
EventContent :: term(),
- Options :: (timeout_option() | [timeout_option()])}.
+ Options :: (timeout_option() | [timeout_option()])} |
+ timeout_cancel_action() |
+ timeout_update_action().
+-type timeout_cancel_action() ::
+ {'timeout', 'cancel'} |
+ {{'timeout', Name :: term()}, 'cancel'} |
+ {'state_timeout', 'cancel'}.
+-type timeout_update_action() ::
+ {'timeout', 'update', EventContent :: term()} |
+ {{'timeout', Name :: term()}, 'update', EventContent :: term()} |
+ {'state_timeout', 'update', EventContent :: term()}.
-type reply_action() ::
{'reply', % Reply to a caller
From :: from(), Reply :: term()}.
@@ -420,11 +430,9 @@ timeout_event_type(Type) ->
{state_data = {undefined,undefined} ::
{State :: term(),Data :: term()},
postponed = [] :: [{event_type(),term()}],
- timers = {#{},#{}} ::
- {%% TimerRef => TimeoutType
- TimerRefs :: #{reference() => timeout_event_type()},
- %% TimeoutType => TimerRef
- TimeoutTypes :: #{timeout_event_type() => reference()}},
+ timers = #{} ::
+ #{TimeoutType :: timeout_event_type() =>
+ {TimerRef :: reference(), TimeoutMsg :: term()}},
hibernate = false :: boolean()
}).
@@ -807,13 +815,14 @@ format_status(
Opt,
[PDict,SysState,Parent,Debug,
{#params{name = Name} = P,
- #state{postponed = Postponed} = S}]) ->
+ #state{postponed = Postponed, timers = Timers} = S}]) ->
Header = gen:format_status_header("Status for state machine", Name),
Log = sys:get_log(Debug),
[{header,Header},
{data,
[{"Status",SysState},
{"Parent",Parent},
+ {"Time-outs",list_timeouts(Timers)},
{"Logged Events",Log},
{"Postponed",Postponed}]} |
case format_status(Opt, PDict, update_parent(P, Parent), S) of
@@ -860,6 +869,14 @@ print_event(Dev, SystemEvent, Name) ->
io:format(
Dev, "*DBG* ~tp enter in state ~tp~n",
[Name,State]);
+ {start_timer,Action,State} ->
+ io:format(
+ Dev, "*DBG* ~tp start_timer ~tp in state ~tp~n",
+ [Name,Action,State]);
+ {insert_timeout,Event,State} ->
+ io:format(
+ Dev, "*DBG* ~tp insert_timeout ~tp in state ~tp~n",
+ [Name,Event,State]);
{terminate,Reason,State} ->
io:format(
Dev, "*DBG* ~tp terminate ~tp in state ~tp~n",
@@ -945,15 +962,12 @@ loop_receive(
{'$gen_cast',Cast} ->
loop_receive_result(P, Debug, S, {cast,Cast});
%%
- {timeout,TimerRef,TimeoutMsg} ->
- {TimerRefs,TimeoutTypes} = S#state.timers,
- case TimerRefs of
- #{TimerRef := TimeoutType} ->
+ {timeout,TimerRef,TimeoutType} ->
+ case S#state.timers of
+ #{TimeoutType := {TimerRef,TimeoutMsg}} = Timers ->
%% Our timer
- Timers =
- {maps:remove(TimerRef, TimerRefs),
- maps:remove(TimeoutType, TimeoutTypes)},
- S_1 = S#state{timers = Timers},
+ Timers_1 = maps:remove(TimeoutType, Timers),
+ S_1 = S#state{timers = Timers_1},
loop_receive_result(
P, Debug, S_1, {TimeoutType,TimeoutMsg});
#{} ->
@@ -1514,20 +1528,20 @@ loop_actions_timeout(
true ->
case listify(TimeoutOpts) of
%% Optimization cases
- [] when ?relative_timeout(Time) ->
- RelativeTimeout = {TimeoutType,Time,TimeoutMsg},
+ [{abs,true}] when ?absolute_timeout(Time) ->
loop_actions_list(
P, Debug, S, Q, NextState_NewData,
NextEventsR, Hibernate,
- [RelativeTimeout|TimeoutsR], Postpone,
+ [Timeout|TimeoutsR], Postpone,
CallEnter, StateCall, Actions);
- [{abs,true}] when ?absolute_timeout(Time) ->
+ [{abs,false}] when ?relative_timeout(Time) ->
+ RelativeTimeout = {TimeoutType,Time,TimeoutMsg},
loop_actions_list(
P, Debug, S, Q, NextState_NewData,
NextEventsR, Hibernate,
- [Timeout|TimeoutsR], Postpone,
+ [RelativeTimeout|TimeoutsR], Postpone,
CallEnter, StateCall, Actions);
- [{abs,false}] when ?relative_timeout(Time) ->
+ [] when ?relative_timeout(Time) ->
RelativeTimeout = {TimeoutType,Time,TimeoutMsg},
loop_actions_list(
P, Debug, S, Q, NextState_NewData,
@@ -1544,14 +1558,13 @@ loop_actions_timeout(
[Timeout|TimeoutsR], Postpone,
CallEnter, StateCall, Actions);
false when ?relative_timeout(Time) ->
- RelativeTimeout =
- {TimeoutType,Time,TimeoutMsg},
+ RelativeTimeout = {TimeoutType,Time,TimeoutMsg},
loop_actions_list(
P, Debug, S, Q, NextState_NewData,
NextEventsR, Hibernate,
[RelativeTimeout|TimeoutsR], Postpone,
CallEnter, StateCall, Actions);
- badarg ->
+ _ ->
terminate(
error,
{bad_action_from_state_function,Timeout},
@@ -1576,10 +1589,12 @@ loop_actions_timeout(
P, Debug, S, Q, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postpone,
CallEnter, StateCall, Actions,
- {TimeoutType,Time,_} = Timeout) ->
+ {TimeoutType,Time,_TimeoutMsg} = Timeout) ->
%%
case timeout_event_type(TimeoutType) of
- true when ?relative_timeout(Time) ->
+ true
+ when ?relative_timeout(Time);
+ Time =:= update ->
loop_actions_list(
P, Debug, S, Q, NextState_NewData,
NextEventsR, Hibernate,
@@ -1598,15 +1613,40 @@ loop_actions_timeout(
loop_actions_timeout(
P, Debug, S, Q, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postpone,
- CallEnter, StateCall, Actions, Time) ->
+ CallEnter, StateCall, Actions,
+ {TimeoutType,cancel} = Action) ->
+ %%
+ case timeout_event_type(TimeoutType) of
+ true ->
+ Timeout = {TimeoutType,infinity,undefined},
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate,
+ [Timeout|TimeoutsR], Postpone,
+ CallEnter, StateCall, Actions);
+ false ->
+ terminate(
+ error,
+ {bad_action_from_state_function,Action},
+ ?STACKTRACE(), P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ hibernate = Hibernate},
+ Q)
+ end;
+loop_actions_timeout(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions,
+ Time) ->
%%
if
?relative_timeout(Time) ->
- RelativeTimeout = {timeout,Time,Time},
+ Timeout = {timeout,Time,Time},
loop_actions_list(
P, Debug, S, Q, NextState_NewData,
NextEventsR, Hibernate,
- [RelativeTimeout|TimeoutsR], Postpone,
+ [Timeout|TimeoutsR], Postpone,
CallEnter, StateCall, Actions);
true ->
terminate(
@@ -1683,23 +1723,20 @@ loop_state_transition(
%% State transition to the same state
%%
loop_keep_state(
- P, Debug, #state{timers = {TimerRefs,TimeoutTypes} = Timers} = S,
+ P, Debug, #state{timers = Timers} = S,
Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postponed) ->
%%
%% Cancel event timeout
%%
- case TimeoutTypes of
- %% Optimization
- %% - only cancel timer when there is a timer to cancel
- #{timeout := TimerRef} ->
+ case Timers of
+ #{timeout := {TimerRef,_TimeoutMsg}} ->
%% Event timeout active
loop_next_events(
P, Debug, S,
Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postponed,
- cancel_timer_by_ref_and_type(
- TimerRef, timeout, TimerRefs, TimeoutTypes));
+ cancel_timer(timeout, TimerRef, Timers));
_ ->
%% No event timeout active
loop_next_events(
@@ -1741,34 +1778,32 @@ loop_state_change(
end.
%%
loop_state_change(
- P, Debug, #state{timers = {TimerRefs,TimeoutTypes} = Timers} = S,
+ P, Debug, #state{timers = Timers} = S,
Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR) ->
%%
%% Cancel state and event timeout
%%
- case TimeoutTypes of
+ case Timers of
%% Optimization
- %% - only cancel timeout when there is an active timeout
+ %% - only cancel timeout when it is active
%%
- #{state_timeout := TimerRef} ->
+ #{state_timeout := {TimerRef,_TimeoutMsg}} ->
%% State timeout active
%% - cancel event timeout too since it is faster than inspecting
loop_next_events(
P, Debug, S, Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, [],
- cancel_timer_by_type(
+ cancel_timer(
timeout,
- cancel_timer_by_ref_and_type(
- TimerRef, state_timeout, TimerRefs, TimeoutTypes)));
- #{timeout := TimerRef} ->
+ cancel_timer(state_timeout, TimerRef, Timers)));
+ #{timeout := {TimerRef,_TimeoutMsg}} ->
%% Event timeout active but not state timeout
%% - cancel event timeout only
loop_next_events(
P, Debug, S, Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, [],
- cancel_timer_by_ref_and_type(
- TimerRef, timeout, TimerRefs, TimeoutTypes));
+ cancel_timer(timeout, TimerRef, Timers));
_ ->
%% No state nor event timeout active.
loop_next_events(
@@ -1778,7 +1813,7 @@ loop_state_change(
end.
%% Continue state transition with processing of
-%% inserted events and timeout events
+%% timeouts and inserted events
%%
loop_next_events(
P, Debug, S,
@@ -1786,7 +1821,7 @@ loop_next_events(
NextEventsR, Hibernate, [], Postponed,
Timers) ->
%%
- %% Optimization when there are no timeout actions
+ %% Optimization when there are no timeouts
%% hence no timeout zero events to append to Events
%% - avoid loop_timeouts
loop_done(
@@ -1811,7 +1846,8 @@ loop_next_events(
NextEventsR, Hibernate, TimeoutsR, Postponed,
Timers, Seen, TimeoutEvents).
-%% Continue state transition with processing of timeout events
+%% Continue state transition with processing of timeouts
+%% and finally inserted events
%%
loop_timeouts(
P, Debug, S,
@@ -1819,6 +1855,8 @@ loop_timeouts(
NextEventsR, Hibernate, [], Postponed,
Timers, _Seen, TimeoutEvents) ->
%%
+ %% End of timeouts
+ %%
S_1 =
S#state{
state_data = NextState_NewData,
@@ -1854,37 +1892,28 @@ loop_timeouts(
NextEventsR, Hibernate, [Timeout|TimeoutsR], Postponed,
Timers, Seen, TimeoutEvents) ->
%%
- case Timeout of
- {TimeoutType,Time,TimeoutMsg} ->
- %% Relative timeout
- case Seen of
- #{TimeoutType := _} ->
- %% Type seen before - ignore
- loop_timeouts(
- P, Debug, S,
- Events, NextState_NewData,
- NextEventsR, Hibernate, TimeoutsR, Postponed,
- Timers, Seen, TimeoutEvents);
- #{} ->
- loop_timeouts(
+ TimeoutType = element(1, Timeout),
+ case Seen of
+ #{TimeoutType := _} ->
+ %% Type seen before - ignore
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents);
+ #{} ->
+ case Timeout of
+ {_,Time,TimeoutMsg} ->
+ %% Relative timeout or update
+ loop_timeouts_start(
P, Debug, S,
Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postponed,
Timers, Seen, TimeoutEvents,
- TimeoutType, Time, TimeoutMsg, [])
- end;
- {TimeoutType,Time,TimeoutMsg,TimeoutOpts} ->
- %% Absolute timeout
- case Seen of
- #{TimeoutType := _} ->
- %% Type seen before - ignore
- loop_timeouts(
- P, Debug, S,
- Events, NextState_NewData,
- NextEventsR, Hibernate, TimeoutsR, Postponed,
- Timers, Seen, TimeoutEvents);
- #{} ->
- loop_timeouts(
+ TimeoutType, Time, TimeoutMsg, []);
+ {_,Time,TimeoutMsg,TimeoutOpts} ->
+ %% Absolute timeout
+ loop_timeouts_start(
P, Debug, S,
Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postponed,
@@ -1892,8 +1921,10 @@ loop_timeouts(
TimeoutType, Time, TimeoutMsg, listify(TimeoutOpts))
end
end.
+
+%% Loop helper to start or restart a timeout
%%
-loop_timeouts(
+loop_timeouts_start(
P, Debug, S,
Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postponed,
@@ -1920,51 +1951,79 @@ loop_timeouts(
NextEventsR, Hibernate, TimeoutsR, Postponed,
Timers, Seen, [{TimeoutType,TimeoutMsg}|TimeoutEvents],
TimeoutType);
+ update ->
+ loop_timeouts_update(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents,
+ TimeoutType, TimeoutMsg);
_ ->
%% (Re)start the timer
TimerRef =
- erlang:start_timer(Time, self(), TimeoutMsg, TimeoutOpts),
- {TimerRefs,TimeoutTypes} = Timers,
- case TimeoutTypes of
- #{TimeoutType := OldTimerRef} ->
- %% Cancel the running timer,
- %% update the timeout type,
- %% insert the new timer ref,
- %% and remove the old timer ref
- Timers_1 =
- {maps:remove(
- OldTimerRef,
- TimerRefs#{TimerRef => TimeoutType}),
- TimeoutTypes#{TimeoutType := TimerRef}},
- cancel_timer(OldTimerRef),
- loop_timeouts(
- P, Debug, S,
- Events, NextState_NewData,
+ erlang:start_timer(Time, self(), TimeoutType, TimeoutOpts),
+ case Debug of
+ ?not_sys_debug ->
+ loop_timeouts_register(
+ P, Debug, S, Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postponed,
- Timers_1, Seen#{TimeoutType => true}, TimeoutEvents);
- #{} ->
- %% Insert the new timer type and ref
- Timers_1 =
- {TimerRefs#{TimerRef => TimeoutType},
- TimeoutTypes#{TimeoutType => TimerRef}},
- loop_timeouts(
- P, Debug, S,
- Events, NextState_NewData,
+ Timers, Seen, TimeoutEvents,
+ TimeoutType, TimerRef, TimeoutMsg);
+ _ ->
+ {State,_Data} = NextState_NewData,
+ Debug_1 =
+ sys_debug(
+ Debug, P#params.name,
+ {start_timer,
+ {TimeoutType,Time,TimeoutMsg,TimeoutOpts},
+ State}),
+ loop_timeouts_register(
+ P, Debug_1, S, Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postponed,
- Timers_1, Seen#{TimeoutType => true}, TimeoutEvents)
+ Timers, Seen, TimeoutEvents,
+ TimeoutType, TimerRef, TimeoutMsg)
end
end.
+%% Loop helper to register a newly started timer
+%% and to cancel any running timer
+%%
+loop_timeouts_register(
+ P, Debug, S, Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents,
+ TimeoutType, TimerRef, TimeoutMsg) ->
+ %%
+ case Timers of
+ #{TimeoutType := {OldTimerRef,_OldTimeoutMsg}} ->
+ %% Cancel the running timer,
+ %% and update timer type and ref
+ cancel_timer(OldTimerRef),
+ Timers_1 = Timers#{TimeoutType := {TimerRef,TimeoutMsg}},
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers_1, Seen#{TimeoutType => true}, TimeoutEvents);
+ #{} ->
+ %% Insert the new timer type and ref
+ Timers_1 = Timers#{TimeoutType => {TimerRef,TimeoutMsg}},
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers_1, Seen#{TimeoutType => true}, TimeoutEvents)
+ end.
+
%% Loop helper to cancel a timeout
%%
loop_timeouts_cancel(
P, Debug, S,
Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postponed,
- {TimerRefs,TimeoutTypes} = Timers, Seen, TimeoutEvents,
- TimeoutType) ->
+ Timers, Seen, TimeoutEvents, TimeoutType) ->
%% This function body should have been:
- %% Timers_1 = cancel_timer_by_type(TimeoutType, Timers),
+ %% Timers_1 = cancel_timer(TimeoutType, Timers),
%% loop_timeouts(
%% P, Debug, S,
%% Events, NextState_NewData,
@@ -1974,14 +2033,12 @@ loop_timeouts_cancel(
%% Explicitly separate cases to get separate code paths for when
%% the map key exists vs. not, since otherwise the external call
%% to erlang:cancel_timer/1 and to map:remove/2 within
- %% cancel_timer_by_type/2 would cause all live registers
+ %% cancel_timer/2 would cause all live registers
%% to be saved to and restored from the stack also for
%% the case when the map key TimeoutType does not exist
- case TimeoutTypes of
- #{TimeoutType := TimerRef} ->
- Timers_1 =
- cancel_timer_by_ref_and_type(
- TimerRef, TimeoutType, TimerRefs, TimeoutTypes),
+ case Timers of
+ #{TimeoutType := {TimerRef,_TimeoutMsg}} ->
+ Timers_1 = cancel_timer(TimeoutType, TimerRef, Timers),
loop_timeouts(
P, Debug, S,
Events, NextState_NewData,
@@ -1995,6 +2052,36 @@ loop_timeouts_cancel(
Timers, Seen#{TimeoutType => true}, TimeoutEvents)
end.
+%% Loop helper to update the timeout message,
+%% or insert an event if no timer is running
+%%
+loop_timeouts_update(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents,
+ TimeoutType, TimeoutMsg) ->
+ %%
+ case Timers of
+ #{TimeoutType := {TimerRef,_OldTimeoutMsg}} ->
+ Timers_1 = Timers#{TimeoutType := {TimerRef,TimeoutMsg}},
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers_1, Seen#{TimeoutType => true},
+ TimeoutEvents);
+ #{} ->
+ TimeoutEvents_1 =
+ [{TimeoutType,TimeoutMsg}|TimeoutEvents],
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen#{TimeoutType => true},
+ TimeoutEvents_1)
+ end.
+
%% Continue state transition with prepending timeout zero events
%% before event queue reversal i.e appending timeout zero events
%%
@@ -2056,13 +2143,13 @@ parse_timeout_opts_abs(Opts, Abs) ->
%% Enqueue immediate timeout events (timeout 0 events)
%%
-%% Event timer timeout 0 events gets special treatment since
-%% an event timer is cancelled by any received event,
-%% so if there are enqueued events before the event timer
-%% timeout 0 event - the event timer is cancelled hence no event.
+%% Event timeout 0 events gets special treatment since
+%% an event timeout is cancelled by any received event,
+%% so if there are enqueued events before the event
+%% timeout 0 event - the event timeout is cancelled hence no event.
%%
%% Other (state_timeout and {timeout,Name}) timeout 0 events
-%% that are after an event timer timeout 0 event are considered to
+%% that occur after an event timer timeout 0 event are considered to
%% belong to timers that were started after the event timer
%% timeout 0 event fired, so they do not cancel the event timer.
%%
@@ -2079,7 +2166,8 @@ prepend_timeout_events(
{State,_Data} = S#state.state_data,
Debug_1 =
sys_debug(
- Debug, P#params.name, {in,TimeoutEvent,State}),
+ Debug, P#params.name,
+ {insert_timeout,TimeoutEvent,State}),
prepend_timeout_events(
P, Debug_1, S, TimeoutEvents, [TimeoutEvent])
end;
@@ -2099,7 +2187,8 @@ prepend_timeout_events(
{State,_Data} = S#state.state_data,
Debug_1 =
sys_debug(
- Debug, P#params.name, {in,TimeoutEvent,State}),
+ Debug, P#params.name,
+ {insert_timeout,TimeoutEvent,State}),
prepend_timeout_events(
P, Debug_1, S, TimeoutEvents, [TimeoutEvent|EventsR])
end.
@@ -2190,7 +2279,9 @@ error_info(
name = Name,
callback_mode = CallbackMode,
state_enter = StateEnter} = P,
- #state{postponed = Postponed} = S,
+ #state{
+ postponed = Postponed,
+ timers = Timers} = S,
Q) ->
Log = sys:get_log(Debug),
?LOG_ERROR(#{label=>{gen_statem,terminate},
@@ -2200,6 +2291,7 @@ error_info(
callback_mode=>CallbackMode,
state_enter=>StateEnter,
state=>format_status(terminate, get(), P, S),
+ timeouts=>list_timeouts(Timers),
log=>Log,
reason=>{Class,Reason,Stacktrace},
client_info=>client_stacktrace(Q)},
@@ -2238,6 +2330,7 @@ format_log(#{label:={gen_statem,terminate},
callback_mode:=CallbackMode,
state_enter:=StateEnter,
state:=FmtData,
+ timeouts:=Timeouts,
log:=Log,
reason:={Class,Reason,Stacktrace},
client_info:=ClientInfo}) ->
@@ -2293,6 +2386,10 @@ format_log(#{label:={gen_statem,terminate},
[] -> "";
_ -> "** Stacktrace =~n** ~tp~n"
end ++
+ case Timeouts of
+ {0,_} -> "";
+ _ -> "** Time-outs: ~p~n"
+ end ++
case Log of
[] -> "";
_ -> "** Log =~n** ~tp~n"
@@ -2317,6 +2414,10 @@ format_log(#{label:={gen_statem,terminate},
[] -> [];
_ -> [error_logger:limit_term(FixedStacktrace)]
end ++
+ case Timeouts of
+ {0,_} -> [];
+ _ -> [error_logger:limit_term(Timeouts)]
+ end ++
case Log of
[] -> [];
_ -> [[error_logger:limit_term(T) || T <- Log]]
@@ -2370,40 +2471,55 @@ listify(Item) ->
[Item].
--define(cancel_timer(TimerRef),
- case erlang:cancel_timer(TimerRef) of
- false ->
- %% No timer found and we have not seen the timeout message
- receive
- {timeout,(TimerRef),_} ->
- ok
- end;
- _ ->
- %% Timer was running
- ok
- end).
-
+-define(
+ cancel_timer(TimerRef),
+ case erlang:cancel_timer(TimerRef) of
+ false ->
+ %% No timer found and we have not seen the timeout message
+ receive
+ {timeout,(TimerRef),_} ->
+ ok
+ end;
+ _ ->
+ %% Timer was running
+ ok
+ end).
+%%
+%% Cancel timer and consume timeout message
+%%
-compile({inline, [cancel_timer/1]}).
cancel_timer(TimerRef) ->
?cancel_timer(TimerRef).
+-define(
+ cancel_timer(TimeoutType, TimerRef, Timers),
+ begin
+ ?cancel_timer(TimerRef),
+ maps:remove(begin TimeoutType end, begin Timers end)
+ end).
+%%
+%% Cancel timer and remove from Timers
+%%
+-compile({inline, [cancel_timer/3]}).
+cancel_timer(TimeoutType, TimerRef, Timers) ->
+ ?cancel_timer(TimeoutType, TimerRef, Timers).
+
%% Cancel timer if running, otherwise no op
%%
%% Remove the timer from Timers
--compile({inline, [cancel_timer_by_type/2]}).
-cancel_timer_by_type(TimeoutType, {TimerRefs,TimeoutTypes} = Timers) ->
- case TimeoutTypes of
- #{TimeoutType := TimerRef} ->
- ?cancel_timer(TimerRef),
- {maps:remove(TimerRef, TimerRefs),
- maps:remove(TimeoutType, TimeoutTypes)};
+-compile({inline, [cancel_timer/2]}).
+cancel_timer(TimeoutType, Timers) ->
+ case Timers of
+ #{TimeoutType := {TimerRef, _TimeoutMsg}} ->
+ ?cancel_timer(TimeoutType, TimerRef, Timers);
#{} ->
Timers
end.
--compile({inline, [cancel_timer_by_ref_and_type/4]}).
-cancel_timer_by_ref_and_type(
- TimerRef, TimeoutType, TimerRefs, TimeoutTypes) ->
- ?cancel_timer(TimerRef),
- {maps:remove(TimerRef, TimerRefs),
- maps:remove(TimeoutType, TimeoutTypes)}.
+%% Return a list of all pending timeouts
+list_timeouts(Timers) ->
+ {maps:size(Timers),
+ maps:fold(
+ fun(TimeoutType, {_TimerRef,TimeoutMsg}, Acc) ->
+ [{TimeoutType,TimeoutMsg}|Acc]
+ end, [], Timers)}.
diff --git a/lib/stdlib/src/sys.erl b/lib/stdlib/src/sys.erl
index 6ff9aa33b4..93bf4743d2 100644
--- a/lib/stdlib/src/sys.erl
+++ b/lib/stdlib/src/sys.erl
@@ -51,6 +51,8 @@
| {'code_change', Event :: _, State :: _}
| {'postpone', Event :: _, State :: _, NextState :: _}
| {'consume', Event :: _, State :: _, NextState :: _}
+ | {'start_timer', Action :: _, State :: _}
+ | {'insert_timeout', Event :: _, State :: _}
| {'enter', State :: _}
| {'terminate', Reason :: _, State :: _}
| term().
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index 16cf8f43f9..aa4d258cbf 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2016-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2016-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
suite() ->
[{ct_hooks,[ts_install_cth]},
- {timetrap,{minutes,1}}].
+ {timetrap,{seconds,10}}].
all() ->
[{group, start},
@@ -38,7 +38,8 @@ all() ->
{group, abnormal},
{group, abnormal_handle_event},
shutdown, stop_and_reply, state_enter, event_order,
- state_timeout, event_types, generic_timers, code_change,
+ state_timeout, timeout_cancel_and_update,
+ event_types, generic_timers, code_change,
{group, sys},
hibernate, auto_hibernate, enter_loop, {group, undef_callbacks},
undef_in_terminate].
@@ -518,10 +519,11 @@ abnormal2(Config) ->
?MODULE, start_arg(Config, []), [{debug,[log]}]),
%% bad return value in the gen_statem loop
- {{{bad_return_from_state_function,badreturn},_},_} =
+ Cause = bad_return_from_state_function,
+ {{{Cause,badreturn},_},_} =
?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason),
receive
- {'EXIT',Pid,{{bad_return_from_state_function,badreturn},_}} -> ok
+ {'EXIT',Pid,{{Cause,badreturn},_}} -> ok
after 5000 ->
ct:fail(gen_statem_did_not_die)
end,
@@ -538,10 +540,11 @@ abnormal3(Config) ->
?MODULE, start_arg(Config, []), [{debug,[log]}]),
%% bad return value in the gen_statem loop
- {{{bad_action_from_state_function,badaction},_},_} =
+ Cause = bad_action_from_state_function,
+ {{{Cause,badaction},_},_} =
?EXPECT_FAILURE(gen_statem:call(Pid, badaction), Reason),
receive
- {'EXIT',Pid,{{bad_action_from_state_function,badaction},_}} -> ok
+ {'EXIT',Pid,{{Cause,badaction},_}} -> ok
after 5000 ->
ct:fail(gen_statem_did_not_die)
end,
@@ -559,10 +562,11 @@ abnormal4(Config) ->
%% bad return value in the gen_statem loop
BadTimeout = {badtimeout,4711,ouch},
- {{{bad_action_from_state_function,BadTimeout},_},_} =
- ?EXPECT_FAILURE(gen_statem:call(Pid, BadTimeout), Reason),
+ Cause = bad_action_from_state_function,
+ {{{Cause,BadTimeout},_},_} =
+ ?EXPECT_FAILURE(gen_statem:call(Pid, {badtimeout,BadTimeout}), Reason),
receive
- {'EXIT',Pid,{{bad_action_from_state_function,BadTimeout},_}} -> ok
+ {'EXIT',Pid,{{Cause,BadTimeout},_}} -> ok
after 5000 ->
ct:fail(gen_statem_did_not_die)
end,
@@ -822,7 +826,8 @@ state_timeout(_Config) ->
self() ! message_to_self,
{next_state, state1, {Time,From},
%% Verify that internal events goes before external
- [{state_timeout,Time,1},
+ [{timeout,Time,1}, % Exercise different cancel code path
+ {state_timeout,Time,1},
{next_event,internal,1}]}
end,
state1 =>
@@ -872,8 +877,9 @@ state_timeout(_Config) ->
{reply,From,ok}}
end},
- {ok,STM} = gen_statem:start_link(?MODULE, {map_statem,Machine,[]}, []),
- sys:trace(STM, true),
+ {ok,STM} =
+ gen_statem:start_link(
+ ?MODULE, {map_statem,Machine,[]}, [{debug,[trace]}]),
TRef = erlang:start_timer(1000, self(), kull),
ok = gen_statem:call(STM, {go,500}),
ok = gen_statem:call(STM, check),
@@ -899,6 +905,88 @@ state_timeout(_Config) ->
+timeout_cancel_and_update(_Config) ->
+ process_flag(trap_exit, true),
+ %%
+ Machine =
+ #{init =>
+ fun () ->
+ {ok,start,0}
+ end,
+ start =>
+ fun
+ ({call,From}, test, 0) ->
+ self() ! message_to_self,
+ {next_state, state1, From,
+ %% Verify that internal events goes before external
+ [{state_timeout,17,1},
+ {next_event,internal,1}]}
+ end,
+ state1 =>
+ fun
+ (internal, 1, _) ->
+ {keep_state_and_data,
+ [{state_timeout,cancel},
+ {{timeout,a},17,1}]};
+ (info, message_to_self, _) ->
+ {keep_state_and_data,
+ [{{timeout,a},update,a}]};
+ ({timeout,a}, a, Data) ->
+ {next_state,state2,Data,
+ [{state_timeout,17,2},
+ {next_event,internal,2}]}
+ end,
+ state2 =>
+ fun
+ (internal, 2, _) ->
+ receive after 50 -> ok end,
+ %% Now state_timeout 17 should have triggered
+ {keep_state_and_data,
+ [{state_timeout,update,b},
+ {timeout,17,2}]};
+ (state_timeout, b, From) ->
+ {next_state,state3,3,
+ [{reply,From,ok},
+ 17000]}
+ end,
+ state3 =>
+ fun
+ ({call,From}, stop, 3) ->
+ {stop_and_reply, normal,
+ [{reply,From,ok}]}
+ end
+ },
+ %%
+ {ok,STM} =
+ gen_statem:start_link(
+ ?MODULE, {map_statem,Machine,[]}, [{debug,[trace]}]),
+ ok = gen_statem:call(STM, test),
+ {status, STM, {module,gen_statem}, Info} = sys:get_status(STM),
+ ct:log("Status info: ~p~n", [Info]),
+ {_,Timeouts} = dig_data_tuple(Info),
+ {_, {1,[{timeout,17000}]}} = lists:keyfind("Time-outs", 1, Timeouts),
+ %%
+ ok = gen_statem:call(STM, stop),
+ receive
+ {'EXIT',STM,normal} ->
+ ok
+ after 500 ->
+ ct:fail(did_not_stop)
+ end,
+ %%
+ verify_empty_msgq().
+
+dig_data_tuple([{data,_} = DataTuple|_]) -> DataTuple;
+dig_data_tuple([H|T]) when is_list(H) ->
+ case dig_data_tuple(H) of
+ false -> dig_data_tuple(T);
+ DataTuple -> DataTuple
+ end;
+dig_data_tuple([_|T]) -> dig_data_tuple(T);
+dig_data_tuple([]) -> false.
+
+
+
%% Test that all event types can be sent with {next_event,EventType,_}
event_types(_Config) ->
process_flag(trap_exit, true),
@@ -1042,7 +1130,8 @@ generic_timers(_Config) ->
sys1(Config) ->
{ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []),
- {status, Pid, {module,gen_statem}, _} = sys:get_status(Pid),
+ {status, Pid, {module,gen_statem}, Info} = sys:get_status(Pid),
+ ct:log("Status info: ~p~n", [Info]),
sys:suspend(Pid),
Parent = self(),
Tag = make_ref(),
@@ -1892,7 +1981,7 @@ idle({call,_From}, badreturn, _Data) ->
badreturn;
idle({call,_From}, badaction, Data) ->
{keep_state, Data, [badaction]};
-idle({call,_From}, {badtimeout,_,_} = BadTimeout, Data) ->
+idle({call,_From}, {badtimeout,BadTimeout}, Data) ->
{keep_state, Data, BadTimeout};
idle({call,From}, {delayed_answer,T}, Data) ->
receive
diff --git a/lib/stdlib/test/rand_SUITE.erl b/lib/stdlib/test/rand_SUITE.erl
index 7685c17967..b3d8a7e5d3 100644
--- a/lib/stdlib/test/rand_SUITE.erl
+++ b/lib/stdlib/test/rand_SUITE.erl
@@ -1257,12 +1257,17 @@ short_jump(Config) when is_list(Config) ->
fun ({Alg,AlgState}) ->
{Alg,rand:exro928_jump_2pow20(AlgState)}
end),
- short_jump(
- crypto:rand_seed_alg_s(crypto_aes, integer_to_list(Seed)),
- fun ({Alg,AlgState}) ->
- {Alg,crypto:rand_plugin_aes_jump_2pow20(AlgState)}
- end),
- ok.
+ try crypto:strong_rand_bytes(1) of
+ _ ->
+ short_jump(
+ crypto:rand_seed_alg_s(crypto_aes, integer_to_list(Seed)),
+ fun ({Alg,AlgState}) ->
+ {Alg,crypto:rand_plugin_aes_jump_2pow20(AlgState)}
+ end),
+ ok
+ catch error:undef ->
+ {skip,no_crypto}
+ end.
short_jump({#{bits := Bits},_} = State_0, Jump2Pow20) ->
Range = 1 bsl Bits,
diff --git a/lib/stdlib/test/supervisor_SUITE.erl b/lib/stdlib/test/supervisor_SUITE.erl
index 9370067910..36751c641b 100644
--- a/lib/stdlib/test/supervisor_SUITE.erl
+++ b/lib/stdlib/test/supervisor_SUITE.erl
@@ -2380,10 +2380,10 @@ scale_start_stop_many_children() ->
[N2, StartT2 div 1000, StopT2 div 1000]),
%% Scaling should be more or less linear, but allowing a bit more
- %% to avoid false alarms
+ %% to avoid false alarms (add 1 to avoid div zero)
ScaleLimit = (N2 div N1) * 10,
- StartScale = StartT2 div StartT1,
- StopScale = StopT2 div StopT1,
+ StartScale = StartT2 div (StartT1+1),
+ StopScale = StopT2 div (StopT1+1),
ct:log("Scale limit: ~w~nStart scale: ~w~nStop scale: ~w",
[ScaleLimit, StartScale, StopScale]),
diff --git a/lib/syntax_tools/doc/src/notes.xml b/lib/syntax_tools/doc/src/notes.xml
index a2dd78f280..2ec1b6cb07 100644
--- a/lib/syntax_tools/doc/src/notes.xml
+++ b/lib/syntax_tools/doc/src/notes.xml
@@ -60,6 +60,22 @@
</section>
+<section><title>Syntax_Tools 2.1.7.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p> Add missing calls to <c>erl_syntax:unwrap/1</c>. The
+ nodes concerned represent names and values of maps and
+ map types. </p>
+ <p>
+ Own Id: OTP-16012 Aux Id: PR-2348 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Syntax_Tools 2.1.7</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -786,4 +802,3 @@
<p>Miscellaneous changes.</p>
</section>
</chapter>
-
diff --git a/make/otp.mk.in b/make/otp.mk.in
index cc76f00e7e..63748c2f2b 100644
--- a/make/otp.mk.in
+++ b/make/otp.mk.in
@@ -321,5 +321,5 @@ $(MAN6DIR)/%.6 $(MAN6DIR)/%.7: $(XMLDIR)/%_app.xml
$(XMLDIR)/%.xml: $(XMLDIR)/%.xmlsrc
$(gen_verbose)escript $(DOCGEN)/priv/bin/codeline_preprocessing.escript $(shell pwd) $< $@
-.fo.pdf:
+$(PDFDIR)/%.pdf: %.fo
$(FOP) -c $(FOP_CONFIG) -cache $(ERL_TOP)/make/$(TARGET)/fop-fonts.cache -fo $< -pdf $@
diff --git a/otp_versions.table b/otp_versions.table
index 47b95b7ee1..7f906e11c1 100644
--- a/otp_versions.table
+++ b/otp_versions.table
@@ -6,6 +6,7 @@ OTP-22.0.3 : compiler-7.4.2 dialyzer-4.0.1 erts-10.4.2 ssl-9.3.2 stdlib-3.9.2 #
OTP-22.0.2 : compiler-7.4.1 crypto-4.5.1 erts-10.4.1 stdlib-3.9.1 # asn1-5.0.9 common_test-1.17.3 debugger-4.2.7 dialyzer-4.0 diameter-2.2.1 edoc-0.11 eldap-1.2.8 erl_docgen-0.9.1 erl_interface-3.12 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.19 inets-7.0.8 jinterface-1.10 kernel-6.4 megaco-3.18.5 mnesia-4.16 observer-2.9.1 odbc-2.12.4 os_mon-2.5 parsetools-2.1.8 public_key-1.6.7 reltool-0.8 runtime_tools-1.13.3 sasl-3.4 snmp-5.3 ssh-4.7.7 ssl-9.3.1 syntax_tools-2.2 tftp-1.0.1 tools-3.2 wx-1.8.8 xmerl-1.3.21 :
OTP-22.0.1 : ssl-9.3.1 # asn1-5.0.9 common_test-1.17.3 compiler-7.4 crypto-4.5 debugger-4.2.7 dialyzer-4.0 diameter-2.2.1 edoc-0.11 eldap-1.2.8 erl_docgen-0.9.1 erl_interface-3.12 erts-10.4 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.19 inets-7.0.8 jinterface-1.10 kernel-6.4 megaco-3.18.5 mnesia-4.16 observer-2.9.1 odbc-2.12.4 os_mon-2.5 parsetools-2.1.8 public_key-1.6.7 reltool-0.8 runtime_tools-1.13.3 sasl-3.4 snmp-5.3 ssh-4.7.7 stdlib-3.9 syntax_tools-2.2 tftp-1.0.1 tools-3.2 wx-1.8.8 xmerl-1.3.21 :
OTP-22.0 : asn1-5.0.9 common_test-1.17.3 compiler-7.4 crypto-4.5 debugger-4.2.7 dialyzer-4.0 edoc-0.11 eldap-1.2.8 erl_docgen-0.9.1 erl_interface-3.12 erts-10.4 hipe-3.19 inets-7.0.8 jinterface-1.10 kernel-6.4 megaco-3.18.5 mnesia-4.16 observer-2.9.1 odbc-2.12.4 os_mon-2.5 public_key-1.6.7 reltool-0.8 runtime_tools-1.13.3 sasl-3.4 snmp-5.3 ssh-4.7.7 ssl-9.3 stdlib-3.9 syntax_tools-2.2 tools-3.2 wx-1.8.8 xmerl-1.3.21 # diameter-2.2.1 et-1.6.4 eunit-2.3.7 ftp-1.0.2 parsetools-2.1.8 tftp-1.0.1 :
+OTP-21.3.8.7 : erts-10.3.5.5 inets-7.0.7.1 kernel-6.3.1.3 ssh-4.7.6.1 syntax_tools-2.1.7.1 # asn1-5.0.8 common_test-1.17.2.1 compiler-7.3.2 crypto-4.4.2 debugger-4.2.6 dialyzer-3.3.2 diameter-2.2.1 edoc-0.10 eldap-1.2.7 erl_docgen-0.9 erl_interface-3.11.3 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.18.3 jinterface-1.9.1 megaco-3.18.4 mnesia-4.15.6 observer-2.9 odbc-2.12.3 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.6.1 reltool-0.7.8 runtime_tools-1.13.2 sasl-3.3 snmp-5.2.12 ssl-9.2.3.5 stdlib-3.8.2.2 tftp-1.0.1 tools-3.1.0.1 wx-1.8.7 xmerl-1.3.20.1 :
OTP-21.3.8.6 : ssl-9.2.3.5 # asn1-5.0.8 common_test-1.17.2.1 compiler-7.3.2 crypto-4.4.2 debugger-4.2.6 dialyzer-3.3.2 diameter-2.2.1 edoc-0.10 eldap-1.2.7 erl_docgen-0.9 erl_interface-3.11.3 erts-10.3.5.4 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.18.3 inets-7.0.7 jinterface-1.9.1 kernel-6.3.1.2 megaco-3.18.4 mnesia-4.15.6 observer-2.9 odbc-2.12.3 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.6.1 reltool-0.7.8 runtime_tools-1.13.2 sasl-3.3 snmp-5.2.12 ssh-4.7.6 stdlib-3.8.2.2 syntax_tools-2.1.7 tftp-1.0.1 tools-3.1.0.1 wx-1.8.7 xmerl-1.3.20.1 :
OTP-21.3.8.5 : erts-10.3.5.4 ssl-9.2.3.4 # asn1-5.0.8 common_test-1.17.2.1 compiler-7.3.2 crypto-4.4.2 debugger-4.2.6 dialyzer-3.3.2 diameter-2.2.1 edoc-0.10 eldap-1.2.7 erl_docgen-0.9 erl_interface-3.11.3 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.18.3 inets-7.0.7 jinterface-1.9.1 kernel-6.3.1.2 megaco-3.18.4 mnesia-4.15.6 observer-2.9 odbc-2.12.3 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.6.1 reltool-0.7.8 runtime_tools-1.13.2 sasl-3.3 snmp-5.2.12 ssh-4.7.6 stdlib-3.8.2.2 syntax_tools-2.1.7 tftp-1.0.1 tools-3.1.0.1 wx-1.8.7 xmerl-1.3.20.1 :
OTP-21.3.8.4 : common_test-1.17.2.1 erts-10.3.5.3 kernel-6.3.1.2 public_key-1.6.6.1 ssl-9.2.3.3 stdlib-3.8.2.2 # asn1-5.0.8 compiler-7.3.2 crypto-4.4.2 debugger-4.2.6 dialyzer-3.3.2 diameter-2.2.1 edoc-0.10 eldap-1.2.7 erl_docgen-0.9 erl_interface-3.11.3 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.18.3 inets-7.0.7 jinterface-1.9.1 megaco-3.18.4 mnesia-4.15.6 observer-2.9 odbc-2.12.3 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 reltool-0.7.8 runtime_tools-1.13.2 sasl-3.3 snmp-5.2.12 ssh-4.7.6 syntax_tools-2.1.7 tftp-1.0.1 tools-3.1.0.1 wx-1.8.7 xmerl-1.3.20.1 :
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index 23e9054547..360bf958ad 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -108,7 +108,9 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<item>
Co-located callback code for each state,
for all
- <seealso marker="#Event Types"><em>Event Types</em></seealso>
+ <seealso marker="#Event Types and Event Content">
+ <em>Event Types</em>
+ </seealso>
(such as <em>call</em>, <em>cast</em> and <em>info</em>)
</item>
<item>
@@ -136,7 +138,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
(<seealso marker="#State Time-Outs">State Time-Outs</seealso>,
<seealso marker="#Event Time-Outs">Event Time-Outs</seealso>
and
- <seealso marker="#Generic Time-Outs">Generic Time-outs</seealso>
+ <seealso marker="#Generic Time-Outs">Generic Time-Outs</seealso>
(named time-outs))
</item>
</list>
@@ -353,9 +355,10 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</taglist>
<p>
The state is either the name of the function itself or an argument to it.
- The other arguments are the <c>EventType</c> described in section
- <seealso marker="#Event Types">Event Types</seealso>,
- the event dependent <c>EventContent</c>,
+ The other arguments are the <c>EventType</c>
+ and the event dependent <c>EventContent</c>,
+ both described in section
+ <seealso marker="#Event Types and Event Content">Event Types and Event Content</seealso>,
and the current server <c>Data</c>.
</p>
<p>
@@ -561,34 +564,48 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-state_timeout">
- <c>{state_timeout, EventContent, Time}</c>
+ <c>{state_timeout, Time, EventContent}</c>
+ </seealso>
+ <br />
+ <c>{state_timeout, Time, EventContent, Opts}</c><br />
+ <seealso marker="stdlib:gen_statem#type-timeout_update_action">
+ <c>{state_timeout, update, EventContent}</c>
</seealso>
<br />
- <c>{state_timeout, EventContent, Time, Opts}</c>
+ <seealso marker="stdlib:gen_statem#type-timeout_cancel_action">
+ <c>{state_timeout, cancel}</c>
+ </seealso>
</tag>
<item>
- Start a state time-out, read more in sections
+ Start, update or cancel a state time-out, read more in sections
<seealso marker="#Time-Outs">Time-Outs</seealso> and
<seealso marker="#State Time-Outs">State Time-Outs</seealso>.
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-generic_timeout">
- <c>{{timeout, Name}, EventContent, Time}</c>
+ <c>{{timeout, Name}, Time, EventContent}</c>
+ </seealso>
+ <br />
+ <c>{{timeout, Name}, Time, EventContent, Opts}</c><br />
+ <seealso marker="stdlib:gen_statem#type-timeout_update_action">
+ <c>{{timeout, Name}, update, EventContent}</c>
</seealso>
<br />
- <c>{{timeout, Name}, EventContent, Time, Opts}</c>
+ <seealso marker="stdlib:gen_statem#type-timeout_cancel_action">
+ <c>{{timeout, Name}, cancel}</c>
+ </seealso>
</tag>
<item>
- Start a generic time-out, read more in sections
+ Start, update or cancel a generic time-out, read more in sections
<seealso marker="#Time-Outs">Time-Outs</seealso> and
<seealso marker="#Generic Time-Outs">Generic Time-Outs</seealso>.
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-event_timeout">
- <c>{timeout, EventContent, Time}</c>
+ <c>{timeout, Time, EventContent}</c>
</seealso>
<br />
- <c>{timeout, EventContent, Time, Opts}</c><br />
+ <c>{timeout, Time, EventContent, Opts}</c><br />
<c>Time</c>
</tag>
<item>
@@ -624,19 +641,42 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
and set a time-out to use absolute instead of relative time
(using the <c>Opts</c> field).
</p>
+ <p>
+ Among these <em>transition actions</em> only to reply to a caller
+ is an immediate action. The others are collected and handled
+ later during the <em>state transition</em>.
+ <seealso marker="#Inserted Events">Inserted Events</seealso>
+ are stored and inserted all together,
+ and the rest set transition options
+ where the last of a specific type override the previous.
+ See the description of a <em>state transition</em>
+ in the <c>gen_statem(3)</c> manual page for type
+ <seealso marker="stdlib:gen_statem#type-transition_option"><c>transition_option()</c></seealso>.
+ </p>
+ <p>
+ The different
+ <seealso marker="#Time-Outs">Time-Outs</seealso> and
+ <seealso marker="#Inserted Events"><c>next_event</c></seealso>
+ actions generate new events with corresponding
+ <seealso marker="#Event Types and Event Content">
+ Event Types and Event Content
+ </seealso>.
+ </p>
</section>
<!-- =================================================================== -->
<section>
- <marker id="Event Types" />
- <title>Event Types</title>
+ <marker id="Event Types and Event Content" />
+ <title>Event Types and Event Content</title>
<p>
Events are categorized in different
<seealso marker="stdlib:gen_statem#type-event_type"><em>event types</em></seealso>.
Events of all types are for a given state
handled in the same callback function, and that function gets
<c>EventType</c> and <c>EventContent</c> as arguments.
+ The meaning of the <c>EventContent</c>
+ depends on the <c>EventType</c>.
</p>
<p>
The following is a complete list of <em>event types</em> and where
@@ -650,7 +690,10 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</tag>
<item>
Generated by
- <seealso marker="stdlib:gen_statem#cast/2"><c>gen_statem:cast</c></seealso>.
+ <seealso marker="stdlib:gen_statem#cast/2">
+ <c>gen_statem:cast(ServerRef, Msg)</c>
+ </seealso>
+ where <c>Msg</c> becomes the <c>EventContent</c>.
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-external_event_type">
@@ -659,11 +702,17 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</tag>
<item>
Generated by
- <seealso marker="stdlib:gen_statem#call/2"><c>gen_statem:call</c></seealso>,
- where <c>From</c> is the reply address to use
+ <seealso marker="stdlib:gen_statem#call/2">
+ <c>gen_statem:call(ServerRef, Request)</c>
+ </seealso>
+ where <c>Request</c> becomes the <c>EventContent</c>.
+ <c>From</c> is the reply address to use
when replying either through the <em>transition action</em>
- <c>{reply,From,Msg}</c> or by calling
- <seealso marker="stdlib:gen_statem#reply/1"><c>gen_statem:reply</c></seealso>.
+ <c>{reply,From,Reply}</c> or by calling
+ <seealso marker="stdlib:gen_statem#reply/1">
+ <c>gen_statem:reply(From, Reply)</c>
+ </seealso>
+ from the <em>callback module</em>.
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-external_event_type">
@@ -673,6 +722,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<item>
Generated by any regular process message sent to
the <c>gen_statem</c> process.
+ The process message becomes the <c>EventContent</c>.
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-timeout_event_type">
@@ -724,7 +774,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</tag>
<item>
Generated by <em>transition action</em>
- <seealso marker="stdlib:gen_statem#type-enter_action"><c>{next_event,internal,EventContent}</c></seealso>.
+ <seealso marker="stdlib:gen_statem#type-action"><c>{next_event,internal,EventContent}</c></seealso>.
All <em>event types</em> above can also be generated using
the <c>next_event</c> action:
<c>{next_event,EventType,EventContent}</c>.
@@ -793,7 +843,7 @@ StateName(EventType, EventContent, Data) ->
<section>
<marker id="Time-Outs" />
- <title>Time-outs</title>
+ <title>Time-Outs</title>
<p>
Time-outs in <c>gen_statem</c> are started from a
<seealso marker="#Transition Actions">
@@ -844,9 +894,9 @@ StateName(EventType, EventContent, Data) ->
</item>
</taglist>
<p>
- When a time-out is started any running time-out with the same tag,
+ When a time-out is started any running time-out of the same type;
<c>state_timeout</c>, <c>{timeout, Name}</c> or <c>timeout</c>,
- is cancelled, that is the time-out is restarted with the new time.
+ is cancelled, that is, the time-out is restarted with the new time.
</p>
<p>
All time-outs has got an <c>EventContent</c> that is part of the
@@ -869,6 +919,39 @@ StateName(EventType, EventContent, Data) ->
The <c>EventContent</c> will in this case be ignored,
so why not set it to <c>undefined</c>.
</p>
+ <p>
+ A more explicit way to cancel a timer is to use a
+ <seealso marker="#Transition Actions">
+ <em>transition action</em>
+ </seealso>
+ on the form
+ <seealso marker="stdlib:gen_statem#type-timeout_cancel_action">
+ <c>{TimeoutType, cancel}</c>
+ </seealso>
+ which is a feature introduced in OTP 22.1.
+ </p>
+ </section>
+ <section>
+ <marker id="Updating a Time-Out" />
+ <title>Updating a Time-Out</title>
+ <p>
+ While a time-out is running, its <c>EventContent</c>
+ can be updated using a
+ <seealso marker="#Transition Actions">
+ <em>transition action</em>
+ </seealso>
+ on the form
+ <seealso marker="stdlib:gen_statem#type-timeout_update_action">
+ <c>{TimeoutType, update, NewEventContent}</c>
+ </seealso>
+ which is a feature introduced in OTP 22.1.
+ </p>
+ <p>
+ If this feature is used while no such <c>TimeoutType</c>
+ is running then a time-out event is immediately delivered
+ as when starting a
+ <seealso marker="#Time-Out Zero">Time-Out Zero</seealso>.
+ </p>
</section>
<section>
<marker id="Time-Out Zero" />
@@ -1200,10 +1283,12 @@ open(state_timeout, lock, Data) ->
<p>
The timer for a state time-out is automatically cancelled
when the state machine does a <em>state change</em>.
- You can restart a state time-out by setting it to a new time,
- which cancels the running timer and starts a new.
- This implies that you can cancel a state time-out
- by restarting it with time <c>infinity</c>.
+ </p>
+ <p>
+ You can restart, cancel or update a state time-out.
+ See section
+ <seealso marker="#Time-Outs">Time-Outs</seealso>
+ for details.
</p>
</section>
@@ -1472,12 +1557,13 @@ locked(
<p>
An event time-out is cancelled by any other event so you either
get some other event or the time-out event. It is therefore
- not possible nor needed to cancel or restart an event time-out.
- Whatever event you act on has already cancelled
- the event time-out...
+ not possible nor needed to cancel, restart or update an event time-out.
+ Whatever event you act on has already cancelled the event time-out,
+ so there is never a running event time-out
+ while the <em>state callback</em> executes.
</p>
<p>
- Note that an event time-out does not work well with
+ Note that an event time-out does not work well
when you have for example a status call as in section
<seealso marker="#All State Events">All State Events</seealso>,
or handle unknown events, since all kinds of events
@@ -1548,6 +1634,12 @@ open(cast, {button,_}, Data) ->
a late time-out event can be handled by ignoring it
if it arrives in a state where it is known to be late.
</p>
+ <p>
+ You can restart, cancel or update a generic time-out.
+ See section
+ <seealso marker="#Time-Outs">Time-Outs</seealso>
+ for details.
+ </p>
</section>
<!-- =================================================================== -->
@@ -1858,7 +1950,7 @@ open(state_timeout, lock, Data) ->
<seealso marker="#Transition Actions">
<em>transition action</em>
</seealso>
- <c>{next_event,EventType,EventContent}</c>.
+ <seealso marker="stdlib:gen_statem#type-action"><c>{next_event,EventType,EventContent}</c></seealso>.
</p>
<p>
You can generate events of any existing