diff options
62 files changed, 1396 insertions, 780 deletions
diff --git a/erts/doc/src/erl_nif.xml b/erts/doc/src/erl_nif.xml index f64381c99d..412c0e02ac 100644 --- a/erts/doc/src/erl_nif.xml +++ b/erts/doc/src/erl_nif.xml @@ -1177,7 +1177,7 @@ typedef enum { <code type="none"> ERL_NIF_TERM key, value; ErlNifMapIterator iter; -enif_map_iterator_create(env, my_map, ERL_NIF_MAP_ITERATOR_FIRST); +enif_map_iterator_create(env, my_map, &iter, ERL_NIF_MAP_ITERATOR_FIRST); while (enif_map_iterator_get_pair(env, &iter, &key, &value)) { do_something(key,value); diff --git a/erts/emulator/beam/atom.names b/erts/emulator/beam/atom.names index 74b42c647e..0cf21b4635 100644 --- a/erts/emulator/beam/atom.names +++ b/erts/emulator/beam/atom.names @@ -265,6 +265,7 @@ atom get_seq_token atom get_tcw atom getenv atom gather_gc_info_result +atom gather_io_bytes atom gather_sched_wall_time_result atom getting_linked atom getting_unlinked diff --git a/erts/emulator/beam/dist.c b/erts/emulator/beam/dist.c index ae46174a14..1354cee267 100644 --- a/erts/emulator/beam/dist.c +++ b/erts/emulator/beam/dist.c @@ -2088,6 +2088,7 @@ erts_dist_command(Port *prt, int reds_limit) DistEntry *dep = prt->dist_entry; Uint (*send)(Port *prt, ErtsDistOutputBuf *obuf); erts_aint32_t sched_flags; + ErtsSchedulerData *esdp = erts_get_scheduler_data(); ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt)); @@ -2142,12 +2143,12 @@ erts_dist_command(Port *prt, int reds_limit) ErtsDistOutputBuf *fob; size = (*send)(prt, foq.first); + esdp->io.out += (Uint64) size; #ifdef ERTS_RAW_DIST_MSG_DBG erts_fprintf(stderr, ">> "); bw(foq.first->extp, size); #endif reds += ERTS_PORT_REDS_DIST_CMD_DATA(size); - erts_smp_atomic_add_nob(&erts_bytes_out, size); fob = foq.first; obufsize += size_obuf(fob); foq.first = foq.first->next; @@ -2227,12 +2228,12 @@ erts_dist_command(Port *prt, int reds_limit) ASSERT(&oq.first->data[0] <= oq.first->extp && oq.first->extp < oq.first->ext_endp); size = (*send)(prt, oq.first); + esdp->io.out += (Uint64) size; #ifdef ERTS_RAW_DIST_MSG_DBG erts_fprintf(stderr, ">> "); bw(oq.first->extp, size); #endif reds += ERTS_PORT_REDS_DIST_CMD_DATA(size); - erts_smp_atomic_add_nob(&erts_bytes_out, size); fob = oq.first; obufsize += size_obuf(fob); oq.first = oq.first->next; diff --git a/erts/emulator/beam/erl_alloc.types b/erts/emulator/beam/erl_alloc.types index 2721e13250..92c397ffd3 100644 --- a/erts/emulator/beam/erl_alloc.types +++ b/erts/emulator/beam/erl_alloc.types @@ -274,6 +274,7 @@ type BUSY_CALLER SHORT_LIVED SYSTEM busy_caller type PROC_SYS_TSK SHORT_LIVED PROCESSES proc_sys_task type PROC_SYS_TSK_QS SHORT_LIVED PROCESSES proc_sys_task_queues type NEW_TIME_OFFSET SHORT_LIVED SYSTEM new_time_offset +type IOB_REQ SHORT_LIVED SYSTEM io_bytes_request +if threads_no_smp # Need thread safe allocs, but std_alloc and fix_alloc are not; diff --git a/erts/emulator/beam/erl_bif_info.c b/erts/emulator/beam/erl_bif_info.c index 397c68e199..7eb31fb80e 100644 --- a/erts/emulator/beam/erl_bif_info.c +++ b/erts/emulator/beam/erl_bif_info.c @@ -60,6 +60,7 @@ static Export* alloc_info_trap = NULL; static Export* alloc_sizes_trap = NULL; +static Export* gather_io_bytes_trap = NULL; static Export *gather_sched_wall_time_res_trap; static Export *gather_gc_info_res_trap; @@ -3220,7 +3221,6 @@ BIF_RETTYPE process_display_2(BIF_ALIST_2) BIF_RET(am_true); } - /* this is a general call which return some possibly useful information */ BIF_RETTYPE statistics_1(BIF_ALIST_1) @@ -3293,23 +3293,8 @@ BIF_RETTYPE statistics_1(BIF_ALIST_1) res = TUPLE2(hp, b1, b2); BIF_RET(res); } else if (BIF_ARG_1 == am_io) { - Eterm r1, r2; - Eterm in, out; - Uint hsz = 9; - Uint bytes_in = (Uint) erts_smp_atomic_read_nob(&erts_bytes_in); - Uint bytes_out = (Uint) erts_smp_atomic_read_nob(&erts_bytes_out); - - (void) erts_bld_uint(NULL, &hsz, bytes_in); - (void) erts_bld_uint(NULL, &hsz, bytes_out); - hp = HAlloc(BIF_P, hsz); - in = erts_bld_uint(&hp, NULL, bytes_in); - out = erts_bld_uint(&hp, NULL, bytes_out); - - r1 = TUPLE2(hp, am_input, in); - hp += 3; - r2 = TUPLE2(hp, am_output, out); - hp += 3; - BIF_RET(TUPLE2(hp, r1, r2)); + Eterm ref = erts_request_io_bytes(BIF_P); + BIF_TRAP2(gather_io_bytes_trap, BIF_P, ref, make_small(erts_no_schedulers)); } else if (ERTS_IS_ATOM_STR("run_queues", BIF_ARG_1)) { Eterm res, *hp, **hpp; @@ -4296,59 +4281,41 @@ BIF_RETTYPE erts_debug_lock_counters_1(BIF_ALIST_1) BIF_RET(am_ok); } else if (is_tuple(BIF_ARG_1)) { - Eterm* tp = tuple_val(BIF_ARG_1); + Eterm* ptr = tuple_val(BIF_ARG_1); + + if ((arityval(ptr[0]) == 2) && (ptr[2] == am_false || ptr[2] == am_true)) { + int lock_opt = 0, enable = (ptr[2] == am_true) ? 1 : 0; + if (ERTS_IS_ATOM_STR("copy_save", ptr[1])) { + lock_opt = ERTS_LCNT_OPT_COPYSAVE; + } else if (ERTS_IS_ATOM_STR("process_locks", ptr[1])) { + lock_opt = ERTS_LCNT_OPT_PROCLOCK; + } else if (ERTS_IS_ATOM_STR("port_locks", ptr[1])) { + lock_opt = ERTS_LCNT_OPT_PORTLOCK; + } else if (ERTS_IS_ATOM_STR("suspend", ptr[1])) { + lock_opt = ERTS_LCNT_OPT_SUSPEND; + } else if (ERTS_IS_ATOM_STR("location", ptr[1])) { + lock_opt = ERTS_LCNT_OPT_LOCATION; + } else { + BIF_ERROR(BIF_P, BADARG); + } - switch (arityval(tp[0])) { - case 2: { - int opt = 0; - int val = 0; - if (ERTS_IS_ATOM_STR("copy_save", tp[1])) { - opt = ERTS_LCNT_OPT_COPYSAVE; - } else if (ERTS_IS_ATOM_STR("process_locks", tp[1])) { - opt = ERTS_LCNT_OPT_PROCLOCK; - } else if (ERTS_IS_ATOM_STR("port_locks", tp[1])) { - opt = ERTS_LCNT_OPT_PORTLOCK; - } else if (ERTS_IS_ATOM_STR("suspend", tp[1])) { - opt = ERTS_LCNT_OPT_SUSPEND; - } else if (ERTS_IS_ATOM_STR("location", tp[1])) { - opt = ERTS_LCNT_OPT_LOCATION; - } else { - BIF_ERROR(BIF_P, BADARG); - } - if (tp[2] == am_true) { - val = 1; - } else if (tp[2] == am_false) { - val = 0; - } else { - BIF_ERROR(BIF_P, BADARG); - } + erts_smp_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN); + erts_smp_thr_progress_block(); - erts_smp_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN); - erts_smp_thr_progress_block(); - - if (val) { - res = erts_lcnt_set_rt_opt(opt) ? am_true : am_false; - } else { - res = erts_lcnt_clear_rt_opt(opt) ? am_true : am_false; - } + if (enable) res = erts_lcnt_set_rt_opt(lock_opt) ? am_true : am_false; + else res = erts_lcnt_clear_rt_opt(lock_opt) ? am_true : am_false; + #ifdef ERTS_SMP - if (res != tp[2]) { - if (opt == ERTS_LCNT_OPT_PORTLOCK) { - erts_lcnt_enable_io_lock_count(val); - } else if (opt == ERTS_LCNT_OPT_PROCLOCK) { - erts_lcnt_enable_proc_lock_count(val); - } - } + if (res != ptr[2] && lock_opt == ERTS_LCNT_OPT_PORTLOCK) { + erts_lcnt_enable_io_lock_count(enable); + } else if (res != ptr[2] && lock_opt == ERTS_LCNT_OPT_PROCLOCK) { + erts_lcnt_enable_proc_lock_count(enable); + } #endif - erts_smp_thr_progress_unblock(); - erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN); - BIF_RET(res); - break; - } - - default: - break; - } + erts_smp_thr_progress_unblock(); + erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN); + BIF_RET(res); + } } #endif @@ -4388,6 +4355,8 @@ erts_bif_info_init(void) = 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); + gather_io_bytes_trap + = erts_export_put(am_erts_internal, am_gather_io_bytes, 2); process_info_init(); os_info_init(); } diff --git a/erts/emulator/beam/erl_lock_count.c b/erts/emulator/beam/erl_lock_count.c index c6d8f4df95..c4956d8569 100644 --- a/erts/emulator/beam/erl_lock_count.c +++ b/erts/emulator/beam/erl_lock_count.c @@ -20,7 +20,7 @@ /* * Description: Statistics for locks. * - * Author: Bj�rn-Egil Dahlberg + * Author: Björn-Egil Dahlberg * Date: 2008-07-03 */ @@ -49,7 +49,7 @@ const char *str_undefined = "undefined"; static ethr_tsd_key lcnt_thr_data_key; static int lcnt_n_thr; -static erts_lcnt_thread_data_t *lcnt_thread_data[4096]; +static erts_lcnt_thread_data_t *lcnt_thread_data[2048]; /* local functions */ @@ -83,12 +83,12 @@ static ERTS_INLINE int lcnt_log2(Uint64 v) { static char* lcnt_lock_type(Uint16 flag) { switch(flag & ERTS_LCNT_LT_ALL) { - case ERTS_LCNT_LT_SPINLOCK: return "spinlock"; - case ERTS_LCNT_LT_RWSPINLOCK: return "rw_spinlock"; - case ERTS_LCNT_LT_MUTEX: return "mutex"; - case ERTS_LCNT_LT_RWMUTEX: return "rw_mutex"; - case ERTS_LCNT_LT_PROCLOCK: return "proclock"; - default: return ""; + case ERTS_LCNT_LT_SPINLOCK: return "spinlock"; + case ERTS_LCNT_LT_RWSPINLOCK: return "rw_spinlock"; + case ERTS_LCNT_LT_MUTEX: return "mutex"; + case ERTS_LCNT_LT_RWMUTEX: return "rw_mutex"; + case ERTS_LCNT_LT_PROCLOCK: return "proclock"; + default: return ""; } } @@ -116,15 +116,15 @@ static void lcnt_time(erts_lcnt_time_t *time) { static void lcnt_time_diff(erts_lcnt_time_t *d, erts_lcnt_time_t *t1, erts_lcnt_time_t *t0) { long ds; long dns; - + ds = t1->s - t0->s; dns = t1->ns - t0->ns; - + /* the difference should not be able to get bigger than 1 sec in ns*/ - + if (dns < 0) { - ds -= 1; - dns += 1000000000LL; + ds -= 1; + dns += 1000000000LL; } ASSERT(ds >= 0); @@ -145,7 +145,7 @@ static void lcnt_time_add(erts_lcnt_time_t *t, erts_lcnt_time_t *d) { static erts_lcnt_thread_data_t *lcnt_thread_data_alloc(void) { erts_lcnt_thread_data_t *eltd; - + eltd = (erts_lcnt_thread_data_t*)malloc(sizeof(erts_lcnt_thread_data_t)); if (!eltd) { ERTS_INTERNAL_ERROR("Lock counter failed to allocate memory!"); @@ -164,7 +164,6 @@ static erts_lcnt_thread_data_t *lcnt_get_thread_data(void) { return (erts_lcnt_thread_data_t *)ethr_tsd_get(lcnt_thr_data_key); } - /* debug */ #if 0 @@ -183,15 +182,15 @@ static void print_lock_x(erts_lcnt_lock_t *lock, Uint16 flag, char *action) { type = lcnt_lock_type(lock->flag); r_state = ethr_atomic_read(&lock->r_state); w_state = ethr_atomic_read(&lock->w_state); - + if (lock->flag & flag) { erts_fprintf(stderr,"%10s [%24s] [r/w state %4ld/%4ld] %2s id %T\r\n", - action, - lock->name, - r_state, - w_state, - type, - lock->id); + action, + lock->name, + r_state, + w_state, + type, + lock->id); } } #endif @@ -201,18 +200,18 @@ static erts_lcnt_lock_stats_t *lcnt_get_lock_stats(erts_lcnt_lock_t *lock, char erts_lcnt_lock_stats_t *stats = NULL; if (erts_lcnt_rt_options & ERTS_LCNT_OPT_LOCATION) { - for (i = 0; i < lock->n_stats; i++) { - if ((lock->stats[i].file == file) && (lock->stats[i].line == line)) { - return &(lock->stats[i]); - } - } - if (lock->n_stats < ERTS_LCNT_MAX_LOCK_LOCATIONS) { - stats = &lock->stats[lock->n_stats]; - lock->n_stats++; - stats->file = file; - stats->line = line; - return stats; - } + for (i = 0; i < lock->n_stats; i++) { + if ((lock->stats[i].file == file) && (lock->stats[i].line == line)) { + return &(lock->stats[i]); + } + } + if (lock->n_stats < ERTS_LCNT_MAX_LOCK_LOCATIONS) { + stats = &lock->stats[lock->n_stats]; + lock->n_stats++; + stats->file = file; + stats->line = line; + return stats; + } } return &lock->stats[0]; } @@ -222,53 +221,48 @@ static void lcnt_update_stats_hist(erts_lcnt_hist_t *hist, erts_lcnt_time_t *tim unsigned long r; if (time_wait->s > 0 || time_wait->ns > ERTS_LCNT_HISTOGRAM_MAX_NS) { - idx = ERTS_LCNT_HISTOGRAM_SLOT_SIZE - 1; + idx = ERTS_LCNT_HISTOGRAM_SLOT_SIZE - 1; } else { - r = time_wait->ns >> ERTS_LCNT_HISTOGRAM_RSHIFT; - if (r) idx = lcnt_log2(r); - else idx = 0; + r = time_wait->ns >> ERTS_LCNT_HISTOGRAM_RSHIFT; + if (r) idx = lcnt_log2(r); + else idx = 0; } hist->ns[idx]++; } static void lcnt_update_stats(erts_lcnt_lock_stats_t *stats, int lock_in_conflict, - erts_lcnt_time_t *time_wait) { - + erts_lcnt_time_t *time_wait) { + ethr_atomic_inc(&stats->tries); if (lock_in_conflict) - ethr_atomic_inc(&stats->colls); + ethr_atomic_inc(&stats->colls); if (time_wait) { - lcnt_time_add(&(stats->timer), time_wait); - stats->timer_n++; - lcnt_update_stats_hist(&stats->hist,time_wait); + lcnt_time_add(&(stats->timer), time_wait); + stats->timer_n++; + lcnt_update_stats_hist(&stats->hist,time_wait); } } -/* - * interface - */ +/* interface */ void erts_lcnt_init() { erts_lcnt_thread_data_t *eltd = NULL; - + /* init lock */ if (ethr_mutex_init(&lcnt_data_lock) != 0) abort(); /* init tsd */ lcnt_n_thr = 0; - ethr_tsd_key_create(&lcnt_thr_data_key,"lcnt_data"); lcnt_lock(); - erts_lcnt_rt_options = ERTS_LCNT_OPT_PROCLOCK | ERTS_LCNT_OPT_LOCATION; - + erts_lcnt_rt_options = ERTS_LCNT_OPT_LOCATION | ERTS_LCNT_OPT_PROCLOCK; eltd = lcnt_thread_data_alloc(); - ethr_tsd_set(lcnt_thr_data_key, eltd); - + /* init lcnt structure */ erts_lcnt_data = (erts_lcnt_data_t*)malloc(sizeof(erts_lcnt_data_t)); if (!erts_lcnt_data) { @@ -293,7 +287,7 @@ void erts_lcnt_late_init() { erts_lcnt_lock_list_t *erts_lcnt_list_init(void) { erts_lcnt_lock_list_t *list; - + list = (erts_lcnt_lock_list_t*)malloc(sizeof(erts_lcnt_lock_list_t)); if (!list) { ERTS_INTERNAL_ERROR("Lock counter failed to allocate memory!"); @@ -307,14 +301,14 @@ erts_lcnt_lock_list_t *erts_lcnt_list_init(void) { /* only do this on the list with the deleted locks! */ void erts_lcnt_list_clear(erts_lcnt_lock_list_t *list) { erts_lcnt_lock_t *lock = NULL, - *next = NULL; + *next = NULL; lock = list->head; - + while(lock != NULL) { - next = lock->next; - free(lock); - lock = next; + next = lock->next; + free(lock); + lock = next; } list->head = NULL; @@ -327,26 +321,25 @@ void erts_lcnt_list_insert(erts_lcnt_lock_list_t *list, erts_lcnt_lock_t *lock) tail = list->tail; if (tail) { - tail->next = lock; - lock->prev = tail; + tail->next = lock; + lock->prev = tail; } else { - list->head = lock; - lock->prev = NULL; - ASSERT(!lock->next); + list->head = lock; + lock->prev = NULL; + ASSERT(!lock->next); } lock->next = NULL; list->tail = lock; - + list->n++; } void erts_lcnt_list_delete(erts_lcnt_lock_list_t *list, erts_lcnt_lock_t *lock) { - if (lock->next) lock->next->prev = lock->prev; if (lock->prev) lock->prev->next = lock->next; if (list->head == lock) list->head = lock->next; if (list->tail == lock) list->tail = lock->prev; - + lock->prev = NULL; lock->next = NULL; list->n--; @@ -364,12 +357,9 @@ void erts_lcnt_init_lock(erts_lcnt_lock_t *lock, char *name, Uint16 flag ) { void erts_lcnt_init_lock_x(erts_lcnt_lock_t *lock, char *name, Uint16 flag, Eterm id) { int i; - if (!name) { - lock->flag = 0; - return; - } + if (name == NULL) { ERTS_LCNT_CLEAR_FLAG(lock); return; } lcnt_lock(); - + lock->next = NULL; lock->prev = NULL; lock->flag = flag; @@ -378,46 +368,55 @@ void erts_lcnt_init_lock_x(erts_lcnt_lock_t *lock, char *name, Uint16 flag, Eter ethr_atomic_init(&lock->r_state, 0); ethr_atomic_init(&lock->w_state, 0); - #ifdef DEBUG ethr_atomic_init(&lock->flowstate, 0); #endif - + lock->n_stats = 1; for (i = 0; i < ERTS_LCNT_MAX_LOCK_LOCATIONS; i++) { - lcnt_clear_stats(&lock->stats[i]); + lcnt_clear_stats(&lock->stats[i]); } erts_lcnt_list_insert(erts_lcnt_data->current_locks, lock); lcnt_unlock(); } - +/* init empty, instead of zero struct */ +void erts_lcnt_init_lock_empty(erts_lcnt_lock_t *lock) { + lock->next = NULL; + lock->prev = NULL; + lock->flag = 0; + lock->name = NULL; + lock->id = NIL; + ethr_atomic_init(&lock->r_state, 0); + ethr_atomic_init(&lock->w_state, 0); +#ifdef DEBUG + ethr_atomic_init(&lock->flowstate, 0); +#endif + lock->n_stats = 0; + sys_memzero(lock->stats, sizeof(lock->stats)); +} +/* destroy lock */ void erts_lcnt_destroy_lock(erts_lcnt_lock_t *lock) { - erts_lcnt_lock_t *deleted_lock; - - if (!ERTS_LCNT_LOCK_TYPE(lock)) return; - + if (ERTS_LCNT_IS_LOCK_INVALID(lock)) return; lcnt_lock(); if (erts_lcnt_rt_options & ERTS_LCNT_OPT_COPYSAVE) { - /* copy structure and insert the copy */ - - deleted_lock = (erts_lcnt_lock_t*)malloc(sizeof(erts_lcnt_lock_t)); + erts_lcnt_lock_t *deleted_lock; + /* copy structure and insert the copy */ + deleted_lock = (erts_lcnt_lock_t*)malloc(sizeof(erts_lcnt_lock_t)); if (!deleted_lock) { ERTS_INTERNAL_ERROR("Lock counter failed to allocate memory!"); } - memcpy(deleted_lock, lock, sizeof(erts_lcnt_lock_t)); - - deleted_lock->next = NULL; - deleted_lock->prev = NULL; - - erts_lcnt_list_insert(erts_lcnt_data->deleted_locks, deleted_lock); + memcpy(deleted_lock, lock, sizeof(erts_lcnt_lock_t)); + deleted_lock->next = NULL; + deleted_lock->prev = NULL; + erts_lcnt_list_insert(erts_lcnt_data->deleted_locks, deleted_lock); } /* delete original */ erts_lcnt_list_delete(erts_lcnt_data->current_locks, lock); - lock->flag = 0; - + ERTS_LCNT_CLEAR_FLAG(lock); + lcnt_unlock(); } @@ -426,16 +425,15 @@ void erts_lcnt_destroy_lock(erts_lcnt_lock_t *lock) { void erts_lcnt_lock_opt(erts_lcnt_lock_t *lock, Uint16 option) { erts_aint_t r_state = 0, w_state = 0; erts_lcnt_thread_data_t *eltd; - + if (erts_lcnt_rt_options & ERTS_LCNT_OPT_SUSPEND) return; - if (!ERTS_LCNT_LOCK_TYPE(lock)) return; + if (ERTS_LCNT_IS_LOCK_INVALID(lock)) return; eltd = lcnt_get_thread_data(); - ASSERT(eltd); - + w_state = ethr_atomic_read(&lock->w_state); - + if (option & ERTS_LCNT_LO_WRITE) { r_state = ethr_atomic_read(&lock->r_state); ethr_atomic_inc( &lock->w_state); @@ -443,48 +441,47 @@ void erts_lcnt_lock_opt(erts_lcnt_lock_t *lock, Uint16 option) { if (option & ERTS_LCNT_LO_READ) { ethr_atomic_inc( &lock->r_state); } - + /* we cannot acquire w_lock if either w or r are taken */ /* we cannot acquire r_lock if w_lock is taken */ - + if ((w_state > 0) || (r_state > 0)) { - eltd->lock_in_conflict = 1; - if (eltd->timer_set == 0) { - lcnt_time(&eltd->timer); - } - eltd->timer_set++; + eltd->lock_in_conflict = 1; + if (eltd->timer_set == 0) { + lcnt_time(&eltd->timer); + } + eltd->timer_set++; } else { - eltd->lock_in_conflict = 0; + eltd->lock_in_conflict = 0; } } void erts_lcnt_lock(erts_lcnt_lock_t *lock) { erts_aint_t w_state; erts_lcnt_thread_data_t *eltd; - + if (erts_lcnt_rt_options & ERTS_LCNT_OPT_SUSPEND) return; - if (!ERTS_LCNT_LOCK_TYPE(lock)) return; + if (ERTS_LCNT_IS_LOCK_INVALID(lock)) return; w_state = ethr_atomic_read(&lock->w_state); ethr_atomic_inc(&lock->w_state); - eltd = lcnt_get_thread_data(); ASSERT(eltd); if (w_state > 0) { - eltd->lock_in_conflict = 1; - /* only set the timer if nobody else has it - * This should only happen when proc_locks aquires several locks - * 'atomicly'. All other locks will block the thread if w_state > 0 - * i.e. locked. - */ - if (eltd->timer_set == 0) { - lcnt_time(&eltd->timer); - } - eltd->timer_set++; + eltd->lock_in_conflict = 1; + /* only set the timer if nobody else has it + * This should only happen when proc_locks aquires several locks + * 'atomicly'. All other locks will block the thread if w_state > 0 + * i.e. locked. + */ + if (eltd->timer_set == 0) { + lcnt_time(&eltd->timer); + } + eltd->timer_set++; } else { - eltd->lock_in_conflict = 0; + eltd->lock_in_conflict = 0; } } @@ -493,16 +490,19 @@ void erts_lcnt_lock(erts_lcnt_lock_t *lock) { void erts_lcnt_lock_unaquire(erts_lcnt_lock_t *lock) { /* should check if this thread was "waiting" */ if (erts_lcnt_rt_options & ERTS_LCNT_OPT_SUSPEND) return; - if (!ERTS_LCNT_LOCK_TYPE(lock)) return; + if (ERTS_LCNT_IS_LOCK_INVALID(lock)) return; ethr_atomic_dec(&lock->w_state); } -/* erts_lcnt_lock_post - * used when we get a lock (i.e. directly after a lock operation) +/* + * erts_lcnt_lock_post + * + * Used when we get a lock (i.e. directly after a lock operation) * if the timer was set then we had to wait for the lock * lock_post will calculate the wait time. */ + void erts_lcnt_lock_post(erts_lcnt_lock_t *lock) { erts_lcnt_lock_post_x(lock, (char*)str_undefined, 0); } @@ -517,31 +517,31 @@ void erts_lcnt_lock_post_x(erts_lcnt_lock_t *lock, char *file, unsigned int line #endif if (erts_lcnt_rt_options & ERTS_LCNT_OPT_SUSPEND) return; - if (!ERTS_LCNT_LOCK_TYPE(lock)) return; - + if (ERTS_LCNT_IS_LOCK_INVALID(lock)) return; + #ifdef DEBUG if (!(lock->flag & (ERTS_LCNT_LT_RWMUTEX | ERTS_LCNT_LT_RWSPINLOCK))) { - flowstate = ethr_atomic_read(&lock->flowstate); - ASSERT(flowstate == 0); - ethr_atomic_inc(&lock->flowstate); + flowstate = ethr_atomic_read(&lock->flowstate); + ASSERT(flowstate == 0); + ethr_atomic_inc(&lock->flowstate); } #endif - + eltd = lcnt_get_thread_data(); - + ASSERT(eltd); /* if lock was in conflict, time it */ stats = lcnt_get_lock_stats(lock, file, line); if (eltd->timer_set) { - lcnt_time(&timer); - - lcnt_time_diff(&time_wait, &timer, &(eltd->timer)); - lcnt_update_stats(stats, eltd->lock_in_conflict, &time_wait); - eltd->timer_set--; - ASSERT(eltd->timer_set >= 0); + lcnt_time(&timer); + + lcnt_time_diff(&time_wait, &timer, &(eltd->timer)); + lcnt_update_stats(stats, eltd->lock_in_conflict, &time_wait); + eltd->timer_set--; + ASSERT(eltd->timer_set >= 0); } else { - lcnt_update_stats(stats, eltd->lock_in_conflict, NULL); + lcnt_update_stats(stats, eltd->lock_in_conflict, NULL); } } @@ -550,27 +550,28 @@ void erts_lcnt_lock_post_x(erts_lcnt_lock_t *lock, char *file, unsigned int line void erts_lcnt_unlock_opt(erts_lcnt_lock_t *lock, Uint16 option) { if (erts_lcnt_rt_options & ERTS_LCNT_OPT_SUSPEND) return; - if (!ERTS_LCNT_LOCK_TYPE(lock)) return; + if (ERTS_LCNT_IS_LOCK_INVALID(lock)) return; if (option & ERTS_LCNT_LO_WRITE) ethr_atomic_dec(&lock->w_state); if (option & ERTS_LCNT_LO_READ ) ethr_atomic_dec(&lock->r_state); } void erts_lcnt_unlock(erts_lcnt_lock_t *lock) { -#ifdef DEBUG - erts_aint_t w_state; - erts_aint_t flowstate; -#endif if (erts_lcnt_rt_options & ERTS_LCNT_OPT_SUSPEND) return; - if (!ERTS_LCNT_LOCK_TYPE(lock)) return; + if (ERTS_LCNT_IS_LOCK_INVALID(lock)) return; #ifdef DEBUG - /* flowstate */ - flowstate = ethr_atomic_read(&lock->flowstate); - ASSERT(flowstate == 1); - ethr_atomic_dec(&lock->flowstate); - - /* write state */ - w_state = ethr_atomic_read(&lock->w_state); - ASSERT(w_state > 0); + { + erts_aint_t w_state; + erts_aint_t flowstate; + + /* flowstate */ + flowstate = ethr_atomic_read(&lock->flowstate); + ASSERT(flowstate == 1); + ethr_atomic_dec(&lock->flowstate); + + /* write state */ + w_state = ethr_atomic_read(&lock->w_state); + ASSERT(w_state > 0); + } #endif ethr_atomic_dec(&lock->w_state); } @@ -579,35 +580,34 @@ void erts_lcnt_unlock(erts_lcnt_lock_t *lock) { void erts_lcnt_trylock_opt(erts_lcnt_lock_t *lock, int res, Uint16 option) { if (erts_lcnt_rt_options & ERTS_LCNT_OPT_SUSPEND) return; - if (!ERTS_LCNT_LOCK_TYPE(lock)) return; + if (ERTS_LCNT_IS_LOCK_INVALID(lock)) return; /* Determine lock_state via res instead of state */ if (res != EBUSY) { - if (option & ERTS_LCNT_LO_WRITE) ethr_atomic_inc(&lock->w_state); - if (option & ERTS_LCNT_LO_READ ) ethr_atomic_inc(&lock->r_state); - lcnt_update_stats(&(lock->stats[0]), 0, NULL); + if (option & ERTS_LCNT_LO_WRITE) ethr_atomic_inc(&lock->w_state); + if (option & ERTS_LCNT_LO_READ ) ethr_atomic_inc(&lock->r_state); + lcnt_update_stats(&(lock->stats[0]), 0, NULL); } else { ethr_atomic_inc(&lock->stats[0].tries); ethr_atomic_inc(&lock->stats[0].colls); } } - + void erts_lcnt_trylock(erts_lcnt_lock_t *lock, int res) { /* Determine lock_state via res instead of state */ -#ifdef DEBUG - erts_aint_t flowstate; -#endif if (erts_lcnt_rt_options & ERTS_LCNT_OPT_SUSPEND) return; - if (!ERTS_LCNT_LOCK_TYPE(lock)) return; + if (ERTS_LCNT_IS_LOCK_INVALID(lock)) return; if (res != EBUSY) { - #ifdef DEBUG - flowstate = ethr_atomic_read(&lock->flowstate); - ASSERT(flowstate == 0); - ethr_atomic_inc( &lock->flowstate); + { + erts_aint_t flowstate; + flowstate = ethr_atomic_read(&lock->flowstate); + ASSERT(flowstate == 0); + ethr_atomic_inc( &lock->flowstate); + } #endif - ethr_atomic_inc(&lock->w_state); - lcnt_update_stats(&(lock->stats[0]), 0, NULL); + ethr_atomic_inc(&lock->w_state); + lcnt_update_stats(&(lock->stats[0]), 0, NULL); } else { ethr_atomic_inc(&lock->stats[0].tries); ethr_atomic_inc(&lock->stats[0].colls); @@ -662,13 +662,13 @@ void erts_lcnt_clear_counters(void) { lcnt_lock(); list = erts_lcnt_data->current_locks; - + for (lock = list->head; lock != NULL; lock = lock->next) { - for( i = 0; i < ERTS_LCNT_MAX_LOCK_LOCATIONS; i++) { - stats = &lock->stats[i]; - lcnt_clear_stats(stats); - } - lock->n_stats = 1; + for( i = 0; i < ERTS_LCNT_MAX_LOCK_LOCATIONS; i++) { + stats = &lock->stats[i]; + lcnt_clear_stats(stats); + } + lock->n_stats = 1; } /* empty deleted locks in lock list */ @@ -681,14 +681,14 @@ void erts_lcnt_clear_counters(void) { erts_lcnt_data_t *erts_lcnt_get_data(void) { erts_lcnt_time_t timer_stop; - + lcnt_lock(); - + lcnt_time(&timer_stop); lcnt_time_diff(&(erts_lcnt_data->duration), &timer_stop, &timer_start); - + lcnt_unlock(); - + return erts_lcnt_data; } diff --git a/erts/emulator/beam/erl_lock_count.h b/erts/emulator/beam/erl_lock_count.h index 09fadd7e9e..051f55271d 100644 --- a/erts/emulator/beam/erl_lock_count.h +++ b/erts/emulator/beam/erl_lock_count.h @@ -20,7 +20,7 @@ /* * Description: Statistics for locks. * - * Author: Bj�rn-Egil Dahlberg + * Author: Björn-Egil Dahlberg * Date: 2008-07-03 * Abstract: * Locks statistics internal representation. @@ -95,30 +95,35 @@ #define ERTS_LCNT_LO_WRITE (((Uint16) 1) << 7) #define ERTS_LCNT_LO_READ_WRITE ( ERTS_LCNT_LO_READ \ - | ERTS_LCNT_LO_WRITE ) + | ERTS_LCNT_LO_WRITE ) #define ERTS_LCNT_LT_ALL ( ERTS_LCNT_LT_SPINLOCK \ - | ERTS_LCNT_LT_RWSPINLOCK \ - | ERTS_LCNT_LT_MUTEX \ - | ERTS_LCNT_LT_RWMUTEX \ - | ERTS_LCNT_LT_PROCLOCK ) + | ERTS_LCNT_LT_RWSPINLOCK \ + | ERTS_LCNT_LT_MUTEX \ + | ERTS_LCNT_LT_RWMUTEX \ + | ERTS_LCNT_LT_PROCLOCK ) + +#define ERTS_LCNT_LOCK_TYPE(lock) ((lock)->flag & ERTS_LCNT_LT_ALL) +#define ERTS_LCNT_IS_LOCK_INVALID(lock) (!((lock)->flag & ERTS_LCNT_LT_ALL)) +#define ERTS_LCNT_CLEAR_FLAG(lock) ((lock)->flag = 0) + /* runtime options */ -#define ERTS_LCNT_OPT_SUSPEND (((Uint16) 1) << 0) -#define ERTS_LCNT_OPT_LOCATION (((Uint16) 1) << 1) -#define ERTS_LCNT_OPT_PROCLOCK (((Uint16) 1) << 2) -#define ERTS_LCNT_OPT_COPYSAVE (((Uint16) 1) << 3) -#define ERTS_LCNT_OPT_PORTLOCK (((Uint16) 1) << 4) +#define ERTS_LCNT_OPT_SUSPEND (((Uint16) 1) << 0) +#define ERTS_LCNT_OPT_LOCATION (((Uint16) 1) << 1) +#define ERTS_LCNT_OPT_PROCLOCK (((Uint16) 1) << 2) +#define ERTS_LCNT_OPT_PORTLOCK (((Uint16) 1) << 3) +#define ERTS_LCNT_OPT_COPYSAVE (((Uint16) 1) << 4) typedef struct { unsigned long s; unsigned long ns; } erts_lcnt_time_t; - + extern erts_lcnt_time_t timer_start; typedef struct { - Uint32 ns[ERTS_LCNT_HISTOGRAM_SLOT_SIZE]; /* log2 array of nano seconds occurences */ + Uint32 ns[ERTS_LCNT_HISTOGRAM_SLOT_SIZE]; /* log2 array of nano seconds occurences */ } erts_lcnt_hist_t; typedef struct erts_lcnt_lock_stats_s { @@ -129,10 +134,10 @@ typedef struct erts_lcnt_lock_stats_s { char *file; /* which file the lock was taken */ unsigned int line; /* line number in file */ - + ethr_atomic_t tries; /* n tries to get lock */ ethr_atomic_t colls; /* n collisions of tries to get lock */ - + unsigned long timer_n; /* #times waited for lock */ erts_lcnt_time_t timer; /* total wait time for lock */ erts_lcnt_hist_t hist; @@ -155,7 +160,7 @@ typedef struct erts_lcnt_lock_s { /* statistics */ unsigned int n_stats; erts_lcnt_lock_stats_t stats[ERTS_LCNT_MAX_LOCK_LOCATIONS]; /* first entry is "undefined"*/ - + /* chains for list handling */ /* data is hold by lcnt_lock */ struct erts_lcnt_lock_s *prev; @@ -167,7 +172,7 @@ typedef struct { erts_lcnt_lock_t *tail; unsigned long n; } erts_lcnt_lock_list_t; - + typedef struct { erts_lcnt_time_t duration; /* time since last clear */ erts_lcnt_lock_list_t *current_locks; @@ -205,6 +210,7 @@ void erts_lcnt_list_delete(erts_lcnt_lock_list_t *list, erts_lcnt_lock_t *lock); /* lock operations (global) */ void erts_lcnt_init_lock(erts_lcnt_lock_t *lock, char *name, Uint16 flag); void erts_lcnt_init_lock_x(erts_lcnt_lock_t *lock, char *name, Uint16 flag, Eterm id); +void erts_lcnt_init_lock_empty(erts_lcnt_lock_t *lock); void erts_lcnt_destroy_lock(erts_lcnt_lock_t *lock); void erts_lcnt_lock(erts_lcnt_lock_t *lock); @@ -226,7 +232,5 @@ void erts_lcnt_clear_counters(void); char *erts_lcnt_lock_type(Uint16 type); erts_lcnt_data_t *erts_lcnt_get_data(void); -#define ERTS_LCNT_LOCK_TYPE(lockp) ((lockp)->flag & ERTS_LCNT_LT_ALL) - #endif /* ifdef ERTS_ENABLE_LOCK_COUNT */ #endif /* ifndef ERTS_LOCK_COUNT_H__ */ diff --git a/erts/emulator/beam/erl_port.h b/erts/emulator/beam/erl_port.h index 3920fae2d9..66cbb67a0d 100644 --- a/erts/emulator/beam/erl_port.h +++ b/erts/emulator/beam/erl_port.h @@ -266,8 +266,7 @@ erts_prtsd_set(Port *prt, int ix, void *data) #endif -extern erts_smp_atomic_t erts_bytes_out; /* no bytes written out */ -extern erts_smp_atomic_t erts_bytes_in; /* no bytes sent into the system */ +Eterm erts_request_io_bytes(Process *c_p); /* port status flags */ diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c index 43e84a1be1..88c1b5c121 100644 --- a/erts/emulator/beam/erl_process.c +++ b/erts/emulator/beam/erl_process.c @@ -5542,6 +5542,9 @@ init_scheduler_data(ErtsSchedulerData* esdp, int num, esdp->thr_id = (Uint32) num; erts_sched_bif_unique_init(esdp); + esdp->io.out = (Uint64) 0; + esdp->io.in = (Uint64) 0; + if (daww_ptr) { init_aux_work_data(&esdp->aux_work_data, esdp, *daww_ptr); #ifdef ERTS_SMP diff --git a/erts/emulator/beam/erl_process.h b/erts/emulator/beam/erl_process.h index 241a82d6c2..a6c3790991 100644 --- a/erts/emulator/beam/erl_process.h +++ b/erts/emulator/beam/erl_process.h @@ -659,6 +659,11 @@ struct ErtsSchedulerData_ { ErtsSchedAllocData alloc_data; + struct { + Uint64 out; + Uint64 in; + } io; + Uint64 reductions; ErtsSchedWallTime sched_wall_time; ErtsGCInfo gc_info; diff --git a/erts/emulator/beam/erl_process_lock.c b/erts/emulator/beam/erl_process_lock.c index b28bc498f6..0548c6137c 100644 --- a/erts/emulator/beam/erl_process_lock.c +++ b/erts/emulator/beam/erl_process_lock.c @@ -110,6 +110,9 @@ static struct { erts_pix_lock_t erts_pix_locks[ERTS_NO_OF_PIX_LOCKS]; +#ifdef ERTS_ENABLE_LOCK_COUNT +static void lcnt_enable_proc_lock_count(Process *proc, int enable); +#endif void erts_init_proc_lock(int cpus) @@ -1083,30 +1086,32 @@ erts_proc_lock_fin(Process *p) /* --- Process lock counting ----------------------------------------------- */ #if ERTS_PROC_LOCK_OWN_IMPL && defined(ERTS_ENABLE_LOCK_COUNT) -void erts_lcnt_proc_lock_init(Process *p) { - if (erts_lcnt_rt_options & ERTS_LCNT_OPT_PROCLOCK) { - if (p->common.id != ERTS_INVALID_PID) { - erts_lcnt_init_lock_x(&(p->lock.lcnt_main), "proc_main", ERTS_LCNT_LT_PROCLOCK, p->common.id); - erts_lcnt_init_lock_x(&(p->lock.lcnt_msgq), "proc_msgq", ERTS_LCNT_LT_PROCLOCK, p->common.id); - erts_lcnt_init_lock_x(&(p->lock.lcnt_btm), "proc_btm", ERTS_LCNT_LT_PROCLOCK, p->common.id); - erts_lcnt_init_lock_x(&(p->lock.lcnt_link), "proc_link", ERTS_LCNT_LT_PROCLOCK, p->common.id); - erts_lcnt_init_lock_x(&(p->lock.lcnt_status), "proc_status", ERTS_LCNT_LT_PROCLOCK, p->common.id); - } else { - erts_lcnt_init_lock(&(p->lock.lcnt_main), "proc_main", ERTS_LCNT_LT_PROCLOCK); - erts_lcnt_init_lock(&(p->lock.lcnt_msgq), "proc_msgq", ERTS_LCNT_LT_PROCLOCK); - erts_lcnt_init_lock(&(p->lock.lcnt_btm), "proc_btm", ERTS_LCNT_LT_PROCLOCK); - erts_lcnt_init_lock(&(p->lock.lcnt_link), "proc_link", ERTS_LCNT_LT_PROCLOCK); - erts_lcnt_init_lock(&(p->lock.lcnt_status), "proc_status", ERTS_LCNT_LT_PROCLOCK); - } - } else { - sys_memzero(&(p->lock.lcnt_main), sizeof(p->lock.lcnt_main)); - sys_memzero(&(p->lock.lcnt_msgq), sizeof(p->lock.lcnt_msgq)); - sys_memzero(&(p->lock.lcnt_btm), sizeof(p->lock.lcnt_btm)); - sys_memzero(&(p->lock.lcnt_link), sizeof(p->lock.lcnt_link)); - sys_memzero(&(p->lock.lcnt_status), sizeof(p->lock.lcnt_status)); - } + +void erts_lcnt_enable_proc_lock_count(int enable) { + int ix, max = erts_ptab_max(&erts_proc); + Process *proc = NULL; + for (ix = 0; ix < max; ++ix) { + if ((proc = erts_pix2proc(ix)) != NULL) + lcnt_enable_proc_lock_count(proc, enable); + } /* for all processes */ } - + +void erts_lcnt_proc_lock_init(Process *p) { + if (!(erts_lcnt_rt_options & ERTS_LCNT_OPT_PROCLOCK)) { + erts_lcnt_init_lock_empty(&(p->lock.lcnt_main)); + erts_lcnt_init_lock_empty(&(p->lock.lcnt_msgq)); + erts_lcnt_init_lock_empty(&(p->lock.lcnt_btm)); + erts_lcnt_init_lock_empty(&(p->lock.lcnt_link)); + erts_lcnt_init_lock_empty(&(p->lock.lcnt_status)); + } else { /* now the common case */ + Eterm pid = (p->common.id != ERTS_INVALID_PID) ? p->common.id : NIL; + erts_lcnt_init_lock_x(&(p->lock.lcnt_main), "proc_main", ERTS_LCNT_LT_PROCLOCK, pid); + erts_lcnt_init_lock_x(&(p->lock.lcnt_msgq), "proc_msgq", ERTS_LCNT_LT_PROCLOCK, pid); + erts_lcnt_init_lock_x(&(p->lock.lcnt_btm), "proc_btm", ERTS_LCNT_LT_PROCLOCK, pid); + erts_lcnt_init_lock_x(&(p->lock.lcnt_link), "proc_link", ERTS_LCNT_LT_PROCLOCK, pid); + erts_lcnt_init_lock_x(&(p->lock.lcnt_status),"proc_status",ERTS_LCNT_LT_PROCLOCK, pid); + } /* the lock names should really be aligned to four characters */ +} /* logic reversed */ void erts_lcnt_proc_lock_destroy(Process *p) { erts_lcnt_destroy_lock(&(p->lock.lcnt_main)); @@ -1116,28 +1121,31 @@ void erts_lcnt_proc_lock_destroy(Process *p) { erts_lcnt_destroy_lock(&(p->lock.lcnt_status)); } -void erts_lcnt_proc_lock(erts_proc_lock_t *lock, ErtsProcLocks locks) { - if (erts_lcnt_rt_options & ERTS_LCNT_OPT_PROCLOCK) { - if (locks & ERTS_PROC_LOCK_MAIN) { - erts_lcnt_lock(&(lock->lcnt_main)); - } - if (locks & ERTS_PROC_LOCK_MSGQ) { - erts_lcnt_lock(&(lock->lcnt_msgq)); - } - if (locks & ERTS_PROC_LOCK_BTM) { - erts_lcnt_lock(&(lock->lcnt_btm)); - } - if (locks & ERTS_PROC_LOCK_LINK) { - erts_lcnt_lock(&(lock->lcnt_link)); - } - if (locks & ERTS_PROC_LOCK_STATUS) { - erts_lcnt_lock(&(lock->lcnt_status)); +static void lcnt_enable_proc_lock_count(Process *proc, int enable) { + if (enable) { + if (!ERTS_LCNT_LOCK_TYPE(&(proc->lock.lcnt_main))) { + erts_lcnt_proc_lock_init(proc); + } } + else { + if (ERTS_LCNT_LOCK_TYPE(&(proc->lock.lcnt_main))) { + erts_lcnt_proc_lock_destroy(proc); + } } } -void erts_lcnt_proc_lock_post_x(erts_proc_lock_t *lock, ErtsProcLocks locks, char *file, unsigned int line) { - if (erts_lcnt_rt_options & ERTS_LCNT_OPT_PROCLOCK) { +void erts_lcnt_proc_lock(erts_proc_lock_t *lock, ErtsProcLocks locks) { + if (!(erts_lcnt_rt_options & ERTS_LCNT_OPT_PROCLOCK)) return; + if (locks & ERTS_PROC_LOCK_MAIN) { erts_lcnt_lock(&(lock->lcnt_main)); } + if (locks & ERTS_PROC_LOCK_MSGQ) { erts_lcnt_lock(&(lock->lcnt_msgq)); } + if (locks & ERTS_PROC_LOCK_BTM) { erts_lcnt_lock(&(lock->lcnt_btm)); } + if (locks & ERTS_PROC_LOCK_LINK) { erts_lcnt_lock(&(lock->lcnt_link)); } + if (locks & ERTS_PROC_LOCK_STATUS) { erts_lcnt_lock(&(lock->lcnt_status)); } +} + +void erts_lcnt_proc_lock_post_x(erts_proc_lock_t *lock, ErtsProcLocks locks, + char *file, unsigned int line) { + if (!(erts_lcnt_rt_options & ERTS_LCNT_OPT_PROCLOCK)) return; if (locks & ERTS_PROC_LOCK_MAIN) { erts_lcnt_lock_post_x(&(lock->lcnt_main), file, line); } @@ -1153,90 +1161,34 @@ void erts_lcnt_proc_lock_post_x(erts_proc_lock_t *lock, ErtsProcLocks locks, cha if (locks & ERTS_PROC_LOCK_STATUS) { erts_lcnt_lock_post_x(&(lock->lcnt_status), file, line); } - } } void erts_lcnt_proc_lock_unaquire(erts_proc_lock_t *lock, ErtsProcLocks locks) { - if (erts_lcnt_rt_options & ERTS_LCNT_OPT_PROCLOCK) { - if (locks & ERTS_PROC_LOCK_MAIN) { - erts_lcnt_lock_unaquire(&(lock->lcnt_main)); - } - if (locks & ERTS_PROC_LOCK_MSGQ) { - erts_lcnt_lock_unaquire(&(lock->lcnt_msgq)); - } - if (locks & ERTS_PROC_LOCK_BTM) { - erts_lcnt_lock_unaquire(&(lock->lcnt_btm)); - } - if (locks & ERTS_PROC_LOCK_LINK) { - erts_lcnt_lock_unaquire(&(lock->lcnt_link)); - } - if (locks & ERTS_PROC_LOCK_STATUS) { - erts_lcnt_lock_unaquire(&(lock->lcnt_status)); - } - } + if (!(erts_lcnt_rt_options & ERTS_LCNT_OPT_PROCLOCK)) return; + if (locks & ERTS_PROC_LOCK_MAIN) { erts_lcnt_lock_unaquire(&(lock->lcnt_main)); } + if (locks & ERTS_PROC_LOCK_MSGQ) { erts_lcnt_lock_unaquire(&(lock->lcnt_msgq)); } + if (locks & ERTS_PROC_LOCK_BTM) { erts_lcnt_lock_unaquire(&(lock->lcnt_btm)); } + if (locks & ERTS_PROC_LOCK_LINK) { erts_lcnt_lock_unaquire(&(lock->lcnt_link)); } + if (locks & ERTS_PROC_LOCK_STATUS) { erts_lcnt_lock_unaquire(&(lock->lcnt_status)); } } void erts_lcnt_proc_unlock(erts_proc_lock_t *lock, ErtsProcLocks locks) { - if (erts_lcnt_rt_options & ERTS_LCNT_OPT_PROCLOCK) { - if (locks & ERTS_PROC_LOCK_MAIN) { - erts_lcnt_unlock(&(lock->lcnt_main)); - } - if (locks & ERTS_PROC_LOCK_MSGQ) { - erts_lcnt_unlock(&(lock->lcnt_msgq)); - } - if (locks & ERTS_PROC_LOCK_BTM) { - erts_lcnt_unlock(&(lock->lcnt_btm)); - } - if (locks & ERTS_PROC_LOCK_LINK) { - erts_lcnt_unlock(&(lock->lcnt_link)); - } - if (locks & ERTS_PROC_LOCK_STATUS) { - erts_lcnt_unlock(&(lock->lcnt_status)); - } - } + if (!(erts_lcnt_rt_options & ERTS_LCNT_OPT_PROCLOCK)) return; + if (locks & ERTS_PROC_LOCK_MAIN) { erts_lcnt_unlock(&(lock->lcnt_main)); } + if (locks & ERTS_PROC_LOCK_MSGQ) { erts_lcnt_unlock(&(lock->lcnt_msgq)); } + if (locks & ERTS_PROC_LOCK_BTM) { erts_lcnt_unlock(&(lock->lcnt_btm)); } + if (locks & ERTS_PROC_LOCK_LINK) { erts_lcnt_unlock(&(lock->lcnt_link)); } + if (locks & ERTS_PROC_LOCK_STATUS) { erts_lcnt_unlock(&(lock->lcnt_status)); } } void erts_lcnt_proc_trylock(erts_proc_lock_t *lock, ErtsProcLocks locks, int res) { - if (erts_lcnt_rt_options & ERTS_LCNT_OPT_PROCLOCK) { - if (locks & ERTS_PROC_LOCK_MAIN) { - erts_lcnt_trylock(&(lock->lcnt_main), res); - } - if (locks & ERTS_PROC_LOCK_MSGQ) { - erts_lcnt_trylock(&(lock->lcnt_msgq), res); - } - if (locks & ERTS_PROC_LOCK_BTM) { - erts_lcnt_trylock(&(lock->lcnt_btm), res); - } - if (locks & ERTS_PROC_LOCK_LINK) { - erts_lcnt_trylock(&(lock->lcnt_link), res); - } - if (locks & ERTS_PROC_LOCK_STATUS) { - erts_lcnt_trylock(&(lock->lcnt_status), res); - } - } -} - - -void erts_lcnt_enable_proc_lock_count(int enable) -{ - int i, max = erts_ptab_max(&erts_proc); - - for (i = 0; i < max; ++i) { - Process* p = erts_pix2proc(i); - if (p) { - if (enable) { - if (!ERTS_LCNT_LOCK_TYPE(&(p->lock.lcnt_main))) { - erts_lcnt_proc_lock_init(p); - } - } else { - if (ERTS_LCNT_LOCK_TYPE(&(p->lock.lcnt_main))) { - erts_lcnt_proc_lock_destroy(p); - } - } - } - } -} - -#endif /* ifdef ERTS_ENABLE_LOCK_COUNT */ + if (!(erts_lcnt_rt_options & ERTS_LCNT_OPT_PROCLOCK)) return; + if (locks & ERTS_PROC_LOCK_MAIN) { erts_lcnt_trylock(&(lock->lcnt_main), res); } + if (locks & ERTS_PROC_LOCK_MSGQ) { erts_lcnt_trylock(&(lock->lcnt_msgq), res); } + if (locks & ERTS_PROC_LOCK_BTM) { erts_lcnt_trylock(&(lock->lcnt_btm), res); } + if (locks & ERTS_PROC_LOCK_LINK) { erts_lcnt_trylock(&(lock->lcnt_link), res); } + if (locks & ERTS_PROC_LOCK_STATUS) { erts_lcnt_trylock(&(lock->lcnt_status), res); } +} /* reversed logic */ +#endif /* ERTS_ENABLE_LOCK_COUNT */ /* --- Process lock checking ----------------------------------------------- */ diff --git a/erts/emulator/beam/io.c b/erts/emulator/beam/io.c index 23f208c2eb..75d80b1a58 100644 --- a/erts/emulator/beam/io.c +++ b/erts/emulator/beam/io.c @@ -65,8 +65,6 @@ static erts_smp_tsd_key_t driver_list_last_error_key; /* Save last DDLL error o per thread basis (for BC interfaces) */ ErtsPTab erts_port erts_align_attribute(ERTS_CACHE_LINE_SIZE); /* The port table */ -erts_smp_atomic_t erts_bytes_out; /* No bytes sent out of the system */ -erts_smp_atomic_t erts_bytes_in; /* No bytes gotten into the system */ const ErlDrvTermData driver_term_nil = (ErlDrvTermData)NIL; @@ -80,6 +78,9 @@ int erts_port_synchronous_ops = 0; int erts_port_schedule_all_ops = 0; int erts_port_parallelism = 0; +static erts_atomic64_t bytes_in; +static erts_atomic64_t bytes_out; + static void deliver_result(Eterm sender, Eterm pid, Eterm res); static int init_driver(erts_driver_t *, ErlDrvEntry *, DE_Handle *); static void terminate_port(Port *p); @@ -1534,12 +1535,10 @@ erts_schedule_proc2port_signal(Process *c_p, } static ERTS_INLINE void -send_badsig(Port *prt) -{ +send_badsig(Port *prt) { ErtsProcLocks rp_locks = ERTS_PROC_LOCKS_XSIG_SEND; Process* rp; Eterm connected = ERTS_PORT_GET_CONNECTED(prt); - ERTS_SMP_CHK_NO_PROC_LOCKS; ERTS_LC_ASSERT(erts_get_scheduler_id()); @@ -1559,15 +1558,13 @@ send_badsig(Port *prt) 0); if (rp_locks) erts_smp_proc_unlock(rp, rp_locks); - } -} + } /* exit sent */ +} /* send_badsig */ static void -badsig_received(int bang_op, - Port *prt, +badsig_received(int bang_op, Port *prt, erts_aint32_t state, - int bad_output_value) -{ + int bad_output_value) { /* * if (bang_op) * we are part of a "Prt ! Something" operation @@ -1583,12 +1580,12 @@ badsig_received(int bang_op, } if (bang_op) send_badsig(prt); - } -} + } /* not invalid */ +} /* behaved accordingly */ static int -port_badsig(Port *prt, erts_aint32_t state, int op, ErtsProc2PortSigData *sigdp) -{ +port_badsig(Port *prt, erts_aint32_t state, int op, + ErtsProc2PortSigData *sigdp) { if (op == ERTS_PROC2PORT_SIG_EXEC) badsig_received(sigdp->flags & ERTS_P2P_SIG_DATA_FLG_BANG_OP, prt, @@ -1597,16 +1594,14 @@ port_badsig(Port *prt, erts_aint32_t state, int op, ErtsProc2PortSigData *sigdp) if (sigdp->flags & ERTS_P2P_SIG_DATA_FLG_REPLY) port_sched_op_reply(sigdp->caller, sigdp->ref, am_badarg); return ERTS_PORT_REDS_BADSIG; -} - - -/* - * bad_port_signal() will +} /* port_badsig */ +/* bad_port_signal() will * - preserve signal order of signals. * - send a 'badsig' exit signal to connected process if 'from' is an * internal pid and the port is alive when the bad signal reaches * it. */ + static ErtsPortOpResult bad_port_signal(Process *c_p, int flags, @@ -1681,6 +1676,7 @@ call_driver_outputv(int bang_op, if (bang_op && from != ERTS_PORT_GET_CONNECTED(prt)) send_badsig(prt); else { + ErtsSchedulerData *esdp = erts_get_scheduler_data(); ErlDrvSizeT size = evp->size; ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt) @@ -1698,7 +1694,10 @@ call_driver_outputv(int bang_op, prt->caller = NIL; prt->bytes_out += size; - erts_smp_atomic_add_nob(&erts_bytes_out, size); + if (esdp) + esdp->io.out += (Uint64) size; + else + erts_atomic64_add_nob(&bytes_out, (erts_aint64_t) size); } } @@ -1778,7 +1777,7 @@ call_driver_output(int bang_op, if (bang_op && from != ERTS_PORT_GET_CONNECTED(prt)) send_badsig(prt); else { - + ErtsSchedulerData *esdp = erts_get_scheduler_data(); ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt) || ERTS_IS_CRASH_DUMPING); @@ -1794,7 +1793,10 @@ call_driver_output(int bang_op, prt->caller = NIL; prt->bytes_out += size; - erts_smp_atomic_add_nob(&erts_bytes_out, size); + if (esdp) + esdp->io.out += (Uint64) size; + else + erts_atomic64_add_nob(&bytes_out, (erts_aint64_t) size); } } @@ -2741,6 +2743,9 @@ void erts_init_io(int port_tab_size, drv_list_rwmtx_opts.type = ERTS_SMP_RWMTX_TYPE_EXTREMELY_FREQUENT_READ; drv_list_rwmtx_opts.lived = ERTS_SMP_RWMTX_LONG_LIVED; + erts_atomic64_init_nob(&bytes_in, 0); + erts_atomic64_init_nob(&bytes_out, 0); + common_element_size = ERTS_ALC_DATA_ALIGN_SIZE(sizeof(Port)); common_element_size += ERTS_ALC_DATA_ALIGN_SIZE(sizeof(ErtsPortTaskBusyPortQ)); common_element_size += 10; /* name */ @@ -2782,9 +2787,6 @@ void erts_init_io(int port_tab_size, legacy_port_tab, 1); - erts_smp_atomic_init_nob(&erts_bytes_out, 0); - erts_smp_atomic_init_nob(&erts_bytes_in, 0); - sys_init_io(); erts_smp_tsd_set(driver_list_lock_status_key, (void *) 1); @@ -2804,7 +2806,6 @@ void erts_init_io(int port_tab_size, } #if defined(ERTS_ENABLE_LOCK_COUNT) && defined(ERTS_SMP) - static ERTS_INLINE void lcnt_enable_drv_lock_count(erts_driver_t *dp, int enable) { if (dp->lock) { @@ -2844,25 +2845,26 @@ static ERTS_INLINE void lcnt_enable_port_lock_count(Port *prt, int enable) } } -void erts_lcnt_enable_io_lock_count(int enable) -{ +void erts_lcnt_enable_io_lock_count(int enable) { erts_driver_t *dp; - int i, max = erts_ptab_max(&erts_port); + int ix, max = erts_ptab_max(&erts_port); + Port *prt; - for (i = 0; i < max; i++) { - Port *prt = erts_pix2port(i); - if (prt) + for (ix = 0; ix < max; ix++) { + if ((prt = erts_pix2port(ix)) != NULL) { lcnt_enable_port_lock_count(prt, enable); - } + } + } /* for all ports */ lcnt_enable_drv_lock_count(&vanilla_driver, enable); lcnt_enable_drv_lock_count(&spawn_driver, enable); lcnt_enable_drv_lock_count(&fd_driver, enable); - for (dp = driver_list; dp; dp = dp->next) + /* enable lock counting in all drivers */ + for (dp = driver_list; dp; dp = dp->next) { lcnt_enable_drv_lock_count(dp, enable); -} -#endif - + } +} /* enable/disable lock counting of ports */ +#endif /* defined(ERTS_ENABLE_LOCK_COUNT) && defined(ERTS_SMP) */ /* * Buffering of data when using line oriented I/O on ports */ @@ -4574,6 +4576,102 @@ erts_port_info(Process* c_p, } typedef struct { + Uint sched_id; + Eterm pid; + Uint32 refn[ERTS_REF_NUMBERS]; + erts_smp_atomic32_t refc; +} ErtsIOBytesReq; + +static void +reply_io_bytes(void *vreq) +{ + ErtsIOBytesReq *req = (ErtsIOBytesReq *) vreq; + Process *rp; + + rp = erts_proc_lookup(req->pid); + if (rp) { + ErlOffHeap *ohp = NULL; + ErlHeapFragment *bp = NULL; + ErtsProcLocks rp_locks; + Eterm ref, msg, ein, eout, *hp; + Uint64 in, out; + Uint hsz; + ErtsSchedulerData *esdp = erts_get_scheduler_data(); + Uint sched_id = esdp->no; + in = esdp->io.in; + out = esdp->io.out; + if (req->sched_id != sched_id) + rp_locks = 0; + else { + in += (Uint64) erts_atomic64_read_nob(&bytes_in); + out += (Uint64) erts_atomic64_read_nob(&bytes_out); + rp_locks = ERTS_PROC_LOCK_MAIN; + } + + hsz = 5 /* 4-tuple */ + REF_THING_SIZE; + + erts_bld_uint64(NULL, &hsz, in); + erts_bld_uint64(NULL, &hsz, out); + + hp = erts_alloc_message_heap(hsz, &bp, &ohp, rp, &rp_locks); + + ref = make_internal_ref(hp); + write_ref_thing(hp, req->refn[0], req->refn[1], req->refn[2]); + hp += REF_THING_SIZE; + + ein = erts_bld_uint64(&hp, NULL, in); + eout = erts_bld_uint64(&hp, NULL, out); + + msg = TUPLE4(hp, ref, make_small(sched_id), ein, eout); + erts_queue_message(rp, &rp_locks, bp, msg, NIL); + + if (req->sched_id == sched_id) + rp_locks &= ~ERTS_PROC_LOCK_MAIN; + if (rp_locks) + erts_smp_proc_unlock(rp, rp_locks); + } + + if (erts_smp_atomic32_dec_read_nob(&req->refc) == 0) + erts_free(ERTS_ALC_T_IOB_REQ, req); +} + +Eterm +erts_request_io_bytes(Process *c_p) +{ + Uint *hp; + Eterm ref; + Uint32 *refn; + ErtsSchedulerData *esdp = ERTS_PROC_GET_SCHDATA(c_p); + ErtsIOBytesReq *req = erts_alloc(ERTS_ALC_T_IOB_REQ, + sizeof(ErtsIOBytesReq)); + + hp = HAlloc(c_p, REF_THING_SIZE); + ref = erts_sched_make_ref_in_buffer(esdp, hp); + refn = internal_ref_numbers(ref); + + req->sched_id = esdp->no; + req->pid = c_p->common.id; + req->refn[0] = refn[0]; + req->refn[1] = refn[1]; + req->refn[2] = refn[2]; + erts_smp_atomic32_init_nob(&req->refc, + (erts_aint32_t) erts_no_schedulers); + +#ifdef ERTS_SMP + if (erts_no_schedulers > 1) + erts_schedule_multi_misc_aux_work(1, + erts_no_schedulers, + reply_io_bytes, + (void *) req); +#endif + + reply_io_bytes((void *) req); + + return ref; +} + + +typedef struct { int to; void *arg; } prt_one_lnk_data; @@ -5111,6 +5209,7 @@ driver_deliver_term(Eterm to, ErlDrvTermData* data, int len) struct b2t_states__ b2t; int scheduler; int is_heap_need_limited = 1; + ErtsSchedulerData *esdp = erts_get_scheduler_data(); ERTS_UNDEF(mess,NIL); ERTS_UNDEF(scheduler,1); @@ -5418,7 +5517,10 @@ driver_deliver_term(Eterm to, ErlDrvTermData* data, int len) Uint size = ptr[1]; Uint offset = ptr[2]; - erts_smp_atomic_add_nob(&erts_bytes_in, (erts_aint_t) size); + if (esdp) + esdp->io.in += (Uint64) size; + else + erts_atomic64_add_nob(&bytes_in, (erts_aint64_t) size); if (size <= ERL_ONHEAP_BIN_LIMIT) { ErlHeapBin* hbp = (ErlHeapBin *) erts_produce_heap(&factory, @@ -5452,7 +5554,10 @@ driver_deliver_term(Eterm to, ErlDrvTermData* data, int len) byte *bufp = (byte *) ptr[0]; Uint size = (Uint) ptr[1]; - erts_smp_atomic_add_nob(&erts_bytes_in, (erts_aint_t) size); + if (esdp) + esdp->io.in += (Uint64) size; + else + erts_atomic64_add_nob(&bytes_in, (erts_aint64_t) size); if (size <= ERL_ONHEAP_BIN_LIMIT) { ErlHeapBin* hbp = (ErlHeapBin *) erts_produce_heap(&factory, @@ -5489,7 +5594,10 @@ driver_deliver_term(Eterm to, ErlDrvTermData* data, int len) } case ERL_DRV_STRING: /* char*, length */ - erts_smp_atomic_add_nob(&erts_bytes_in, (erts_aint_t) ptr[1]); + if (esdp) + esdp->io.in += (Uint64) ptr[1]; + else + erts_atomic64_add_nob(&bytes_in, (erts_aint64_t) ptr[1]); erts_reserve_heap(&factory, 2*ptr[1]); mess = buf_to_intlist(&factory.hp, (char*)ptr[0], ptr[1], NIL); ptr += 2; @@ -5765,6 +5873,7 @@ int driver_output_binary(ErlDrvPort ix, char* hbuf, ErlDrvSizeT hlen, { erts_aint32_t state; Port* prt = erts_drvport2port_state(ix, &state); + ErtsSchedulerData *esdp = erts_get_scheduler_data(); ERTS_SMP_CHK_NO_PROC_LOCKS; @@ -5775,7 +5884,10 @@ int driver_output_binary(ErlDrvPort ix, char* hbuf, ErlDrvSizeT hlen, return 0; prt->bytes_in += (hlen + len); - erts_smp_atomic_add_nob(&erts_bytes_in, (erts_aint_t) (hlen + len)); + if (esdp) + esdp->io.in += (Uint64) (hlen + len); + else + erts_atomic64_add_nob(&bytes_in, (erts_aint64_t) (hlen + len)); if (state & ERTS_PORT_SFLG_DISTRIBUTION) { return erts_net_message(prt, prt->dist_entry, @@ -5800,6 +5912,7 @@ int driver_output2(ErlDrvPort ix, char* hbuf, ErlDrvSizeT hlen, { erts_aint32_t state; Port* prt = erts_drvport2port_state(ix, &state); + ErtsSchedulerData *esdp = erts_get_scheduler_data(); ERTS_SMP_CHK_NO_PROC_LOCKS; @@ -5811,7 +5924,10 @@ int driver_output2(ErlDrvPort ix, char* hbuf, ErlDrvSizeT hlen, return 0; prt->bytes_in += (hlen + len); - erts_smp_atomic_add_nob(&erts_bytes_in, (erts_aint_t) (hlen + len)); + if (esdp) + esdp->io.in += (Uint64) (hlen + len); + else + erts_atomic64_add_nob(&bytes_in, (erts_aint64_t) (hlen + len)); if (state & ERTS_PORT_SFLG_DISTRIBUTION) { if (len == 0) return erts_net_message(prt, @@ -5851,6 +5967,7 @@ int driver_outputv(ErlDrvPort ix, char* hbuf, ErlDrvSizeT hlen, ErlDrvBinary** binv; Port* prt; erts_aint32_t state; + ErtsSchedulerData *esdp = erts_get_scheduler_data(); ERTS_SMP_CHK_NO_PROC_LOCKS; @@ -5889,7 +6006,10 @@ int driver_outputv(ErlDrvPort ix, char* hbuf, ErlDrvSizeT hlen, /* XXX handle distribution !!! */ prt->bytes_in += (hlen + size); - erts_smp_atomic_add_nob(&erts_bytes_in, (erts_aint_t) (hlen + size)); + if (esdp) + esdp->io.in += (Uint64) (hlen + size); + else + erts_atomic64_add_nob(&bytes_in, (erts_aint64_t) (hlen + size)); deliver_vec_message(prt, ERTS_PORT_GET_CONNECTED(prt), hbuf, hlen, binv, iov, n, size); return 0; diff --git a/erts/emulator/beam/sys.h b/erts/emulator/beam/sys.h index cd53069872..78eb0bfe2b 100644 --- a/erts/emulator/beam/sys.h +++ b/erts/emulator/beam/sys.h @@ -20,6 +20,22 @@ #ifndef __SYS_H__ #define __SYS_H__ +#ifdef ERTS_INLINE +# ifndef ERTS_CAN_INLINE +# define ERTS_CAN_INLINE 1 +# endif +#else +# if defined(__GNUC__) +# define ERTS_CAN_INLINE 1 +# define ERTS_INLINE __inline__ +# elif defined(__WIN32__) +# define ERTS_CAN_INLINE 1 +# define ERTS_INLINE __inline +# else +# define ERTS_CAN_INLINE 0 +# define ERTS_INLINE +# endif +#endif #if defined(DEBUG) || defined(ERTS_ENABLE_LOCK_CHECK) # undef ERTS_CAN_INLINE @@ -94,23 +110,6 @@ typedef int ErtsSysFdType; typedef ERTS_SYS_FD_TYPE ErtsSysFdType; #endif -#ifdef ERTS_INLINE -# ifndef ERTS_CAN_INLINE -# define ERTS_CAN_INLINE 1 -# endif -#else -# if defined(__GNUC__) -# define ERTS_CAN_INLINE 1 -# define ERTS_INLINE __inline__ -# elif defined(__WIN32__) -# define ERTS_CAN_INLINE 1 -# define ERTS_INLINE __inline -# else -# define ERTS_CAN_INLINE 0 -# define ERTS_INLINE -# endif -#endif - #if !defined(__GNUC__) # define ERTS_AT_LEAST_GCC_VSN__(MAJ, MIN, PL) 0 #elif !defined(__GNUC_MINOR__) diff --git a/erts/emulator/drivers/common/inet_drv.c b/erts/emulator/drivers/common/inet_drv.c index 10ef20fc82..119d6c097d 100644 --- a/erts/emulator/drivers/common/inet_drv.c +++ b/erts/emulator/drivers/common/inet_drv.c @@ -835,6 +835,10 @@ static int my_strncasecmp(const char *s1, const char *s2, size_t n) #define TCP_ADDF_PENDING_SHUT_RDWR 32 /* Call shutdown(sock, SHUT_RDWR) when queue empties */ #define TCP_ADDF_PENDING_SHUTDOWN \ (TCP_ADDF_PENDING_SHUT_WR | TCP_ADDF_PENDING_SHUT_RDWR) +#define TCP_ADDF_SHOW_ECONNRESET 64 /* Tell user about incoming RST */ +#define TCP_ADDF_DELAYED_ECONNRESET 128 /* An ECONNRESET error occured on send or shutdown */ +#define TCP_ADDF_SHUTDOWN_WR_DONE 256 /* A shutdown(sock, SHUT_WR) or SHUT_RDWR was made */ +#define TCP_ADDF_LINGER_ZERO 512 /* Discard driver queue on port close */ /* *_REQ_* replies */ #define INET_REP_ERROR 0 @@ -879,6 +883,7 @@ static int my_strncasecmp(const char *s1, const char *s2, size_t n) #define INET_LOPT_MSGQ_HIWTRMRK 36 /* set local msgq high watermark */ #define INET_LOPT_MSGQ_LOWTRMRK 37 /* set local msgq low watermark */ #define INET_LOPT_NETNS 38 /* Network namespace pathname */ +#define INET_LOPT_TCP_SHOW_ECONNRESET 39 /* tell user about incoming RST */ /* SCTP options: a separate range, from 100: */ #define SCTP_OPT_RTOINFO 100 #define SCTP_OPT_ASSOCINFO 101 @@ -6138,7 +6143,12 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len) desc->active_count = 0; if ((desc->stype == SOCK_STREAM) && (desc->active != INET_PASSIVE) && (desc->state == INET_STATE_CLOSED)) { - tcp_closed_message((tcp_descriptor *) desc); + tcp_descriptor *tdesc = (tcp_descriptor *) desc; + if (tdesc->tcp_add_flags & TCP_ADDF_DELAYED_ECONNRESET) { + tdesc->tcp_add_flags &= ~TCP_ADDF_DELAYED_ECONNRESET; + tcp_error_message(tdesc, ECONNRESET); + } + tcp_closed_message(tdesc); if (desc->exitf) { driver_exit(desc->port, 0); return 0; /* Give up on this socket, descriptor lost */ @@ -6255,6 +6265,16 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len) continue; #endif + case INET_LOPT_TCP_SHOW_ECONNRESET: + if (desc->sprotocol == IPPROTO_TCP) { + tcp_descriptor* tdesc = (tcp_descriptor*) desc; + if (ival) + tdesc->tcp_add_flags |= TCP_ADDF_SHOW_ECONNRESET; + else + tdesc->tcp_add_flags &= ~TCP_ADDF_SHOW_ECONNRESET; + } + continue; + case INET_OPT_REUSEADDR: #ifdef __WIN32__ continue; /* Bjorn says */ @@ -6299,6 +6319,13 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len) arg_sz = sizeof(li_val); DEBUGF(("inet_set_opts(%ld): s=%d, SO_LINGER=%d,%d", (long)desc->port, desc->s, li_val.l_onoff,li_val.l_linger)); + if (desc->sprotocol == IPPROTO_TCP) { + tcp_descriptor* tdesc = (tcp_descriptor*) desc; + if (li_val.l_onoff && li_val.l_linger == 0) + tdesc->tcp_add_flags |= TCP_ADDF_LINGER_ZERO; + else + tdesc->tcp_add_flags &= ~TCP_ADDF_LINGER_ZERO; + } break; case INET_OPT_PRIORITY: @@ -7234,6 +7261,17 @@ static ErlDrvSSizeT inet_fill_opts(inet_descriptor* desc, continue; #endif + case INET_LOPT_TCP_SHOW_ECONNRESET: + if (desc->sprotocol == IPPROTO_TCP) { + tcp_descriptor* tdesc = (tcp_descriptor*) desc; + *ptr++ = opt; + ival = !!(tdesc->tcp_add_flags & TCP_ADDF_SHOW_ECONNRESET); + put_int32(ival, ptr); + } else { + TRUNCATE_TO(0,ptr); + } + continue; + case INET_OPT_PRIORITY: #ifdef SO_PRIORITY type = SO_PRIORITY; @@ -9077,6 +9115,11 @@ static tcp_descriptor* tcp_inet_copy(tcp_descriptor* desc,SOCKET s, copy_desc->low = desc->low; copy_desc->send_timeout = desc->send_timeout; copy_desc->send_timeout_close = desc->send_timeout_close; + + if (desc->tcp_add_flags & TCP_ADDF_SHOW_ECONNRESET) + copy_desc->tcp_add_flags |= TCP_ADDF_SHOW_ECONNRESET; + else + copy_desc->tcp_add_flags &= ~TCP_ADDF_SHOW_ECONNRESET; /* The new port will be linked and connected to the original caller */ port = driver_create_port(port, owner, "tcp_inet", (ErlDrvData) copy_desc); @@ -9438,7 +9481,11 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd, if (desc->tcp_add_flags & TCP_ADDF_DELAYED_CLOSE_RECV) { desc->tcp_add_flags &= ~(TCP_ADDF_DELAYED_CLOSE_RECV| TCP_ADDF_DELAYED_CLOSE_SEND); - return ctl_reply(INET_REP_ERROR, "closed", 6, rbuf, rsize); + if (desc->tcp_add_flags & TCP_ADDF_DELAYED_ECONNRESET) { + desc->tcp_add_flags &= ~TCP_ADDF_DELAYED_ECONNRESET; + return ctl_reply(INET_REP_ERROR, "econnreset", 10, rbuf, rsize); + } else + return ctl_reply(INET_REP_ERROR, "closed", 6, rbuf, rsize); } return ctl_error(ENOTCONN, rbuf, rsize); } @@ -9499,6 +9546,8 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd, return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); } if (IS_SOCKET_ERROR(sock_shutdown(INETP(desc)->s, how))) { + if (how != TCP_SHUT_RD) + desc->tcp_add_flags |= TCP_ADDF_SHUTDOWN_WR_DONE; return ctl_error(sock_errno(), rbuf, rsize); } else { return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize); @@ -9633,7 +9682,13 @@ static void tcp_inet_commandv(ErlDrvData e, ErlIOVec* ev) if (!IS_CONNECTED(INETP(desc))) { if (desc->tcp_add_flags & TCP_ADDF_DELAYED_CLOSE_SEND) { desc->tcp_add_flags &= ~TCP_ADDF_DELAYED_CLOSE_SEND; - inet_reply_error_am(INETP(desc), am_closed); + if (desc->tcp_add_flags & TCP_ADDF_DELAYED_ECONNRESET) { + /* Don't clear flag. Leave it enabled for the next receive + * operation. + */ + inet_reply_error(INETP(desc), ECONNRESET); + } else + inet_reply_error_am(INETP(desc), am_closed); } else inet_reply_error(INETP(desc), ENOTCONN); @@ -9652,6 +9707,8 @@ static void tcp_inet_flush(ErlDrvData e) /* Discard send queue to avoid hanging port (OTP-7615) */ tcp_clear_output(desc); } + if (desc->tcp_add_flags & TCP_ADDF_LINGER_ZERO) + tcp_clear_output(desc); } static void tcp_inet_process_exit(ErlDrvData e, ErlDrvMonitor *monitorp) @@ -10014,7 +10071,10 @@ static int tcp_recv(tcp_descriptor* desc, int request_len) int err = sock_errno(); if (err == ECONNRESET) { DEBUGF((" => detected close (connreset)\r\n")); - return tcp_recv_closed(desc); + if (desc->tcp_add_flags & TCP_ADDF_SHOW_ECONNRESET) + return tcp_recv_error(desc, err); + else + return tcp_recv_closed(desc); } if (err == ERRNO_BLOCK) { DEBUGF((" => would block\r\n")); @@ -10226,7 +10286,19 @@ static void tcp_inet_event(ErlDrvData e, ErlDrvEvent event) if (netEv.lNetworkEvents & FD_CLOSE) { /* error in err = netEv.iErrorCode[FD_CLOSE_BIT] */ DEBUGF(("Detected close in %s, line %d\r\n", __FILE__, __LINE__)); - tcp_recv_closed(desc); + if (desc->tcp_add_flags & TCP_ADDF_SHOW_ECONNRESET) { + err = netEv.iErrorCode[FD_CLOSE_BIT]; + if (err == ECONNRESET) + tcp_recv_error(desc, err); + else if (err == ECONNABORTED && IS_CONNECTED(INETP(desc))) { + /* translate this error to ECONNRESET */ + tcp_recv_error(desc, ECONNRESET); + } + else + tcp_recv_closed(desc); + } + else + tcp_recv_closed(desc); } DEBUGF(("tcp_inet_event(%ld) }\r\n", (long)desc->inet.port)); return; @@ -10535,6 +10607,9 @@ static int tcp_inet_input(tcp_descriptor* desc, HANDLE event) static int tcp_send_or_shutdown_error(tcp_descriptor* desc, int err) { + int show_econnreset = (err == ECONNRESET + && desc->tcp_add_flags & TCP_ADDF_SHOW_ECONNRESET); + /* * If the port is busy, we must do some clean-up before proceeding. */ @@ -10550,14 +10625,21 @@ static int tcp_send_or_shutdown_error(tcp_descriptor* desc, int err) /* * We used to handle "expected errors" differently from unexpected ones. - * Now we handle all errors in the same way. We just have to distinguish - * between passive and active sockets. + * Now we handle all errors in the same way (unless the show_econnreset + * socket option is enabled). We just have to distinguish between passive + * and active sockets. */ DEBUGF(("driver_failure_eof(%ld) in %s, line %d\r\n", (long)desc->inet.port, __FILE__, __LINE__)); if (desc->inet.active) { - tcp_closed_message(desc); - inet_reply_error_am(INETP(desc), am_closed); + if (show_econnreset) { + tcp_error_message(desc, err); + tcp_closed_message(desc); + inet_reply_error(INETP(desc), err); + } else { + tcp_closed_message(desc); + inet_reply_error_am(INETP(desc), am_closed); + } if (desc->inet.exitf) driver_exit(desc->inet.port, 0); else @@ -10569,7 +10651,10 @@ static int tcp_send_or_shutdown_error(tcp_descriptor* desc, int err) erl_inet_close(INETP(desc)); if (desc->inet.caller) { - inet_reply_error_am(INETP(desc), am_closed); + if (show_econnreset) + inet_reply_error(INETP(desc), err); + else + inet_reply_error_am(INETP(desc), am_closed); } else { /* No blocking send op to reply to right now. @@ -10586,12 +10671,46 @@ static int tcp_send_or_shutdown_error(tcp_descriptor* desc, int err) * in the receive operation. */ desc->tcp_add_flags |= TCP_ADDF_DELAYED_CLOSE_RECV; + + if (show_econnreset) { + /* Return {error, econnreset} instead of {error, closed} + * on send or receive operations. + */ + desc->tcp_add_flags |= TCP_ADDF_DELAYED_ECONNRESET; + } } return -1; } static int tcp_send_error(tcp_descriptor* desc, int err) { + /* EPIPE errors usually occur in one of three ways: + * 1. We write to a socket when we've already shutdown() the write side. On + * Windows the error returned for this is ESHUTDOWN rather than EPIPE. + * 2. The TCP peer sends us an RST through no fault of our own (perhaps + * by aborting the connection using SO_LINGER) and we then attempt + * to write to the socket. On Linux and Windows we would actually + * receive an ECONNRESET error for this, but on the BSDs, Darwin, + * Illumos and presumably Solaris, it's an EPIPE. + * 3. We cause the TCP peer to send us an RST by writing to a socket + * after we receive a FIN from them. Our first write will be + * successful, but if the they have closed the connection (rather + * than just shutting down the write side of it) this will cause their + * OS to send us an RST. Then, when we attempt to write to the socket + * a second time, we will get an EPIPE error. On Windows we get an + * ECONNABORTED. + * + * What we are going to do here is to treat all EPIPE messages that aren't + * of type 1 as ECONNRESET errors. This will allow users who have the + * show_econnreset socket option enabled to receive {error, econnreset} on + * both send and recv operations to indicate that an RST has been received. + */ +#ifdef __WIN_32__ + if (err == ECONNABORTED) + err = ECONNRESET; +#endif + if (err == EPIPE && !(desc->tcp_add_flags & TCP_ADDF_SHUTDOWN_WR_DONE)) + err = ECONNRESET; return tcp_send_or_shutdown_error(desc, err); } @@ -10811,6 +10930,8 @@ static void tcp_shutdown_async(tcp_descriptor* desc) TCP_SHUT_WR : TCP_SHUT_RDWR; if (IS_SOCKET_ERROR(sock_shutdown(INETP(desc)->s, how))) tcp_shutdown_error(desc, sock_errno()); + else + desc->tcp_add_flags |= TCP_ADDF_SHUTDOWN_WR_DONE; } #ifdef __OSE__ diff --git a/erts/preloaded/ebin/erts_internal.beam b/erts/preloaded/ebin/erts_internal.beam Binary files differindex 0e0811af3f..dc8c711e1a 100644 --- a/erts/preloaded/ebin/erts_internal.beam +++ b/erts/preloaded/ebin/erts_internal.beam diff --git a/erts/preloaded/ebin/prim_inet.beam b/erts/preloaded/ebin/prim_inet.beam Binary files differindex 6729f06b79..5a188be3ba 100644 --- a/erts/preloaded/ebin/prim_inet.beam +++ b/erts/preloaded/ebin/prim_inet.beam diff --git a/erts/preloaded/src/erts_internal.erl b/erts/preloaded/src/erts_internal.erl index 65a1f1ed3a..cf8edefd7d 100644 --- a/erts/preloaded/src/erts_internal.erl +++ b/erts/preloaded/src/erts_internal.erl @@ -40,7 +40,7 @@ -export([flush_monitor_messages/3]). --export([await_result/1]). +-export([await_result/1, gather_io_bytes/2]). -export([time_unit/0]). @@ -67,6 +67,23 @@ await_result(Ref) when is_reference(Ref) -> end. %% +%% statistics(io) end up in gather_io_bytes/2 +%% + +gather_io_bytes(Ref, No) when is_reference(Ref), + is_integer(No), + No > 0 -> + gather_io_bytes(Ref, No, 0, 0). + +gather_io_bytes(_Ref, 0, InAcc, OutAcc) -> + {{input, InAcc}, {output, OutAcc}}; +gather_io_bytes(Ref, No, InAcc, OutAcc) -> + receive + {Ref, _SchedId, In, Out} -> + gather_io_bytes(Ref, No-1, InAcc + In, OutAcc + Out) + end. + +%% %% Statically linked port NIFs %% diff --git a/erts/preloaded/src/prim_inet.erl b/erts/preloaded/src/prim_inet.erl index 622e1be869..5e0b38aa68 100644 --- a/erts/preloaded/src/prim_inet.erl +++ b/erts/preloaded/src/prim_inet.erl @@ -146,11 +146,16 @@ shutdown_1(S, How) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% close(S) when is_port(S) -> - case subscribe(S, [subs_empty_out_q]) of - {ok, [{subs_empty_out_q,N}]} when N > 0 -> - close_pend_loop(S, N); %% wait for pending output to be sent + case getopt(S, linger) of + {ok,{true,0}} -> + close_port(S); _ -> - close_port(S) + case subscribe(S, [subs_empty_out_q]) of + {ok, [{subs_empty_out_q,N}]} when N > 0 -> + close_pend_loop(S, N); %% wait for pending output to be sent + _ -> + close_port(S) + end end. close_pend_loop(S, N) -> @@ -1140,6 +1145,7 @@ enc_opt(delay_send) -> ?INET_LOPT_TCP_DELAY_SEND; enc_opt(packet_size) -> ?INET_LOPT_PACKET_SIZE; enc_opt(read_packets) -> ?INET_LOPT_READ_PACKETS; enc_opt(netns) -> ?INET_LOPT_NETNS; +enc_opt(show_econnreset) -> ?INET_LOPT_TCP_SHOW_ECONNRESET; enc_opt(raw) -> ?INET_OPT_RAW; % Names of SCTP opts: enc_opt(sctp_rtoinfo) -> ?SCTP_OPT_RTOINFO; @@ -1197,6 +1203,7 @@ dec_opt(?INET_LOPT_TCP_DELAY_SEND) -> delay_send; dec_opt(?INET_LOPT_PACKET_SIZE) -> packet_size; dec_opt(?INET_LOPT_READ_PACKETS) -> read_packets; dec_opt(?INET_LOPT_NETNS) -> netns; +dec_opt(?INET_LOPT_TCP_SHOW_ECONNRESET) -> show_econnreset; dec_opt(?INET_OPT_RAW) -> raw; dec_opt(I) when is_integer(I) -> undefined. @@ -1296,6 +1303,7 @@ type_opt_1(delay_send) -> bool; type_opt_1(packet_size) -> uint; type_opt_1(read_packets) -> uint; type_opt_1(netns) -> binary; +type_opt_1(show_econnreset) -> bool; %% %% SCTP options (to be set). If the type is a record type, the corresponding %% record signature is returned, otherwise, an "elementary" type tag diff --git a/lib/compiler/src/beam_jump.erl b/lib/compiler/src/beam_jump.erl index 80b2998ddc..5bfaa41b7d 100644 --- a/lib/compiler/src/beam_jump.erl +++ b/lib/compiler/src/beam_jump.erl @@ -490,16 +490,17 @@ is_label_used_in_1([], _, _) -> false. is_label_used_in_block({set,_,_,Info}, Lbl) -> case Info of - {bif,_,{f,F}} -> F =:= Lbl; - {alloc,_,{gc_bif,_,{f,F}}} -> F =:= Lbl; + {bif,_,{f,F}} -> F =:= Lbl; + {alloc,_,{gc_bif,_,{f,F}}} -> F =:= Lbl; {alloc,_,{put_map,_,{f,F}}} -> F =:= Lbl; - {'catch',{f,F}} -> F =:= Lbl; - {alloc,_,_} -> false; - {put_tuple,_} -> false; - {get_tuple_element,_} -> false; - {set_tuple_element,_} -> false; - {line,_} -> false; - _ when is_atom(Info) -> false + {get_map_elements,{f,F}} -> F =:= Lbl; + {'catch',{f,F}} -> F =:= Lbl; + {alloc,_,_} -> false; + {put_tuple,_} -> false; + {get_tuple_element,_} -> false; + {set_tuple_element,_} -> false; + {line,_} -> false; + _ when is_atom(Info) -> false end. %% remove_unused_labels(Instructions0) -> Instructions diff --git a/lib/compiler/test/Makefile b/lib/compiler/test/Makefile index 98125fc84e..6553d10077 100644 --- a/lib/compiler/test/Makefile +++ b/lib/compiler/test/Makefile @@ -33,6 +33,7 @@ MODULES= \ num_bif_SUITE \ receive_SUITE \ record_SUITE \ + regressions_SUITE \ trycatch_SUITE \ warnings_SUITE \ z_SUITE \ diff --git a/lib/compiler/test/regressions_SUITE.erl b/lib/compiler/test/regressions_SUITE.erl new file mode 100644 index 0000000000..51bcc5737f --- /dev/null +++ b/lib/compiler/test/regressions_SUITE.erl @@ -0,0 +1,98 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2015. 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% +%% + +%% Test specific code snippets that has crashed the compiler in the past. +-module(regressions_SUITE). +-include_lib("test_server/include/test_server.hrl"). + +-export([all/0, groups/0, init_per_testcase/2,end_per_testcase/2]). + +-export([maps/1]). + +groups() -> + [{p,test_lib:parallel(), + [maps]}]. + +% Default timetrap timeout (set in init_per_testcase). +-define(default_timeout, ?t:minutes(2)). + +init_per_testcase(_Case, Config) -> + ?line Dog = ?t:timetrap(?default_timeout), + [{watchdog, Dog} | Config]. + +end_per_testcase(_Case, Config) -> + Dog = ?config(watchdog, Config), + test_server:timetrap_cancel(Dog), + ok. + +all() -> + test_lib:recompile(?MODULE), + [{group,p}]. + +%%% test cases + +maps(Config) when is_list(Config) -> + Ts = [{beam_bool_get_elements, + <<"century(#{ron := operator}, _century) -> + if 0.0; _century, _century, _century -> _century end. + ">>}], + ok = run(Config, Ts), + ok. + +%% aux + +run(Config, Tests) -> + F = fun({N,P}) -> + io:format("Compiling test for: ~w~n", [N]), + case catch run_test(Config, P) of + {'EXIT', Reason} -> + ?t:format("~nTest ~p failed.~nReason: ~p~n", [N, Reason]), + fail(); + _ -> ok + end + end, + lists:foreach(F, Tests). + + +run_test(Conf, Test0) -> + Module = "regressions_"++test_lib:uniq(), + Filename = Module ++ ".erl", + DataDir = ?config(priv_dir, Conf), + Test = ["-module(", Module, "). ", Test0], + File = filename:join(DataDir, Filename), + Def = [binary,export_all,return], + Opts = [ Opt ++ Def || + Opt <- [ [no_postopt], + [no_copt], + [no_postopt,no_copt], + [inline], + [inline,no_postopt], + [] + ]], + ok = file:write_file(File, Test), + lists:foreach(fun(Opt) -> + io:format(" - compiling with ~p~n", [Opt]), + {ok,_M,_Bin,_} = compile:file(File,Opt) + end, Opts), + file:delete(File), + ok. + +fail() -> + io:format("failed~n"), + ?t:fail(). diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl index a4971cec0f..ce9a0e3d26 100644 --- a/lib/inets/src/http_client/httpc_handler.erl +++ b/lib/inets/src/http_client/httpc_handler.erl @@ -1390,6 +1390,8 @@ case_insensitive_header(Str) -> activate_once(#session{socket = Socket, socket_type = SocketType}) -> http_transport:setopts(SocketType, Socket, [{active, once}]). +close_socket(#session{socket = {remote_close,_}}) -> + ok; close_socket(#session{socket = Socket, socket_type = SocketType}) -> http_transport:close(SocketType, Socket). diff --git a/lib/inets/src/http_server/httpd_conf.erl b/lib/inets/src/http_server/httpd_conf.erl index 9c70f8d1b8..89a7e96f3e 100644 --- a/lib/inets/src/http_server/httpd_conf.erl +++ b/lib/inets/src/http_server/httpd_conf.erl @@ -19,141 +19,29 @@ %% -module(httpd_conf). -%% EWSAPI --export([is_directory/1, is_file/1, make_integer/1, clean/1, - custom_clean/3, check_enum/2]). - %% Application internal API -export([load/1, load/2, load_mime_types/1, store/1, store/2, remove/1, remove_all/1, get_config/3, get_config/4, lookup_socket_type/1, lookup/2, lookup/3, lookup/4, - validate_properties/1]). + validate_properties/1, white_space_clean/1]). + +%% Deprecated +-export([is_directory/1, is_file/1, make_integer/1, clean/1, + custom_clean/3, check_enum/2]). + +-deprecated({is_directory, 1, next_major_release}). +-deprecated({is_file, 1, next_major_release}). +-deprecated({make_integer, 1, next_major_release}). +-deprecated({clean, 1, next_major_release}). +-deprecated({custom_clean, 3, next_major_release}). +-deprecated({check_enum, 2, next_major_release}). -define(VMODULE,"CONF"). -include("httpd_internal.hrl"). -include("httpd.hrl"). -include_lib("inets/src/http_lib/http_internal.hrl"). - -%%%========================================================================= -%%% EWSAPI -%%%========================================================================= -%%------------------------------------------------------------------------- -%% is_directory(FilePath) -> Result -%% FilePath = string() -%% Result = {ok,Directory} | {error,Reason} -%% Directory = string() -%% Reason = string() | enoent | eacces | enotdir | FileInfo -%% FileInfo = File info record -%% -%% Description: Checks if FilePath is a directory in which case it is -%% returned. -%%------------------------------------------------------------------------- -is_directory(Directory) -> - case file:read_file_info(Directory) of - {ok,FileInfo} -> - #file_info{type = Type, access = Access} = FileInfo, - is_directory(Type,Access,FileInfo,Directory); - {error,Reason} -> - {error,Reason} - end. -is_directory(directory,read,_FileInfo,Directory) -> - {ok,Directory}; -is_directory(directory,read_write,_FileInfo,Directory) -> - {ok,Directory}; -is_directory(_Type,_Access,FileInfo,_Directory) -> - {error,FileInfo}. - - -%%------------------------------------------------------------------------- -%% is_file(FilePath) -> Result -%% FilePath = string() -%% Result = {ok,File} | {error,Reason} -%% File = string() -%% Reason = string() | enoent | eacces | enotdir | FileInfo -%% FileInfo = File info record -%% -%% Description: Checks if FilePath is a regular file in which case it -%% is returned. -%%------------------------------------------------------------------------- -is_file(File) -> - case file:read_file_info(File) of - {ok,FileInfo} -> - #file_info{type = Type, access = Access} = FileInfo, - is_file(Type,Access,FileInfo,File); - {error,Reason} -> - {error,Reason} - end. -is_file(regular,read,_FileInfo,File) -> - {ok,File}; -is_file(regular,read_write,_FileInfo,File) -> - {ok,File}; -is_file(_Type,_Access,FileInfo,_File) -> - {error,FileInfo}. - - -%%------------------------------------------------------------------------- -%% make_integer(String) -> Result -%% String = string() -%% Result = {ok,integer()} | {error,nomatch} -%% -%% Description: make_integer/1 returns an integer representation of String. -%%------------------------------------------------------------------------- -make_integer(String) -> - case inets_regexp:match(clean(String),"[0-9]+") of - {match, _, _} -> - {ok, list_to_integer(clean(String))}; - nomatch -> - {error, nomatch} - end. - - -%%------------------------------------------------------------------------- -%% clean(String) -> Stripped -%% String = Stripped = string() -%% -%% Description:clean/1 removes leading and/or trailing white spaces -%% from String. -%%------------------------------------------------------------------------- -clean(String) -> - {ok,CleanedString,_} = - inets_regexp:gsub(String, "^[ \t\n\r\f]*|[ \t\n\r\f]*\$",""), - CleanedString. - - -%%------------------------------------------------------------------------- -%% custom_clean(String,Before,After) -> Stripped -%% Before = After = regexp() -%% String = Stripped = string() -%% -%% Description: custom_clean/3 removes leading and/or trailing white -%% spaces and custom characters from String. -%%------------------------------------------------------------------------- -custom_clean(String,MoreBefore,MoreAfter) -> - {ok,CleanedString,_} = inets_regexp:gsub(String,"^[ \t\n\r\f"++MoreBefore++ - "]*|[ \t\n\r\f"++MoreAfter++"]*\$",""), - CleanedString. - - -%%------------------------------------------------------------------------- -%% check_enum(EnumString,ValidEnumStrings) -> Result -%% EnumString = string() -%% ValidEnumStrings = [string()] -%% Result = {ok,atom()} | {error,not_valid} -%% -%% Description: check_enum/2 checks if EnumString is a valid -%% enumeration of ValidEnumStrings in which case it is returned as an -%% atom. -%%------------------------------------------------------------------------- -check_enum(_Enum,[]) -> - {error, not_valid}; -check_enum(Enum,[Enum|_Rest]) -> - {ok, list_to_atom(Enum)}; -check_enum(Enum, [_NotValid|Rest]) -> - check_enum(Enum, Rest). - - %%%========================================================================= %%% Application internal API %%%========================================================================= @@ -192,7 +80,7 @@ load("MaxHeaderSize " ++ MaxHeaderSize, []) -> {ok, Integer} -> {ok, [], {max_header_size,Integer}}; {error, _} -> - {error, ?NICE(clean(MaxHeaderSize)++ + {error, ?NICE(string:strip(MaxHeaderSize)++ " is an invalid number of MaxHeaderSize")} end; @@ -201,7 +89,7 @@ load("MaxURISize " ++ MaxHeaderSize, []) -> {ok, Integer} -> {ok, [], {max_uri_size, Integer}}; {error, _} -> - {error, ?NICE(clean(MaxHeaderSize)++ + {error, ?NICE(string:strip(MaxHeaderSize)++ " is an invalid number of MaxHeaderSize")} end; @@ -210,12 +98,12 @@ load("MaxContentLength " ++ Max, []) -> {ok, Integer} -> {ok, [], {max_content_length, Integer}}; {error, _} -> - {error, ?NICE(clean(Max) ++ + {error, ?NICE(string:strip(Max) ++ " is an invalid number of MaxContentLength")} end; load("ServerName " ++ ServerName, []) -> - {ok,[], {server_name, clean(ServerName)}}; + {ok,[], {server_name, string:strip(ServerName)}}; load("ServerTokens " ++ ServerTokens, []) -> %% These are the valid *plain* server tokens: @@ -223,28 +111,28 @@ load("ServerTokens " ++ ServerTokens, []) -> %% It can also be a "private" server token: private:<any string> case string:tokens(ServerTokens, [$:]) of ["private", Private] -> - {ok,[], {server_tokens, clean(Private)}}; + {ok,[], {server_tokens, string:strip(Private)}}; [TokStr] -> - Tok = list_to_atom(clean(TokStr)), + Tok = list_to_atom(string:strip(TokStr)), case lists:member(Tok, [none, prod, major, minor, minimum, os, full]) of true -> {ok,[], {server_tokens, Tok}}; false -> - {error, ?NICE(clean(ServerTokens) ++ + {error, ?NICE(string:strip(ServerTokens) ++ " is an invalid ServerTokens")} end; _ -> - {error, ?NICE(clean(ServerTokens) ++ " is an invalid ServerTokens")} + {error, ?NICE(string:strip(ServerTokens) ++ " is an invalid ServerTokens")} end; load("SocketType " ++ SocketType, []) -> %% ssl is the same as HTTP_DEFAULT_SSL_KIND %% essl is the pure Erlang-based ssl (the "new" ssl) - case check_enum(clean(SocketType), ["ssl", "essl", "ip_comm"]) of + case check_enum(string:strip(SocketType), ["ssl", "essl", "ip_comm"]) of {ok, ValidSocketType} -> {ok, [], {socket_type, ValidSocketType}}; {error,_} -> - {error, ?NICE(clean(SocketType) ++ " is an invalid SocketType")} + {error, ?NICE(string:strip(SocketType) ++ " is an invalid SocketType")} end; load("Port " ++ Port, []) -> @@ -252,7 +140,7 @@ load("Port " ++ Port, []) -> {ok, Integer} -> {ok, [], {port, Integer}}; {error, _} -> - {error, ?NICE(clean(Port)++" is an invalid Port")} + {error, ?NICE(string:strip(Port)++" is an invalid Port")} end; load("BindAddress " ++ Address0, []) -> @@ -308,7 +196,7 @@ load("BindAddress " ++ Address0, []) -> end; load("KeepAlive " ++ OnorOff, []) -> - case list_to_atom(clean(OnorOff)) of + case list_to_atom(string:strip(OnorOff)) of off -> {ok, [], {keep_alive, false}}; _ -> @@ -320,7 +208,7 @@ load("MaxKeepAliveRequests " ++ MaxRequests, []) -> {ok, Integer} -> {ok, [], {max_keep_alive_request, Integer}}; {error, _} -> - {error, ?NICE(clean(MaxRequests) ++ + {error, ?NICE(string:strip(MaxRequests) ++ " is an invalid MaxKeepAliveRequests")} end; @@ -330,7 +218,7 @@ load("MaxKeepAliveRequest " ++ MaxRequests, []) -> {ok, Integer} -> {ok, [], {max_keep_alive_request, Integer}}; {error, _} -> - {error, ?NICE(clean(MaxRequests) ++ + {error, ?NICE(string:strip(MaxRequests) ++ " is an invalid MaxKeepAliveRequest")} end; @@ -339,7 +227,7 @@ load("KeepAliveTimeout " ++ Timeout, []) -> {ok, Integer} -> {ok, [], {keep_alive_timeout, Integer}}; {error, _} -> - {error, ?NICE(clean(Timeout)++" is an invalid KeepAliveTimeout")} + {error, ?NICE(string:strip(Timeout)++" is an invalid KeepAliveTimeout")} end; load("Modules " ++ Modules, []) -> @@ -347,18 +235,18 @@ load("Modules " ++ Modules, []) -> {ok, [], {modules,[list_to_atom(X) || X <- ModuleList]}}; load("ServerAdmin " ++ ServerAdmin, []) -> - {ok, [], {server_admin,clean(ServerAdmin)}}; + {ok, [], {server_admin,string:strip(ServerAdmin)}}; load("ServerRoot " ++ ServerRoot, []) -> - case is_directory(clean(ServerRoot)) of + case is_directory(string:strip(ServerRoot)) of {ok, Directory} -> {ok, [], [{server_root,string:strip(Directory,right,$/)}]}; {error, _} -> - {error, ?NICE(clean(ServerRoot)++" is an invalid ServerRoot")} + {error, ?NICE(string:strip(ServerRoot)++" is an invalid ServerRoot")} end; load("MimeTypes " ++ MimeTypes, []) -> - case load_mime_types(clean(MimeTypes)) of + case load_mime_types(white_space_clean(MimeTypes)) of {ok, MimeTypesList} -> {ok, [], [{mime_types, MimeTypesList}]}; {error, Reason} -> @@ -370,24 +258,24 @@ load("MaxClients " ++ MaxClients, []) -> {ok, Integer} -> {ok, [], {max_clients,Integer}}; {error, _} -> - {error, ?NICE(clean(MaxClients) ++ + {error, ?NICE(string:strip(MaxClients) ++ " is an invalid number of MaxClients")} end; load("DocumentRoot " ++ DocumentRoot,[]) -> - case is_directory(clean(DocumentRoot)) of + case is_directory(string:strip(DocumentRoot)) of {ok, Directory} -> {ok, [], {document_root,string:strip(Directory,right,$/)}}; {error, _} -> - {error, ?NICE(clean(DocumentRoot)++" is an invalid DocumentRoot")} + {error, ?NICE(string:strip(DocumentRoot)++" is an invalid DocumentRoot")} end; load("DefaultType " ++ DefaultType, []) -> - {ok, [], {default_type,clean(DefaultType)}}; + {ok, [], {default_type,string:strip(DefaultType)}}; load("SSLCertificateFile " ++ SSLCertificateFile, []) -> - case is_file(clean(SSLCertificateFile)) of + case is_file(string:strip(SSLCertificateFile)) of {ok, File} -> {ok, [], {ssl_certificate_file,File}}; {error, _} -> - {error, ?NICE(clean(SSLCertificateFile)++ + {error, ?NICE(string:strip(SSLCertificateFile)++ " is an invalid SSLCertificateFile")} end; load("SSLLogLevel " ++ SSLLogAlert, []) -> @@ -398,69 +286,69 @@ load("SSLLogLevel " ++ SSLLogAlert, []) -> {ok, [], {ssl_log_alert, true}} end; load("SSLCertificateKeyFile " ++ SSLCertificateKeyFile, []) -> - case is_file(clean(SSLCertificateKeyFile)) of + case is_file(string:strip(SSLCertificateKeyFile)) of {ok, File} -> {ok, [], {ssl_certificate_key_file,File}}; {error, _} -> - {error, ?NICE(clean(SSLCertificateKeyFile)++ + {error, ?NICE(string:strip(SSLCertificateKeyFile)++ " is an invalid SSLCertificateKeyFile")} end; load("SSLVerifyClient " ++ SSLVerifyClient, []) -> - case make_integer(clean(SSLVerifyClient)) of + case make_integer(string:strip(SSLVerifyClient)) of {ok, Integer} when (Integer >=0) andalso (Integer =< 2) -> {ok, [], {ssl_verify_client,Integer}}; {ok, _Integer} -> - {error,?NICE(clean(SSLVerifyClient) ++ + {error,?NICE(string:strip(SSLVerifyClient) ++ " is an invalid SSLVerifyClient")}; {error, nomatch} -> - {error,?NICE(clean(SSLVerifyClient) ++ + {error,?NICE(string:strip(SSLVerifyClient) ++ " is an invalid SSLVerifyClient")} end; load("SSLVerifyDepth " ++ SSLVerifyDepth, []) -> - case make_integer(clean(SSLVerifyDepth)) of + case make_integer(string:strip(SSLVerifyDepth)) of {ok, Integer} when Integer > 0 -> {ok, [], {ssl_verify_client_depth,Integer}}; {ok, _Integer} -> - {error,?NICE(clean(SSLVerifyDepth) ++ + {error,?NICE(string:strip(SSLVerifyDepth) ++ " is an invalid SSLVerifyDepth")}; {error, nomatch} -> - {error,?NICE(clean(SSLVerifyDepth) ++ + {error,?NICE(string:strip(SSLVerifyDepth) ++ " is an invalid SSLVerifyDepth")} end; load("SSLCiphers " ++ SSLCiphers, []) -> - {ok, [], {ssl_ciphers, clean(SSLCiphers)}}; + {ok, [], {ssl_ciphers, string:strip(SSLCiphers)}}; load("SSLCACertificateFile " ++ SSLCACertificateFile, []) -> - case is_file(clean(SSLCACertificateFile)) of + case is_file(string:strip(SSLCACertificateFile)) of {ok, File} -> {ok, [], {ssl_ca_certificate_file,File}}; {error, _} -> - {error, ?NICE(clean(SSLCACertificateFile)++ + {error, ?NICE(string:strip(SSLCACertificateFile)++ " is an invalid SSLCACertificateFile")} end; load("SSLPasswordCallbackModule " ++ SSLPasswordCallbackModule, []) -> {ok, [], {ssl_password_callback_module, - list_to_atom(clean(SSLPasswordCallbackModule))}}; + list_to_atom(string:strip(SSLPasswordCallbackModule))}}; load("SSLPasswordCallbackFunction " ++ SSLPasswordCallbackFunction, []) -> {ok, [], {ssl_password_callback_function, - list_to_atom(clean(SSLPasswordCallbackFunction))}}; + list_to_atom(string:strip(SSLPasswordCallbackFunction))}}; load("SSLPasswordCallbackArguments " ++ SSLPasswordCallbackArguments, []) -> {ok, [], {ssl_password_callback_arguments, SSLPasswordCallbackArguments}}; load("DisableChunkedTransferEncodingSend " ++ TrueOrFalse, []) -> - case list_to_atom(clean(TrueOrFalse)) of + case list_to_atom(string:strip(TrueOrFalse)) of true -> {ok, [], {disable_chunked_transfer_encoding_send, true}}; _ -> {ok, [], {disable_chunked_transfer_encoding_send, false}} end; load("LogFormat " ++ LogFormat, []) -> - {ok,[],{log_format, list_to_atom(httpd_conf:clean(LogFormat))}}; + {ok,[],{log_format, list_to_atom(string:strip(LogFormat))}}; load("ErrorLogFormat " ++ LogFormat, []) -> - {ok,[],{error_log_format, list_to_atom(httpd_conf:clean(LogFormat))}}. + {ok,[],{error_log_format, list_to_atom(string:strip(LogFormat))}}. clean_address(Addr) -> - string:strip(string:strip(clean(Addr), left, $[), right, $]). + string:strip(string:strip(string:strip(Addr), left, $[), right, $]). make_ipfamily(IpFamilyStr) -> @@ -1086,7 +974,7 @@ verify_modules([]) -> verify_modules([Mod|Rest]) -> case code:which(Mod) of non_existing -> - {error, ?NICE(atom_to_list(Mod)++" does not exist")}; + {error, ?NICE(string:strip(atom_to_list(Mod), right, $\n) ++" does not exist")}; _Path -> verify_modules(Rest) end. @@ -1128,7 +1016,7 @@ parse_mime_types(Stream,MimeTypesList) -> eof -> eof; String -> - clean(String) + white_space_clean(String) end, parse_mime_types(Stream, MimeTypesList, Line). parse_mime_types(Stream, MimeTypesList, eof) -> @@ -1314,4 +1202,68 @@ plain_server_tokens() -> error_report(Where,M,F,Error) -> error_logger:error_report([{?MODULE, Where}, {apply, {M, F, []}}, Error]). +white_space_clean(String) -> + {ok,CleanedString,_} = + inets_regexp:gsub(String, "^[ \t\n\r\f]*|[ \t\n\r\f]*\$",""), + CleanedString. + + +%%%========================================================================= +%%% Deprecated remove in 19 +%%%========================================================================= +is_directory(Directory) -> + case file:read_file_info(Directory) of + {ok,FileInfo} -> + #file_info{type = Type, access = Access} = FileInfo, + is_directory(Type,Access,FileInfo,Directory); + {error,Reason} -> + {error,Reason} + end. +is_directory(directory,read,_FileInfo,Directory) -> + {ok,Directory}; +is_directory(directory,read_write,_FileInfo,Directory) -> + {ok,Directory}; +is_directory(_Type,_Access,FileInfo,_Directory) -> + {error,FileInfo}. + +is_file(File) -> + case file:read_file_info(File) of + {ok,FileInfo} -> + #file_info{type = Type, access = Access} = FileInfo, + is_file(Type,Access,FileInfo,File); + {error,Reason} -> + {error,Reason} + end. +is_file(regular,read,_FileInfo,File) -> + {ok,File}; +is_file(regular,read_write,_FileInfo,File) -> + {ok,File}; +is_file(_Type,_Access,FileInfo,_File) -> + {error,FileInfo}. + +make_integer(String) -> + case inets_regexp:match(string:strip(String),"[0-9]+") of + {match, _, _} -> + {ok, list_to_integer(string:strip(String))}; + nomatch -> + {error, nomatch} + end. + +clean(String) -> + {ok,CleanedString,_} = + inets_regexp:gsub(String, "^[ \t\n\r\f]*|[ \t\n\r\f]*\$",""), + CleanedString. + +custom_clean(String,MoreBefore,MoreAfter) -> + {ok,CleanedString,_} = inets_regexp:gsub(String,"^[ \t\n\r\f"++MoreBefore++ + "]*|[ \t\n\r\f"++MoreAfter++"]*\$",""), + CleanedString. + +check_enum(_Enum,[]) -> + {error, not_valid}; +check_enum(Enum,[Enum|_Rest]) -> + {ok, list_to_atom(Enum)}; +check_enum(Enum, [_NotValid|Rest]) -> + check_enum(Enum, Rest). + diff --git a/lib/inets/src/http_server/httpd_example.erl b/lib/inets/src/http_server/httpd_example.erl index 6fc07f033c..b2c2ea7480 100644 --- a/lib/inets/src/http_server/httpd_example.erl +++ b/lib/inets/src/http_server/httpd_example.erl @@ -126,9 +126,7 @@ delay(Time) when is_integer(Time) -> i("httpd_example:delay(~p) -> done, now reply",[Time]), delay_reply("delay ok"); delay(Time) when is_list(Time) -> - delay(httpd_conf:make_integer(Time)); -delay({ok,Time}) when is_integer(Time) -> - delay(Time); + delay(list_to_integer(Time)); delay({error,_Reason}) -> i("delay -> called with invalid time"), delay_reply("delay failed: invalid delay time"). diff --git a/lib/inets/src/http_server/mod_actions.erl b/lib/inets/src/http_server/mod_actions.erl index c3946ff9b4..e493ddbf93 100644 --- a/lib/inets/src/http_server/mod_actions.erl +++ b/lib/inets/src/http_server/mod_actions.erl @@ -84,14 +84,14 @@ load("Action "++ Action, []) -> {ok,[MimeType, CGIScript]} -> {ok,[],{action, {MimeType, CGIScript}}}; {ok,_} -> - {error,?NICE(httpd_conf:clean(Action)++" is an invalid Action")} + {error,?NICE(string:strip(Action)++" is an invalid Action")} end; load("Script " ++ Script,[]) -> case inets_regexp:split(Script, " ") of {ok,[Method, CGIScript]} -> {ok,[],{script, {Method, CGIScript}}}; {ok,_} -> - {error,?NICE(httpd_conf:clean(Script)++" is an invalid Script")} + {error,?NICE(string:strip(Script)++" is an invalid Script")} end. store({action, {MimeType, CGIScript}} = Conf, _) when is_list(MimeType), diff --git a/lib/inets/src/http_server/mod_alias.erl b/lib/inets/src/http_server/mod_alias.erl index 5039cd56b5..e0d34288cd 100644 --- a/lib/inets/src/http_server/mod_alias.erl +++ b/lib/inets/src/http_server/mod_alias.erl @@ -212,7 +212,7 @@ load("Alias " ++ Alias, []) -> {ok, [FakeName, RealName]} -> {ok,[],{alias,{FakeName,RealName}}}; {ok, _} -> - {error,?NICE(httpd_conf:clean(Alias)++" is an invalid Alias")} + {error,?NICE(string:strip(Alias)++" is an invalid Alias")} end; load("ReWrite " ++ Rule, Acc) -> load_re_write(Rule, Acc, "ReWrite", re_write); @@ -223,7 +223,7 @@ load("ScriptAlias " ++ ScriptAlias, []) -> RealName1 = filename:join(filename:split(RealName)), {ok, [], {script_alias, {FakeName, RealName1++"/"}}}; {ok, _} -> - {error, ?NICE(httpd_conf:clean(ScriptAlias)++ + {error, ?NICE(string:strip(ScriptAlias)++ " is an invalid ScriptAlias")} end; load("ScriptReWrite " ++ Rule, Acc) -> @@ -234,7 +234,7 @@ load_re_write(Rule0, Acc, Type, Tag) -> fun ($\s) -> true; ($\t) -> true; (_) -> false end, Rule0) of "" -> - {error, ?NICE(httpd_conf:clean(Rule0)++" is an invalid "++Type)}; + {error, ?NICE(string:strip(Rule0)++" is an invalid "++Type)}; Rule -> case string:chr(Rule, $\s) of 0 -> diff --git a/lib/inets/src/http_server/mod_auth.erl b/lib/inets/src/http_server/mod_auth.erl index 1f4470622d..ae716a6cff 100644 --- a/lib/inets/src/http_server/mod_auth.erl +++ b/lib/inets/src/http_server/mod_auth.erl @@ -127,35 +127,35 @@ do(Info) -> %% state it was previously. load("<Directory " ++ Directory,[]) -> - Dir = httpd_conf:custom_clean(Directory,"",">"), + Dir = string:strip(string:strip(Directory),right, $>), {ok,[{directory, {Dir, [{path, Dir}]}}]}; load(eof,[{directory, {Directory, _DirData}}|_]) -> {error, ?NICE("Premature end-of-file in "++ Directory)}; load("AuthName " ++ AuthName, [{directory, {Directory, DirData}}|Rest]) -> {ok, [{directory, {Directory, - [{auth_name, httpd_conf:clean(AuthName)} | DirData]}} + [{auth_name, string:strip(AuthName)} | DirData]}} | Rest ]}; load("AuthUserFile " ++ AuthUserFile0, [{directory, {Directory, DirData}}|Rest]) -> - AuthUserFile = httpd_conf:clean(AuthUserFile0), + AuthUserFile = string:strip(AuthUserFile0), {ok, [{directory, {Directory, [{auth_user_file, AuthUserFile}|DirData]}} | Rest ]}; load("AuthGroupFile " ++ AuthGroupFile0, [{directory, {Directory, DirData}}|Rest]) -> - AuthGroupFile = httpd_conf:clean(AuthGroupFile0), + AuthGroupFile = string:strip(AuthGroupFile0), {ok,[{directory, {Directory, [{auth_group_file, AuthGroupFile}|DirData]}} | Rest]}; load("AuthAccessPassword " ++ AuthAccessPassword0, [{directory, {Directory, DirData}}|Rest]) -> - AuthAccessPassword = httpd_conf:clean(AuthAccessPassword0), + AuthAccessPassword = string:strip(AuthAccessPassword0), {ok,[{directory, {Directory, [{auth_access_password, AuthAccessPassword}|DirData]}} | Rest]}; load("AuthDBType " ++ Type, [{directory, {Dir, DirData}}|Rest]) -> - case httpd_conf:clean(Type) of + case string:strip(Type) of "plain" -> {ok, [{directory, {Dir, [{auth_type, plain}|DirData]}} | Rest ]}; "mnesia" -> @@ -163,7 +163,7 @@ load("AuthDBType " ++ Type, "dets" -> {ok, [{directory, {Dir, [{auth_type, dets}|DirData]}} | Rest ]}; _ -> - {error, ?NICE(httpd_conf:clean(Type)++" is an invalid AuthDBType")} + {error, ?NICE(string:strip(Type)++" is an invalid AuthDBType")} end; load("require " ++ Require,[{directory, {Directory, DirData}}|Rest]) -> @@ -175,7 +175,7 @@ load("require " ++ Require,[{directory, {Directory, DirData}}|Rest]) -> {ok,[{directory, {Directory, [{require_group,Groups}|DirData]}} | Rest]}; {ok,_} -> - {error,?NICE(httpd_conf:clean(Require) ++" is an invalid require")} + {error,?NICE(string:strip(Require) ++" is an invalid require")} end; load("allow " ++ Allow,[{directory, {Directory, DirData}}|Rest]) -> @@ -187,7 +187,7 @@ load("allow " ++ Allow,[{directory, {Directory, DirData}}|Rest]) -> {ok,[{directory, {Directory, [{allow_from,Hosts}|DirData]}} | Rest]}; {ok,_} -> - {error,?NICE(httpd_conf:clean(Allow) ++" is an invalid allow")} + {error,?NICE(string:strip(Allow) ++" is an invalid allow")} end; load("deny " ++ Deny,[{directory, {Directory, DirData}}|Rest]) -> @@ -199,7 +199,7 @@ load("deny " ++ Deny,[{directory, {Directory, DirData}}|Rest]) -> {ok,[{{directory, Directory, [{deny_from, Hosts}|DirData]}} | Rest]}; {ok, _} -> - {error,?NICE(httpd_conf:clean(Deny) ++" is an invalid deny")} + {error,?NICE(string:strip(Deny) ++" is an invalid deny")} end; load("</Directory>",[{directory, {Directory, DirData}}|Rest]) -> @@ -207,14 +207,14 @@ load("</Directory>",[{directory, {Directory, DirData}}|Rest]) -> load("AuthMnesiaDB " ++ AuthMnesiaDB, [{directory, {Dir, DirData}}|Rest]) -> - case httpd_conf:clean(AuthMnesiaDB) of + case string:strip(AuthMnesiaDB) of "On" -> {ok,[{directory, {Dir,[{auth_type,mnesia}|DirData]}}|Rest]}; "Off" -> {ok,[{directory, {Dir,[{auth_type,plain}|DirData]}}|Rest]}; _ -> - {error, ?NICE(httpd_conf:clean(AuthMnesiaDB) ++ - " is an invalid AuthMnesiaDB")} + {error, ?NICE(string:strip(AuthMnesiaDB) ++ + " is an invalid AuthMnesiaDB")} end. store({directory, {Directory, DirData}}, ConfigList) diff --git a/lib/inets/src/http_server/mod_auth_plain.erl b/lib/inets/src/http_server/mod_auth_plain.erl index 7bb86fc812..b411202efc 100644 --- a/lib/inets/src/http_server/mod_auth_plain.erl +++ b/lib/inets/src/http_server/mod_auth_plain.erl @@ -231,7 +231,7 @@ parse_group(Stream, GroupList) -> eof -> eof; String -> - httpd_conf:clean(String) + httpd_conf:white_space_clean(String) end, parse_group(Stream, GroupList, Line). @@ -265,7 +265,7 @@ parse_passwd(Stream, PasswdList) -> eof -> eof; String -> - httpd_conf:clean(String) + httpd_conf:white_space_clean(String) end, parse_passwd(Stream, PasswdList, Line). diff --git a/lib/inets/src/http_server/mod_cgi.erl b/lib/inets/src/http_server/mod_cgi.erl index d933b0a4ba..fbcfa78e5f 100644 --- a/lib/inets/src/http_server/mod_cgi.erl +++ b/lib/inets/src/http_server/mod_cgi.erl @@ -95,24 +95,24 @@ do(ModData) -> %% or cache %% load("ScriptNoCache " ++ CacheArg, [])-> - case catch list_to_atom(httpd_conf:clean(CacheArg)) of + case catch list_to_atom(string:strip(CacheArg)) of true -> {ok, [], {script_nocache, true}}; false -> {ok, [], {script_nocache, false}}; _ -> - {error, ?NICE(httpd_conf:clean(CacheArg)++ + {error, ?NICE(string:strip(CacheArg)++ " is an invalid ScriptNoCache directive")} end; %% ScriptTimeout Seconds, The number of seconds that the server %% maximum will wait for the script to %% generate a part of the document load("ScriptTimeout " ++ Timeout, [])-> - case catch list_to_integer(httpd_conf:clean(Timeout)) of + case catch list_to_integer(string:strip(Timeout)) of TimeoutSec when is_integer(TimeoutSec) -> {ok, [], {script_timeout,TimeoutSec*1000}}; _ -> - {error, ?NICE(httpd_conf:clean(Timeout)++ + {error, ?NICE(string:strip(Timeout)++ " is an invalid ScriptTimeout")} end. diff --git a/lib/inets/src/http_server/mod_disk_log.erl b/lib/inets/src/http_server/mod_disk_log.erl index 5a3766de66..89a3193aa3 100644 --- a/lib/inets/src/http_server/mod_disk_log.erl +++ b/lib/inets/src/http_server/mod_disk_log.erl @@ -139,70 +139,70 @@ do(Info) -> load("TransferDiskLogSize " ++ TransferDiskLogSize, []) -> case inets_regexp:split(TransferDiskLogSize," ") of {ok,[MaxBytes,MaxFiles]} -> - case httpd_conf:make_integer(MaxBytes) of + case make_integer(MaxBytes) of {ok,MaxBytesInteger} -> - case httpd_conf:make_integer(MaxFiles) of + case make_integer(MaxFiles) of {ok,MaxFilesInteger} -> {ok,[],{transfer_disk_log_size, {MaxBytesInteger,MaxFilesInteger}}}; {error,_} -> {error, - ?NICE(httpd_conf:clean(TransferDiskLogSize)++ + ?NICE(string:strip(TransferDiskLogSize)++ " is an invalid TransferDiskLogSize")} end; {error,_} -> - {error,?NICE(httpd_conf:clean(TransferDiskLogSize)++ + {error,?NICE(string:strip(TransferDiskLogSize)++ " is an invalid TransferDiskLogSize")} end end; load("TransferDiskLog " ++ TransferDiskLog,[]) -> - {ok,[],{transfer_disk_log,httpd_conf:clean(TransferDiskLog)}}; + {ok,[],{transfer_disk_log,string:strip(TransferDiskLog)}}; load("ErrorDiskLogSize " ++ ErrorDiskLogSize, []) -> case inets_regexp:split(ErrorDiskLogSize," ") of {ok,[MaxBytes,MaxFiles]} -> - case httpd_conf:make_integer(MaxBytes) of + case make_integer(MaxBytes) of {ok,MaxBytesInteger} -> - case httpd_conf:make_integer(MaxFiles) of + case make_integer(MaxFiles) of {ok,MaxFilesInteger} -> {ok,[],{error_disk_log_size, {MaxBytesInteger,MaxFilesInteger}}}; {error,_} -> - {error,?NICE(httpd_conf:clean(ErrorDiskLogSize)++ + {error,?NICE(string:strip(ErrorDiskLogSize)++ " is an invalid ErrorDiskLogSize")} end; {error,_} -> - {error,?NICE(httpd_conf:clean(ErrorDiskLogSize)++ + {error,?NICE(string:strip(ErrorDiskLogSize)++ " is an invalid ErrorDiskLogSize")} end end; load("ErrorDiskLog " ++ ErrorDiskLog, []) -> - {ok, [], {error_disk_log, httpd_conf:clean(ErrorDiskLog)}}; + {ok, [], {error_disk_log, string:strip(ErrorDiskLog)}}; load("SecurityDiskLogSize " ++ SecurityDiskLogSize, []) -> case inets_regexp:split(SecurityDiskLogSize, " ") of {ok, [MaxBytes, MaxFiles]} -> - case httpd_conf:make_integer(MaxBytes) of + case make_integer(MaxBytes) of {ok, MaxBytesInteger} -> - case httpd_conf:make_integer(MaxFiles) of + case make_integer(MaxFiles) of {ok, MaxFilesInteger} -> {ok, [], {security_disk_log_size, {MaxBytesInteger, MaxFilesInteger}}}; {error,_} -> {error, - ?NICE(httpd_conf:clean(SecurityDiskLogSize) ++ + ?NICE(string:strip(SecurityDiskLogSize) ++ " is an invalid SecurityDiskLogSize")} end; {error, _} -> - {error, ?NICE(httpd_conf:clean(SecurityDiskLogSize) ++ + {error, ?NICE(string:strip(SecurityDiskLogSize) ++ " is an invalid SecurityDiskLogSize")} end end; load("SecurityDiskLog " ++ SecurityDiskLog, []) -> - {ok, [], {security_disk_log, httpd_conf:clean(SecurityDiskLog)}}; + {ok, [], {security_disk_log, string:strip(SecurityDiskLog)}}; load("DiskLogFormat " ++ Format, []) -> - case httpd_conf:clean(Format) of + case string:strip(Format) of "internal" -> {ok, [], {disk_log_format,internal}}; "external" -> @@ -314,7 +314,7 @@ log_size(ConfigList, Tag) -> proplists:get_value(Tag, ConfigList, {500*1024,8}). create_disk_log(LogFile, SizeTag, ConfigList) -> - Filename = httpd_conf:clean(LogFile), + Filename = string:strip(LogFile), {MaxBytes, MaxFiles} = log_size(ConfigList, SizeTag), case filename:pathtype(Filename) of absolute -> @@ -413,3 +413,11 @@ log_internal_info(Info,Date,[{internal_info,Reason}|Rest]) -> log_internal_info(Info,Date,[_|Rest]) -> log_internal_info(Info,Date,Rest). +make_integer(List) -> + try list_to_integer(List) of + N -> + {ok, N} + catch + _:_ -> + {error, {badarg, list_to_integer, List}} + end. diff --git a/lib/inets/src/http_server/mod_esi.erl b/lib/inets/src/http_server/mod_esi.erl index b11df34f9e..d385afbd70 100644 --- a/lib/inets/src/http_server/mod_esi.erl +++ b/lib/inets/src/http_server/mod_esi.erl @@ -98,40 +98,40 @@ load("ErlScriptAlias " ++ ErlScriptAlias, []) -> case inets_regexp:split(ErlScriptAlias," ") of {ok, [ErlName | StrModules]} -> Modules = lists:map(fun(Str) -> - list_to_atom(httpd_conf:clean(Str)) + list_to_atom(string:strip(Str)) end, StrModules), {ok, [], {erl_script_alias, {ErlName, Modules}}}; {ok, _} -> - {error, ?NICE(httpd_conf:clean(ErlScriptAlias) ++ + {error, ?NICE(string:strip(ErlScriptAlias) ++ " is an invalid ErlScriptAlias")} end; load("EvalScriptAlias " ++ EvalScriptAlias, []) -> case inets_regexp:split(EvalScriptAlias, " ") of {ok, [EvalName | StrModules]} -> Modules = lists:map(fun(Str) -> - list_to_atom(httpd_conf:clean(Str)) + list_to_atom(string:strip(Str)) end, StrModules), {ok, [], {eval_script_alias, {EvalName, Modules}}}; {ok, _} -> - {error, ?NICE(httpd_conf:clean(EvalScriptAlias) ++ + {error, ?NICE(string:strip(EvalScriptAlias) ++ " is an invalid EvalScriptAlias")} end; load("ErlScriptTimeout " ++ Timeout, [])-> - case catch list_to_integer(httpd_conf:clean(Timeout)) of + case catch list_to_integer(string:strip(Timeout)) of TimeoutSec when is_integer(TimeoutSec) -> {ok, [], {erl_script_timeout, TimeoutSec * 1000}}; _ -> - {error, ?NICE(httpd_conf:clean(Timeout) ++ + {error, ?NICE(string:strip(Timeout) ++ " is an invalid ErlScriptTimeout")} end; load("ErlScriptNoCache " ++ CacheArg, [])-> - case catch list_to_atom(httpd_conf:clean(CacheArg)) of + case catch list_to_atom(string:strip(CacheArg)) of true -> {ok, [], {erl_script_nocache, true}}; false -> {ok, [], {erl_script_nocache, false}}; _ -> - {error, ?NICE(httpd_conf:clean(CacheArg)++ + {error, ?NICE(string:strip(CacheArg)++ " is an invalid ErlScriptNoCache directive")} end. diff --git a/lib/inets/src/http_server/mod_htaccess.erl b/lib/inets/src/http_server/mod_htaccess.erl index e1f66d01c8..d526fbe156 100644 --- a/lib/inets/src/http_server/mod_htaccess.erl +++ b/lib/inets/src/http_server/mod_htaccess.erl @@ -34,7 +34,7 @@ % Names on accessfiles %---------------------------------------------------------------------- load("AccessFileName" ++ FileNames, _Context)-> - CleanFileNames=httpd_conf:clean(FileNames), + CleanFileNames=string:strip(FileNames), {ok,[],{access_files,string:tokens(CleanFileNames," ")}}. store({access_files, Files} = Conf, _) when is_list(Files)-> diff --git a/lib/inets/src/http_server/mod_log.erl b/lib/inets/src/http_server/mod_log.erl index a912f5616c..2367d9cfeb 100644 --- a/lib/inets/src/http_server/mod_log.erl +++ b/lib/inets/src/http_server/mod_log.erl @@ -127,11 +127,11 @@ do(Info) -> %% Description: See httpd(3) ESWAPI CALLBACK FUNCTIONS %%------------------------------------------------------------------------- load("TransferLog " ++ TransferLog, []) -> - {ok,[],{transfer_log,httpd_conf:clean(TransferLog)}}; + {ok,[],{transfer_log,string:strip(TransferLog)}}; load("ErrorLog " ++ ErrorLog, []) -> - {ok,[],{error_log,httpd_conf:clean(ErrorLog)}}; + {ok,[],{error_log,string:strip(ErrorLog)}}; load("SecurityLog " ++ SecurityLog, []) -> - {ok, [], {security_log, httpd_conf:clean(SecurityLog)}}. + {ok, [], {security_log, string:strip(SecurityLog)}}. %%-------------------------------------------------------------------------- %% store(Directive, DirectiveList) -> {ok, NewDirective} | @@ -200,7 +200,7 @@ transfer_log(Info,RFC931,AuthUser,Date,StatusCode,Bytes) -> end. create_log(LogFile, ConfigList) -> - Filename = httpd_conf:clean(LogFile), + Filename = string:strip(LogFile), case filename:pathtype(Filename) of absolute -> case file:open(Filename, [read, write]) of diff --git a/lib/inets/src/http_server/mod_security.erl b/lib/inets/src/http_server/mod_security.erl index a85383a921..b09bd5e008 100644 --- a/lib/inets/src/http_server/mod_security.erl +++ b/lib/inets/src/http_server/mod_security.erl @@ -101,38 +101,38 @@ do(Info) -> end. load("<Directory " ++ Directory, []) -> - Dir = httpd_conf:custom_clean(Directory,"",">"), + Dir = string:strip(string:strip(Directory),right, $>), {ok, [{security_directory, {Dir, [{path, Dir}]}}]}; load(eof,[{security_directory, {Directory, _DirData}}|_]) -> {error, ?NICE("Premature end-of-file in "++Directory)}; load("SecurityDataFile " ++ FileName, [{security_directory, {Dir, DirData}}]) -> - File = httpd_conf:clean(FileName), + File = string:strip(FileName), {ok, [{security_directory, {Dir, [{data_file, File}|DirData]}}]}; load("SecurityCallbackModule " ++ ModuleName, [{security_directory, {Dir, DirData}}]) -> - Mod = list_to_atom(httpd_conf:clean(ModuleName)), + Mod = list_to_atom(string:strip(ModuleName)), {ok, [{security_directory, {Dir, [{callback_module, Mod}|DirData]}}]}; load("SecurityMaxRetries " ++ Retries, [{security_directory, {Dir, DirData}}]) -> load_return_int_tag("SecurityMaxRetries", max_retries, - httpd_conf:clean(Retries), Dir, DirData); + string:strip(Retries), Dir, DirData); load("SecurityBlockTime " ++ Time, [{security_directory, {Dir, DirData}}]) -> load_return_int_tag("SecurityBlockTime", block_time, - httpd_conf:clean(Time), Dir, DirData); + string:strip(Time), Dir, DirData); load("SecurityFailExpireTime " ++ Time, [{security_directory, {Dir, DirData}}]) -> load_return_int_tag("SecurityFailExpireTime", fail_expire_time, - httpd_conf:clean(Time), Dir, DirData); + string:strip(Time), Dir, DirData); load("SecurityAuthTimeout " ++ Time0, [{security_directory, {Dir, DirData}}]) -> - Time = httpd_conf:clean(Time0), + Time = string:strip(Time0), load_return_int_tag("SecurityAuthTimeout", auth_timeout, - httpd_conf:clean(Time), Dir, DirData); + string:strip(Time), Dir, DirData); load("AuthName " ++ Name0, [{security_directory, {Dir, DirData}}]) -> - Name = httpd_conf:clean(Name0), + Name = string:strip(Name0), {ok, [{security_directory, {Dir, [{auth_name, Name}|DirData]}}]}; load("</Directory>",[{security_directory, {Dir, DirData}}]) -> {ok, [], {security_directory, {Dir, DirData}}}. diff --git a/lib/kernel/doc/src/inet.xml b/lib/kernel/doc/src/inet.xml index 77a8caaaf6..4dd9e0e221 100644 --- a/lib/kernel/doc/src/inet.xml +++ b/lib/kernel/doc/src/inet.xml @@ -1037,6 +1037,36 @@ setcap cap_sys_admin,cap_sys_ptrace,cap_dac_read_search+epi beam.smp <marker id="option-sndbuf"></marker> </item> + <tag><c>{show_econnreset, Boolean}</c>(TCP/IP sockets)</tag> + <item> + <p>When this option is set to <c>false</c>, as it is by + default, an RST that is received from the TCP peer is treated + as a normal close (as though a FIN was sent). A caller + to <seealso marker="gen_tcp#recv/2">gen_tcp:recv/2</seealso> + will get <c>{error, closed}</c>. In active + mode the controlling process will receive a + <c>{tcp_close, Socket}</c> message, indicating that the + peer has closed the connection.</p> + <p>Setting this option to <c>true</c> will allow you to + distinguish between a connection that was closed normally, + and one which was aborted (intentionally or unintentionally) + by the TCP peer. A call to + <seealso marker="gen_tcp#recv/2">gen_tcp:recv/2</seealso> + will return <c>{error, econnreset}</c>. In + active mode, the controlling process will receive a + <c>{tcp_error, Socket, econnreset}</c> message + before the usual <c>{tcp_closed, Socket}</c>, as is + the case for any other socket error. Calls to + <seealso marker="gen_tcp#send/2">gen_tcp:send/2</seealso> + will also return <c>{error, econnreset}</c> when it + is detected that a TCP peer has sent an RST.</p> + <p>A connected socket returned from + <seealso marker="gen_tcp#accept/1">gen_tcp:accept/1</seealso> + will inherit the <c>show_econnreset</c> setting from the + listening socket.</p> + <marker id="option-show_econnreset"></marker> + </item> + <tag><c>{sndbuf, Size}</c></tag> <item> <p>The minimum size of the send buffer to use for the socket. diff --git a/lib/kernel/src/gen_tcp.erl b/lib/kernel/src/gen_tcp.erl index bc8ffbe5e3..86251fc8f1 100644 --- a/lib/kernel/src/gen_tcp.erl +++ b/lib/kernel/src/gen_tcp.erl @@ -58,6 +58,7 @@ {reuseaddr, boolean()} | {send_timeout, non_neg_integer() | infinity} | {send_timeout_close, boolean()} | + {show_econnreset, boolean()} | {sndbuf, non_neg_integer()} | {tos, non_neg_integer()} | {ipv6_v6only, boolean()}. @@ -89,6 +90,7 @@ reuseaddr | send_timeout | send_timeout_close | + show_econnreset | sndbuf | tos | ipv6_v6only. diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl index d668738109..1ae90aaf0c 100644 --- a/lib/kernel/src/inet.erl +++ b/lib/kernel/src/inet.erl @@ -654,7 +654,7 @@ options() -> multicast_if, multicast_ttl, multicast_loop, exit_on_close, high_watermark, low_watermark, high_msgq_watermark, low_msgq_watermark, - send_timeout, send_timeout_close + send_timeout, send_timeout_close, show_econnreset ]. %% Return a list of statistics options @@ -672,7 +672,8 @@ connect_options() -> [tos, priority, reuseaddr, keepalive, linger, sndbuf, recbuf, nodelay, header, active, packet, packet_size, buffer, mode, deliver, exit_on_close, high_watermark, low_watermark, high_msgq_watermark, - low_msgq_watermark, send_timeout, send_timeout_close, delay_send, raw]. + low_msgq_watermark, send_timeout, send_timeout_close, delay_send, raw, + show_econnreset]. connect_options(Opts, Family) -> BaseOpts = @@ -740,7 +741,7 @@ listen_options() -> header, active, packet, buffer, mode, deliver, backlog, ipv6_v6only, exit_on_close, high_watermark, low_watermark, high_msgq_watermark, low_msgq_watermark, send_timeout, send_timeout_close, delay_send, - packet_size, raw]. + packet_size, raw, show_econnreset]. listen_options(Opts, Family) -> BaseOpts = diff --git a/lib/kernel/src/inet_int.hrl b/lib/kernel/src/inet_int.hrl index 889b596a22..7bd973af99 100644 --- a/lib/kernel/src/inet_int.hrl +++ b/lib/kernel/src/inet_int.hrl @@ -147,6 +147,7 @@ -define(INET_LOPT_MSGQ_HIWTRMRK, 36). -define(INET_LOPT_MSGQ_LOWTRMRK, 37). -define(INET_LOPT_NETNS, 38). +-define(INET_LOPT_TCP_SHOW_ECONNRESET, 39). % Specific SCTP options: separate range: -define(SCTP_OPT_RTOINFO, 100). -define(SCTP_OPT_ASSOCINFO, 101). diff --git a/lib/kernel/src/rpc.erl b/lib/kernel/src/rpc.erl index 2300b7e901..d8250418f9 100644 --- a/lib/kernel/src/rpc.erl +++ b/lib/kernel/src/rpc.erl @@ -22,7 +22,7 @@ %% facility %% This code used to reside in net.erl, but has now been moved to -%% a searate module. +%% a separate module. -define(NAME, rex). diff --git a/lib/kernel/test/gen_tcp_misc_SUITE.erl b/lib/kernel/test/gen_tcp_misc_SUITE.erl index 76a9708a58..44e7984401 100644 --- a/lib/kernel/test/gen_tcp_misc_SUITE.erl +++ b/lib/kernel/test/gen_tcp_misc_SUITE.erl @@ -31,6 +31,11 @@ init_per_testcase/2, end_per_testcase/2, otp_3924/1, otp_3924_sender/4, closed_socket/1, shutdown_active/1, shutdown_passive/1, shutdown_pending/1, + show_econnreset_active/1, show_econnreset_active_once/1, + show_econnreset_passive/1, econnreset_after_sync_send/1, + econnreset_after_async_send_active/1, + econnreset_after_async_send_active_once/1, + econnreset_after_async_send_passive/1, linger_zero/1, default_options/1, http_bad_packet/1, busy_send/1, busy_disconnect_passive/1, busy_disconnect_active/1, fill_sendq/1, partial_recv_and_close/1, @@ -94,6 +99,11 @@ all() -> iter_max_socks, passive_sockets, active_n, accept_closed_by_other_process, otp_3924, closed_socket, shutdown_active, shutdown_passive, shutdown_pending, + show_econnreset_active, show_econnreset_active_once, + show_econnreset_passive, econnreset_after_sync_send, + econnreset_after_async_send_active, + econnreset_after_async_send_active_once, + econnreset_after_async_send_passive, linger_zero, default_options, http_bad_packet, busy_send, busy_disconnect_passive, busy_disconnect_active, fill_sendq, partial_recv_and_close, @@ -1079,6 +1089,311 @@ shutdown_pending(Config) when is_list(Config) -> gen_tcp:close(S) end. +%% +%% Test 'show_econnreset' option +%% + +show_econnreset_active(Config) when is_list(Config) -> + %% First confirm everything works with option turned off. + {ok, L} = gen_tcp:listen(0, []), + {ok, Port} = inet:port(L), + {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]), + {ok, S} = gen_tcp:accept(L), + ok = gen_tcp:close(L), + ok = inet:setopts(Client, [{linger, {true, 0}}]), + ok = gen_tcp:close(Client), + receive + {tcp_closed, S} -> + ok; + Other -> + ?t:fail({unexpected1, Other}) + after 1000 -> + ?t:fail({timeout, {server, no_tcp_closed}}) + end, + + %% Now test with option switched on. + %% Note: We are also testing that the show_econnreset option is + %% inherited from the listening socket by the accepting socket. + {ok, L1} = gen_tcp:listen(0, [{show_econnreset, true}]), + {ok, Port1} = inet:port(L1), + {ok, Client1} = gen_tcp:connect(localhost, Port1, [{active, false}]), + {ok, S1} = gen_tcp:accept(L1), + ok = gen_tcp:close(L1), + ok = inet:setopts(Client1, [{linger, {true, 0}}]), + ok = gen_tcp:close(Client1), + receive + {tcp_error, S1, econnreset} -> + receive + {tcp_closed, S1} -> + ok; + Other1 -> + ?t:fail({unexpected2, Other1}) + after 1 -> + ?t:fail({timeout, {server, no_tcp_closed}}) + end; + Other2 -> + ?t:fail({unexpected3, Other2}) + after 1000 -> + ?t:fail({timeout, {server, no_tcp_error}}) + end. + +show_econnreset_active_once(Config) when is_list(Config) -> + %% Now test using {active, once} + {ok, L} = gen_tcp:listen(0, + [{active, false}, + {show_econnreset, true}]), + {ok, Port} = inet:port(L), + {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]), + {ok, S} = gen_tcp:accept(L), + ok = gen_tcp:close(L), + ok = inet:setopts(Client, [{linger, {true, 0}}]), + ok = gen_tcp:close(Client), + ok = ?t:sleep(20), + ok = receive Msg -> {unexpected_msg, Msg} after 0 -> ok end, + ok = inet:setopts(S, [{active, once}]), + receive + {tcp_error, S, econnreset} -> + receive + {tcp_closed, S} -> + ok; + Other1 -> + ?t:fail({unexpected1, Other1}) + after 1 -> + ?t:fail({timeout, {server, no_tcp_closed}}) + end; + Other2 -> + ?t:fail({unexpected2, Other2}) + after 1000 -> + ?t:fail({timeout, {server, no_tcp_error}}) + end. + +show_econnreset_passive(Config) when is_list(Config) -> + %% First confirm everything works with option turned off. + {ok, L} = gen_tcp:listen(0, [{active, false}]), + {ok, Port} = inet:port(L), + {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]), + {ok, S} = gen_tcp:accept(L), + ok = gen_tcp:close(L), + ok = inet:setopts(S, [{linger, {true, 0}}]), + ok = gen_tcp:close(S), + ok = ?t:sleep(1), + {error, closed} = gen_tcp:recv(Client, 0), + + %% Now test with option switched on. + {ok, L1} = gen_tcp:listen(0, [{active, false}]), + {ok, Port1} = inet:port(L1), + {ok, Client1} = gen_tcp:connect(localhost, Port1, + [{active, false}, + {show_econnreset, true}]), + {ok, S1} = gen_tcp:accept(L1), + ok = gen_tcp:close(L1), + ok = inet:setopts(S1, [{linger, {true, 0}}]), + ok = gen_tcp:close(S1), + ok = ?t:sleep(1), + {error, econnreset} = gen_tcp:recv(Client1, 0). + +econnreset_after_sync_send(Config) when is_list(Config) -> + %% First confirm everything works with option turned off. + {ok, L} = gen_tcp:listen(0, [{active, false}]), + {ok, Port} = inet:port(L), + {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]), + {ok, S} = gen_tcp:accept(L), + ok = gen_tcp:close(L), + ok = inet:setopts(S, [{linger, {true, 0}}]), + ok = gen_tcp:close(S), + ok = ?t:sleep(20), + {error, closed} = gen_tcp:send(Client, "Whatever"), + + %% Now test with option switched on. + {ok, L1} = gen_tcp:listen(0, [{active, false}]), + {ok, Port1} = inet:port(L1), + {ok, Client1} = gen_tcp:connect(localhost, Port1, + [{active, false}, + {show_econnreset, true}]), + {ok, S1} = gen_tcp:accept(L1), + ok = gen_tcp:close(L1), + ok = inet:setopts(S1, [{linger, {true, 0}}]), + ok = gen_tcp:close(S1), + ok = ?t:sleep(20), + {error, econnreset} = gen_tcp:send(Client1, "Whatever"). + +econnreset_after_async_send_active(Config) when is_list(Config) -> + {OS, _} = os:type(), + Payload = lists:duplicate(1024 * 1024, $.), + + %% First confirm everything works with option turned off. + {ok, L} = gen_tcp:listen(0, [{active, false}, {recbuf, 4096}]), + {ok, Port} = inet:port(L), + {ok, Client} = gen_tcp:connect(localhost, Port, [{sndbuf, 4096}]), + {ok, S} = gen_tcp:accept(L), + ok = gen_tcp:close(L), + ok = gen_tcp:send(Client, Payload), + case erlang:port_info(Client, queue_size) of + {queue_size, N} when N > 0 -> ok; + {queue_size, 0} when OS =:= win32 -> ok; + {queue_size, 0} = T -> ?t:fail(T) + end, + ok = gen_tcp:send(S, "Whatever"), + ok = ?t:sleep(20), + ok = inet:setopts(S, [{linger, {true, 0}}]), + ok = gen_tcp:close(S), + ok = ?t:sleep(20), + receive + {tcp, Client, "Whatever"} -> + receive + {tcp_closed, Client} -> + ok; + Other1 -> + ?t:fail({unexpected1, Other1}) + end; + Other2 -> + ?t:fail({unexpected2, Other2}) + end, + + %% Now test with option switched on. + {ok, L1} = gen_tcp:listen(0, [{active, false}, {recbuf, 4096}]), + {ok, Port1} = inet:port(L1), + {ok, Client1} = gen_tcp:connect(localhost, Port1, + [{sndbuf, 4096}, + {show_econnreset, true}]), + {ok, S1} = gen_tcp:accept(L1), + ok = gen_tcp:close(L1), + ok = gen_tcp:send(Client1, Payload), + case erlang:port_info(Client1, queue_size) of + {queue_size, N1} when N1 > 0 -> ok; + {queue_size, 0} when OS =:= win32 -> ok; + {queue_size, 0} = T1 -> ?t:fail(T1) + end, + ok = gen_tcp:send(S1, "Whatever"), + ok = ?t:sleep(20), + ok = inet:setopts(S1, [{linger, {true, 0}}]), + ok = gen_tcp:close(S1), + ok = ?t:sleep(20), + receive + {tcp, Client1, "Whatever"} -> + receive + {tcp_error, Client1, econnreset} -> + receive + {tcp_closed, Client1} -> + ok; + Other3 -> + ?t:fail({unexpected3, Other3}) + end; + Other4 -> + ?t:fail({unexpected4, Other4}) + end; + Other5 -> + ?t:fail({unexpected5, Other5}) + end. + +econnreset_after_async_send_active_once(Config) when is_list(Config) -> + {OS, _} = os:type(), + {ok, L} = gen_tcp:listen(0, [{active, false}, {recbuf, 4096}]), + {ok, Port} = inet:port(L), + {ok, Client} = gen_tcp:connect(localhost, Port, + [{active, false}, + {sndbuf, 4096}, + {show_econnreset, true}]), + {ok,S} = gen_tcp:accept(L), + ok = gen_tcp:close(L), + Payload = lists:duplicate(1024 * 1024, $.), + ok = gen_tcp:send(Client, Payload), + case erlang:port_info(Client, queue_size) of + {queue_size, N} when N > 0 -> ok; + {queue_size, 0} when OS =:= win32 -> ok; + {queue_size, 0} = T -> ?t:fail(T) + end, + ok = gen_tcp:send(S, "Whatever"), + ok = ?t:sleep(20), + ok = inet:setopts(S, [{linger, {true, 0}}]), + ok = gen_tcp:close(S), + ok = ?t:sleep(20), + ok = receive Msg -> {unexpected_msg, Msg} after 0 -> ok end, + ok = inet:setopts(Client, [{active, once}]), + receive + {tcp_error, Client, econnreset} -> + receive + {tcp_closed, Client} -> + ok; + Other -> + ?t:fail({unexpected1, Other}) + end; + Other -> + ?t:fail({unexpected2, Other}) + end. + +econnreset_after_async_send_passive(Config) when is_list(Config) -> + {OS, _} = os:type(), + Payload = lists:duplicate(1024 * 1024, $.), + + %% First confirm everything works with option turned off. + {ok, L} = gen_tcp:listen(0, [{active, false}, {recbuf, 4096}]), + {ok, Port} = inet:port(L), + {ok, Client} = gen_tcp:connect(localhost, Port, + [{active, false}, + {sndbuf, 4096}]), + {ok, S} = gen_tcp:accept(L), + ok = gen_tcp:close(L), + ok = inet:setopts(S, [{linger, {true, 0}}]), + ok = gen_tcp:send(S, "Whatever"), + ok = gen_tcp:send(Client, Payload), + case erlang:port_info(Client, queue_size) of + {queue_size, N} when N > 0 -> ok; + {queue_size, 0} when OS =:= win32 -> ok; + {queue_size, 0} = T -> ?t:fail(T) + end, + ok = gen_tcp:close(S), + ok = ?t:sleep(20), + {error, closed} = gen_tcp:recv(Client, 0), + + %% Now test with option switched on. + {ok, L1} = gen_tcp:listen(0, [{active, false}, {recbuf, 4096}]), + {ok, Port1} = inet:port(L1), + {ok, Client1} = gen_tcp:connect(localhost, Port1, + [{active, false}, + {sndbuf, 4096}, + {show_econnreset, true}]), + {ok, S1} = gen_tcp:accept(L1), + ok = gen_tcp:close(L1), + ok = inet:setopts(S1, [{linger, {true, 0}}]), + ok = gen_tcp:send(S1, "Whatever"), + ok = gen_tcp:send(Client1, Payload), + ok = gen_tcp:close(S1), + ok = ?t:sleep(20), + {error, econnreset} = gen_tcp:recv(Client1, 0). + +%% +%% Test {linger {true, 0}} aborts a connection +%% + +linger_zero(Config) when is_list(Config) -> + %% All the econnreset tests will prove that {linger, {true, 0}} aborts + %% a connection when the driver queue is empty. We will test here + %% that it also works when the driver queue is not empty. + {OS, _} = os:type(), + {ok, L} = gen_tcp:listen(0, [{active, false}, + {recbuf, 4096}, + {show_econnreset, true}]), + {ok, Port} = inet:port(L), + {ok, Client} = gen_tcp:connect(localhost, Port, + [{active, false}, + {sndbuf, 4096}]), + {ok, S} = gen_tcp:accept(L), + ok = gen_tcp:close(L), + PayloadSize = 1024 * 1024, + Payload = lists:duplicate(PayloadSize, $.), + ok = gen_tcp:send(Client, Payload), + case erlang:port_info(Client, queue_size) of + {queue_size, N} when N > 0 -> ok; + {queue_size, 0} when OS =:= win32 -> ok; + {queue_size, 0} = T -> ?t:fail(T) + end, + ok = inet:setopts(Client, [{linger, {true, 0}}]), + ok = gen_tcp:close(Client), + ok = ?t:sleep(1), + undefined = erlang:port_info(Client, connected), + {error, econnreset} = gen_tcp:recv(S, PayloadSize). + %% Thanks to Luke Gorrie. Tests for a very specific problem with %% corrupt data. The testcase will be killed by the timetrap timeout diff --git a/lib/runtime_tools/doc/src/dbg.xml b/lib/runtime_tools/doc/src/dbg.xml index d31ccd834d..94556b7b51 100644 --- a/lib/runtime_tools/doc/src/dbg.xml +++ b/lib/runtime_tools/doc/src/dbg.xml @@ -167,7 +167,8 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ <item>If the <c>Item</c> is a <c>pid()</c>, the corresponding process is traced. The process may be a remote process (on another Erlang node). The node must be in the list of - traced nodes (<seealso marker="#n">see</seealso><c>n/1</c> and <c>tracer/0/2/3</c>).</item> + traced nodes (see <seealso marker="#n"><c>n/1</c></seealso> and + <c>tracer/0/2/3</c>).</item> <item>If the <c>Item</c> is the atom <c>all</c>, all processes in the system as well as all processes created hereafter are to be traced. This also affects all nodes added with the @@ -648,7 +649,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ <p>If <c>Nodename</c> is the local node, the error reason <c>cant_add_local_node</c> is returned. </p> - <p>If a trace port (<seealso marker="#trace_port">see</seealso><c>trace_port/2</c>) is + <p>If a trace port (see <seealso marker="#trace_port"><c>trace_port/2</c></seealso>) is running on the local node, remote nodes can not be traced with a tracer process. The error reason <c>cant_trace_remote_pid_to_local_port</c> is returned. A @@ -761,7 +762,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ type which is independent of the tracer on the trace control node.</p> </note> - <p>For details, <seealso marker="#tracer2">see</seealso><c>tracer/2</c>.</p> + <p>For details, see <seealso marker="#tracer2"><c>tracer/2</c></seealso>.</p> </desc> </func> <func> diff --git a/lib/ssh/doc/src/ssh_connection.xml b/lib/ssh/doc/src/ssh_connection.xml index 5422633dc3..e2b4e2ceb7 100644 --- a/lib/ssh/doc/src/ssh_connection.xml +++ b/lib/ssh/doc/src/ssh_connection.xml @@ -85,7 +85,7 @@ <tag><em>data_events()</em></tag> <item> <taglist> - <tag><c><![CDATA[{data, ssh_channel_id(), ssh_data_type_code(), binary() = Data}]]></c></tag> + <tag><c><![CDATA[{data, ssh_channel_id(), ssh_data_type_code(), Data :: binary()}]]></c></tag> <item><p>Data has arrived on the channel. This event is sent as a result of calling <seealso marker="ssh_connection#send-3"> ssh_connection:send/[3,4,5]</seealso>.</p></item> @@ -110,15 +110,15 @@ referred to are on OS-level and not something generated by an Erlang program.</p></item> - <tag><c><![CDATA[{exit_signal, ssh_channel_id(), string() = ExitSignal, string() = ErrorMsg, - string() = LanguageString}]]></c></tag> + <tag><c><![CDATA[{exit_signal, ssh_channel_id(), ExitSignal :: string(), ErrorMsg ::string(), + LanguageString :: string()}]]></c></tag> <item><p>A remote execution can terminate violently because of a signal. Then this message can be received. For details on valid string values, see <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url> Section 6.10, which shows a special case of these signals.</p></item> - <tag><c><![CDATA[{exit_status, ssh_channel_id(), integer() = ExitStatus}]]></c></tag> + <tag><c><![CDATA[{exit_status, ssh_channel_id(), ExitStatus :: integer()}]]></c></tag> <item><p>When the command running at the other end terminates, the following message can be sent to return the exit status of the command. A zero <c>exit_status</c> usually means that the command @@ -148,18 +148,18 @@ with the boolean value of <c>WantReply</c> as the second argument.</p> <taglist> - <tag><c><![CDATA[{env, ssh_channel_id(), boolean() = WantReply, - string() = Var, string() = Value}]]></c></tag> + <tag><c><![CDATA[{env, ssh_channel_id(), WantReply :: boolean(), + Var ::string(), Value :: string()}]]></c></tag> <item><p>Environment variables can be passed to the shell/command to be started later. This event is sent as a result of calling <seealso marker="ssh_connection#setenv-5"> ssh_connection:setenv/5</seealso>. </p></item> <tag><c><![CDATA[{pty, ssh_channel_id(), - boolean() = WantReply, {string() = Terminal, integer() = CharWidth, - integer() = RowHeight, integer() = PixelWidth, integer() = PixelHeight, - [{atom() | integer() = Opcode, - integer() = Value}] = TerminalModes}}]]></c></tag> + WantReply :: boolean(), {Terminal :: string(), CharWidth :: integer(), + RowHeight :: integer(), PixelWidth :: integer(), PixelHeight :: integer(), + TerminalModes :: [{Opcode :: atom() | integer(), + Value :: integer()}]}}]]></c></tag> <item><p>A pseudo-terminal has been requested for the session. <c>Terminal</c> is the value of the TERM environment variable value, that is, <c>vt100</c>. Zero dimension parameters must @@ -174,20 +174,20 @@ echo</c>. This event is sent as a result of calling <seealso marker="ssh_connection#ptty_alloc/4">ssh_connection:ptty_alloc/4</seealso>.</p></item> - <tag><c><![CDATA[{shell, boolean() = WantReply}]]></c></tag> + <tag><c><![CDATA[{shell, WantReply :: boolean()}]]></c></tag> <item><p>This message requests that the user default shell is started at the other end. This event is sent as a result of calling <seealso marker="ssh_connection#shell-2"> ssh_connection:shell/2</seealso>. </p></item> - <tag><c><![CDATA[{window_change, ssh_channel_id(), integer() = CharWidth, - integer() = RowHeight, integer() = PixWidth, integer() = PixHeight}]]></c></tag> + <tag><c><![CDATA[{window_change, ssh_channel_id(), CharWidth() :: integer(), + RowHeight :: integer(), PixWidth :: integer(), PixHeight :: integer()}]]></c></tag> <item><p>When the window (terminal) size changes on the client side, it <em>can</em> send a message to the server side to inform it of the new dimensions. No API function generates this event.</p></item> <tag><c><![CDATA[{exec, ssh_channel_id(), - boolean() = WantReply, string() = Cmd}]]></c></tag> + WantReply :: boolean(), Cmd :: string()}]]></c></tag> <item><p>This message requests that the server starts execution of the given command. This event is sent as a result of calling <seealso marker="ssh_connection#exec-4">ssh_connection:exec/4 </seealso>. @@ -256,7 +256,7 @@ <taglist> <tag><c>N x {ssh_cm, ssh_connection_ref(), - {data, ssh_channel_id(), ssh_data_type_code(), binary() = Data}}</c></tag> + {data, ssh_channel_id(), ssh_data_type_code(), Data :: binary()}}</c></tag> <item><p>The result of executing the command can be only one line or thousands of lines depending on the command.</p></item> @@ -265,12 +265,12 @@ <tag><c>0 or 1 x {ssh_cm, ssh_connection_ref(), {exit_signal, - ssh_channel_id(), string() = ExitSignal, string() = ErrorMsg, string() = LanguageString}}</c></tag> + ssh_channel_id(), ExitSignal :: string(), ErrorMsg :: string(), LanguageString :: string()}}</c></tag> <item><p>Not all systems send signals. For details on valid string values, see RFC 4254, Section 6.10</p></item> <tag><c>0 or 1 x {ssh_cm, ssh_connection_ref(), {exit_status, - ssh_channel_id(), integer() = ExitStatus}}</c></tag> + ssh_channel_id(), ExitStatus :: integer()}}</c></tag> <item><p>It is recommended by the SSH Connection Protocol to send this message, but that is not always the case.</p></item> diff --git a/lib/ssl/test/ssl_ECC_SUITE.erl b/lib/ssl/test/ssl_ECC_SUITE.erl index 3566a8a0a5..399ca7d0ee 100644 --- a/lib/ssl/test/ssl_ECC_SUITE.erl +++ b/lib/ssl/test/ssl_ECC_SUITE.erl @@ -31,8 +31,6 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [ {group, 'tlsv1.2'}, diff --git a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl b/lib/ssl/test/ssl_alpn_handshake_SUITE.erl index ae76f5849e..ee79a9d641 100644 --- a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_alpn_handshake_SUITE.erl @@ -30,8 +30,6 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [{group, 'tlsv1.2'}, {group, 'tlsv1.1'}, diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index e131c363d1..44a5f192e1 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -45,9 +45,6 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- - -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [ {group, basic}, diff --git a/lib/ssl/test/ssl_certificate_verify_SUITE.erl b/lib/ssl/test/ssl_certificate_verify_SUITE.erl index dab7a941db..9ace41acf8 100644 --- a/lib/ssl/test/ssl_certificate_verify_SUITE.erl +++ b/lib/ssl/test/ssl_certificate_verify_SUITE.erl @@ -37,9 +37,6 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- - -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [{group, active}, {group, passive}, diff --git a/lib/ssl/test/ssl_cipher_SUITE.erl b/lib/ssl/test/ssl_cipher_SUITE.erl index 3433f9a445..4cf80e1f43 100644 --- a/lib/ssl/test/ssl_cipher_SUITE.erl +++ b/lib/ssl/test/ssl_cipher_SUITE.erl @@ -34,9 +34,6 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- - -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [aes_decipher_good, aes_decipher_fail, padding_test]. diff --git a/lib/ssl/test/ssl_crl_SUITE.erl b/lib/ssl/test/ssl_crl_SUITE.erl index c6bf8898ad..79e1910b18 100644 --- a/lib/ssl/test/ssl_crl_SUITE.erl +++ b/lib/ssl/test/ssl_crl_SUITE.erl @@ -31,10 +31,6 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- - -suite() -> - [{ct_hooks,[ts_install_cth]}]. - all() -> [ {group, check_true}, diff --git a/lib/ssl/test/ssl_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl index 1a1b2af8d4..5a6556ffb8 100644 --- a/lib/ssl/test/ssl_dist_SUITE.erl +++ b/lib/ssl/test/ssl_dist_SUITE.erl @@ -38,10 +38,6 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- - -suite() -> - [{ct_hooks,[ts_install_cth]}]. - all() -> [basic, payload, plain_options, plain_verify_options]. diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl index d4433393a1..7ee5f50b58 100644 --- a/lib/ssl/test/ssl_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_handshake_SUITE.erl @@ -31,8 +31,6 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [decode_hello_handshake, decode_single_hello_extension_correctly, decode_supported_elliptic_curves_hello_extension_correctly, diff --git a/lib/ssl/test/ssl_npn_handshake_SUITE.erl b/lib/ssl/test/ssl_npn_handshake_SUITE.erl index 8e95679306..fa55898835 100644 --- a/lib/ssl/test/ssl_npn_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_npn_handshake_SUITE.erl @@ -29,8 +29,6 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [{group, 'tlsv1.2'}, {group, 'tlsv1.1'}, diff --git a/lib/ssl/test/ssl_npn_hello_SUITE.erl b/lib/ssl/test/ssl_npn_hello_SUITE.erl index 68ff9172e9..288bca621e 100644 --- a/lib/ssl/test/ssl_npn_hello_SUITE.erl +++ b/lib/ssl/test/ssl_npn_hello_SUITE.erl @@ -32,9 +32,6 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- - -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [encode_and_decode_npn_client_hello_test, encode_and_decode_npn_server_hello_test, diff --git a/lib/ssl/test/ssl_packet_SUITE.erl b/lib/ssl/test/ssl_packet_SUITE.erl index d50498f547..12a9cad127 100644 --- a/lib/ssl/test/ssl_packet_SUITE.erl +++ b/lib/ssl/test/ssl_packet_SUITE.erl @@ -44,9 +44,6 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- - -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [ {group, 'tlsv1.2'}, diff --git a/lib/ssl/test/ssl_payload_SUITE.erl b/lib/ssl/test/ssl_payload_SUITE.erl index f95eae51b7..aa0cc70315 100644 --- a/lib/ssl/test/ssl_payload_SUITE.erl +++ b/lib/ssl/test/ssl_payload_SUITE.erl @@ -29,8 +29,6 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [ {group, 'tlsv1.2'}, diff --git a/lib/ssl/test/ssl_session_cache_SUITE.erl b/lib/ssl/test/ssl_session_cache_SUITE.erl index 36d086338e..3501d7e120 100644 --- a/lib/ssl/test/ssl_session_cache_SUITE.erl +++ b/lib/ssl/test/ssl_session_cache_SUITE.erl @@ -41,8 +41,6 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [session_cleanup, session_cache_process_list, diff --git a/lib/ssl/test/ssl_sni_SUITE.erl b/lib/ssl/test/ssl_sni_SUITE.erl index b059ff991b..1785dc4b86 100644 --- a/lib/ssl/test/ssl_sni_SUITE.erl +++ b/lib/ssl/test/ssl_sni_SUITE.erl @@ -28,7 +28,6 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [no_sni_header, sni_match, diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl index 21ce4c4a29..463ab8088c 100644 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl @@ -37,8 +37,6 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [ {group, basic}, diff --git a/lib/stdlib/doc/src/unicode_usage.xml b/lib/stdlib/doc/src/unicode_usage.xml index 29b8940c62..eb3a556e10 100644 --- a/lib/stdlib/doc/src/unicode_usage.xml +++ b/lib/stdlib/doc/src/unicode_usage.xml @@ -850,8 +850,9 @@ Eshell V5.10.1 (abort with ^G) expected to be in Unicode.</p> <p>If Unicode file names are enabled, the calls to <seealso marker="kernel:os#getenv/0"><c>os:getenv/0</c></seealso>, - <seealso marker="kernel:os#getenv/1"><c>os:getenv/1</c></seealso> and - <seealso marker="kernel:os#putenv/2"><c>os:putenv/2</c></seealso> + <seealso marker="kernel:os#getenv/1"><c>os:getenv/1</c></seealso>, + <seealso marker="kernel:os#putenv/2"><c>os:putenv/2</c></seealso> and + <seealso marker="kernel:os#unsetenv/1"><c>os:unsetenv/1</c></seealso> will handle Unicode strings. On Unix-like platforms, the built-in functions will translate environment variables in UTF-8 to/from Unicode strings, possibly with code points > 255. On Windows the diff --git a/lib/stdlib/src/otp_internal.erl b/lib/stdlib/src/otp_internal.erl index 0340015c35..62807e2e54 100644 --- a/lib/stdlib/src/otp_internal.erl +++ b/lib/stdlib/src/otp_internal.erl @@ -634,6 +634,19 @@ obsolete_1(ssl, negotiated_next_protocol, 1) -> obsolete_1(ssl, connection_info, 1) -> {deprecated, "deprecated; use connection_information/[1,2] instead"}; +obsolete_1(httpd_conf, check_enum, 2) -> + {deprecated, "deprecated; use lists:member/2 instead"}; +obsolete_1(httpd_conf, clean, 1) -> + {deprecated, "deprecated; use sting:strip/1 instead or possible the re module"}; +obsolete_1(httpd_conf, custom_clean, 3) -> + {deprecated, "deprecated; use sting:strip/3 instead or possible the re module"}; +obsolete_1(httpd_conf, is_directory, 1) -> + {deprecated, "deprecated; use filelib:is_dir/1 instead"}; +obsolete_1(httpd_conf, is_file, 1) -> + {deprecated, "deprecated; use filelib:is_file/1 instead"}; +obsolete_1(httpd_conf, make_integer, 1) -> + {deprecated, "deprecated; use erlang:list_to_integer/1 instead"}; + obsolete_1(_, _, _) -> no. diff --git a/lib/stdlib/src/supervisor.erl b/lib/stdlib/src/supervisor.erl index 1d7396adee..ac51bb3b79 100644 --- a/lib/stdlib/src/supervisor.erl +++ b/lib/stdlib/src/supervisor.erl @@ -393,6 +393,15 @@ handle_call({start_child, EArgs}, _From, State) when ?is_simple(State) -> {reply, What, State} end; +handle_call({start_child, ChildSpec}, _From, State) -> + case check_childspec(ChildSpec) of + {ok, Child} -> + {Resp, NState} = handle_start_child(Child, State), + {reply, Resp, NState}; + What -> + {reply, {error, What}, State} + end; + %% terminate_child for simple_one_for_one can only be done with pid handle_call({terminate_child, Name}, _From, State) when not is_pid(Name), ?is_simple(State) -> @@ -411,20 +420,10 @@ handle_call({terminate_child, Name}, _From, State) -> {reply, {error, not_found}, State} end; -%%% The requests delete_child and restart_child are invalid for -%%% simple_one_for_one supervisors. -handle_call({_Req, _Data}, _From, State) when ?is_simple(State) -> +%% restart_child request is invalid for simple_one_for_one supervisors +handle_call({restart_child, _Name}, _From, State) when ?is_simple(State) -> {reply, {error, simple_one_for_one}, State}; -handle_call({start_child, ChildSpec}, _From, State) -> - case check_childspec(ChildSpec) of - {ok, Child} -> - {Resp, NState} = handle_start_child(Child, State), - {reply, Resp, NState}; - What -> - {reply, {error, What}, State} - end; - handle_call({restart_child, Name}, _From, State) -> case get_child(Name, State) of {value, Child} when Child#child.pid =:= undefined -> @@ -446,6 +445,10 @@ handle_call({restart_child, Name}, _From, State) -> {reply, {error, not_found}, State} end; +%% delete_child request is invalid for simple_one_for_one supervisors +handle_call({delete_child, _Name}, _From, State) when ?is_simple(State) -> + {reply, {error, simple_one_for_one}, State}; + handle_call({delete_child, Name}, _From, State) -> case get_child(Name, State) of {value, Child} when Child#child.pid =:= undefined -> diff --git a/lib/stdlib/test/supervisor_SUITE.erl b/lib/stdlib/test/supervisor_SUITE.erl index 015b09f35e..31d4b44f30 100644 --- a/lib/stdlib/test/supervisor_SUITE.erl +++ b/lib/stdlib/test/supervisor_SUITE.erl @@ -39,7 +39,8 @@ sup_start_ignore_temporary_child_start_child_simple/1, sup_start_ignore_permanent_child_start_child_simple/1, sup_start_error_return/1, sup_start_fail/1, - sup_start_map/1, sup_start_map_faulty_specs/1, + sup_start_map/1, sup_start_map_simple/1, + sup_start_map_faulty_specs/1, sup_stop_infinity/1, sup_stop_timeout/1, sup_stop_brutal_kill/1, child_adm/1, child_adm_simple/1, child_specs/1, extra_return/1, sup_flags/1]). @@ -103,7 +104,7 @@ groups() -> sup_start_ignore_permanent_child_start_child_simple, sup_start_error_return, sup_start_fail]}, {sup_start_map, [], - [sup_start_map, sup_start_map_faulty_specs]}, + [sup_start_map, sup_start_map_simple, sup_start_map_faulty_specs]}, {sup_stop, [], [sup_stop_infinity, sup_stop_timeout, sup_stop_brutal_kill]}, @@ -323,6 +324,30 @@ sup_start_map(Config) when is_list(Config) -> terminate(Pid, shutdown). %%------------------------------------------------------------------------- +%% Tests that the supervisor process starts correctly with map +%% startspec, and that the full childspec can be read when using +%% simple_one_for_one strategy. +sup_start_map_simple(Config) when is_list(Config) -> + process_flag(trap_exit, true), + SupFlags = #{strategy=>simple_one_for_one}, + ChildSpec = #{id=>undefined, + start=>{supervisor_1, start_child, []}, + restart=>temporary}, + {ok, Pid} = start_link({ok, {SupFlags, [ChildSpec]}}), + + {ok, Child1} = supervisor:start_child(Pid, []), + {ok, Child2} = supervisor:start_child(Pid, []), + {ok, Child3} = supervisor:start_child(Pid, []), + + Spec = ChildSpec#{type=>worker, shutdown=>5000, modules=>[supervisor_1]}, + + {ok, Spec} = supervisor:get_childspec(Pid, Child1), + {ok, Spec} = supervisor:get_childspec(Pid, Child2), + {ok, Spec} = supervisor:get_childspec(Pid, Child3), + {error,not_found} = supervisor:get_childspec(Pid, self()), + terminate(Pid, shutdown). + +%%------------------------------------------------------------------------- %% Tests that the supervisor produces good error messages when start- %% and child specs are faulty. sup_start_map_faulty_specs(Config) when is_list(Config) -> diff --git a/system/doc/programming_examples/funs.xmlsrc b/system/doc/programming_examples/funs.xmlsrc index d4c32bc854..80877f0164 100644 --- a/system/doc/programming_examples/funs.xmlsrc +++ b/system/doc/programming_examples/funs.xmlsrc @@ -111,7 +111,9 @@ foreach(fun(Pid) -> Pid ! M end, L)</code> <section> <title>Syntax of Funs</title> - <p>Funs are written with the following syntax:</p> + <p>Funs are written with the following syntax (see <seealso + marker="doc/reference_manual:expressions#funs">Fun Expressions + </seealso> for full description):</p> <code type="none"> F = fun (Arg1, Arg2, ... ArgN) -> ... |