aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--bootstrap/lib/kernel/ebin/os.beambin5276 -> 5280 bytes
-rw-r--r--erts/configure.in1
-rw-r--r--erts/doc/src/erl.xml24
-rw-r--r--erts/emulator/beam/atom.names1
-rw-r--r--erts/emulator/beam/erl_alloc.c18
-rw-r--r--erts/emulator/beam/erl_alloc.types2
-rwxr-xr-xerts/emulator/beam/erl_bif_info.c19
-rw-r--r--erts/emulator/beam/erl_gc.c173
-rw-r--r--erts/emulator/beam/erl_init.c61
-rw-r--r--erts/emulator/beam/erl_process.c7
-rw-r--r--erts/emulator/beam/erl_process.h9
-rw-r--r--erts/emulator/beam/erl_ptab.c342
-rw-r--r--erts/emulator/beam/erl_ptab.h13
-rwxr-xr-xerts/emulator/beam/global.h8
-rw-r--r--erts/emulator/beam/io.c6
-rw-r--r--erts/emulator/test/process_SUITE.erl12
-rw-r--r--erts/preloaded/ebin/erlang.beambin92912 -> 93520 bytes
-rw-r--r--erts/preloaded/src/erlang.erl20
-rw-r--r--lib/diameter/include/diameter_gen.hrl200
-rw-r--r--lib/diameter/src/base/diameter_codec.erl175
-rw-r--r--lib/diameter/src/base/diameter_traffic.erl143
-rw-r--r--lib/diameter/src/base/diameter_watchdog.erl4
-rw-r--r--lib/diameter/src/compiler/diameter_codegen.erl6
-rw-r--r--lib/diameter/test/diameter_3xxx_SUITE.erl46
-rw-r--r--lib/diameter/test/diameter_app_SUITE.erl2
-rw-r--r--lib/diameter/test/diameter_codec_SUITE_data/diameter_test_unknown.erl4
-rw-r--r--lib/diameter/test/diameter_codec_test.erl10
-rw-r--r--lib/diameter/test/diameter_distribution_SUITE.erl10
-rw-r--r--lib/diameter/test/diameter_examples_SUITE.erl27
-rw-r--r--lib/diameter/test/diameter_gen_tcp_SUITE.erl106
-rw-r--r--lib/diameter/test/diameter_tls_SUITE.erl18
-rw-r--r--lib/diameter/test/diameter_traffic_SUITE.erl150
-rw-r--r--lib/diameter/test/modules.mk1
-rw-r--r--lib/inets/src/http_client/httpc_handler.erl55
-rw-r--r--lib/inets/src/http_lib/http_chunk.erl53
-rw-r--r--lib/inets/test/httpc_SUITE.erl9
-rw-r--r--lib/kernel/src/os.erl23
-rw-r--r--lib/kernel/test/os_SUITE.erl45
-rw-r--r--lib/odbc/configure.in3
-rw-r--r--lib/stdlib/doc/src/c.xml4
-rw-r--r--lib/stdlib/src/c.erl6
-rw-r--r--lib/stdlib/test/c_SUITE.erl11
43 files changed, 1276 insertions, 555 deletions
diff --git a/.gitignore b/.gitignore
index a3e03dc46f..7ccedd3ff3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -287,13 +287,13 @@ JAVADOC-GENERATED
# otp_mibs
-/lib/otp_mibs/include/[A-Z]*.hrl
+/lib/otp_mibs/include/OTP*.hrl
/lib/otp_mibs/mibs/v1/OTP*.mib.v1
/lib/otp_mibs/priv/mibs/OTP*.bin
# os_mon
-/lib/os_mon/include/[A-Z]*.hrl
+/lib/os_mon/include/OTP*.hrl
/lib/os_mon/mibs/v1/OTP*.mib.v1
/lib/os_mon/priv/mibs/OTP*.bin
diff --git a/bootstrap/lib/kernel/ebin/os.beam b/bootstrap/lib/kernel/ebin/os.beam
index 3972fe7b91..bb981bbf89 100644
--- a/bootstrap/lib/kernel/ebin/os.beam
+++ b/bootstrap/lib/kernel/ebin/os.beam
Binary files differ
diff --git a/erts/configure.in b/erts/configure.in
index b9c9a76ef6..c47c211c4e 100644
--- a/erts/configure.in
+++ b/erts/configure.in
@@ -624,6 +624,7 @@ case $chk_arch_ in
armv5teb) ARCH=arm;;
armv5tel) ARCH=arm;;
armv5tejl) ARCH=arm;;
+ armv6l) ARCH=arm;;
armv7l) ARCH=arm;;
tile) ARCH=tile;;
*) ARCH=noarch;;
diff --git a/erts/doc/src/erl.xml b/erts/doc/src/erl.xml
index a68e62d051..8dca7402e8 100644
--- a/erts/doc/src/erl.xml
+++ b/erts/doc/src/erl.xml
@@ -657,10 +657,11 @@
<p>Se also <seealso marker="stdlib:io#printable_range/0">
io:printable_range/0</seealso>.</p>
</item>
- <tag><marker id="+P"/><marker id="max_processes"><c><![CDATA[+P Number]]></c></marker></tag>
+ <tag><marker id="+P"/><marker id="max_processes"><c><![CDATA[+P Number|legacy]]></c></marker></tag>
<item>
<p>Sets the maximum number of simultaneously existing processes for this
- system. Valid range for <c>Number</c> is <c>[1024-134217727]</c></p>
+ system if a <c>Number</c> is passed as value. Valid range for
+ <c>Number</c> is <c>[1024-134217727]</c></p>
<p><em>NOTE</em>: The actual maximum chosen may be much larger than
the <c>Number</c> passed. Currently the runtime system often,
but not always, chooses a value that is a power of 2. This might,
@@ -668,11 +669,19 @@
checked by calling
<seealso marker="erlang#system_info_process_limit">erlang:system_info(process_limit)</seealso>.</p>
<p>The default value is <c>262144</c></p>
+ <p>If <c>legacy</c> is passed as value, the legacy algorithm for
+ allocation of process identifiers will be used. Using the legacy
+ algorithm, identifiers will be allocated in a strictly increasing
+ fashion until largest possible identifier has been reached. Note that
+ this algorithm suffers from performance issues and can under certain
+ circumstances be extremely expensive. The legacy algoritm is deprecated,
+ and the <c>legacy</c> option is scheduled for removal in OTP-R18.</p>
</item>
- <tag><marker id="+Q"/><marker id="max_ports"><c><![CDATA[+Q Number]]></c></marker></tag>
+ <tag><marker id="+Q"/><marker id="max_ports"><c><![CDATA[+Q Number|legacy]]></c></marker></tag>
<item>
<p>Sets the maximum number of simultaneously existing ports for this
- system. Valid range for <c>Number</c> is <c>[1024-134217727]</c></p>
+ system if a Number is passed as value. Valid range for <c>Number</c>
+ is <c>[1024-134217727]</c></p>
<p><em>NOTE</em>: The actual maximum chosen may be much larger than
the actual <c>Number</c> passed. Currently the runtime system often,
but not always, chooses a value that is a power of 2. This might,
@@ -691,6 +700,13 @@
for setting the maximum number of simultaneously existing ports. This
environment variable is deprecated, and scheduled for removal in
OTP-R17, but can still be used.</p>
+ <p>If <c>legacy</c> is passed as value, the legacy algorithm for
+ allocation of port identifiers will be used. Using the legacy
+ algorithm, identifiers will be allocated in a strictly increasing
+ fashion until largest possible identifier has been reached. Note that
+ this algorithm suffers from performance issues and can under certain
+ circumstances be extremely expensive. The legacy algoritm is deprecated,
+ and the <c>legacy</c> option is scheduled for removal in OTP-R18.</p>
</item>
<tag><marker id="compat_rel"><c><![CDATA[+R ReleaseNumber]]></c></marker></tag>
<item>
diff --git a/erts/emulator/beam/atom.names b/erts/emulator/beam/atom.names
index ce60bb9bbc..9e12080732 100644
--- a/erts/emulator/beam/atom.names
+++ b/erts/emulator/beam/atom.names
@@ -248,6 +248,7 @@ atom get_data
atom get_seq_token
atom get_tcw
atom getenv
+atom gather_gc_info_result
atom gather_sched_wall_time_result
atom getting_linked
atom getting_unlinked
diff --git a/erts/emulator/beam/erl_alloc.c b/erts/emulator/beam/erl_alloc.c
index 9de4ef1ff9..403776aade 100644
--- a/erts/emulator/beam/erl_alloc.c
+++ b/erts/emulator/beam/erl_alloc.c
@@ -2176,7 +2176,6 @@ erts_memory(int *print_to_p, void *print_to_arg, void *proc, Eterm earg)
if (want_tot_or_sys || want.processes || want.processes_used) {
- int max_processes = erts_ptab_max(&erts_proc);
UWord tmp;
if (ERTS_MEM_NEED_ALL_ALCU)
@@ -2186,7 +2185,7 @@ erts_memory(int *print_to_p, void *print_to_arg, void *proc, Eterm earg)
fi, ERTS_ALC_NO_FIXED_SIZES);
tmp = alcu_size(ERTS_ALC_A_EHEAP, NULL, 0);
}
- tmp += max_processes*sizeof(erts_smp_atomic_t);
+ tmp += erts_ptab_mem_size(&erts_proc);
tmp += erts_bif_timer_memory_size();
tmp += erts_tot_link_lh_size();
@@ -2312,13 +2311,11 @@ struct aa_values {
Eterm
erts_allocated_areas(int *print_to_p, void *print_to_arg, void *proc)
{
-#define MAX_AA_VALUES (23)
+#define MAX_AA_VALUES (24)
struct aa_values values[MAX_AA_VALUES];
Eterm res = THE_NON_VALUE;
int i, length;
Uint reserved_atom_space, atom_space;
- int max_processes = erts_ptab_max(&erts_proc);
- int max_ports = erts_ptab_max(&erts_port);
if (proc) {
ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_MAIN
@@ -2349,8 +2346,8 @@ erts_allocated_areas(int *print_to_p, void *print_to_arg, void *proc)
values[i].arity = 2;
values[i].name = "static";
- values[i].ui[0] =
- max_ports*sizeof(erts_smp_atomic_t) /* Port table */
+ values[i].ui[0] =
+ sizeof(ErtsPTab)*2 /* proc & port tables */
+ erts_timer_wheel_memory_size(); /* Timer wheel */
i++;
@@ -2429,7 +2426,12 @@ erts_allocated_areas(int *print_to_p, void *print_to_arg, void *proc)
values[i].arity = 2;
values[i].name = "process_table";
- values[i].ui[0] = max_processes*sizeof(Process*);
+ values[i].ui[0] = erts_ptab_mem_size(&erts_proc);
+ i++;
+
+ values[i].arity = 2;
+ values[i].name = "port_table";
+ values[i].ui[0] = erts_ptab_mem_size(&erts_port);
i++;
values[i].arity = 2;
diff --git a/erts/emulator/beam/erl_alloc.types b/erts/emulator/beam/erl_alloc.types
index 5a92ab7f24..f913525726 100644
--- a/erts/emulator/beam/erl_alloc.types
+++ b/erts/emulator/beam/erl_alloc.types
@@ -366,6 +366,7 @@ type MONITOR_SH STANDARD_LOW PROCESSES monitor_sh
type NLINK_SH STANDARD_LOW PROCESSES nlink_sh
type AINFO_REQ STANDARD_LOW SYSTEM alloc_info_request
type SCHED_WTIME_REQ STANDARD_LOW SYSTEM sched_wall_time_request
+type GC_INFO_REQ STANDARD_LOW SYSTEM gc_info_request
+else # "fullword"
@@ -383,6 +384,7 @@ type MONITOR_SH FIXED_SIZE PROCESSES monitor_sh
type NLINK_SH FIXED_SIZE PROCESSES nlink_sh
type AINFO_REQ SHORT_LIVED SYSTEM alloc_info_request
type SCHED_WTIME_REQ SHORT_LIVED SYSTEM sched_wall_time_request
+type GC_INFO_REQ SHORT_LIVED SYSTEM gc_info_request
+endif
diff --git a/erts/emulator/beam/erl_bif_info.c b/erts/emulator/beam/erl_bif_info.c
index 54eefe8d12..1744afbae1 100755
--- a/erts/emulator/beam/erl_bif_info.c
+++ b/erts/emulator/beam/erl_bif_info.c
@@ -59,6 +59,7 @@ static Export* alloc_info_trap = NULL;
static Export* alloc_sizes_trap = NULL;
static Export *gather_sched_wall_time_res_trap;
+static Export *gather_gc_info_res_trap;
#define DECL_AM(S) Eterm AM_ ## S = am_atom_put(#S, sizeof(#S) - 1)
@@ -3103,18 +3104,10 @@ BIF_RETTYPE statistics_1(BIF_ALIST_1)
res = TUPLE2(hp, cs, SMALL_ZERO);
BIF_RET(res);
} else if (BIF_ARG_1 == am_garbage_collection) {
- Uint hsz = 4;
- ErtsGCInfo gc_info;
- Eterm gcs;
- Eterm recl;
- erts_gc_info(&gc_info);
- (void) erts_bld_uint(NULL, &hsz, gc_info.garbage_collections);
- (void) erts_bld_uint(NULL, &hsz, gc_info.reclaimed);
- hp = HAlloc(BIF_P, hsz);
- gcs = erts_bld_uint(&hp, NULL, gc_info.garbage_collections);
- recl = erts_bld_uint(&hp, NULL, gc_info.reclaimed);
- res = TUPLE3(hp, gcs, recl, SMALL_ZERO);
- BIF_RET(res);
+ res = erts_gc_info_request(BIF_P);
+ if (is_non_value(res))
+ BIF_RET(am_undefined);
+ BIF_TRAP1(gather_gc_info_res_trap, BIF_P, res);
} else if (BIF_ARG_1 == am_reductions) {
Uint reds;
Uint diff;
@@ -4082,6 +4075,8 @@ erts_bif_info_init(void)
alloc_sizes_trap = erts_export_put(am_erlang, am_alloc_sizes, 1);
gather_sched_wall_time_res_trap
= erts_export_put(am_erlang, am_gather_sched_wall_time_result, 1);
+ gather_gc_info_res_trap
+ = erts_export_put(am_erlang, am_gather_gc_info_result, 1);
process_info_init();
os_info_init();
}
diff --git a/erts/emulator/beam/erl_gc.c b/erts/emulator/beam/erl_gc.c
index 298909c921..0d12e658d9 100644
--- a/erts/emulator/beam/erl_gc.c
+++ b/erts/emulator/beam/erl_gc.c
@@ -47,10 +47,6 @@
*/
#define ALENGTH(a) (sizeof(a)/sizeof(a[0]))
-static erts_smp_spinlock_t info_lck;
-static Uint garbage_cols; /* no of garbage collections */
-static Uint reclaimed; /* no of words reclaimed in GCs */
-
# define STACK_SZ_ON_HEAP(p) ((p)->hend - (p)->stop)
# define OverRunCheck(P) \
if ((P)->stop < (P)->htop) { \
@@ -120,6 +116,8 @@ static void offset_rootset(Process *p, Sint offs, char* area, Uint area_size,
static void offset_off_heap(Process* p, Sint offs, char* area, Uint area_size);
static void offset_mqueue(Process *p, Sint offs, char* area, Uint area_size);
+static void init_gc_info(ErtsGCInfo *gcip);
+
#ifdef HARDDEBUG
static void disallow_heap_frag_ref_in_heap(Process* p);
static void disallow_heap_frag_ref_in_old_heap(Process* p);
@@ -137,13 +135,41 @@ static int num_heap_sizes; /* Number of heap sizes. */
Uint erts_test_long_gc_sleep; /* Only used for testing... */
+typedef struct {
+ Process *proc;
+ Eterm ref;
+ Eterm ref_heap[REF_THING_SIZE];
+ Uint req_sched;
+ erts_smp_atomic32_t refc;
+} ErtsGCInfoReq;
+
+#if !HALFWORD_HEAP
+ERTS_SCHED_PREF_QUICK_ALLOC_IMPL(gcireq,
+ ErtsGCInfoReq,
+ 5,
+ ERTS_ALC_T_GC_INFO_REQ)
+#else
+static ERTS_INLINE ErtsGCInfoReq *
+gcireq_alloc(void)
+{
+ return erts_alloc(ERTS_ALC_T_GC_INFO_REQ,
+ sizeof(ErtsGCInfoReq));
+}
+
+static ERTS_INLINE void
+gcireq_free(ErtsGCInfoReq *ptr)
+{
+ erts_free(ERTS_ALC_T_GC_INFO_REQ, ptr);
+}
+#endif
+
/*
* Initialize GC global data.
*/
void
erts_init_gc(void)
{
- int i = 0;
+ int i = 0, ix;
Sint max_heap_size = 0;
ASSERT(offsetof(ProcBin,thing_word) == offsetof(struct erl_off_heap_header,thing_word));
@@ -156,9 +182,6 @@ erts_init_gc(void)
ASSERT(offsetof(ProcBin,next) == offsetof(ErlFunThing,next));
ASSERT(offsetof(ProcBin,next) == offsetof(ExternalThing,next));
- erts_smp_spinlock_init(&info_lck, "gc_info");
- garbage_cols = 0;
- reclaimed = 0;
erts_test_long_gc_sleep = 0;
/*
@@ -199,6 +222,16 @@ erts_init_gc(void)
}
}
num_heap_sizes = i;
+
+ for (ix = 0; ix < erts_no_schedulers; ix++) {
+ ErtsSchedulerData *esdp = ERTS_SCHEDULER_IX(ix);
+ init_gc_info(&esdp->gc_info);
+ }
+
+#if !HALFWORD_HEAP
+ init_gcireq_alloc();
+#endif
+
}
/*
@@ -287,17 +320,6 @@ erts_heap_sizes(Process* p)
return res;
}
-void
-erts_gc_info(ErtsGCInfo *gcip)
-{
- if (gcip) {
- erts_smp_spin_lock(&info_lck);
- gcip->garbage_collections = garbage_cols;
- gcip->reclaimed = reclaimed;
- erts_smp_spin_unlock(&info_lck);
- }
-}
-
void
erts_offset_heap(Eterm* hp, Uint sz, Sint offs, Eterm* low, Eterm* high)
{
@@ -378,6 +400,7 @@ erts_garbage_collect(Process* p, int need, Eterm* objv, int nobj)
Uint reclaimed_now = 0;
int done = 0;
Uint ms1, s1, us1;
+ ErtsSchedulerData *esdp = erts_get_scheduler_data();
#ifdef USE_VM_PROBES
DTRACE_CHARBUF(pidbuf, DTRACE_TERM_BUF_SIZE);
#endif
@@ -455,11 +478,9 @@ erts_garbage_collect(Process* p, int need, Eterm* objv, int nobj)
monitor_large_heap(p);
}
- erts_smp_spin_lock(&info_lck);
- garbage_cols++;
- reclaimed += reclaimed_now;
- erts_smp_spin_unlock(&info_lck);
-
+ esdp->gc_info.garbage_cols++;
+ esdp->gc_info.reclaimed += reclaimed_now;
+
FLAGS(p) &= ~F_FORCE_GC;
#ifdef CHECK_FOR_HOLES
@@ -2543,6 +2564,110 @@ offset_rootset(Process *p, Sint offs, char* area, Uint area_size,
offset_one_rootset(p, offs, area, area_size, objv, nobj);
}
+static void
+init_gc_info(ErtsGCInfo *gcip)
+{
+ gcip->reclaimed = 0;
+ gcip->garbage_cols = 0;
+}
+
+static void
+reply_gc_info(void *vgcirp)
+{
+ Uint64 reclaimed = 0, garbage_cols = 0;
+ ErtsSchedulerData *esdp = erts_get_scheduler_data();
+ ErtsGCInfoReq *gcirp = (ErtsGCInfoReq *) vgcirp;
+ ErtsProcLocks rp_locks = (gcirp->req_sched == esdp->no
+ ? ERTS_PROC_LOCK_MAIN
+ : 0);
+ Process *rp = gcirp->proc;
+ Eterm ref_copy = NIL, msg;
+ Eterm *hp = NULL;
+ Eterm **hpp;
+ Uint sz, *szp;
+ ErlOffHeap *ohp = NULL;
+ ErlHeapFragment *bp = NULL;
+
+ ASSERT(esdp);
+
+ reclaimed = esdp->gc_info.reclaimed;
+ garbage_cols = esdp->gc_info.garbage_cols;
+
+ sz = 0;
+ hpp = NULL;
+ szp = &sz;
+
+ while (1) {
+ if (hpp)
+ ref_copy = STORE_NC(hpp, ohp, gcirp->ref);
+ else
+ *szp += REF_THING_SIZE;
+
+ msg = erts_bld_tuple(hpp, szp, 3,
+ make_small(esdp->no),
+ erts_bld_uint64(hpp, szp, garbage_cols),
+ erts_bld_uint64(hpp, szp, reclaimed));
+
+ msg = erts_bld_tuple(hpp, szp, 2, ref_copy, msg);
+ if (hpp)
+ break;
+
+ hp = erts_alloc_message_heap(sz, &bp, &ohp, rp, &rp_locks);
+ szp = NULL;
+ hpp = &hp;
+ }
+
+ erts_queue_message(rp, &rp_locks, bp, msg, NIL
+#ifdef USE_VM_PROBES
+ , NIL
+#endif
+ );
+
+ if (gcirp->req_sched == esdp->no)
+ rp_locks &= ~ERTS_PROC_LOCK_MAIN;
+
+ if (rp_locks)
+ erts_smp_proc_unlock(rp, rp_locks);
+
+ erts_smp_proc_dec_refc(rp);
+
+ if (erts_smp_atomic32_dec_read_nob(&gcirp->refc) == 0)
+ gcireq_free(vgcirp);
+}
+
+Eterm
+erts_gc_info_request(Process *c_p)
+{
+ ErtsSchedulerData *esdp = ERTS_PROC_GET_SCHDATA(c_p);
+ Eterm ref;
+ ErtsGCInfoReq *gcirp;
+ Eterm *hp;
+
+ gcirp = gcireq_alloc();
+ ref = erts_make_ref(c_p);
+ hp = &gcirp->ref_heap[0];
+
+ gcirp->proc = c_p;
+ gcirp->ref = STORE_NC(&hp, NULL, ref);
+ gcirp->req_sched = esdp->no;
+ erts_smp_atomic32_init_nob(&gcirp->refc,
+ (erts_aint32_t) erts_no_schedulers);
+
+ erts_smp_proc_add_refc(c_p, (Sint32) erts_no_schedulers);
+
+#ifdef ERTS_SMP
+ if (erts_no_schedulers > 1)
+ erts_schedule_multi_misc_aux_work(1,
+ erts_no_schedulers,
+ reply_gc_info,
+ (void *) gcirp);
+#endif
+
+ reply_gc_info((void *) gcirp);
+
+ return ref;
+}
+
#if defined(DEBUG) || defined(ERTS_OFFHEAP_DEBUG)
static int
diff --git a/erts/emulator/beam/erl_init.c b/erts/emulator/beam/erl_init.c
index e6a96d427f..b3a3c3d403 100644
--- a/erts/emulator/beam/erl_init.c
+++ b/erts/emulator/beam/erl_init.c
@@ -131,8 +131,10 @@ extern void ConWaitForExit(void);
static void erl_init(int ncpu,
int proc_tab_sz,
+ int legacy_proc_tab,
int port_tab_sz,
- int port_tab_sz_ignore_files);
+ int port_tab_sz_ignore_files,
+ int legacy_port_tab);
static erts_atomic_t exiting;
@@ -280,7 +282,9 @@ erts_short_init(void)
int ncpu = early_init(NULL, NULL);
erl_init(ncpu,
ERTS_DEFAULT_MAX_PROCESSES,
+ 0,
ERTS_DEFAULT_MAX_PORTS,
+ 0,
0);
erts_initialized = 1;
}
@@ -288,19 +292,21 @@ erts_short_init(void)
static void
erl_init(int ncpu,
int proc_tab_sz,
+ int legacy_proc_tab,
int port_tab_sz,
- int port_tab_sz_ignore_files)
+ int port_tab_sz_ignore_files,
+ int legacy_port_tab)
{
init_benchmarking();
erts_init_monitors();
- erts_init_gc();
erts_init_time();
erts_init_sys_common_misc();
- erts_init_process(ncpu, proc_tab_sz);
+ erts_init_process(ncpu, proc_tab_sz, legacy_proc_tab);
erts_init_scheduling(no_schedulers,
no_schedulers_online);
erts_init_cpu_topology(); /* Must be after init_scheduling */
+ erts_init_gc(); /* Must be after init_scheduling */
erts_alloc_late_init();
H_MIN_SIZE = erts_next_heap_size(H_MIN_SIZE, 0);
@@ -327,7 +333,7 @@ erl_init(int ncpu,
init_dist();
erl_drv_thr_init();
erts_init_async();
- erts_init_io(port_tab_sz, port_tab_sz_ignore_files);
+ erts_init_io(port_tab_sz, port_tab_sz_ignore_files, legacy_port_tab);
init_load();
erts_init_bif();
erts_init_bif_chksum();
@@ -923,6 +929,9 @@ erl_start(int argc, char **argv)
int proc_tab_sz = ERTS_DEFAULT_MAX_PROCESSES;
int port_tab_sz = ERTS_DEFAULT_MAX_PORTS;
int port_tab_sz_ignore_files = 0;
+ int legacy_proc_tab = 0;
+ int legacy_port_tab = 0;
+
envbufsz = sizeof(envbuf);
if (erts_sys_getenv_raw(ERL_MAX_ETS_TABLES_ENV, envbuf, &envbufsz) == 0)
@@ -1268,27 +1277,35 @@ erl_start(int argc, char **argv)
case 'P': /* set maximum number of processes */
arg = get_arg(argv[i]+2, argv[i+1], &i);
- errno = 0;
- proc_tab_sz = strtol(arg, NULL, 10);
- if (errno != 0
- || proc_tab_sz < ERTS_MIN_PROCESSES
- || ERTS_MAX_PROCESSES < proc_tab_sz) {
- erts_fprintf(stderr, "bad number of processes %s\n", arg);
- erts_usage();
+ if (strcmp(arg, "legacy") == 0)
+ legacy_proc_tab = 1;
+ else {
+ errno = 0;
+ proc_tab_sz = strtol(arg, NULL, 10);
+ if (errno != 0
+ || proc_tab_sz < ERTS_MIN_PROCESSES
+ || ERTS_MAX_PROCESSES < proc_tab_sz) {
+ erts_fprintf(stderr, "bad number of processes %s\n", arg);
+ erts_usage();
+ }
}
break;
case 'Q': /* set maximum number of ports */
arg = get_arg(argv[i]+2, argv[i+1], &i);
- errno = 0;
- port_tab_sz = strtol(arg, NULL, 10);
- if (errno != 0
- || port_tab_sz < ERTS_MIN_PROCESSES
- || ERTS_MAX_PROCESSES < port_tab_sz) {
- erts_fprintf(stderr, "bad number of ports %s\n", arg);
- erts_usage();
+ if (strcmp(arg, "legacy") == 0)
+ legacy_port_tab = 1;
+ else {
+ errno = 0;
+ port_tab_sz = strtol(arg, NULL, 10);
+ if (errno != 0
+ || port_tab_sz < ERTS_MIN_PROCESSES
+ || ERTS_MAX_PROCESSES < port_tab_sz) {
+ erts_fprintf(stderr, "bad number of ports %s\n", arg);
+ erts_usage();
+ }
+ port_tab_sz_ignore_files = 1;
}
- port_tab_sz_ignore_files = 1;
break;
case 'S' : /* Was handled in early_init() just read past it */
@@ -1642,8 +1659,10 @@ erl_start(int argc, char **argv)
erl_init(ncpu,
proc_tab_sz,
+ legacy_proc_tab,
port_tab_sz,
- port_tab_sz_ignore_files);
+ port_tab_sz_ignore_files,
+ legacy_port_tab);
load_preloaded();
erts_end_staging_code_ix();
diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c
index 7415a5721f..81799ddbb1 100644
--- a/erts/emulator/beam/erl_process.c
+++ b/erts/emulator/beam/erl_process.c
@@ -485,7 +485,7 @@ release_process(void *vproc)
/* initialize the scheduler */
void
-erts_init_process(int ncpu, int proc_tab_size)
+erts_init_process(int ncpu, int proc_tab_size, int legacy_proc_tab)
{
#ifdef ERTS_SMP
@@ -505,7 +505,8 @@ erts_init_process(int ncpu, int proc_tab_size)
(ErtsPTabElementCommon *) &erts_invalid_process.common,
proc_tab_size,
sizeof(Process),
- "process_table");
+ "process_table",
+ legacy_proc_tab);
last_reductions = 0;
last_exact_reductions = 0;
@@ -4670,8 +4671,8 @@ erts_init_scheduling(int no_schedulers, int no_schedulers_online)
esdp->reductions = 0;
init_sched_wall_time(&esdp->sched_wall_time);
-
erts_port_task_handle_init(&esdp->nosuspend_port_task_handle);
+
}
init_misc_aux_work();
diff --git a/erts/emulator/beam/erl_process.h b/erts/emulator/beam/erl_process.h
index 5a1f6bbe8d..7dd8116857 100644
--- a/erts/emulator/beam/erl_process.h
+++ b/erts/emulator/beam/erl_process.h
@@ -420,6 +420,11 @@ typedef struct {
} ErtsSchedWallTime;
typedef struct {
+ Uint64 reclaimed;
+ Uint64 garbage_cols;
+} ErtsGCInfo;
+
+typedef struct {
int sched;
erts_aint32_t aux_work;
} ErtsDelayedAuxWorkWakeupJob;
@@ -507,6 +512,7 @@ struct ErtsSchedulerData_ {
Uint64 reductions;
ErtsSchedWallTime sched_wall_time;
+ ErtsGCInfo gc_info;
ErtsPortTaskHandle nosuspend_port_task_handle;
#ifdef ERTS_DO_VERIFY_UNUSED_TEMP_ALLOC
@@ -1126,6 +1132,7 @@ void erts_early_init_scheduling(int);
void erts_init_scheduling(int, int);
Eterm erts_sched_wall_time_request(Process *c_p, int set, int enable);
+Eterm erts_gc_info_request(Process *c_p);
Uint64 erts_get_proc_interval(void);
Uint64 erts_ensure_later_proc_interval(Uint64);
Uint64 erts_step_proc_interval(void);
@@ -1352,7 +1359,7 @@ void erts_schedule_multi_misc_aux_work(int ignore_self,
erts_aint32_t erts_set_aux_work_timeout(int, erts_aint32_t, int);
void erts_sched_notify_check_cpu_bind(void);
Uint erts_active_schedulers(void);
-void erts_init_process(int, int);
+void erts_init_process(int, int, int);
Eterm erts_process_status(Process *, ErtsProcLocks, Process *, Eterm);
Uint erts_run_queues_len(Uint *);
void erts_add_to_runq(Process *);
diff --git a/erts/emulator/beam/erl_ptab.c b/erts/emulator/beam/erl_ptab.c
index 5bbc71c659..8da135b2c8 100644
--- a/erts/emulator/beam/erl_ptab.c
+++ b/erts/emulator/beam/erl_ptab.c
@@ -34,6 +34,8 @@
typedef struct ErtsPTabListBifData_ ErtsPTabListBifData;
+#define ERTS_PTAB_NEW_MAX_RESERVE_FAIL 1000
+
#define ERTS_PTAB_LIST_BIF_TAB_INSPECT_INDICES_PER_RED 25
#define ERTS_PTAB_LIST_BIF_TAB_CHUNK_SIZE 1000
#define ERTS_PTAB_LIST_BIF_MIN_START_REDS \
@@ -415,6 +417,27 @@ last_data_cmp(Uint64 ld1, Uint64 ld2)
#define ERTS_PTAB_LastData2EtermData(LD) \
((Eterm) ((LD) & ~(~((Uint64) 0) << ERTS_PTAB_ID_DATA_SIZE)))
+static ERTS_INLINE Uint32
+ix_to_free_id_data_ix(ErtsPTab *ptab, Uint32 ix)
+{
+ Uint32 dix;
+
+ dix = ((ix & ptab->r.o.dix_cl_mask) << ptab->r.o.dix_cl_shift);
+ dix += ((ix >> ptab->r.o.dix_cli_shift) & ptab->r.o.dix_cli_mask);
+ ASSERT(0 <= dix && dix < ptab->r.o.max);
+ return dix;
+}
+
+UWord
+erts_ptab_mem_size(ErtsPTab *ptab)
+{
+ UWord size = ptab->r.o.max*sizeof(erts_smp_atomic_t);
+ if (ptab->r.o.free_id_data)
+ size += ptab->r.o.max*sizeof(Uint32);
+ return size;
+}
+
+
void
erts_ptab_init_table(ErtsPTab *ptab,
ErtsAlcType_t atype,
@@ -422,10 +445,11 @@ erts_ptab_init_table(ErtsPTab *ptab,
ErtsPTabElementCommon *invalid_element,
int size,
UWord element_size,
- char *name)
+ char *name,
+ int legacy)
{
- size_t tab_sz;
- int bits;
+ size_t tab_sz, alloc_sz;
+ Uint32 bits, cl, cli, ix, ix_per_cache_line, tab_cache_lines;
char *tab_end;
erts_smp_atomic_t *tab_entry;
erts_smp_rwmtx_opt_t rwmtx_opts = ERTS_SMP_RWMTX_OPT_DEFAULT_INITER;
@@ -448,7 +472,10 @@ erts_ptab_init_table(ErtsPTab *ptab,
ptab->r.o.max = size;
tab_sz = ERTS_ALC_CACHE_LINE_ALIGN_SIZE(size*sizeof(erts_smp_atomic_t));
- ptab->r.o.tab = erts_alloc_permanent_cache_aligned(atype, tab_sz);
+ alloc_sz = tab_sz;
+ if (!legacy)
+ alloc_sz += ERTS_ALC_CACHE_LINE_ALIGN_SIZE(size*sizeof(Uint32));
+ ptab->r.o.tab = erts_alloc_permanent_cache_aligned(atype, alloc_sz);
tab_end = ((char *) ptab->r.o.tab) + tab_sz;
tab_entry = ptab->r.o.tab;
while (tab_end > ((char *) tab_entry)) {
@@ -456,28 +483,57 @@ erts_ptab_init_table(ErtsPTab *ptab,
tab_entry++;
}
- ptab->r.o.tab_cache_lines = tab_sz/ERTS_CACHE_LINE_SIZE;
- ptab->r.o.pix_per_cache_line = (ERTS_CACHE_LINE_SIZE
- / sizeof(erts_smp_atomic_t));
+ tab_cache_lines = tab_sz/ERTS_CACHE_LINE_SIZE;
+ ix_per_cache_line = (ERTS_CACHE_LINE_SIZE/sizeof(erts_smp_atomic_t));
ASSERT((ptab->r.o.max & (ptab->r.o.max - 1)) == 0); /* power of 2 */
- ASSERT((ptab->r.o.pix_per_cache_line
- & (ptab->r.o.pix_per_cache_line - 1)) == 0); /* power of 2 */
- ASSERT((ptab->r.o.tab_cache_lines
- & (ptab->r.o.tab_cache_lines - 1)) == 0); /* power of 2 */
-
- ptab->r.o.pix_mask
- = (1 << bits) - 1;
- ptab->r.o.pix_cl_mask
- = ptab->r.o.tab_cache_lines-1;
- ptab->r.o.pix_cl_shift
- = erts_fit_in_bits_int32(ptab->r.o.pix_per_cache_line-1);
- ptab->r.o.pix_cli_shift
- = erts_fit_in_bits_int32(ptab->r.o.pix_cl_mask);
- ptab->r.o.pix_cli_mask
- = (1 << (bits - ptab->r.o.pix_cli_shift)) - 1;
+ ASSERT((ix_per_cache_line & (ix_per_cache_line - 1)) == 0); /* power of 2 */
+ ASSERT((tab_cache_lines & (tab_cache_lines - 1)) == 0); /* power of 2 */
+
+ ptab->r.o.pix_mask = (1 << bits) - 1;
+ ptab->r.o.pix_cl_mask = tab_cache_lines-1;
+ ptab->r.o.pix_cl_shift = erts_fit_in_bits_int32(ix_per_cache_line-1);
+ ptab->r.o.pix_cli_shift = erts_fit_in_bits_int32(ptab->r.o.pix_cl_mask);
+ ptab->r.o.pix_cli_mask = (1 << (bits - ptab->r.o.pix_cli_shift)) - 1;
ASSERT(ptab->r.o.pix_cl_shift + ptab->r.o.pix_cli_shift == bits);
+ if (legacy) {
+ ptab->r.o.free_id_data = NULL;
+ ptab->r.o.dix_cl_mask = 0;
+ ptab->r.o.dix_cl_shift = 0;
+ ptab->r.o.dix_cli_shift = 0;
+ ptab->r.o.dix_cli_mask = 0;
+ }
+ else {
+
+ tab_sz = ERTS_ALC_CACHE_LINE_ALIGN_SIZE(size*sizeof(Uint32));
+ ptab->r.o.free_id_data = (Uint32 *) tab_end;
+
+ tab_cache_lines = tab_sz/ERTS_CACHE_LINE_SIZE;
+ ix_per_cache_line = (ERTS_CACHE_LINE_SIZE/sizeof(Uint32));
+
+ ptab->r.o.dix_cl_mask = tab_cache_lines-1;
+ ptab->r.o.dix_cl_shift = erts_fit_in_bits_int32(ix_per_cache_line-1);
+ ptab->r.o.dix_cli_shift = erts_fit_in_bits_int32(ptab->r.o.dix_cl_mask);
+ ptab->r.o.dix_cli_mask = (1 << (bits - ptab->r.o.dix_cli_shift)) - 1;
+
+ ASSERT((ix_per_cache_line & (ix_per_cache_line - 1)) == 0); /* power of 2 */
+ ASSERT((tab_cache_lines & (tab_cache_lines - 1)) == 0); /* power of 2 */
+
+ ASSERT(ptab->r.o.dix_cl_shift + ptab->r.o.dix_cli_shift == bits);
+
+ ix = 0;
+ for (cl = 0; cl < tab_cache_lines; cl++) {
+ for (cli = 0; cli < ix_per_cache_line; cli++) {
+ ptab->r.o.free_id_data[ix] = cli*tab_cache_lines+cl;
+ ix++;
+ }
+ }
+
+ erts_smp_atomic32_init_nob(&ptab->vola.tile.aid_ix, -1);
+ erts_smp_atomic32_init_nob(&ptab->vola.tile.fid_ix, -1);
+
+ }
ptab->r.o.invalid_element = invalid_element;
ptab->r.o.invalid_data = erts_ptab_id2data(ptab, invalid_element->id);
ptab->r.o.release_element = release_element;
@@ -522,9 +578,7 @@ erts_ptab_new_element(ErtsPTab *ptab,
void *init_arg,
void (*init_ptab_el)(void *, Eterm))
{
- int pix;
- Uint64 ld, exp_ld;
- Eterm data;
+ Uint32 pix, ix, data;
erts_aint32_t count;
erts_aint_t invalid = (erts_aint_t) ptab->r.o.invalid_element;
@@ -551,62 +605,108 @@ erts_ptab_new_element(ErtsPTab *ptab,
ptab_el->u.alive.started_interval
= erts_smp_current_interval_nob(erts_ptab_interval(ptab));
- ld = last_data_read_acqb(ptab);
+ if (ptab->r.o.free_id_data) {
- /* Reserve slot */
- while (1) {
- ld++;
- pix = erts_ptab_data2pix(ptab, ERTS_PTAB_LastData2EtermData(ld));
- if (erts_smp_atomic_read_nob(&ptab->r.o.tab[pix]) == ERTS_AINT_NULL) {
- erts_aint_t val;
- val = erts_smp_atomic_cmpxchg_relb(&ptab->r.o.tab[pix],
- invalid,
- ERTS_AINT_NULL);
+ ix = (Uint32) erts_smp_atomic32_inc_read_acqb(&ptab->vola.tile.aid_ix);
+ ix = ix_to_free_id_data_ix(ptab, ix);
+
+ data = ptab->r.o.free_id_data[ix];
+
+ init_ptab_el(init_arg, (Eterm) data);
+
+#ifdef ERTS_SMP
+ erts_smp_atomic32_init_nob(&ptab_el->refc, 1);
+#endif
+
+ pix = erts_ptab_data2pix(ptab, (Eterm) data);
+
+#ifdef DEBUG
+ ASSERT(ERTS_AINT_NULL == erts_smp_atomic_xchg_relb(&ptab->r.o.tab[pix],
+ (erts_aint_t) ptab_el));
+#else
+ erts_smp_atomic_set_relb(&ptab->r.o.tab[pix], (erts_aint_t) ptab_el);
+#endif
+
+ erts_ptab_runlock(ptab);
- if (ERTS_AINT_NULL == val)
- break;
- }
}
+ else {
+ int rlocked = ERTS_PTAB_NEW_MAX_RESERVE_FAIL;
+ Uint64 ld, exp_ld;
+ /* Deprecated legacy algorithm... */
+
+ restart:
+
+ ptab_el->u.alive.started_interval
+ = erts_smp_current_interval_nob(erts_ptab_interval(ptab));
- data = ERTS_PTAB_LastData2EtermData(ld);
+ ld = last_data_read_acqb(ptab);
+
+ /* Reserve slot */
+ while (1) {
+ ld++;
+ pix = erts_ptab_data2pix(ptab, ERTS_PTAB_LastData2EtermData(ld));
+ if (erts_smp_atomic_read_nob(&ptab->r.o.tab[pix])
+ == ERTS_AINT_NULL) {
+ erts_aint_t val;
+ val = erts_smp_atomic_cmpxchg_relb(&ptab->r.o.tab[pix],
+ invalid,
+ ERTS_AINT_NULL);
+
+ if (ERTS_AINT_NULL == val)
+ break;
+ }
+ if (rlocked && --rlocked == 0) {
+ erts_ptab_runlock(ptab);
+ erts_ptab_rwlock(ptab);
+ goto restart;
+ }
+ }
- if (data == ptab->r.o.invalid_data) {
- /* Do not use invalid data; fix it... */
- ld += ptab->r.o.max;
- ASSERT(pix == erts_ptab_data2pix(ptab,
- ERTS_PTAB_LastData2EtermData(ld)));
data = ERTS_PTAB_LastData2EtermData(ld);
- ASSERT(data != ptab->r.o.invalid_data);
- }
- exp_ld = last_data_read_nob(ptab);
+ if (data == ptab->r.o.invalid_data) {
+ /* Do not use invalid data; fix it... */
+ ld += ptab->r.o.max;
+ ASSERT(pix == erts_ptab_data2pix(ptab,
+ ERTS_PTAB_LastData2EtermData(ld)));
+ data = ERTS_PTAB_LastData2EtermData(ld);
+ ASSERT(data != ptab->r.o.invalid_data);
+ }
- /* Move last data forward */
- while (1) {
- Uint64 act_ld;
- if (last_data_cmp(ld, exp_ld) < 0)
- break;
- act_ld = last_data_cmpxchg_relb(ptab, ld, exp_ld);
- if (act_ld == exp_ld)
- break;
- exp_ld = act_ld;
- }
+ exp_ld = last_data_read_nob(ptab);
+
+ /* Move last data forward */
+ while (1) {
+ Uint64 act_ld;
+ if (last_data_cmp(ld, exp_ld) < 0)
+ break;
+ act_ld = last_data_cmpxchg_relb(ptab, ld, exp_ld);
+ if (act_ld == exp_ld)
+ break;
+ exp_ld = act_ld;
+ }
- init_ptab_el(init_arg, data);
+ init_ptab_el(init_arg, data);
#ifdef ERTS_SMP
- erts_smp_atomic32_init_nob(&ptab_el->refc, 1);
+ erts_smp_atomic32_init_nob(&ptab_el->refc, 1);
#endif
- /* Move into slot reserved */
+ /* Move into slot reserved */
#ifdef DEBUG
- ASSERT(invalid == erts_smp_atomic_xchg_relb(&ptab->r.o.tab[pix],
+ ASSERT(invalid == erts_smp_atomic_xchg_relb(&ptab->r.o.tab[pix],
(erts_aint_t) ptab_el));
#else
- erts_smp_atomic_set_relb(&ptab->r.o.tab[pix], (erts_aint_t) ptab_el);
+ erts_smp_atomic_set_relb(&ptab->r.o.tab[pix], (erts_aint_t) ptab_el);
#endif
- erts_ptab_runlock(ptab);
+ if (rlocked)
+ erts_ptab_runlock(ptab);
+ else
+ erts_ptab_rwunlock(ptab);
+
+ }
return 1;
}
@@ -647,7 +747,9 @@ erts_ptab_delete_element(ErtsPTab *ptab,
ErtsPTabElementCommon *ptab_el)
{
int maybe_save;
- int pix = erts_ptab_id2pix(ptab, ptab_el->id);
+ Uint32 pix, ix, data;
+
+ pix = erts_ptab_id2pix(ptab, ptab_el->id);
ASSERT(erts_get_scheduler_id()); /* *Need* to be a scheduler */
@@ -660,6 +762,26 @@ erts_ptab_delete_element(ErtsPTab *ptab,
erts_smp_atomic_set_relb(&ptab->r.o.tab[pix], ERTS_AINT_NULL);
+ if (ptab->r.o.free_id_data) {
+
+ /* Next data for this slot... */
+ data = (Uint32) erts_ptab_id2data(ptab, ptab_el->id);
+ data += ptab->r.o.max;
+ data &= ~(~((Uint32) 0) << ERTS_PTAB_ID_DATA_SIZE);
+ if (data == ptab->r.o.invalid_data) { /* make sure not invalid */
+ data += ptab->r.o.max;
+ data &= ~(~((Uint32) 0) << ERTS_PTAB_ID_DATA_SIZE);
+ }
+
+ ASSERT(data != ptab->r.o.invalid_data);
+ ASSERT(pix == erts_ptab_data2pix(ptab, data));
+
+ ix = (Uint32) erts_smp_atomic32_inc_read_relb(&ptab->vola.tile.fid_ix);
+ ix = ix_to_free_id_data_ix(ptab, ix);
+
+ ptab->r.o.free_id_data[ix] = data;
+ }
+
ASSERT(erts_smp_atomic32_read_nob(&ptab->vola.tile.count) > 0);
erts_smp_atomic32_dec_relb(&ptab->vola.tile.count);
@@ -1280,42 +1402,86 @@ erts_ptab_test_next_id(ErtsPTab *ptab, int set, Uint next)
erts_ptab_rwlock(ptab);
- if (!set)
- ld = last_data_read_nob(ptab);
- else {
+ if (ptab->r.o.free_id_data) {
+ Uint32 aid_ix, dix;
- ld = (Uint64) next;
- data = ERTS_PTAB_LastData2EtermData(ld);
- if (ptab->r.o.invalid_data == data) {
- ld += ptab->r.o.max;
- ASSERT(erts_ptab_data2pix(ptab, data)
- == erts_ptab_data2pix(ptab,
- ERTS_PTAB_LastData2EtermData(ld)));
+ if (set) {
+ Uint32 max_ix, ser, num, start;
+ max_ix = ptab->r.o.max - 1;
+ ser = next & ~max_ix;
+ start = num = next & max_ix;
+
+ aid_ix = (Uint32) erts_smp_atomic32_read_nob(&ptab->vola.tile.aid_ix) + 1;
+
+ do {
+ Uint32 pix = erts_ptab_data2pix(ptab, num);
+ if (ERTS_AINT_NULL == erts_ptab_pix2intptr_nob(ptab, pix)) {
+ dix = ix_to_free_id_data_ix(ptab, aid_ix);
+ ptab->r.o.free_id_data[dix] = ser + num;
+ ASSERT(pix == erts_ptab_data2pix(ptab, ser+num));
+ if (aid_ix == max_ix)
+ aid_ix = 0;
+ else
+ aid_ix++;
+ }
+ if (num == max_ix)
+ num = 0;
+ else
+ num++;
+ } while (num != start);
+
+#ifdef DEBUG
+ if (aid_ix == 0)
+ aid_ix = max_ix;
+ else
+ aid_ix--;
+ ASSERT((aid_ix & max_ix) == (((Uint32) erts_atomic32_read_nob(&ptab->vola.tile.fid_ix)) & max_ix));
+#endif
}
- last_data_set_relb(ptab, ld);
+
+ aid_ix = (Uint32) erts_smp_atomic32_read_nob(&ptab->vola.tile.aid_ix) + 1;
+ dix = ix_to_free_id_data_ix(ptab, aid_ix);
+ res = (Sint) ptab->r.o.free_id_data[dix];
}
+ else {
+ /* Deprecated legacy algorithm... */
+ if (!set)
+ ld = last_data_read_nob(ptab);
+ else {
- while (1) {
- int pix;
- ld++;
- pix = (int) (ld % ptab->r.o.max);
- if (first_pix < 0)
- first_pix = pix;
- else if (pix == first_pix) {
- res = -1;
- break;
- }
- if (ERTS_AINT_NULL == erts_ptab_pix2intptr_nob(ptab, pix)) {
+ ld = (Uint64) next;
data = ERTS_PTAB_LastData2EtermData(ld);
if (ptab->r.o.invalid_data == data) {
ld += ptab->r.o.max;
ASSERT(erts_ptab_data2pix(ptab, data)
== erts_ptab_data2pix(ptab,
ERTS_PTAB_LastData2EtermData(ld)));
+ }
+ last_data_set_relb(ptab, ld);
+ }
+
+ while (1) {
+ int pix;
+ ld++;
+ pix = (int) (ld % ptab->r.o.max);
+ if (first_pix < 0)
+ first_pix = pix;
+ else if (pix == first_pix) {
+ res = -1;
+ break;
+ }
+ if (ERTS_AINT_NULL == erts_ptab_pix2intptr_nob(ptab, pix)) {
data = ERTS_PTAB_LastData2EtermData(ld);
+ if (ptab->r.o.invalid_data == data) {
+ ld += ptab->r.o.max;
+ ASSERT(erts_ptab_data2pix(ptab, data)
+ == erts_ptab_data2pix(ptab,
+ ERTS_PTAB_LastData2EtermData(ld)));
+ data = ERTS_PTAB_LastData2EtermData(ld);
+ }
+ res = data;
+ break;
}
- res = data;
- break;
}
}
diff --git a/erts/emulator/beam/erl_ptab.h b/erts/emulator/beam/erl_ptab.h
index 7fa1251900..84ff7d0de4 100644
--- a/erts/emulator/beam/erl_ptab.h
+++ b/erts/emulator/beam/erl_ptab.h
@@ -94,18 +94,23 @@ typedef struct {
erts_smp_atomic_t last_data;
#endif
erts_smp_atomic32_t count;
+ erts_smp_atomic32_t aid_ix;
+ erts_smp_atomic32_t fid_ix;
} ErtsPTabVolatileData;
typedef struct {
erts_smp_atomic_t *tab;
+ Uint32 *free_id_data;
Uint32 max;
- Uint32 tab_cache_lines;
- Uint32 pix_per_cache_line;
Uint32 pix_mask;
Uint32 pix_cl_mask;
Uint32 pix_cl_shift;
Uint32 pix_cli_mask;
Uint32 pix_cli_shift;
+ Uint32 dix_cl_mask;
+ Uint32 dix_cl_shift;
+ Uint32 dix_cli_mask;
+ Uint32 dix_cli_shift;
ErtsPTabElementCommon *invalid_element;
Eterm invalid_data;
void (*release_element)(void *);
@@ -179,7 +184,8 @@ void erts_ptab_init_table(ErtsPTab *ptab,
ErtsPTabElementCommon *invalid_element,
int size,
UWord element_size,
- char *name);
+ char *name,
+ int legacy);
int erts_ptab_new_element(ErtsPTab *ptab,
ErtsPTabElementCommon *ptab_el,
void *init_arg,
@@ -187,6 +193,7 @@ int erts_ptab_new_element(ErtsPTab *ptab,
void erts_ptab_delete_element(ErtsPTab *ptab,
ErtsPTabElementCommon *ptab_el);
int erts_ptab_initialized(ErtsPTab *ptab);
+UWord erts_ptab_mem_size(ErtsPTab *ptab);
ERTS_GLB_INLINE erts_interval_t *erts_ptab_interval(ErtsPTab *ptab);
ERTS_GLB_INLINE int erts_ptab_max(ErtsPTab *ptab);
diff --git a/erts/emulator/beam/global.h b/erts/emulator/beam/global.h
index 012c1c7e6a..26ed5f82c1 100755
--- a/erts/emulator/beam/global.h
+++ b/erts/emulator/beam/global.h
@@ -688,12 +688,6 @@ void MD5Final(unsigned char [16], MD5_CTX *);
/* ggc.c */
-
-typedef struct {
- Uint garbage_collections;
- Uint reclaimed;
-} ErtsGCInfo;
-
void erts_gc_info(ErtsGCInfo *gcip);
void erts_init_gc(void);
int erts_garbage_collect(Process*, int, Eterm*, int);
@@ -724,7 +718,7 @@ int erts_add_driver_entry(ErlDrvEntry *drv, DE_Handle *handle, int driver_list_l
void erts_destroy_driver(erts_driver_t *drv);
int erts_save_suspend_process_on_port(Port*, Process*);
Port *erts_open_driver(erts_driver_t*, Eterm, char*, SysDriverOpts*, int *, int *);
-void erts_init_io(int, int);
+void erts_init_io(int, int, int);
void erts_raw_port_command(Port*, byte*, Uint);
void driver_report_exit(ErlDrvPort, int);
LineBuf* allocate_linebuf(int);
diff --git a/erts/emulator/beam/io.c b/erts/emulator/beam/io.c
index b6b7b47bd6..13cff24b95 100644
--- a/erts/emulator/beam/io.c
+++ b/erts/emulator/beam/io.c
@@ -2707,7 +2707,8 @@ erts_port_link(Process *c_p, Port *prt, Eterm to, Eterm *refp)
}
void erts_init_io(int port_tab_size,
- int port_tab_size_ignore_files)
+ int port_tab_size_ignore_files,
+ int legacy_port_tab)
{
ErlDrvEntry** dp;
UWord common_element_size;
@@ -2750,7 +2751,8 @@ void erts_init_io(int port_tab_size,
(ErtsPTabElementCommon *) &erts_invalid_port.common,
port_tab_size,
common_element_size, /* Doesn't need to be excact */
- "port_table");
+ "port_table",
+ legacy_port_tab);
erts_smp_atomic_init_nob(&erts_bytes_out, 0);
erts_smp_atomic_init_nob(&erts_bytes_in, 0);
diff --git a/erts/emulator/test/process_SUITE.erl b/erts/emulator/test/process_SUITE.erl
index 863cd2d654..72f3e8fe85 100644
--- a/erts/emulator/test/process_SUITE.erl
+++ b/erts/emulator/test/process_SUITE.erl
@@ -1494,6 +1494,7 @@ processes_bif_cleaner() ->
spawn_initial_hangarounds(Cleaner) ->
TabSz = erlang:system_info(process_limit),
+ erts_debug:set_internal_state(next_pid,TabSz),
spawn_initial_hangarounds(Cleaner,
TabSz,
TabSz*2,
@@ -1538,14 +1539,21 @@ hangaround(Cleaner, Type) ->
spawn_initial_hangarounds(_Cleaner, NP, Max, Len, HAs) when NP > Max ->
{Len, HAs};
spawn_initial_hangarounds(Cleaner, NP, Max, Len, HAs) ->
- erts_debug:set_internal_state(next_pid,NP),
+ Skip = 30,
HA1 = spawn_opt(?MODULE, hangaround, [Cleaner, initial_hangaround],
[{priority, low}]),
HA2 = spawn_opt(?MODULE, hangaround, [Cleaner, initial_hangaround],
[{priority, normal}]),
HA3 = spawn_opt(?MODULE, hangaround, [Cleaner, initial_hangaround],
[{priority, high}]),
- spawn_initial_hangarounds(Cleaner, NP+30, Max, Len+3, [HA1,HA2,HA3|HAs]).
+ spawn_drop(Skip),
+ spawn_initial_hangarounds(Cleaner, NP+Skip, Max, Len+3, [HA1,HA2,HA3|HAs]).
+
+spawn_drop(N) when N =< 0 ->
+ ok;
+spawn_drop(N) ->
+ spawn(fun () -> ok end),
+ spawn_drop(N-1).
do_processes(WantReds) ->
erts_debug:set_internal_state(reds_left, WantReds),
diff --git a/erts/preloaded/ebin/erlang.beam b/erts/preloaded/ebin/erlang.beam
index 308cb99be5..315f9ebd47 100644
--- a/erts/preloaded/ebin/erlang.beam
+++ b/erts/preloaded/ebin/erlang.beam
Binary files differ
diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl
index 7106c0a4fb..6929ca3fa5 100644
--- a/erts/preloaded/src/erlang.erl
+++ b/erts/preloaded/src/erlang.erl
@@ -45,7 +45,8 @@
-export([alloc_info/1, alloc_sizes/1]).
-export([gather_sched_wall_time_result/1,
- await_sched_wall_time_modifications/2]).
+ await_sched_wall_time_modifications/2,
+ gather_gc_info_result/1]).
-deprecated([hash/2]).
@@ -3536,3 +3537,20 @@ sched_wall_time(Ref, N, Acc) ->
{Ref, undefined} -> sched_wall_time(Ref, N-1, undefined);
{Ref, SWT} -> sched_wall_time(Ref, N-1, [SWT|Acc])
end.
+
+-spec erlang:gather_gc_info_result(Ref) -> [{pos_integer(),
+ pos_integer(),
+ 0}] when
+ Ref :: reference().
+
+gather_gc_info_result(Ref) when erlang:is_reference(Ref) ->
+ gc_info(Ref, erlang:system_info(schedulers), {0,0}).
+
+gc_info(_Ref, 0, {Colls,Recl}) ->
+ {Colls,Recl,0};
+gc_info(Ref, N, {OrigColls,OrigRecl}) ->
+ receive
+ {Ref, {_,Colls, Recl}} ->
+ gc_info(Ref, N-1, {Colls+OrigColls,Recl+OrigRecl})
+ end.
+
diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl
index 03aa557c2e..55aae3a243 100644
--- a/lib/diameter/include/diameter_gen.hrl
+++ b/lib/diameter/include/diameter_gen.hrl
@@ -25,11 +25,23 @@
-define(THROW(T), throw({?MODULE, T})).
-%%% ---------------------------------------------------------------------------
-%%% # encode_avps/3
-%%%
-%%% Returns: binary()
-%%% ---------------------------------------------------------------------------
+-type parent_name() :: atom(). %% parent = Message or AVP
+-type parent_record() :: tuple(). %%
+-type avp_name() :: atom().
+-type avp_record() :: tuple().
+-type avp_values() :: [{avp_name(), term()}].
+
+-type non_grouped_avp() :: #diameter_avp{}.
+-type grouped_avp() :: nonempty_improper_list(#diameter_avp{}, [avp()]).
+-type avp() :: non_grouped_avp() | grouped_avp().
+
+%% ---------------------------------------------------------------------------
+%% # encode_avps/2
+%% ---------------------------------------------------------------------------
+
+-spec encode_avps(parent_name(), parent_record() | avp_values())
+ -> binary()
+ | no_return().
encode_avps(Name, Vals)
when is_list(Vals) ->
@@ -129,42 +141,26 @@ pack_AVP(Name, #diameter_avp{name = AvpName,
orelse ?THROW([known_avp_as_AVP, Name, AvpName, Data]),
e(AvpName, [Data]).
-%%% ---------------------------------------------------------------------------
-%%% # decode_avps/2
-%%%
-%%% Returns: {Rec, Avps, Failed}
-%%%
-%%% Rec = decoded message record
-%%% Avps = list of Avp
-%%% Failed = list of {ResultCode, #diameter_avp{}}
-%%%
-%%% Avp = #diameter_avp{} if type is not Grouped
-%%% | list of Avp where first element has type Grouped
-%%% and following elements are its component
-%%% AVP's.
-%%% ---------------------------------------------------------------------------
+%% ---------------------------------------------------------------------------
+%% # decode_avps/2
+%% ---------------------------------------------------------------------------
+
+-spec decode_avps(parent_name(), [#diameter_avp{}])
+ -> {parent_record(), [avp()], Failed}
+ when Failed :: [{5000..5999, #diameter_avp{}}].
decode_avps(Name, Recs) ->
- d_rc(Name, lists:foldl(fun(T,A) -> decode(Name, T, A) end,
- {[], {newrec(Name), []}},
- Recs)).
+ {Avps, {Rec, Failed}}
+ = lists:foldl(fun(T,A) -> decode(Name, T, A) end,
+ {[], {newrec(Name), []}},
+ Recs),
+ {Rec, Avps, Failed ++ missing(Rec, Name)}.
+%% Append 5005 errors so that a 5014 for the same AVP will take
+%% precedence in a Result-Code/Failed-AVP setting.
newrec(Name) ->
'#new-'(name2rec(Name)).
-%% No errors so far: keep looking.
-d_rc(Name, {Avps, {Rec, [] = Failed}}) ->
- try
- true = have_required_avps(Rec, Name),
- {Rec, Avps, Failed}
- catch
- throw: {?MODULE, {AvpName, Reason}} ->
- diameter_lib:log({decode, error},
- ?MODULE,
- ?LINE,
- {AvpName, Reason, Rec}),
- {Rec, Avps, [{5005, empty_avp(AvpName)}]}
- end;
%% 3588:
%%
%% DIAMETER_MISSING_AVP 5005
@@ -175,9 +171,17 @@ d_rc(Name, {Avps, {Rec, [] = Failed}}) ->
%% Vendor-Id if applicable. The value field of the missing AVP
%% should be of correct minimum length and contain zeroes.
-%% Or not. Only need to report the first error so look no further.
-d_rc(_, {Avps, {Rec, Failed}}) ->
- {Rec, Avps, lists:reverse(Failed)}.
+missing(Rec, Name) ->
+ [{5005, empty_avp(F)} || F <- '#info-'(element(1, Rec), fields),
+ A <- [avp_arity(Name, F)],
+ false <- [have_arity(A, '#get-'(F, Rec))]].
+
+%% Maximum arities have already been checked in building the record.
+
+have_arity({Min, _}, L) ->
+ Min =< length(L);
+have_arity(N, V) ->
+ N /= 1 orelse V /= undefined.
%% empty_avp/1
@@ -192,25 +196,6 @@ empty_avp(Name) ->
data = empty_value(Name),
type = Type}.
-%% have_required_avps/2
-
-have_required_avps(Rec, Name) ->
- lists:foreach(fun(F) -> hra(Name, F, Rec) end,
- '#info-'(element(1, Rec), fields)),
- true.
-
-hra(Name, AvpName, Rec) ->
- Arity = avp_arity(Name, AvpName),
- hra(Arity, '#get-'(AvpName, Rec))
- orelse ?THROW({AvpName, {insufficient_arity, Arity}}).
-
-%% Maximum arities have already been checked in building the record.
-
-hra({Min, _}, L) ->
- Min =< length(L);
-hra(N, V) ->
- N /= 1 orelse V /= undefined.
-
%% 3588, ch 7:
%%
%% The Result-Code AVP describes the error that the Diameter node
@@ -227,23 +212,22 @@ decode(Name, #diameter_avp{code = Code, vendor_id = Vid} = Avp, Acc) ->
%% decode/4
-%% Don't know this AVP: see if it can be packed in an 'AVP' field
-%% undecoded, unless it's mandatory. Need to give Failed-AVP special
-%% treatment since it'll contain any unrecognized mandatory AVP's.
-decode(Name, 'AVP', #diameter_avp{is_mandatory = M} = Avp, {Avps, Acc}) ->
- {[Avp | Avps], if M, Name /= 'Failed-AVP' ->
- unknown(Avp, Acc);
- true ->
- pack_AVP(Name, Avp, Acc)
- end};
-%% Note that the type field is 'undefined' in this case.
-
-%% Or try to decode.
decode(Name, {AvpName, Type}, Avp, Acc) ->
- d(Name, Avp#diameter_avp{name = AvpName, type = Type}, Acc).
+ d(Name, Avp#diameter_avp{name = AvpName, type = Type}, Acc);
+
+decode(Name, 'AVP', Avp, Acc) ->
+ decode_AVP(Name, Avp, Acc).
%% d/3
+%% Don't try to decode the value of a Failed-AVP component since it
+%% probably won't. Note that matching on 'Failed-AVP' assumes that
+%% this is the RFC AVP, with code 279. Strictly, this doesn't need to
+%% be the case, so we're assuming no one defines another Failed-AVP.
+d('Failed-AVP' = Name, Avp, Acc) ->
+ decode_AVP(Name, Avp, Acc);
+
+%% Or try to decode.
d(Name, Avp, {Avps, Acc}) ->
#diameter_avp{name = AvpName,
data = Data}
@@ -265,17 +249,25 @@ d(Name, Avp, {Avps, Acc}) ->
?LINE,
{Reason, Avp, erlang:get_stacktrace()}),
{Rec, Failed} = Acc,
- {[Avp|Avps], {Rec, [{rc(Reason), Avp} | Failed]}}
+ {[Avp|Avps], {Rec, [rc(Reason, Avp) | Failed]}}
end.
+%% decode_AVP/3
+%%
+%% Don't know this AVP: see if it can be packed in an 'AVP' field
+%% undecoded. Note that the type field is 'undefined' in this case.
+
+decode_AVP(Name, Avp, {Avps, Acc}) ->
+ {[Avp | Avps], pack_AVP(Name, Avp, Acc)}.
+
%% rc/1
%% diameter_types will raise an error of this form to communicate
%% DIAMETER_INVALID_AVP_LENGTH (5014). A module specified to a
%% @custom_types tag in a spec file can also raise an error of this
%% form.
-rc({'DIAMETER', RC, _}) ->
- RC;
+rc({'DIAMETER', 5014 = RC, _}, #diameter_avp{name = AvpName} = Avp) ->
+ {RC, Avp#diameter_avp{data = empty_value(AvpName)}};
%% 3588:
%%
@@ -283,20 +275,13 @@ rc({'DIAMETER', RC, _}) ->
%% The request contained an AVP with an invalid value in its data
%% portion. A Diameter message indicating this error MUST include
%% the offending AVPs within a Failed-AVP AVP.
-rc(_) ->
- 5004.
+rc(_, Avp) ->
+ {5004, Avp}.
%% ungroup/2
-%%
-%% Returns: {Avp, Dec}
-%%
-%% Avp = #diameter_avp{} if type is not Grouped
-%% | list of Avp where first element has type Grouped
-%% and following elements are its component
-%% AVP's.
-%% = as for decode_avps/2
-%%
-%% Dec = #diameter_avp{}, either Avp or its head in the list case.
+
+-spec ungroup(term(), #diameter_avp{})
+ -> {avp(), #diameter_avp{}}.
%% The decoded value in the Grouped case is as returned by grouped_avp/3:
%% a record and a list of component AVP's.
@@ -325,10 +310,18 @@ pack_avp(_, Arity, Avp, Acc) ->
%% pack_AVP/3
-pack_AVP(Name, Avp, Acc) ->
+%% Give Failed-AVP special treatment since it'll contain any
+%% unrecognized mandatory AVP's.
+pack_AVP(Name, #diameter_avp{is_mandatory = true} = Avp, Acc)
+ when Name /= 'Failed-AVP' ->
+ {Rec, Failed} = Acc,
+ {Rec, [{5001, Avp} | Failed]};
+
+pack_AVP(Name, #diameter_avp{is_mandatory = M} = Avp, Acc) ->
case avp_arity(Name, 'AVP') of
0 ->
- unknown(Avp, Acc);
+ {Rec, Failed} = Acc,
+ {Rec, [{if M -> 5001; true -> 5008 end, Avp} | Failed]};
Arity ->
pack(Arity, 'AVP', Avp, Acc)
end.
@@ -345,9 +338,6 @@ pack_AVP(Name, Avp, Acc) ->
%% A message was received with an AVP that MUST NOT be present. The
%% Failed-AVP AVP MUST be included and contain a copy of the
%% offending AVP.
-%%
-unknown(#diameter_avp{is_mandatory = B} = Avp, {Rec, Failed}) ->
- {Rec, [{if B -> 5001; true -> 5008 end, Avp} | Failed]}.
%% pack/4
@@ -386,23 +376,29 @@ value('AVP', Avp) ->
value(_, Avp) ->
Avp#diameter_avp.value.
-%%% ---------------------------------------------------------------------------
-%%% # grouped_avp/3
-%%% ---------------------------------------------------------------------------
+%% ---------------------------------------------------------------------------
+%% # grouped_avp/3
+%% ---------------------------------------------------------------------------
+
+-spec grouped_avp(decode, avp_name(), binary())
+ -> {avp_record(), [avp()]};
+ (encode, avp_name(), avp_record() | avp_values())
+ -> binary()
+ | no_return().
grouped_avp(decode, Name, Data) ->
{Rec, Avps, []} = decode_avps(Name, diameter_codec:collect_avps(Data)),
{Rec, Avps};
-%% Note that a failed match here will result in 5004. Note that this is
-%% the only AVP type that doesn't just return the decoded value, also
-%% returning the list of component #diameter_avp{}'s.
+%% A failed match here will result in 5004. Note that this is the only
+%% AVP type that doesn't just return the decoded record, also
+%% returning the list of component AVP's.
grouped_avp(encode, Name, Data) ->
encode_avps(Name, Data).
-%%% ---------------------------------------------------------------------------
-%%% # empty_group/1
-%%% ---------------------------------------------------------------------------
+%% ---------------------------------------------------------------------------
+%% # empty_group/1
+%% ---------------------------------------------------------------------------
empty_group(Name) ->
list_to_binary(empty_body(Name)).
@@ -423,9 +419,9 @@ z(Name) ->
Bin = diameter_codec:pack_avp(avp_header(Name), empty_value(Name)),
<< <<0>> || <<_>> <= Bin >>.
-%%% ---------------------------------------------------------------------------
-%%% # empty/1
-%%% ---------------------------------------------------------------------------
+%% ---------------------------------------------------------------------------
+%% # empty/1
+%% ---------------------------------------------------------------------------
empty(AvpName) ->
avp(encode, zero, AvpName).
diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl
index e446a0209c..6c0e73de36 100644
--- a/lib/diameter/src/base/diameter_codec.erl
+++ b/lib/diameter/src/base/diameter_codec.erl
@@ -38,6 +38,10 @@
-define(MASK(N,I), ((I) band (1 bsl (N)))).
+-type u32() :: 0..16#FFFFFFFF.
+-type u24() :: 0..16#FFFFFF.
+-type u1() :: 0..1.
+
%% 0 1 2 3
%% 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
%% +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
@@ -55,9 +59,13 @@
%% +-+-+-+-+-+-+-+-+-+-+-+-+-
%%% ---------------------------------------------------------------------------
-%%% # encode/[2-4]
+%%% # encode/2
%%% ---------------------------------------------------------------------------
+-spec encode(module(), Msg :: term())
+ -> #diameter_packet{}
+ | no_return().
+
encode(Mod, #diameter_packet{} = Pkt) ->
try
e(Mod, Pkt)
@@ -217,6 +225,9 @@ rec2msg(Mod, Rec) ->
%% Unsuccessfully decoded AVPs will be placed in #diameter_packet.errors.
+-spec decode(module(), #diameter_packet{} | bitstring())
+ -> #diameter_packet{}.
+
decode(Mod, Pkt) ->
decode(Mod:id(), Mod, Pkt).
@@ -225,9 +236,9 @@ decode(Mod, Pkt) ->
%% question.
decode(?APP_ID_RELAY, _, #diameter_packet{} = Pkt) ->
case collect_avps(Pkt) of
- {Bs, As} ->
+ {E, As} ->
Pkt#diameter_packet{avps = As,
- errors = [Bs]};
+ errors = [E]};
As ->
Pkt#diameter_packet{avps = As}
end;
@@ -251,12 +262,12 @@ decode(Id, Mod, Bin)
when is_bitstring(Bin) ->
decode(Id, Mod, #diameter_packet{header = decode_header(Bin), bin = Bin}).
-decode_avps(MsgName, Mod, Pkt, {Bs, Avps}) -> %% invalid avp bits ...
+decode_avps(MsgName, Mod, Pkt, {E, Avps}) ->
?LOG(invalid, Pkt#diameter_packet.bin),
#diameter_packet{errors = Failed}
= P
= decode_avps(MsgName, Mod, Pkt, Avps),
- P#diameter_packet{errors = [Bs | Failed]};
+ P#diameter_packet{errors = [E | Failed]};
decode_avps('', Mod, Pkt, Avps) -> %% unknown message ...
?LOG(unknown, {Mod, Pkt#diameter_packet.header}),
@@ -275,6 +286,10 @@ decode_avps(MsgName, Mod, Pkt, Avps) -> %% ... or not
%%% # decode_header/1
%%% ---------------------------------------------------------------------------
+-spec decode_header(bitstring())
+ -> #diameter_header{}
+ | false.
+
decode_header(<<Version:8,
MsgLength:24,
CmdFlags:1/binary,
@@ -324,6 +339,13 @@ decode_header(_) ->
%% wraparound counter. The 8-bit counter is incremented each time the
%% system is restarted.
+-spec sequence_numbers(#diameter_packet{}
+ | #diameter_header{}
+ | binary()
+ | Seq)
+ -> Seq
+ when Seq :: {HopByHopId :: u32(), EndToEndId :: u32()}.
+
sequence_numbers({_,_} = T) ->
T;
@@ -345,6 +367,9 @@ sequence_numbers(<<_:12/binary, H:32, E:32, _/binary>>) ->
%%% # hop_by_hop_id/2
%%% ---------------------------------------------------------------------------
+-spec hop_by_hop_id(u32(), binary())
+ -> binary().
+
hop_by_hop_id(Id, <<H:12/binary, _:32, T/binary>>) ->
<<H/binary, Id:32, T/binary>>.
@@ -352,6 +377,10 @@ hop_by_hop_id(Id, <<H:12/binary, _:32, T/binary>>) ->
%%% # msg_name/2
%%% ---------------------------------------------------------------------------
+-spec msg_name(module(), #diameter_header{})
+ -> atom()
+ | {ApplicationId :: u32(), CommandCode :: u24(), Rbit :: u1()}.
+
msg_name(Dict0, #diameter_header{application_id = ?APP_ID_COMMON,
cmd_code = C,
is_request = R}) ->
@@ -367,6 +396,9 @@ msg_name(_, Hdr) ->
%%% # msg_id/1
%%% ---------------------------------------------------------------------------
+-spec msg_id(#diameter_packet{} | #diameter_header{})
+ -> {ApplicationId :: u32(), CommandCode :: u24(), Rbit :: u1()}.
+
msg_id(#diameter_packet{msg = [#diameter_header{} = Hdr | _]}) ->
msg_id(Hdr);
@@ -389,6 +421,12 @@ msg_id(<<_:32, Rbit:1, _:7, CmdCode:24, ApplId:32, _/bitstring>>) ->
%% order in the binary. Note also that grouped avp's aren't unraveled,
%% only those at the top level.
+-spec collect_avps(#diameter_packet{} | bitstring())
+ -> [Avp]
+ | {Error, [Avp]}
+ when Avp :: #diameter_avp{},
+ Error :: {5014, #diameter_avp{}}.
+
collect_avps(#diameter_packet{bin = Bin}) ->
<<_:20/binary, Avps/bitstring>> = Bin,
collect_avps(Avps);
@@ -403,8 +441,8 @@ collect_avps(Bin, N, Acc) ->
{Rest, AVP} ->
collect_avps(Rest, N+1, [AVP#diameter_avp{index = N} | Acc])
catch
- ?FAILURE(_) ->
- {Bin, Acc}
+ ?FAILURE(Error) ->
+ {Error, Acc}
end.
%% 0 1 2 3
@@ -422,42 +460,87 @@ collect_avps(Bin, N, Acc) ->
%% split_avp/1
split_avp(Bin) ->
- 8 =< size(Bin) orelse ?THROW(truncated_header),
+ {Code, V, M, P, Len, HdrLen} = split_head(Bin),
+ {Data, B} = split_data(Bin, HdrLen, Len - HdrLen),
- <<Code:32, Flags:1/binary, Length:24, Rest/bitstring>>
- = Bin,
+ {B, #diameter_avp{code = Code,
+ vendor_id = V,
+ is_mandatory = 1 == M,
+ need_encryption = 1 == P,
+ data = Data}}.
- DataSize = Length - 8, % size(Code+Flags+Length) = 8 octets
- PadSize = (4 - (DataSize rem 4)) rem 4,
+%% split_head/1
- DataSize + PadSize =< size(Rest)
- orelse ?THROW(truncated_data),
+split_head(<<Code:32, 1:1, M:1, P:1, _:5, Len:24, V:32, _/bitstring>>) ->
+ {Code, V, M, P, Len, 12};
- <<Data:DataSize/binary, _:PadSize/binary, R/bitstring>>
- = Rest,
- <<Vbit:1, Mbit:1, Pbit:1, _Reserved:5>>
- = Flags,
+split_head(<<Code:32, 0:1, M:1, P:1, _:5, Len:24, _/bitstring>>) ->
+ {Code, undefined, M, P, Len, 8};
- 0 == Vbit orelse 4 =< size(Data)
- orelse ?THROW(truncated_vendor_id),
+split_head(Bin) ->
+ ?THROW({5014, #diameter_avp{data = Bin}}).
+
+%% 3588:
+%%
+%% DIAMETER_INVALID_AVP_LENGTH 5014
+%% The request contained an AVP with an invalid length. A Diameter
+%% message indicating this error MUST include the offending AVPs
+%% within a Failed-AVP AVP.
- {Vid, D} = vid(Vbit, Data),
- {R, #diameter_avp{code = Code,
- vendor_id = Vid,
- is_mandatory = 1 == Mbit,
- need_encryption = 1 == Pbit,
- data = D}}.
+%% 6733:
+%%
+%% DIAMETER_INVALID_AVP_LENGTH 5014
+%%
+%% The request contained an AVP with an invalid length. A Diameter
+%% message indicating this error MUST include the offending AVPs
+%% within a Failed-AVP AVP. In cases where the erroneous AVP length
+%% value exceeds the message length or is less than the minimum AVP
+%% ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+%% header length, it is sufficient to include the offending AVP
+%% ^^^^^^^^^^^^^
+%% header and a zero filled payload of the minimum required length
+%% for the payloads data type. If the AVP is a Grouped AVP, the
+%% Grouped AVP header with an empty payload would be sufficient to
+%% indicate the offending AVP. In the case where the offending AVP
+%% header cannot be fully decoded when the AVP length is less than
+%% the minimum AVP header length, it is sufficient to include an
+%% offending AVP header that is formulated by padding the incomplete
+%% AVP header with zero up to the minimum AVP header length.
+%%
+%% The underlined clause must be in error since (1) a header less than
+%% the minimum value mean we don't know the identity of the AVP and
+%% (2) the last sentence covers this case.
-%% The RFC is a little misleading when stating that OctetString is
-%% padded to a 32-bit boundary while other types align naturally. All
-%% other types are already multiples of 32 bits so there's no need to
-%% distinguish between types here. Any invalid lengths will result in
-%% decode error in diameter_types.
+%% split_data/3
-vid(1, <<Vid:32, Data/bitstring>>) ->
- {Vid, Data};
-vid(0, Data) ->
- {undefined, Data}.
+split_data(Bin, HdrLen, Len)
+ when 0 =< Len ->
+ split_data(Bin, HdrLen, Len, (4 - (Len rem 4)) rem 4);
+
+split_data(_, _, _) ->
+ invalid_avp_length().
+
+%% split_data/4
+
+split_data(Bin, HdrLen, Len, Pad) ->
+ <<_:HdrLen/binary, T/bitstring>> = Bin,
+ case T of
+ <<Data:Len/binary, _:Pad/binary, Rest/bitstring>> ->
+ {Data, Rest};
+ _ ->
+ invalid_avp_length()
+ end.
+
+%% invalid_avp_length/0
+%%
+%% AVP Length doesn't mesh with payload. Induce a decode error by
+%% returning a payload that no valid Diameter type can have. This is
+%% so that a known AVP will result in 5014 error with a zero'd
+%% payload. Here we simply don't know how to construct this payload.
+%% (Yes, this solution is an afterthought.)
+
+invalid_avp_length() ->
+ {<<0:1>>, <<>>}.
%%% ---------------------------------------------------------------------------
%%% # pack_avp/1
@@ -472,20 +555,35 @@ vid(0, Data) ->
pack_avp(#diameter_avp{data = [#diameter_avp{} | _] = Avps} = A) ->
pack_avp(A#diameter_avp{data = encode_avps(Avps)});
-%% ... data as a type/value tuple, possibly with header data, ...
+%% ... data as a type/value tuple ...
pack_avp(#diameter_avp{data = {Type, Value}} = A)
when is_atom(Type) ->
pack_avp(A#diameter_avp{data = diameter_types:Type(encode, Value)});
+
+%% ... with a header in various forms ...
pack_avp(#diameter_avp{data = {{_,_,_} = T, {Type, Value}}}) ->
pack_avp(T, iolist_to_binary(diameter_types:Type(encode, Value)));
+
pack_avp(#diameter_avp{data = {{_,_,_} = T, Bin}})
when is_binary(Bin) ->
pack_avp(T, Bin);
+
pack_avp(#diameter_avp{data = {Dict, Name, Value}} = A) ->
{Code, _Flags, Vid} = Hdr = Dict:avp_header(Name),
{Name, Type} = Dict:avp_name(Code, Vid),
pack_avp(A#diameter_avp{data = {Hdr, {Type, Value}}});
+pack_avp(#diameter_avp{code = undefined, data = Bin})
+ when is_binary(Bin) ->
+ %% Reset the AVP Length of an AVP Header resulting from a 5014
+ %% error. The RFC doesn't explicitly say to do this but the
+ %% receiver can't correctly extract this and following AVP's
+ %% without a correct length. On the downside, the header doesn't
+ %% reveal if the received header has been padded.
+ Pad = 8*header_length(Bin) - bit_size(Bin),
+ Len = size(<<H:5/binary, _:24, T/binary>> = <<Bin/bitstring, 0:Pad>>),
+ <<H/binary, Len:24, T/binary>>;
+
%% ... or as an iolist.
pack_avp(#diameter_avp{code = Code,
vendor_id = V,
@@ -497,6 +595,11 @@ pack_avp(#diameter_avp{code = Code,
{P, 2#00100000}]),
pack_avp({Code, Flags, V}, iolist_to_binary(Data)).
+header_length(<<_:32, 1:1, _/bitstring>>) ->
+ 12;
+header_length(_) ->
+ 8.
+
flag_avp({true, B}, F) ->
F bor B;
flag_avp({false, _}, F) ->
diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl
index 25b902e3f2..820d37535a 100644
--- a/lib/diameter/src/base/diameter_traffic.erl
+++ b/lib/diameter/src/base/diameter_traffic.erl
@@ -226,10 +226,10 @@ recv_R(false = No, _, _, _, _) -> %% transport has gone down
collect_avps(Pkt) ->
case diameter_codec:collect_avps(Pkt) of
- {_Bs, As} ->
- As;
- As ->
- As
+ {_Error, Avps} ->
+ Avps;
+ Avps ->
+ Avps
end.
%% recv_R/6
@@ -300,7 +300,7 @@ errors(_, #diameter_packet{header = #diameter_header{version = V},
%% AVP's definition.
errors(_, #diameter_packet{errors = [Bs | Es]} = Pkt)
- when is_bitstring(Bs) ->
+ when is_bitstring(Bs) -> %% from old code
Pkt#diameter_packet{errors = [3009 | Es]};
%% DIAMETER_COMMAND_UNSUPPORTED 3001
@@ -595,71 +595,87 @@ reply([Msg], Dict, TPid, Dict0, Fs, ReqPkt)
is_tuple(Msg) ->
reply(Msg, Dict, TPid, Dict0, Fs, ReqPkt#diameter_packet{errors = []});
-%% No errors or a diameter_header/avp list.
reply(Msg, Dict, TPid, Dict0, Fs, ReqPkt) ->
- Pkt = encode(Dict, reset(make_answer_packet(Msg, ReqPkt), Dict), Fs),
+ Pkt = encode(Dict,
+ reset(make_answer_packet(Msg, ReqPkt), Dict, Dict0),
+ Fs),
incr(send, Pkt, Dict, TPid, Dict0), %% count outgoing result codes
send(TPid, Pkt).
-%% reset/2
+%% reset/3
%% Header/avps list: send as is.
-reset(#diameter_packet{msg = [#diameter_header{} | _]} = Pkt, _) ->
+reset(#diameter_packet{msg = [#diameter_header{} | _]} = Pkt, _, _) ->
Pkt;
%% No errors to set or errors explicitly ignored.
-reset(#diameter_packet{errors = Es} = Pkt, _)
+reset(#diameter_packet{errors = Es} = Pkt, _, _)
when Es == [];
Es == false ->
Pkt;
%% Otherwise possibly set Result-Code and/or Failed-AVP.
-reset(#diameter_packet{msg = Msg, errors = Es} = Pkt, Dict) ->
- Pkt#diameter_packet{msg = reset(Msg, Dict, Es)}.
-
-%% reset/3
-
-reset(Msg, Dict, Es)
- when is_list(Es) ->
- {E3, E5, Fs} = partition(Es),
- FailedAVP = failed_avp(Msg, lists:reverse(Fs), Dict),
- reset(set(Msg, FailedAVP, Dict),
- Dict,
- choose(is_answer_message(Msg, Dict), E3, E5));
-
-reset(Msg, Dict, N)
- when is_integer(N) ->
- ResultCode = rc(Msg, {'Result-Code', N}, Dict),
- set(Msg, ResultCode, Dict);
-
-reset(Msg, _, _) ->
- Msg.
-
-partition(Es) ->
- lists:foldl(fun pacc/2, {false, false, []}, Es).
+reset(#diameter_packet{msg = Msg, errors = Es} = Pkt, Dict, Dict0) ->
+ {RC, Failed} = select_error(Msg, Es, Dict0),
+ Pkt#diameter_packet{msg = reset(Msg, Dict, RC, Failed)}.
-%% Note that the errors list can contain not only integer() and
-%% {integer(), #diameter_avp{}} but also #diameter_avp{}. The latter
-%% isn't something that's returned by decode but can be set in a reply
-%% for encode.
+%% select_error/3
+%%
+%% Extract the first appropriate RC or {RC, #diameter_avp{}}
+%% pair from an errors list, and accumulate all #diameter_avp{}.
+%%
+%% RFC 6733:
+%%
+%% 7.5. Failed-AVP AVP
+%%
+%% The Failed-AVP AVP (AVP Code 279) is of type Grouped and provides
+%% debugging information in cases where a request is rejected or not
+%% fully processed due to erroneous information in a specific AVP. The
+%% value of the Result-Code AVP will provide information on the reason
+%% for the Failed-AVP AVP. A Diameter answer message SHOULD contain an
+%% instance of the Failed-AVP AVP that corresponds to the error
+%% indicated by the Result-Code AVP. For practical purposes, this
+%% Failed-AVP would typically refer to the first AVP processing error
+%% that a Diameter node encounters.
+
+select_error(Msg, Es, Dict0) ->
+ {RC, Avps} = lists:foldl(fun(T,A) -> select(T, A, Dict0) end,
+ {is_answer_message(Msg, Dict0), []},
+ Es),
+ {RC, lists:reverse(Avps)}.
+
+%% Only integer() and {integer(), #diameter_avp{}} are the result of
+%% decode. #diameter_avp{} can only be set in a reply for encode.
+
+select(#diameter_avp{} = A, {RC, As}, _) ->
+ {RC, [A|As]};
+
+select(_, {RC, _} = Acc, _)
+ when is_integer(RC) ->
+ Acc;
-pacc({RC, #diameter_avp{} = A}, {E3, E5, Acc})
+select({RC, #diameter_avp{} = A}, {IsAns, As} = Acc, Dict0)
when is_integer(RC) ->
- pacc(RC, {E3, E5, [A|Acc]});
+ case is_result(RC, IsAns, Dict0) of
+ true -> {RC, [A|As]};
+ false -> Acc
+ end;
-pacc(#diameter_avp{} = A, {E3, E5, Acc}) ->
- {E3, E5, [A|Acc]};
+select(RC, {IsAns, As} = Acc, Dict0)
+ when is_boolean(IsAns), is_integer(RC) ->
+ case is_result(RC, IsAns, Dict0) of
+ true -> {RC, As};
+ false -> Acc
+ end.
-pacc(RC, {false, E5, Acc})
- when 3 == RC div 1000 ->
- {RC, E5, Acc};
+%% reset/4
-pacc(RC, {E3, false, Acc})
- when 5 == RC div 1000 ->
- {E3, RC, Acc};
+reset(Msg, Dict, RC, Avps) ->
+ FailedAVP = failed_avp(Msg, Avps, Dict),
+ ResultCode = rc(Msg, RC, Dict),
+ set(set(Msg, FailedAVP, Dict), ResultCode, Dict).
-pacc(_, Acc) ->
- Acc.
+%% eval_packet/2
eval_packet(Pkt, Fs) ->
lists:foreach(fun(F) -> diameter_lib:eval([F,Pkt]) end, Fs).
@@ -725,29 +741,34 @@ set(Rec, Avps, Dict) ->
%% the arity is 1 or {0,1}. In other cases (which probably shouldn't
%% exist in practise) we can't know what's appropriate.
-rc([MsgName | _], {'Result-Code' = K, RC} = T, Dict) ->
- case Dict:avp_arity(MsgName, 'Result-Code') of
- 1 -> [T];
+rc(_, B, _)
+ when is_boolean(B) ->
+ [];
+
+rc([MsgName | _], RC, Dict) ->
+ K = 'Result-Code',
+ case Dict:avp_arity(MsgName, K) of
+ 1 -> [{K, RC}];
{0,1} -> [{K, [RC]}];
_ -> []
end;
-rc(Rec, T, Dict) ->
- rc([Dict:rec2msg(element(1, Rec))], T, Dict).
+rc(Rec, RC, Dict) ->
+ rc([Dict:rec2msg(element(1, Rec))], RC, Dict).
%% failed_avp/3
failed_avp(_, [] = No, _) ->
No;
-failed_avp(Rec, Failed, Dict) ->
- [fa(Rec, [{'AVP', Failed}], Dict)].
+failed_avp(Rec, Avps, Dict) ->
+ [failed(Rec, [{'AVP', Avps}], Dict)].
%% Reply as name and tuple list ...
-fa([MsgName | Values], FailedAvp, Dict) ->
- R = Dict:msg2rec(MsgName),
+failed([MsgName | Values], FailedAvp, Dict) ->
+ RecName = Dict:msg2rec(MsgName),
try
- Dict:'#info-'(R, {index, 'Failed-AVP'}),
+ Dict:'#info-'(RecName, {index, 'Failed-AVP'}),
{'Failed-AVP', [FailedAvp]}
catch
error: _ ->
@@ -758,8 +779,10 @@ fa([MsgName | Values], FailedAvp, Dict) ->
end;
%% ... or record.
-fa(Rec, FailedAvp, Dict) ->
+failed(Rec, FailedAvp, Dict) ->
try
+ RecName = element(1, Rec),
+ Dict:'#info-'(RecName, {index, 'Failed-AVP'}),
{'Failed-AVP', [FailedAvp]}
catch
error: _ ->
diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl
index 41c493ff20..88ccf630e2 100644
--- a/lib/diameter/src/base/diameter_watchdog.erl
+++ b/lib/diameter/src/base/diameter_watchdog.erl
@@ -505,7 +505,9 @@ set_watchdog(#watchdog{tw = TwInit,
tref = TRef}
= S) ->
cancel(TRef),
- S#watchdog{tref = erlang:start_timer(tw(TwInit), self(), tw)}.
+ S#watchdog{tref = erlang:start_timer(tw(TwInit), self(), tw)};
+set_watchdog(stop = No) ->
+ No.
cancel(undefined) ->
ok;
diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl
index 80036879ea..e687145263 100644
--- a/lib/diameter/src/compiler/diameter_codegen.erl
+++ b/lib/diameter/src/compiler/diameter_codegen.erl
@@ -574,12 +574,12 @@ cs_enumerated_avp({AvpName, Values}) ->
lists:flatmap(fun(V) -> c_enumerated_avp(AvpName, V) end, Values).
c_enumerated_avp(AvpName, {_,I}) ->
- [{?clause, [?ATOM(decode), ?Atom(AvpName), ?TERM(<<I:32/integer>>)],
+ [{?clause, [?ATOM(decode), ?Atom(AvpName), ?TERM(<<I:32>>)],
[],
[?TERM(I)]},
{?clause, [?ATOM(encode), ?Atom(AvpName), ?INTEGER(I)],
[],
- [?TERM(<<I:32/integer>>)]}].
+ [?TERM(<<I:32>>)]}].
%%% ------------------------------------------------------------------------
%%% msg_header/1
@@ -700,7 +700,7 @@ c_empty_value({Name, _, _, _}) ->
c_empty_value({Name, _}) ->
{?clause, [?Atom(Name)],
[],
- [?TERM(<<0:32/integer>>)]}.
+ [?TERM(<<0:32>>)]}.
%%% ------------------------------------------------------------------------
%%% # dict/0
diff --git a/lib/diameter/test/diameter_3xxx_SUITE.erl b/lib/diameter/test/diameter_3xxx_SUITE.erl
index 89c78d8b57..0ec0d5020f 100644
--- a/lib/diameter/test/diameter_3xxx_SUITE.erl
+++ b/lib/diameter/test/diameter_3xxx_SUITE.erl
@@ -40,7 +40,7 @@
send_unknown_application/1,
send_unknown_command/1,
send_ok/1,
- send_invalid_avp_bits/1,
+ send_invalid_hdr_bits/1,
send_missing_avp/1,
send_ignore_missing_avp/1,
send_double_error/1,
@@ -136,7 +136,7 @@ tc() ->
[send_unknown_application,
send_unknown_command,
send_ok,
- send_invalid_avp_bits,
+ send_invalid_hdr_bits,
send_missing_avp,
send_ignore_missing_avp,
send_double_error,
@@ -216,27 +216,26 @@ send_ok([_,_]) ->
send_ok(Config) ->
send_ok(?group(Config)).
-%% send_invalid_avp_bits/1
+%% send_invalid_hdr_bits/1
%%
-%% Send a request with an incorrect length on the optional
-%% Origin-State-Id that a callback ignores.
+%% Send a request with an incorrect E-bit that a callback ignores.
%% Callback answers.
-send_invalid_avp_bits([callback, _]) ->
+send_invalid_hdr_bits([callback, _]) ->
#diameter_base_STA{'Result-Code' = 2001, %% SUCCESS
'Failed-AVP' = [],
'AVP' = []}
= call();
%% diameter answers.
-send_invalid_avp_bits([_,_]) ->
- #'diameter_base_answer-message'{'Result-Code' = 3009, %% INVALID_AVP_BITS
+send_invalid_hdr_bits([_,_]) ->
+ #'diameter_base_answer-message'{'Result-Code' = 3008, %% INVALID_HDR_BITS
'Failed-AVP' = [],
'AVP' = []}
= call();
-send_invalid_avp_bits(Config) ->
- send_invalid_avp_bits(?group(Config)).
+send_invalid_hdr_bits(Config) ->
+ send_invalid_hdr_bits(?group(Config)).
%% send_missing_avp/1
%%
@@ -282,8 +281,7 @@ send_ignore_missing_avp(Config) ->
%% send_double_error/1
%%
-%% Send a request with both an incorrect length on the optional
-%% Origin-State-Id and a missing AVP.
+%% Send a request with both an invalid E-bit and a missing AVP.
%% Callback answers with STA.
send_double_error([callback, _]) ->
@@ -294,8 +292,8 @@ send_double_error([callback, _]) ->
%% diameter answers with answer-message.
send_double_error([_,_]) ->
- #'diameter_base_answer-message'{'Result-Code' = 3009, %% INVALID_AVP_BITS
- 'Failed-AVP' = [_],
+ #'diameter_base_answer-message'{'Result-Code' = 3008, %% INVALID_HDR_BITS
+ 'Failed-AVP' = [],
'AVP' = []}
= call();
@@ -392,20 +390,16 @@ prepare(Pkt, Caps, T)
T == send_5xxx ->
sta(Pkt, Caps);
-prepare(Pkt0, Caps, send_invalid_avp_bits) ->
- Req0 = sta(Pkt0, Caps),
- %% Append an Origin-State-Id with an incorrect AVP Length in order
- %% to force 3009.
- Req = Req0#diameter_base_STR{'Origin-State-Id' = [7]},
- #diameter_packet{bin = Bin}
+prepare(Pkt0, Caps, send_invalid_hdr_bits) ->
+ Req = sta(Pkt0, Caps),
+ %% Set the E-bit to force 3008.
+ #diameter_packet{bin = <<H:34, 0:1, T/bitstring>>}
= Pkt
= diameter_codec:encode(?DICT, Pkt0#diameter_packet{msg = Req}),
- Offset = size(Bin) - 12 + 5,
- <<H:Offset/binary, Len:24, T/binary>> = Bin,
- Pkt#diameter_packet{bin = <<H/binary, (Len + 2):24, T/binary>>};
+ Pkt#diameter_packet{bin = <<H:34, 1:1, T/bitstring>>};
prepare(Pkt0, Caps, send_double_error) ->
- dehost(prepare(Pkt0, Caps, send_invalid_avp_bits));
+ dehost(prepare(Pkt0, Caps, send_invalid_hdr_bits));
prepare(Pkt, Caps, T)
when T == send_missing_avp;
@@ -480,9 +474,7 @@ request(send_3xxx, _Req, _Caps) ->
request(send_5xxx, _Req, _Caps) ->
{answer_message, 5999};
-request(send_invalid_avp_bits, Req, Caps) ->
- #diameter_base_STR{'Origin-State-Id' = []}
- = Req,
+request(send_invalid_hdr_bits, Req, Caps) ->
%% Default errors field but a non-answer-message and only 3xxx
%% errors detected means diameter sets neither Result-Code nor
%% Failed-AVP.
diff --git a/lib/diameter/test/diameter_app_SUITE.erl b/lib/diameter/test/diameter_app_SUITE.erl
index 209f72adf1..1e262895a6 100644
--- a/lib/diameter/test/diameter_app_SUITE.erl
+++ b/lib/diameter/test/diameter_app_SUITE.erl
@@ -56,7 +56,7 @@
%% ===========================================================================
suite() ->
- [{timetrap, {seconds, 10}}].
+ [{timetrap, {seconds, 60}}].
all() ->
[keys,
diff --git a/lib/diameter/test/diameter_codec_SUITE_data/diameter_test_unknown.erl b/lib/diameter/test/diameter_codec_SUITE_data/diameter_test_unknown.erl
index bce3d78a37..49f2158b1a 100644
--- a/lib/diameter/test/diameter_codec_SUITE_data/diameter_test_unknown.erl
+++ b/lib/diameter/test/diameter_codec_SUITE_data/diameter_test_unknown.erl
@@ -71,6 +71,6 @@ dec('AR', #diameter_packet
dec('BR', #diameter_packet
{msg = #recv_BR{'Origin-Host' = ?HOST,
'Origin-Realm' = ?REALM},
- errors = [{5008, ?NOT_MANDATORY_YYY},
- {5001, ?MANDATORY_XXX}]}) ->
+ errors = [{5001, ?MANDATORY_XXX},
+ {5008, ?NOT_MANDATORY_YYY}]}) ->
ok.
diff --git a/lib/diameter/test/diameter_codec_test.erl b/lib/diameter/test/diameter_codec_test.erl
index 0baac59c1a..24d4c7665e 100644
--- a/lib/diameter/test/diameter_codec_test.erl
+++ b/lib/diameter/test/diameter_codec_test.erl
@@ -265,7 +265,7 @@ arity(M, Name, AvpName, Rec) ->
%% enum/3
enum(M, Name, {_,E}) ->
- B = <<E:32/integer>>,
+ B = <<E:32>>,
B = M:avp(encode, E, Name),
E = M:avp(decode, B, Name).
@@ -322,15 +322,15 @@ values('Unsigned64') ->
values('Float32') ->
E = (1 bsl 8) - 2,
F = (1 bsl 23) - 1,
- <<Mx:32/float>> = <<0:1/integer, E:8/integer, F:23/integer>>,
- <<Mn:32/float>> = <<1:1/integer, E:8/integer, F:23/integer>>,
+ <<Mx:32/float>> = <<0:1, E:8, F:23>>,
+ <<Mn:32/float>> = <<1:1, E:8, F:23>>,
{[0.0, infinity, '-infinity', Mx, Mn], [0]};
values('Float64') ->
E = (1 bsl 11) - 2,
F = (1 bsl 52) - 1,
- <<Mx:64/float>> = <<0:1/integer, E:11/integer, F:52/integer>>,
- <<Mn:64/float>> = <<1:1/integer, E:11/integer, F:52/integer>>,
+ <<Mx:64/float>> = <<0:1, E:11, F:52>>,
+ <<Mn:64/float>> = <<1:1, E:11, F:52>>,
{[0.0, infinity, '-infinity', Mx, Mn], [0]};
values('Address') ->
diff --git a/lib/diameter/test/diameter_distribution_SUITE.erl b/lib/diameter/test/diameter_distribution_SUITE.erl
index 01d3507b27..f069abbe2f 100644
--- a/lib/diameter/test/diameter_distribution_SUITE.erl
+++ b/lib/diameter/test/diameter_distribution_SUITE.erl
@@ -28,7 +28,7 @@
all/0]).
%% testcases
--export([enslave/1,
+-export([enslave/1, enslave/0,
ping/1,
start/1,
connect/1,
@@ -36,7 +36,7 @@
send_remote/1,
send_timeout/1,
send_failover/1,
- stop/1]).
+ stop/1, stop/0]).
%% diameter callbacks
-export([peer_up/3,
@@ -126,6 +126,9 @@ all() ->
%% Start four slave nodes, one to implement a Diameter server,
%% two three to implement a client.
+enslave() ->
+ [{timetrap, {seconds, 30*length(?NODES)}}].
+
enslave(Config) ->
Here = filename:dirname(code:which(?MODULE)),
Ebin = filename:join([Here, "..", "ebin"]),
@@ -225,6 +228,9 @@ connect(Config) ->
%%
%% Stop the slave nodes.
+stop() ->
+ [{timetrap, {seconds, 30*length(?NODES)}}].
+
stop(_Config) ->
[] = [{N,E} || {N,_} <- ?NODES,
{error, _, _} = E <- [ct_slave:stop(N)]].
diff --git a/lib/diameter/test/diameter_examples_SUITE.erl b/lib/diameter/test/diameter_examples_SUITE.erl
index 585fc9d3b8..1954bc319b 100644
--- a/lib/diameter/test/diameter_examples_SUITE.erl
+++ b/lib/diameter/test/diameter_examples_SUITE.erl
@@ -29,6 +29,7 @@
%% testcases
-export([dict/1, dict/0,
code/1,
+ slave/1, slave/0,
enslave/1,
start/1,
traffic/1,
@@ -65,11 +66,12 @@
%% ===========================================================================
suite() ->
- [{timetrap, {seconds, 45}}].
+ [{timetrap, {minutes, 2}}].
all() ->
[dict,
code,
+ slave,
enslave,
start,
traffic,
@@ -250,6 +252,29 @@ store(Path, Dict) ->
%% ===========================================================================
+%% slave/1
+%%
+%% Return how long slave start/stop is taking since it seems to be
+%% ridiculously long on some hosts.
+
+slave() ->
+ [{timetrap, {minutes, 10}}].
+
+slave(_) ->
+ T0 = now(),
+ {ok, Node} = ct_slave:start(?MODULE, ?TIMEOUTS),
+ T1 = now(),
+ T2 = rpc:call(Node, erlang, now, []),
+ {ok, Node} = ct_slave:stop(?MODULE),
+ now_diff([T0, T1, T2, now()]).
+
+now_diff([T1,T2|_] = Ts) ->
+ [timer:now_diff(T2,T1) | now_diff(tl(Ts))];
+now_diff(_) ->
+ [].
+
+%% ===========================================================================
+
%% enslave/1
%%
%% Start two nodes: one for the server, one for the client.
diff --git a/lib/diameter/test/diameter_gen_tcp_SUITE.erl b/lib/diameter/test/diameter_gen_tcp_SUITE.erl
new file mode 100644
index 0000000000..7e232edb44
--- /dev/null
+++ b/lib/diameter/test/diameter_gen_tcp_SUITE.erl
@@ -0,0 +1,106 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%% Some gen_sctp-specific tests demonstrating problems that were
+%% encountered during diameter development but have nothing
+%% specifically to do with diameter. At least one of them can cause
+%% diameter_traffic_SUITE testcases to fail.
+%%
+
+-module(diameter_gen_tcp_SUITE).
+
+-export([suite/0,
+ all/0]).
+
+%% testcases
+-export([send_long/1]).
+
+-define(LOOPBACK, {127,0,0,1}).
+-define(GEN_OPTS, [binary, {active, true}, {ip, ?LOOPBACK}]).
+
+%% ===========================================================================
+
+suite() ->
+ [{timetrap, {minutes, 2}}].
+
+all() ->
+ [send_long].
+
+%% ===========================================================================
+
+%% send_long/1
+%%
+%% Test that a long message is received.
+
+send_long(_) ->
+ {Sock, SendF} = connection(),
+ B = list_to_binary(lists:duplicate(1 bsl 20, $X)),
+ ok = SendF(B),
+ B = recv(Sock, size(B), []).
+
+recv(_, 0, Acc) ->
+ list_to_binary(lists:reverse(Acc));
+recv(Sock, N, Acc) ->
+ receive
+ {tcp, Sock, Bin} ->
+ recv(Sock, N - size(Bin), [Bin | Acc]);
+ T ->
+ {T, Acc}
+ end.
+
+%% connection/0
+
+connection() ->
+ {ok, LSock} = gen_tcp:listen(0, ?GEN_OPTS),
+ {ok, PortNr} = inet:port(LSock),
+ LPid = self(),
+ {Pid, MRef} = spawn_monitor(fun() -> connect(PortNr, LPid) end),
+ {ok, Sock} = gen_tcp:accept(LSock),
+ receive
+ {Pid, F} ->
+ {Sock, F};
+ {'DOWN', MRef, process, _, _} = T ->
+ T
+ end.
+
+%% connect/2
+
+connect(PortNr, LPid) ->
+ {ok, Sock} = gen_tcp:connect(?LOOPBACK, PortNr, ?GEN_OPTS),
+ LPid ! {self(), fun(B) -> send(Sock, B) end},
+ down(LPid).
+
+%% down/1
+
+down(Pid)
+ when is_pid(Pid) ->
+ down(erlang:monitor(process, Pid));
+
+down(MRef) ->
+ receive {'DOWN', MRef, process, _, Reason} -> Reason end.
+
+%% send/2
+%%
+%% Send from a spawned process just to avoid sending from the
+%% receiving process, in case it's significant.
+
+send(Sock, Bin) ->
+ {_, MRef} = spawn_monitor(fun() -> exit(gen_tcp:send(Sock, Bin)) end),
+ down(MRef).
diff --git a/lib/diameter/test/diameter_tls_SUITE.erl b/lib/diameter/test/diameter_tls_SUITE.erl
index 5a79c63d36..55565692ec 100644
--- a/lib/diameter/test/diameter_tls_SUITE.erl
+++ b/lib/diameter/test/diameter_tls_SUITE.erl
@@ -181,7 +181,7 @@ start_diameter(_Config) ->
ok = diameter:start().
make_certs() ->
- [{timetrap, {seconds, 30}}].
+ [{timetrap, {minutes, 2}}].
make_certs(Config) ->
Dir = proplists:get_value(priv_dir, Config),
@@ -302,9 +302,7 @@ set([H|T], Vs) ->
disconnect({{LRef, _PortNr}, CRef}) ->
ok = diameter:remove_transport(?CLIENT, CRef),
- ok = receive #diameter_event{info = {down, LRef, _, _}} -> ok
- after 2000 -> false
- end.
+ receive #diameter_event{info = {down, LRef, _, _}} -> ok end.
realm(Host) ->
tl(lists:dropwhile(fun(C) -> C /= $. end, Host)).
@@ -365,13 +363,11 @@ ssl([{ssl_options = T, Opts}]) ->
connect(Host, {_LRef, PortNr}, {Caps, Opts}) ->
{ok, Ref} = diameter:add_transport(Host, ?CONNECT(PortNr, Caps, Opts)),
- ok = receive
- #diameter_event{service = Host,
- info = {up, Ref, _, _, #diameter_packet{}}} ->
- ok
- after 2000 ->
- false
- end,
+ receive
+ #diameter_event{service = Host,
+ info = {up, Ref, _, _, #diameter_packet{}}} ->
+ ok
+ end,
Ref.
copts(S, Opts)
diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl
index 781ed234cc..38bdf55af8 100644
--- a/lib/diameter/test/diameter_traffic_SUITE.erl
+++ b/lib/diameter/test/diameter_traffic_SUITE.erl
@@ -49,9 +49,12 @@
send_unsupported_app/1,
send_error_bit/1,
send_unsupported_version/1,
- send_invalid_avp_bits/1,
+ send_long_avp_length/1,
+ send_short_avp_length/1,
+ send_zero_avp_length/1,
send_invalid_avp_length/1,
send_invalid_reject/1,
+ send_unrecognized_mandatory/1,
send_long/1,
send_nopeer/1,
send_noapp/1,
@@ -268,9 +271,12 @@ tc() ->
send_unsupported_app,
send_error_bit,
send_unsupported_version,
- send_invalid_avp_bits,
+ send_long_avp_length,
+ send_short_avp_length,
+ send_zero_avp_length,
send_invalid_avp_length,
send_invalid_reject,
+ send_unrecognized_mandatory,
send_long,
send_nopeer,
send_noapp,
@@ -405,7 +411,7 @@ send_eval(Config) ->
send_bad_answer(Config) ->
Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD},
{'Accounting-Record-Number', 2}],
- {error, timeout} = call(Config, Req).
+ {timeout, _} = call(Config, Req).
%% Send an ACR that the server callback answers explicitly with a
%% protocol error.
@@ -416,13 +422,14 @@ send_protocol_error(Config) ->
?answer_message(?TOO_BUSY)
= call(Config, Req).
-%% Send an ASR with an arbitrary AVP and expect success and the same
-%% AVP in the reply.
+%% Send an ASR with an arbitrary non-mandatory AVP and expect success
+%% and the same AVP in the reply.
send_arbitrary(Config) ->
- Req = ['ASR', {'AVP', [#diameter_avp{name = 'Class', value = "XXX"}]}],
+ Req = ['ASR', {'AVP', [#diameter_avp{name = 'Product-Name',
+ value = "XXX"}]}],
['ASA', _SessionId, {'Result-Code', ?SUCCESS} | Avps]
= call(Config, Req),
- {'AVP', [#diameter_avp{name = 'Class',
+ {'AVP', [#diameter_avp{name = 'Product-Name',
value = "XXX"}]}
= lists:last(Avps).
@@ -455,7 +462,7 @@ send_unknown_mandatory(Config) ->
%% Send an STR that the server ignores.
send_noreply(Config) ->
Req = ['STR', {'Termination-Cause', ?BAD_ANSWER}],
- {error, timeout} = call(Config, Req).
+ {timeout, _} = call(Config, Req).
%% Send an unsupported command and expect 3001.
send_unsupported(Config) ->
@@ -481,19 +488,33 @@ send_unsupported_version(Config) ->
['STA', _SessionId, {'Result-Code', ?UNSUPPORTED_VERSION} | _]
= call(Config, Req).
-%% Send a request containing an incorrect AVP length.
-send_invalid_avp_bits(Config) ->
- Req = ['STR', {'Termination-Cause', ?LOGOUT}],
+%% Send a request containing an AVP length > data size.
+send_long_avp_length(Config) ->
+ send_invalid_avp_length(Config).
- ?answer_message(?INVALID_AVP_BITS)
- = call(Config, Req).
+%% Send a request containing an AVP length < data size.
+send_short_avp_length(Config) ->
+ send_invalid_avp_length(Config).
+
+%% Send a request containing an AVP whose advertised length is < 8.
+send_zero_avp_length(Config) ->
+ send_invalid_avp_length(Config).
%% Send a request containing an AVP length that doesn't match the
%% AVP's type.
send_invalid_avp_length(Config) ->
Req = ['STR', {'Termination-Cause', ?LOGOUT}],
- ['STA', _SessionId, {'Result-Code', ?INVALID_AVP_LENGTH} | _]
+ ['STA', _SessionId,
+ {'Result-Code', ?INVALID_AVP_LENGTH},
+ _OriginHost,
+ _OriginRealm,
+ _UserName,
+ _Class,
+ _ErrorMessage,
+ _ErrorReportingHost,
+ {'Failed-AVP', [#'diameter_base_Failed-AVP'{'AVP' = [_]}]}
+ | _]
= call(Config, Req).
%% Send a request containing 5xxx errors that the server rejects with
@@ -504,6 +525,14 @@ send_invalid_reject(Config) ->
?answer_message(?TOO_BUSY)
= call(Config, Req).
+%% Send an STR containing a known AVP, but one that's not allowed and
+%% sets the M-bit.
+send_unrecognized_mandatory(Config) ->
+ Req = ['STR', {'Termination-Cause', ?LOGOUT}],
+
+ ['STA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | _]
+ = call(Config, Req).
+
%% Send something long that will be fragmented by TCP.
send_long(Config) ->
Req = ['STR', {'Termination-Cause', ?LOGOUT},
@@ -552,7 +581,7 @@ send_all_2(Config) ->
%% Timeout before the server manages an answer.
send_timeout(Config) ->
Req = ['RAR', {'Re-Auth-Request-Type', ?AUTHORIZE_ONLY}],
- {error, timeout} = call(Config, Req, [{timeout, 1000}]).
+ {timeout, _} = call(Config, Req, [{timeout, 1000}]).
%% Explicitly answer with an answer-message and ensure that we
%% received the Session-Id.
@@ -560,7 +589,7 @@ send_error(Config) ->
Req = ['RAR', {'Re-Auth-Request-Type', ?AUTHORIZE_AUTHENTICATE}],
?answer_message(SId, ?TOO_BUSY)
= call(Config, Req),
- undefined /= SId.
+ true = undefined /= SId.
%% Send a request with the detached option and receive it as a message
%% from handle_answer instead.
@@ -568,7 +597,7 @@ send_detach(Config) ->
Req = ['STR', {'Termination-Cause', ?LOGOUT}],
Ref = make_ref(),
ok = call(Config, Req, [{extra, [{self(), Ref}]}, detach]),
- Ans = receive {Ref, T} -> T after 2000 -> false end,
+ Ans = receive {Ref, T} -> T end,
['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
= Ans.
@@ -683,7 +712,7 @@ call(Config, Req, Opts) ->
diameter:call(?CLIENT,
dict(Req, Dict0),
msg(Req, ReqEncoding, Dict0),
- [{extra, [Name, Group]} | Opts]).
+ [{extra, [{Name, Group}, now()]} | Opts]).
origin({A,C}) ->
2*codec(A) + container(C);
@@ -767,14 +796,14 @@ peer_down(_SvcName, _Peer, State) ->
%% pick_peer/6-7
-pick_peer(Peers, _, ?CLIENT, _State, Name, Group)
+pick_peer(Peers, _, ?CLIENT, _State, {Name, Group}, _)
when Name /= send_detach ->
find(Group, Peers).
-pick_peer(_Peers, _, ?CLIENT, _State, send_nopeer, _, ?EXTRA) ->
+pick_peer(_Peers, _, ?CLIENT, _State, {send_nopeer, _}, _, ?EXTRA) ->
false;
-pick_peer(Peers, _, ?CLIENT, _State, send_detach, Group, {_,_}) ->
+pick_peer(Peers, _, ?CLIENT, _State, {send_detach, Group}, _, {_,_}) ->
find(Group, Peers).
find(#group{server_encoding = A, server_container = C}, Peers) ->
@@ -789,13 +818,13 @@ id(Id, {Pid, _Caps}) ->
%% prepare_request/5-6
-prepare_request(_Pkt, ?CLIENT, {_Ref, _Caps}, send_discard, _) ->
+prepare_request(_Pkt, ?CLIENT, {_Ref, _Caps}, {send_discard, _}, _) ->
{discard, unprepared};
-prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, Name, Group) ->
+prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, {Name, Group}, _) ->
{send, prepare(Pkt, Caps, Name, Group)}.
-prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, send_detach, Group, _) ->
+prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, {send_detach, Group}, _, _) ->
{eval_packet, {send, prepare(Pkt, Caps, Group)}, [fun log/2, detach]}.
log(#diameter_packet{bin = Bin} = P, T)
@@ -804,45 +833,63 @@ log(#diameter_packet{bin = Bin} = P, T)
%% prepare/4
-prepare(Pkt, Caps, send_invalid_avp_bits, #group{client_dict0 = Dict0}
- = Group) ->
+prepare(Pkt, Caps, N, #group{client_dict0 = Dict0} = Group)
+ when N == send_long_avp_length;
+ N == send_short_avp_length;
+ N == send_zero_avp_length ->
Req = prepare(Pkt, Caps, Group),
- %% Last AVP in our STR is Termination-Cause of type Unsigned32:
- %% set its length improperly.
+ %% Second last AVP in our STR is Auth-Application-Id of type
+ %% Unsigned32: set AVP Length to a value other than 12 and place
+ %% it last in the message (so as not to mess with Termination-Cause).
#diameter_packet{header = #diameter_header{length = L},
bin = B}
= E
= diameter_codec:encode(Dict0, Pkt#diameter_packet{msg = Req}),
- Offset = L - 7, %% to AVP Length
- <<H:Offset/binary, 12:24/integer, T:4/binary>> = B,
- E#diameter_packet{bin = <<H/binary, 13:24/integer, T/binary>>};
+ Offset = L - 24, %% to Auth-Application-Id
+ <<H:Offset/binary,
+ Hdr:5/binary, 12:24, Data:4/binary,
+ T:12/binary>>
+ = B,
+ AL = case N of
+ send_long_avp_length -> 13;
+ send_short_avp_length -> 11;
+ send_zero_avp_length -> 0
+ end,
+ E#diameter_packet{bin = <<H/binary,
+ T/binary,
+ Hdr/binary, AL:24, Data/binary>>};
prepare(Pkt, Caps, N, #group{client_dict0 = Dict0} = Group)
when N == send_invalid_avp_length;
N == send_invalid_reject ->
Req = prepare(Pkt, Caps, Group),
%% Second last AVP in our STR is Auth-Application-Id of type
- %% Unsigned32: Send a value of length 8.
+ %% Unsigned32: send data of length 8.
#diameter_packet{header = #diameter_header{length = L},
bin = B0}
= E
= diameter_codec:encode(Dict0, Pkt#diameter_packet{msg = Req}),
Offset = L - 7 - 12, %% to AVP Length
- <<H0:Offset/binary, 12:24/integer, T:16/binary>> = B0,
- <<V, L:24/integer, H/binary>> = H0, %% assert
- E#diameter_packet{bin = <<V,
- (L+4):24/integer,
- H/binary,
- 16:24/integer,
- 0:32/integer,
- T/binary>>};
+ <<H0:Offset/binary, 12:24, T:16/binary>> = B0,
+ <<V, L:24, H/binary>> = H0, %% assert
+ E#diameter_packet{bin = <<V, (L+4):24, H/binary, 16:24, 0:32, T/binary>>};
+
+prepare(Pkt, Caps, send_unrecognized_mandatory, #group{client_dict0 = Dict0}
+ = Group) ->
+ Req = prepare(Pkt, Caps, Group),
+ #diameter_packet{bin = <<V, Len:24, T/binary>>}
+ = E
+ = diameter_codec:encode(Dict0, Pkt#diameter_packet{msg = Req}),
+ {Code, Flags, undefined} = Dict0:avp_header('Proxy-State'),
+ Avp = <<Code:32, Flags, 8:24>>,
+ E#diameter_packet{bin = <<V, (Len+8):24, T/binary, Avp/binary>>};
prepare(Pkt, Caps, send_unsupported, #group{client_dict0 = Dict0} = Group) ->
Req = prepare(Pkt, Caps, Group),
#diameter_packet{bin = <<H:5/binary, _CmdCode:3/binary, T/binary>>}
= E
= diameter_codec:encode(Dict0, Pkt#diameter_packet{msg = Req}),
- E#diameter_packet{bin = <<H/binary, 42:24/integer, T/binary>>};
+ E#diameter_packet{bin = <<H/binary, 42:24, T/binary>>};
prepare(Pkt, Caps, send_unsupported_app, #group{client_dict0 = Dict0}
= Group) ->
@@ -850,7 +897,7 @@ prepare(Pkt, Caps, send_unsupported_app, #group{client_dict0 = Dict0}
#diameter_packet{bin = <<H:8/binary, _ApplId:4/binary, T/binary>>}
= E
= diameter_codec:encode(Dict0, Pkt#diameter_packet{msg = Req}),
- E#diameter_packet{bin = <<H/binary, ?BAD_APP:32/integer, T/binary>>};
+ E#diameter_packet{bin = <<H/binary, ?BAD_APP:32, T/binary>>};
prepare(Pkt, Caps, send_error_bit, Group) ->
#diameter_packet{header = Hdr} = Pkt,
@@ -928,10 +975,10 @@ prepare_retransmit(_Pkt, false, _Peer, _Name, _Group) ->
%% handle_answer/6-7
-handle_answer(Pkt, Req, ?CLIENT, Peer, Name, Group) ->
+handle_answer(Pkt, Req, ?CLIENT, Peer, {Name, Group}, _) ->
answer(Pkt, Req, Peer, Name, Group).
-handle_answer(Pkt, Req, ?CLIENT, Peer, send_detach = Name, Group, X) ->
+handle_answer(Pkt, Req, ?CLIENT, Peer, {send_detach = Name, Group}, _, X) ->
{Pid, Ref} = X,
Pid ! {Ref, answer(Pkt, Req, Peer, Name, Group)}.
@@ -944,7 +991,9 @@ answer(Pkt, Req, _Peer, Name, #group{client_dict0 = Dict0}) ->
[Dict:rec2msg(R) | Vs].
answer(Rec, [_|_], N)
- when N == send_invalid_avp_bits;
+ when N == send_long_avp_length;
+ N == send_short_avp_length;
+ N == send_zero_avp_length;
N == send_invalid_avp_length;
N == send_invalid_reject ->
Rec;
@@ -959,7 +1008,11 @@ app(Req, _, Dict0) ->
%% handle_error/6
-handle_error(Reason, _Req, ?CLIENT, _Peer, _Name, _Group) ->
+handle_error(timeout = Reason, _Req, ?CLIENT, _Peer, _, Time) ->
+ Now = now(),
+ {Reason, {Time, Now, timer:now_diff(Now, Time)}};
+
+handle_error(Reason, _Req, ?CLIENT, _Peer, _, _Time) ->
{error, Reason}.
%% handle_request/3
@@ -1085,7 +1138,6 @@ request(#diameter_base_STR{'Session-Id' = SId},
{'Origin-Host', OH},
{'Origin-Realm', OR}]};
-%% send_error
+%% send_error/send_timeout
request(#diameter_base_RAR{}, _Caps) ->
- receive after 2000 -> ok end,
- {protocol_error, ?TOO_BUSY}.
+ receive after 2000 -> {protocol_error, ?TOO_BUSY} end.
diff --git a/lib/diameter/test/modules.mk b/lib/diameter/test/modules.mk
index 1a829f8031..4fea62461c 100644
--- a/lib/diameter/test/modules.mk
+++ b/lib/diameter/test/modules.mk
@@ -38,6 +38,7 @@ MODULES = \
diameter_examples_SUITE \
diameter_failover_SUITE \
diameter_gen_sctp_SUITE \
+ diameter_gen_tcp_SUITE \
diameter_length_SUITE \
diameter_reg_SUITE \
diameter_relay_SUITE \
diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl
index f6b13c2998..55794f57dc 100644
--- a/lib/inets/src/http_client/httpc_handler.erl
+++ b/lib/inets/src/http_client/httpc_handler.erl
@@ -33,7 +33,6 @@
%% connect_and_send/2,
send/2,
cancel/3,
- stream/3,
stream_next/1,
info/1
]).
@@ -65,7 +64,7 @@
options, % #options{}
timers = #timers{}, % #timers{}
profile_name, % atom() - id of httpc_manager process.
- once % send | undefined
+ once = inactive % inactive | once
}).
@@ -231,6 +230,8 @@ init([Parent, Request, Options, ProfileName]) ->
ProxyOptions = handle_proxy_options(Request#request.scheme, Options),
Address = handle_proxy(Request#request.address, ProxyOptions),
{ok, State} =
+ %% #state.once should initially be 'inactive' because we
+ %% activate the socket at first regardless of the state.
case {Address /= Request#request.address, Request#request.scheme} of
{true, https} ->
connect_and_send_upgrade_request(Address, Request,
@@ -425,7 +426,9 @@ handle_cast({cancel, RequestId, From},
handle_cast(stream_next, #state{session = Session} = State) ->
activate_once(Session),
- {noreply, State#state{once = once}}.
+ %% Inactivate the #state.once here because we don't want
+ %% next_body_chunk/1 to activate the socket twice.
+ {noreply, State#state{once = inactive}}.
%%--------------------------------------------------------------------
@@ -478,6 +481,41 @@ handle_info({Proto, _Socket, Data},
NewMFA = {Module, whole_body, [NewBody, NewLength]},
{noreply, NewState#state{mfa = NewMFA,
request = NewRequest}};
+ {Module, decode_size,
+ [TotalChunk, HexList,
+ {MaxBodySize, BodySoFar, AccLength, MaxHeaderSize}]}
+ when BodySoFar =/= <<>> ->
+ ?hcrd("data processed - decode_size", []),
+ %% The response body is chunk-encoded. Steal decoded
+ %% chunks as much as possible to stream.
+ {_, Code, _} = StatusLine,
+ {NewBody, NewRequest} = stream(BodySoFar, Request, Code),
+ NewState = next_body_chunk(State),
+ NewMFA = {Module, decode_size,
+ [TotalChunk, HexList,
+ {MaxBodySize, NewBody, AccLength, MaxHeaderSize}]},
+ {noreply, NewState#state{mfa = NewMFA,
+ request = NewRequest}};
+ {Module, decode_data,
+ [ChunkSize, TotalChunk,
+ {MaxBodySize, BodySoFar, AccLength, MaxHeaderSize}]}
+ when TotalChunk =/= <<>> orelse BodySoFar =/= <<>> ->
+ ?hcrd("data processed - decode_data", []),
+ %% The response body is chunk-encoded. Steal decoded
+ %% chunks as much as possible to stream.
+ ChunkSizeToSteal = min(ChunkSize, byte_size(TotalChunk)),
+ <<StolenChunk:ChunkSizeToSteal/binary, NewTotalChunk/binary>> = TotalChunk,
+ StolenBody = <<BodySoFar/binary, StolenChunk/binary>>,
+ NewChunkSize = ChunkSize - ChunkSizeToSteal,
+ {_, Code, _} = StatusLine,
+
+ {NewBody, NewRequest} = stream(StolenBody, Request, Code),
+ NewState = next_body_chunk(State),
+ NewMFA = {Module, decode_data,
+ [NewChunkSize, NewTotalChunk,
+ {MaxBodySize, NewBody, AccLength, MaxHeaderSize}]},
+ {noreply, NewState#state{mfa = NewMFA,
+ request = NewRequest}};
NewMFA ->
?hcrd("data processed - new mfa", []),
activate_once(Session),
@@ -1027,11 +1065,15 @@ handle_http_msg({Version, StatusCode, ReasonPharse, Headers, Body},
status_line = StatusLine,
headers = Headers})
end;
-handle_http_msg({ChunkedHeaders, Body}, #state{headers = Headers} = State) ->
+handle_http_msg({ChunkedHeaders, Body},
+ #state{status_line = {_, Code, _}, headers = Headers} = State) ->
?hcrt("handle_http_msg",
[{chunked_headers, ChunkedHeaders}, {headers, Headers}]),
NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders),
- handle_response(State#state{headers = NewHeaders, body = Body});
+ {NewBody, NewRequest} = stream(Body, State#state.request, Code),
+ handle_response(State#state{headers = NewHeaders,
+ body = NewBody,
+ request = NewRequest});
handle_http_msg(Body, #state{status_line = {_,Code, _}} = State) ->
?hcrt("handle_http_msg", [{code, Code}]),
{NewBody, NewRequest} = stream(Body, State#state.request, Code),
@@ -1070,8 +1112,7 @@ handle_http_body(Body, #state{headers = Headers,
"chunked" ->
?hcrt("handle_http_body - chunked", []),
case http_chunk:decode(Body, State#state.max_body_size,
- State#state.max_header_size,
- {Code, Request}) of
+ State#state.max_header_size) of
{Module, Function, Args} ->
?hcrt("handle_http_body - new mfa",
[{module, Module},
diff --git a/lib/inets/src/http_lib/http_chunk.erl b/lib/inets/src/http_lib/http_chunk.erl
index 57647438e9..24c939e80c 100644
--- a/lib/inets/src/http_lib/http_chunk.erl
+++ b/lib/inets/src/http_lib/http_chunk.erl
@@ -24,7 +24,7 @@
-include("http_internal.hrl").
%% API
--export([decode/3, decode/4, encode/1, encode_last/0, handle_headers/2]).
+-export([decode/3, encode/1, encode_last/0, handle_headers/2]).
%% Callback API - used for example if the chunkedbody is received a
%% little at a time on a socket.
-export([decode_size/1, ignore_extensions/1, decode_data/1, decode_trailer/1]).
@@ -34,20 +34,14 @@
%%% API
%%%=========================================================================
%%-------------------------------------------------------------------------
-%% decode(ChunkedBody, MaxBodySize, MaxHeaderSize, <Stream>) ->
+%% decode(ChunkedBody, MaxBodySize, MaxHeaderSize) ->
%% {ok, {Headers, Body}} | {Module, Function, Args}
%%
%% Headers = ["Header:Value"]
%% ChunkedBody = binary()
%% MaxBodySize = integer()
%% MaxHeaderSize = integer()
-%% Stream = {Code, Request} - if Request#request.stream =/= none
-%% and Code == 200 the side effect of sending each decode chunk to the
-%% client/file before the whole body is received will take place.
%%
-%% Note: decode/4 should only be used from httpc_handler module.
-%% Otherwhise use the side effect free decode/3.
-%%
%% Description: Decodes a body encoded by the chunked transfer
%% encoding. If the ChunkedBody is not compleate it returns {Module,
%% Function, Args} so that decoding can be continued when more of the
@@ -61,12 +55,9 @@
%% the next pass in the loop.
%%-------------------------------------------------------------------------
decode(ChunkedBody, MaxBodySize, MaxHeaderSize) ->
- decode(ChunkedBody, MaxBodySize, MaxHeaderSize, false).
-
-decode(ChunkedBody, MaxBodySize, MaxHeaderSize, Stream) ->
%% Note decode_size will call decode_data.
- decode_size([ChunkedBody, <<>>, [],
- {MaxBodySize, <<>>, 0, MaxHeaderSize, Stream}]).
+ decode_size([ChunkedBody, <<>>, [],
+ {MaxBodySize, <<>>, 0, MaxHeaderSize}]).
%%-------------------------------------------------------------------------
%% encode(Chunk) -> EncodedChunk
@@ -150,7 +141,7 @@ decode_size(<<>>, HexList, Info) ->
decode_size(Data = <<?CR, ?LF, ChunkRest/binary>>, HexList,
{MaxBodySize, Body,
AccLength,
- MaxHeaderSize, Stream}) ->
+ MaxHeaderSize}) ->
ChunkSize = http_util:hexlist_to_integer(lists:reverse(HexList)),
case ChunkSize of
0 -> % Last chunk, there was no data
@@ -164,7 +155,7 @@ decode_size(Data = <<?CR, ?LF, ChunkRest/binary>>, HexList,
%% to this function comes in.
decode_data(ChunkSize, ChunkRest, {MaxBodySize, Body,
ChunkSize + AccLength ,
- MaxHeaderSize, Stream})
+ MaxHeaderSize})
end;
decode_size(<<";", Rest/binary>>, HexList, Info) ->
%% Note ignore_extensions will call decode_size/1 again when
@@ -189,50 +180,42 @@ ignore_extensions(<<_Octet, Rest/binary>>, NextFunction) ->
ignore_extensions(Rest, NextFunction).
decode_data(ChunkSize, TotalChunk,
- Info = {MaxBodySize, BodySoFar, AccLength, MaxHeaderSize, Stream})
+ Info = {MaxBodySize, BodySoFar, AccLength, MaxHeaderSize})
when ChunkSize =< size(TotalChunk) ->
case TotalChunk of
%% Last chunk
<<Data:ChunkSize/binary, ?CR, ?LF, "0", ";">> ->
%% Note ignore_extensions will call decode_trailer/1
%% once it ignored all extensions.
- {NewBody, _} =
- stream(<<BodySoFar/binary, Data/binary>>, Stream),
{?MODULE, ignore_extensions,
[<<>>,
{?MODULE, decode_trailer, [<<>>, [],[], MaxHeaderSize,
- NewBody,
+ <<BodySoFar/binary, Data/binary>>,
integer_to_list(AccLength)]}]};
<<Data:ChunkSize/binary, ?CR, ?LF, "0", ";", Rest/binary>> ->
%% Note ignore_extensions will call decode_trailer/1
%% once it ignored all extensions.
- {NewBody, _} = stream(<<BodySoFar/binary, Data/binary>>, Stream),
ignore_extensions(Rest, {?MODULE, decode_trailer,
[<<>>, [],[], MaxHeaderSize,
- NewBody,
+ <<BodySoFar/binary, Data/binary>>,
integer_to_list(AccLength)]});
<<Data:ChunkSize/binary, ?CR, ?LF, "0", ?CR, ?LF>> ->
- {NewBody, _} = stream(<<BodySoFar/binary, Data/binary>>, Stream),
{?MODULE, decode_trailer, [<<?CR, ?LF>>, [],[], MaxHeaderSize,
- NewBody,
+ <<BodySoFar/binary, Data/binary>>,
integer_to_list(AccLength)]};
<<Data:ChunkSize/binary, ?CR, ?LF, "0", ?CR, ?LF, Rest/binary>> ->
- {NewBody,_}= stream(<<BodySoFar/binary, Data/binary>>, Stream),
decode_trailer(<<?CR, ?LF, Rest/binary>>, [],[], MaxHeaderSize,
- NewBody,
+ <<BodySoFar/binary, Data/binary>>,
integer_to_list(AccLength));
%% There are more chunks, so here we go agin...
<<Data:ChunkSize/binary, ?CR, ?LF>> ->
- {NewBody, NewStream} =
- stream(<<BodySoFar/binary, Data/binary>>, Stream),
- {?MODULE, decode_size, [<<>>, [], {MaxBodySize, NewBody, AccLength, MaxHeaderSize, NewStream}]};
+ NewBody = <<BodySoFar/binary, Data/binary>>,
+ {?MODULE, decode_size, [<<>>, [], {MaxBodySize, NewBody, AccLength, MaxHeaderSize}]};
<<Data:ChunkSize/binary, ?CR, ?LF, Rest/binary>>
when (AccLength < MaxBodySize) or (MaxBodySize == nolimit) ->
- {NewBody, NewStream} =
- stream(<<BodySoFar/binary, Data/binary>>, Stream),
decode_size(Rest, [],
- {MaxBodySize, NewBody,
- AccLength, MaxHeaderSize, NewStream});
+ {MaxBodySize, <<BodySoFar/binary, Data/binary>>,
+ AccLength, MaxHeaderSize});
<<_:ChunkSize/binary, ?CR, ?LF, _/binary>> ->
throw({error, body_too_big});
_ ->
@@ -286,9 +269,3 @@ decode_trailer(<<Octet, Rest/binary>>, Header, Headers, MaxHeaderSize, Body,
BodyLength) ->
decode_trailer(Rest, [Octet | Header], Headers, MaxHeaderSize,
Body, BodyLength).
-
-stream(BodyPart, false) ->
- {BodyPart, false};
-stream(BodyPart, {Code, Request}) ->
- {NewBody, NewRequest} = httpc_handler:stream(BodyPart, Request, Code),
- {NewBody, {Code, NewRequest}}.
diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl
index 350192464e..0c35f284f7 100644
--- a/lib/inets/test/httpc_SUITE.erl
+++ b/lib/inets/test/httpc_SUITE.erl
@@ -1693,6 +1693,15 @@ receive_streamed_body(RequestId, Body, Pid) ->
ct:print("~p:receive_streamed_body -> requested next stream ~n", [?MODULE]),
receive
{http, {RequestId, stream, BinBodyPart}} ->
+ %% Make sure the httpc hasn't sent us the next 'stream'
+ %% without our request.
+ receive
+ {http, {RequestId, stream, _}} = Msg ->
+ ct:fail({unexpected_flood_of_stream, Msg})
+ after
+ 1000 ->
+ ok
+ end,
receive_streamed_body(RequestId,
<<Body/binary, BinBodyPart/binary>>,
Pid);
diff --git a/lib/kernel/src/os.erl b/lib/kernel/src/os.erl
index 742c000cc1..ded03361ee 100644
--- a/lib/kernel/src/os.erl
+++ b/lib/kernel/src/os.erl
@@ -126,7 +126,7 @@ verify_executable(Name0, [Ext|Rest], OrigExtensions) ->
end;
verify_executable(Name, [], OrigExtensions) when OrigExtensions =/= [""] -> %% Windows
%% Will only happen on windows, hence case insensitivity
- case can_be_full_name(string:to_lower(Name),OrigExtensions) of
+ case can_be_full_name(string:to_lower(Name),OrigExtensions) of
true ->
verify_executable(Name,[""],[""]);
_ ->
@@ -150,7 +150,7 @@ split_path(Path) ->
{win32, _} ->
{ok,Curr} = file:get_cwd(),
split_path(Path, $;, [], [Curr]);
- _ ->
+ _ ->
split_path(Path, $:, [], [])
end.
@@ -187,11 +187,14 @@ cmd(Cmd) ->
{unix, _} ->
unix_cmd(Cmd);
{win32, Wtype} ->
- Command = case {os:getenv("COMSPEC"),Wtype} of
+ Command0 = case {os:getenv("COMSPEC"),Wtype} of
{false,windows} -> lists:concat(["command.com /c", Cmd]);
{false,_} -> lists:concat(["cmd /c", Cmd]);
{Cspec,_} -> lists:concat([Cspec," /c",Cmd])
end,
+ %% open_port/2 awaits string() in Command, but io_lib:chars() can be
+ %% deep lists according to io_lib module description.
+ Command = lists:flatten(Command0),
Port = open_port({spawn, Command}, [stream, in, eof, hide]),
get_data(Port, [])
end.
@@ -213,7 +216,7 @@ unix_cmd(Cmd) ->
end.
%% The -s flag implies that only the positional parameters are set,
-%% and the commands are read from standard input. We set the
+%% and the commands are read from standard input. We set the
%% $1 parameter for easy identification of the resident shell.
%%
-define(SHELL, "/bin/sh -s unix:cmd 2>&1").
@@ -226,7 +229,7 @@ unix_cmd(Cmd) ->
-spec start_port() -> port().
start_port() ->
Ref = make_ref(),
- Request = {Ref,self()},
+ Request = {Ref,self()},
{Pid, Mon} = case whereis(?PORT_CREATOR_NAME) of
undefined ->
spawn_monitor(fun() ->
@@ -273,7 +276,7 @@ start_port_srv_handle({Ref,Client}) ->
Port
catch
error:Reason ->
- {Reason,erlang:get_stacktrace()}
+ {Reason,erlang:get_stacktrace()}
end,
Client ! {Ref,Reply}.
@@ -355,16 +358,16 @@ get_data(Port, Sofar) ->
{Port, {data, Bytes}} ->
get_data(Port, [Sofar|Bytes]);
{Port, eof} ->
- Port ! {self(), close},
+ Port ! {self(), close},
receive
{Port, closed} ->
true
- end,
+ end,
receive
- {'EXIT', Port, _} ->
+ {'EXIT', Port, _} ->
ok
after 1 -> % force context switch
ok
- end,
+ end,
lists:flatten(Sofar)
end.
diff --git a/lib/kernel/test/os_SUITE.erl b/lib/kernel/test/os_SUITE.erl
index 382fd6f6a9..73ed704ae3 100644
--- a/lib/kernel/test/os_SUITE.erl
+++ b/lib/kernel/test/os_SUITE.erl
@@ -18,20 +18,21 @@
%%
-module(os_SUITE).
--export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
+-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2]).
-export([space_in_cwd/1, quoting/1, space_in_name/1, bad_command/1,
- find_executable/1, unix_comment_in_command/1, evil/1]).
+ find_executable/1, unix_comment_in_command/1, deep_list_command/1, evil/1]).
-include_lib("test_server/include/test_server.hrl").
suite() -> [{ct_hooks,[ts_install_cth]}].
-all() ->
+all() ->
[space_in_cwd, quoting, space_in_name, bad_command,
- find_executable, unix_comment_in_command, evil].
+ find_executable, unix_comment_in_command, deep_list_command,
+ evil].
-groups() ->
+groups() ->
[].
init_per_suite(Config) ->
@@ -117,9 +118,9 @@ space_in_name(Config) when is_list(Config) ->
?line ok = file:change_mode(Echo, 8#777), % Make it executable on Unix.
%% Run the echo program.
- %% Quoting on windows depends on if the full path of the executable
+ %% Quoting on windows depends on if the full path of the executable
%% contains special characters. Paths when running common_tests always
- %% include @, why Windows would always fail if we do not double the
+ %% include @, why Windows would always fail if we do not double the
%% quotes (this is the behaviour of cmd.exe, not Erlang's idea).
Quote = case os:type() of
{win32,_} ->
@@ -135,7 +136,7 @@ space_in_name(Config) when is_list(Config) ->
?t:sleep(5),
?line [] = receive_all(),
ok.
-
+
bad_command(doc) ->
"Check that a bad command doesn't crasch the server or the emulator (it used to).";
bad_command(suite) -> [];
@@ -153,17 +154,17 @@ find_executable(suite) -> [];
find_executable(doc) -> [];
find_executable(Config) when is_list(Config) ->
case os:type() of
- {win32, _} ->
+ {win32, _} ->
?line DataDir = filename:join(?config(data_dir, Config), "win32"),
?line ok = file:set_cwd(filename:join([DataDir, "current"])),
?line Bin = filename:join(DataDir, "bin"),
?line Abin = filename:join(DataDir, "abin"),
?line UsrBin = filename:join([DataDir, "usr", "bin"]),
?line {ok, Current} = file:get_cwd(),
-
+
?line Path = lists:concat([Bin, ";", Abin, ";", UsrBin]),
?line io:format("Path = ~s", [Path]),
-
+
%% Search for programs in Bin (second element in PATH).
?line find_exe(Abin, "my_ar", ".exe", Path),
?line find_exe(Abin, "my_ascii", ".com", Path),
@@ -175,18 +176,18 @@ find_executable(Config) when is_list(Config) ->
?line find_exe(Abin, "my_ar.EXE", "", Path),
?line find_exe(Abin, "my_ascii.COM", "", Path),
?line find_exe(Abin, "MY_ADB.BAT", "", Path),
-
+
%% Search for programs in Abin (second element in PATH).
?line find_exe(Abin, "my_ar", ".exe", Path),
?line find_exe(Abin, "my_ascii", ".com", Path),
?line find_exe(Abin, "my_adb", ".bat", Path),
-
+
%% Search for programs in the current working directory.
?line find_exe(Current, "my_program", ".exe", Path),
?line find_exe(Current, "my_command", ".com", Path),
?line find_exe(Current, "my_batch", ".bat", Path),
ok;
- {unix, _} ->
+ {unix, _} ->
DataDir = ?config(data_dir, Config),
%% Smoke test.
@@ -237,6 +238,21 @@ unix_comment_in_command(Config) when is_list(Config) ->
?line test_server:timetrap_cancel(Dog),
ok.
+deep_list_command(doc) ->
+ "Check that a deep list in command works equally on unix and on windows.";
+deep_list_command(suite) -> [];
+deep_list_command(Config) when is_list(Config) ->
+ %% As a 'io_lib' module description says: "There is no guarantee that the
+ %% character lists returned from some of the functions are flat, they can
+ %% be deep lists."
+ %% That's why os:cmd/1 can have arguments that are deep lists.
+ %% It is not a problem for unix, but for windows it is (in R15B02 for ex.).
+ Echo = os:cmd([$e, $c, "ho"]),
+ true = erlang:is_list(Echo),
+ %% FYI: [$e, $c, "ho"] =:= io_lib:format("ec~s", ["ho"])
+ ok.
+
+
-define(EVIL_PROCS, 100).
-define(EVIL_LOOPS, 100).
-define(PORT_CREATOR, os_cmd_port_creator).
@@ -303,4 +319,3 @@ receive_all() ->
X -> [X|receive_all()]
after 0 -> []
end.
-
diff --git a/lib/odbc/configure.in b/lib/odbc/configure.in
index fd28830c0c..83f7a47434 100644
--- a/lib/odbc/configure.in
+++ b/lib/odbc/configure.in
@@ -167,7 +167,8 @@ AC_SUBST(TARGET_FLAGS)
AC_CHECK_SIZEOF(void *)
AC_MSG_CHECKING([for odbc in standard locations])
for rdir in /usr/local/odbc /usr/local /usr/odbc \
- /usr /opt/local/pgm/odbc /usr/local/pgm/odbc; do
+ /usr /opt/local/pgm/odbc /usr/local/pgm/odbc \
+ "$with_odbc"; do
test -f "$erl_xcomp_isysroot$rdir/include/sql.h" || continue
is_odbc_std_location=yes
libdir="$erl_xcomp_sysroot$rdir/lib"
diff --git a/lib/stdlib/doc/src/c.xml b/lib/stdlib/doc/src/c.xml
index ddae388a1b..9cd4581a89 100644
--- a/lib/stdlib/doc/src/c.xml
+++ b/lib/stdlib/doc/src/c.xml
@@ -140,9 +140,9 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w
</func>
<func>
<name name="ls" arity="1"/>
- <fsummary>List files in a directory</fsummary>
+ <fsummary>List files in a directory or a single file</fsummary>
<desc>
- <p>Lists files in directory <c><anno>Dir</anno></c>.</p>
+ <p>Lists files in directory <c><anno>Dir</anno></c> or, if Dir is a file, only list it.</p>
</desc>
</func>
<func>
diff --git a/lib/stdlib/src/c.erl b/lib/stdlib/src/c.erl
index 91d317489c..6e96e3d564 100644
--- a/lib/stdlib/src/c.erl
+++ b/lib/stdlib/src/c.erl
@@ -713,8 +713,10 @@ ls(Dir) ->
case file:list_dir(Dir) of
{ok, Entries} ->
ls_print(sort(Entries));
- {error,_E} ->
- format("Invalid directory\n")
+ {error, enotdir} ->
+ ls_print([Dir]);
+ {error, Error} ->
+ format("~s\n", [file:format_error(Error)])
end.
ls_print([]) -> ok;
diff --git a/lib/stdlib/test/c_SUITE.erl b/lib/stdlib/test/c_SUITE.erl
index 25281365be..8c55b616b9 100644
--- a/lib/stdlib/test/c_SUITE.erl
+++ b/lib/stdlib/test/c_SUITE.erl
@@ -20,7 +20,7 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2]).
-export([c_1/1, c_2/1, c_3/1, c_4/1, nc_1/1, nc_2/1, nc_3/1, nc_4/1,
- memory/1]).
+ ls/1, memory/1]).
-include_lib("test_server/include/test_server.hrl").
@@ -29,7 +29,7 @@
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
- [c_1, c_2, c_3, c_4, nc_1, nc_2, nc_3, nc_4, memory].
+ [c_1, c_2, c_3, c_4, nc_1, nc_2, nc_3, nc_4, ls, memory].
groups() ->
[].
@@ -147,6 +147,13 @@ nc_4(Config) when is_list(Config) ->
?line Result = nc(R,[{outdir,W}]),
?line {ok, m} = Result.
+ls(Config) when is_list(Config) ->
+ Directory = ?config(data_dir, Config),
+ ok = c:ls(Directory),
+ File = filename:join(Directory, "m.erl"),
+ ok = c:ls(File),
+ ok = c:ls("no_such_file").
+
memory(doc) ->
["Checks that c:memory/[0,1] returns consistent results."];
memory(suite) ->