diff options
54 files changed, 1249 insertions, 589 deletions
diff --git a/erts/doc/src/erl.xml b/erts/doc/src/erl.xml index 622b40a80a..1f7c5b5a7f 100644 --- a/erts/doc/src/erl.xml +++ b/erts/doc/src/erl.xml @@ -1009,6 +1009,20 @@ documentation of the <seealso marker="#+sbt">+sbt</seealso> flag. </p> </item> + <tag><marker id="+swct"><c>+sws very_eager|eager|medium|lazy|very_lazy</c></marker></tag> + <item> + <p> + Set scheduler wake cleanup threshold. Default is <c>medium</c>. + This flag controls how eager schedulers should be requesting + wake up due to certain cleanup operations. When a lazy setting + is used, more outstanding cleanup operations can be left undone + while a scheduler is idling. When an eager setting is used, + schedulers will more frequently be woken, potentially increasing + CPU-utilization. + </p> + <p><em>NOTE:</em> This flag may be removed or changed at any time without prior notice. + </p> + </item> <tag><marker id="+sws"><c>+sws default|legacy</c></marker></tag> <item> <p> diff --git a/erts/doc/src/erlang.xml b/erts/doc/src/erlang.xml index 06fefa8efb..7dc59ea954 100644 --- a/erts/doc/src/erlang.xml +++ b/erts/doc/src/erlang.xml @@ -4477,6 +4477,7 @@ true</pre> </func> <func> <name name="spawn_opt" arity="2"/> + <type name="priority_level" /> <fsummary>Create a new process with a fun as entry point</fsummary> <desc> <p>Returns the pid of a new process started by the application @@ -4490,6 +4491,7 @@ true</pre> </func> <func> <name name="spawn_opt" arity="3"/> + <type name="priority_level" /> <fsummary>Create a new process with a fun as entry point on a given node</fsummary> <desc> <p>Returns the pid of a new process started by the application @@ -4501,6 +4503,7 @@ true</pre> </func> <func> <name name="spawn_opt" arity="4"/> + <type name="priority_level" /> <fsummary>Create a new process with a function as entry point</fsummary> <desc> <p>Works exactly like @@ -4602,6 +4605,7 @@ true</pre> </func> <func> <name name="spawn_opt" arity="5"/> + <type name="priority_level" /> <fsummary>Create a new process with a function as entry point on a given node</fsummary> <desc> <p>Returns the pid of a new process started by the application diff --git a/erts/doc/src/notes.xml b/erts/doc/src/notes.xml index 7fc61517a8..c9c46afa4b 100644 --- a/erts/doc/src/notes.xml +++ b/erts/doc/src/notes.xml @@ -30,6 +30,61 @@ </header> <p>This document describes the changes made to the ERTS application.</p> +<section><title>Erts 5.10.1.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + The BIF <seealso + marker="erlang#is_process_alive/1"><c>is_process_alive/1</c></seealso> + could prematurely return <c>false</c> while the process + being inspected was terminating. This bug was introduced + in ERTS-5.10.</p> + <p> + Own Id: OTP-10926</p> + </item> + <item> + <p>Fix a problem in <c>erlang:delete_element/2</c> where + the call could corrupt one word of stack if the heap and + stack met during call.</p> + <p> + Own Id: OTP-10932</p> + </item> + <item> + <p> + The <seealso + marker="erl#+sws"><c>+sws<value></c></seealso> and + <seealso + marker="erl#+swt"><c>+swt<value></c></seealso> + system flags failed if no white space were passed between + the parameter and value parts of the flags. Upon failure, + the runtime system refused to start.</p> + <p> + Own Id: OTP-11000</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Scheduler threads will now by default be less eager + requesting wakeup due to certain cleanup operations. This + can also be controlled using the <seealso + marker="erl#+swct"><c>+swct</c></seealso> command line + argument of <seealso + marker="erl"><c>erl(1)</c></seealso>.</p> + <p> + Own Id: OTP-10994</p> + </item> + </list> + </section> + +</section> + <section><title>Erts 5.10.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/erts/emulator/beam/bif.c b/erts/emulator/beam/bif.c index 9c438679ea..ff237b6a78 100644 --- a/erts/emulator/beam/bif.c +++ b/erts/emulator/beam/bif.c @@ -2543,7 +2543,7 @@ BIF_RETTYPE insert_element_3(BIF_ALIST_3) Eterm* hp; Uint arity; Eterm res; - Sint ix; + Sint ix, c1, c2; if (is_not_tuple(BIF_ARG_2) || is_not_small(BIF_ARG_1)) { BIF_ERROR(BIF_P, BADARG); @@ -2561,14 +2561,12 @@ BIF_RETTYPE insert_element_3(BIF_ALIST_3) res = make_tuple(hp); *hp = make_arityval(arity + 1); - ix--; - arity -= ix; - - while (ix--) { *++hp = *++ptr; } + c1 = ix - 1; + c2 = arity - ix + 1; + while (c1--) { *++hp = *++ptr; } *++hp = BIF_ARG_3; - - while(arity--) { *++hp = *++ptr; } + while (c2--) { *++hp = *++ptr; } BIF_RET(res); } @@ -2579,7 +2577,7 @@ BIF_RETTYPE delete_element_2(BIF_ALIST_3) Eterm* hp; Uint arity; Eterm res; - Sint ix; + Sint ix, c1, c2; if (is_not_tuple(BIF_ARG_2) || is_not_small(BIF_ARG_1)) { BIF_ERROR(BIF_P, BADARG); @@ -2597,14 +2595,12 @@ BIF_RETTYPE delete_element_2(BIF_ALIST_3) res = make_tuple(hp); *hp = make_arityval(arity - 1); - ix--; - arity -= ix; - - while (ix--) { *++hp = *++ptr; } + c1 = ix - 1; + c2 = arity - ix; + while (c1--) { *++hp = *++ptr; } ++ptr; - - while(arity--) { *++hp = *++ptr; } + while (c2--) { *++hp = *++ptr; } BIF_RET(res); } diff --git a/erts/emulator/beam/erl_bif_info.c b/erts/emulator/beam/erl_bif_info.c index 8582a8954b..379d3eecc6 100755 --- a/erts/emulator/beam/erl_bif_info.c +++ b/erts/emulator/beam/erl_bif_info.c @@ -3077,7 +3077,7 @@ BIF_RETTYPE is_process_alive_1(BIF_ALIST_1) if (BIF_ARG_1 == BIF_P->common.id) BIF_RET(am_true); - rp = erts_proc_lookup(BIF_ARG_1); + rp = erts_proc_lookup_raw(BIF_ARG_1); if (!rp) { BIF_RET(am_false); } diff --git a/erts/emulator/beam/erl_db.c b/erts/emulator/beam/erl_db.c index d0afc8ed8d..98c2988323 100644 --- a/erts/emulator/beam/erl_db.c +++ b/erts/emulator/beam/erl_db.c @@ -264,9 +264,10 @@ static void schedule_free_dbtable(DbTable* tb) * function has returned). */ ASSERT(erts_refc_read(&tb->common.ref, 0) == 0); - erts_schedule_thr_prgr_later_op(free_dbtable, - (void *) tb, - &tb->release.data); + erts_schedule_thr_prgr_later_cleanup_op(free_dbtable, + (void *) tb, + &tb->release.data, + sizeof(DbTable)); } static ERTS_INLINE void db_init_lock(DbTable* tb, int use_frequent_read_lock, diff --git a/erts/emulator/beam/erl_init.c b/erts/emulator/beam/erl_init.c index e7e4030900..e6a96d427f 100644 --- a/erts/emulator/beam/erl_init.c +++ b/erts/emulator/beam/erl_init.c @@ -532,6 +532,8 @@ void erts_usage(void) erts_fprintf(stderr, " see the erl(1) documentation for more info.\n"); erts_fprintf(stderr, "-sws val set scheduler wakeup strategy, valid values are:\n"); erts_fprintf(stderr, " default|legacy.\n"); + erts_fprintf(stderr, "-swct val set scheduler wake cleanup threshold, valid values are:\n"); + erts_fprintf(stderr, " very_lazy|lazy|medium|eager|very_eager.\n"); erts_fprintf(stderr, "-swt val set scheduler wakeup threshold, valid values are:\n"); erts_fprintf(stderr, " very_low|low|medium|high|very_high.\n"); erts_fprintf(stderr, "-sss size suggested stack size in kilo words for scheduler threads,\n"); @@ -1413,7 +1415,17 @@ erl_start(int argc, char **argv) erts_usage(); } } - else if (sys_strcmp("wt", sub_param) == 0) { + else if (has_prefix("wct", sub_param)) { + arg = get_arg(sub_param+3, argv[i+1], &i); + if (erts_sched_set_wake_cleanup_threshold(arg) != 0) { + erts_fprintf(stderr, "scheduler wake cleanup threshold: %s\n", + arg); + erts_usage(); + } + VERBOSE(DEBUG_SYSTEM, + ("scheduler wake cleanup threshold: %s\n", arg)); + } + else if (has_prefix("wt", sub_param)) { arg = get_arg(sub_param+2, argv[i+1], &i); if (erts_sched_set_wakeup_other_thresold(arg) != 0) { erts_fprintf(stderr, "scheduler wakeup threshold: %s\n", @@ -1423,7 +1435,7 @@ erl_start(int argc, char **argv) VERBOSE(DEBUG_SYSTEM, ("scheduler wakeup threshold: %s\n", arg)); } - else if (sys_strcmp("ws", sub_param) == 0) { + else if (has_prefix("ws", sub_param)) { arg = get_arg(sub_param+2, argv[i+1], &i); if (erts_sched_set_wakeup_other_type(arg) != 0) { erts_fprintf(stderr, "scheduler wakeup strategy: %s\n", diff --git a/erts/emulator/beam/erl_port_task.c b/erts/emulator/beam/erl_port_task.c index ce045ec94e..0ed08bee01 100644 --- a/erts/emulator/beam/erl_port_task.c +++ b/erts/emulator/beam/erl_port_task.c @@ -151,9 +151,10 @@ static ERTS_INLINE void schedule_port_task_free(ErtsPortTask *ptp) { #ifdef ERTS_SMP - erts_schedule_thr_prgr_later_op(call_port_task_free, - (void *) ptp, - &ptp->u.release); + erts_schedule_thr_prgr_later_cleanup_op(call_port_task_free, + (void *) ptp, + &ptp->u.release, + sizeof(ErtsPortTask)); #else port_task_free(ptp); #endif @@ -772,9 +773,10 @@ static void schedule_port_task_handle_list_free(ErtsPortTaskHandleList *pthlp) { #ifdef ERTS_SMP - erts_schedule_thr_prgr_later_op(free_port_task_handle_list, - (void *) pthlp, - &pthlp->u.release); + erts_schedule_thr_prgr_later_cleanup_op(free_port_task_handle_list, + (void *) pthlp, + &pthlp->u.release, + sizeof(ErtsPortTaskHandleList)); #else erts_free(ERTS_ALC_T_PT_HNDL_LIST, pthlp); #endif @@ -1999,6 +2001,7 @@ begin_port_cleanup(Port *pp, ErtsPortTask **execqp, int *processing_busy_q_p) * Schedule cleanup of port structure... */ #ifdef ERTS_SMP + /* Has to be more or less immediate to release any driver */ erts_schedule_thr_prgr_later_op(release_port, (void *) pp, &pp->common.u.release); diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c index 16b6b1c79d..8bf481dbff 100644 --- a/erts/emulator/beam/erl_process.c +++ b/erts/emulator/beam/erl_process.c @@ -146,6 +146,14 @@ extern BeamInstr beam_continue_exit[]; int erts_sched_compact_load; Uint erts_no_schedulers; +#define ERTS_THR_PRGR_LATER_CLEANUP_OP_THRESHOLD_VERY_LAZY (4*1024*1024) +#define ERTS_THR_PRGR_LATER_CLEANUP_OP_THRESHOLD_LAZY (512*1024) +#define ERTS_THR_PRGR_LATER_CLEANUP_OP_THRESHOLD_MEDIUM (64*1024) +#define ERTS_THR_PRGR_LATER_CLEANUP_OP_THRESHOLD_EAGER (16*1024) +#define ERTS_THR_PRGR_LATER_CLEANUP_OP_THRESHOLD_VERY_EAGER (1024) + +static UWord thr_prgr_later_cleanup_op_threshold = ERTS_THR_PRGR_LATER_CLEANUP_OP_THRESHOLD_MEDIUM; + ErtsPTab erts_proc erts_align_attribute(ERTS_CACHE_LINE_SIZE); int erts_sched_thread_suggested_stack_size = -1; @@ -496,6 +504,7 @@ erts_init_process(int ncpu, int proc_tab_size) #endif (ErtsPTabElementCommon *) &erts_invalid_process.common, proc_tab_size, + sizeof(Process), "process_table"); last_reductions = 0; @@ -909,6 +918,53 @@ unset_aux_work_flags(ErtsSchedulerSleepInfo *ssi, erts_aint32_t flgs) #ifdef ERTS_SMP static ERTS_INLINE void +haw_chk_later_cleanup_op_wakeup(ErtsAuxWorkData *awdp, ErtsThrPrgrVal val) +{ + if (awdp->later_op.first + && erts_thr_progress_cmp(val, awdp->later_op.thr_prgr) >= 0) { + awdp->later_op.size = thr_prgr_later_cleanup_op_threshold; + } +} + +static ERTS_INLINE void +haw_thr_prgr_wakeup(ErtsAuxWorkData *awdp, ErtsThrPrgrVal val) +{ + int cmp = erts_thr_progress_cmp(val, awdp->latest_wakeup); + if (cmp != 0) { + if (cmp > 0) { + awdp->latest_wakeup = val; + haw_chk_later_cleanup_op_wakeup(awdp, val); + } + erts_thr_progress_wakeup(awdp->esdp, val); + } +} + +static ERTS_INLINE void +haw_thr_prgr_soft_wakeup(ErtsAuxWorkData *awdp, ErtsThrPrgrVal val) +{ + if (erts_thr_progress_cmp(val, awdp->latest_wakeup) > 0) { + awdp->latest_wakeup = val; + haw_chk_later_cleanup_op_wakeup(awdp, val); + erts_thr_progress_wakeup(awdp->esdp, val); + } +} + +static ERTS_INLINE void +haw_thr_prgr_later_cleanup_op_wakeup(ErtsAuxWorkData *awdp, ErtsThrPrgrVal val, UWord size) +{ + if (erts_thr_progress_cmp(val, awdp->latest_wakeup) > 0) { + awdp->later_op.thr_prgr = val; + if (awdp->later_op.size > size) + awdp->later_op.size -= size; + else { + awdp->latest_wakeup = val; + awdp->later_op.size = thr_prgr_later_cleanup_op_threshold; + erts_thr_progress_wakeup(awdp->esdp, val); + } + } +} + +static ERTS_INLINE void haw_thr_prgr_current_reset(ErtsAuxWorkData *awdp) { awdp->current_thr_prgr = ERTS_THR_PRGR_INVALID; @@ -1066,8 +1122,7 @@ misc_aux_work_clean(ErtsThrQ_t *q, case ERTS_THR_Q_NEED_THR_PRGR: #ifdef ERTS_SMP set_aux_work_flags(awdp->ssi, ERTS_SSI_AUX_WORK_MISC_THR_PRGR); - erts_thr_progress_wakeup(awdp->esdp, - erts_thr_q_need_thr_progress(q)); + haw_thr_prgr_soft_wakeup(awdp, erts_thr_q_need_thr_progress(q)); #endif case ERTS_THR_Q_CLEAN: break; @@ -1225,8 +1280,7 @@ handle_async_ready_clean(ErtsAuxWorkData *awdp, return aux_work & ~ERTS_SSI_AUX_WORK_ASYNC_READY_CLEAN; #ifdef ERTS_SMP case ERTS_ASYNC_READY_NEED_THR_PRGR: - erts_thr_progress_wakeup(awdp->esdp, - awdp->async_ready.thr_prgr); + haw_thr_prgr_soft_wakeup(awdp, awdp->async_ready.thr_prgr); awdp->async_ready.need_thr_prgr = 1; return aux_work & ~ERTS_SSI_AUX_WORK_ASYNC_READY_CLEAN; #endif @@ -1300,7 +1354,7 @@ handle_delayed_dealloc(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waitin awdp->dd.thr_prgr = wakeup; set_aux_work_flags(ssi, ERTS_SSI_AUX_WORK_DD_THR_PRGR); awdp->dd.thr_prgr = wakeup; - erts_thr_progress_wakeup(awdp->esdp, wakeup); + haw_thr_prgr_soft_wakeup(awdp, wakeup); } else if (awdp->dd.completed_callback) { awdp->dd.completed_callback(awdp->dd.completed_arg); @@ -1341,7 +1395,7 @@ handle_delayed_dealloc_thr_prgr(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, i if (wakeup == ERTS_THR_PRGR_INVALID) wakeup = erts_thr_progress_later(awdp->esdp); awdp->dd.thr_prgr = wakeup; - erts_thr_progress_wakeup(awdp->esdp, wakeup); + haw_thr_prgr_soft_wakeup(awdp, wakeup); } else { unset_aux_work_flags(ssi, ERTS_SSI_AUX_WORK_DD_THR_PRGR); @@ -1376,6 +1430,7 @@ handle_thr_prgr_later_op(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int wait } lop->func(lop->data); if (!awdp->later_op.first) { + awdp->later_op.size = thr_prgr_later_cleanup_op_threshold; awdp->later_op.last = NULL; unset_aux_work_flags(awdp->ssi, ERTS_SSI_AUX_WORK_THR_PRGR_LATER_OP); @@ -1386,6 +1441,29 @@ handle_thr_prgr_later_op(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int wait return aux_work; } +static ERTS_INLINE ErtsThrPrgrVal +enqueue_later_op(ErtsSchedulerData *esdp, + void (*later_func)(void *), + void *later_data, + ErtsThrPrgrLaterOp *lop) +{ + ErtsThrPrgrVal later = erts_thr_progress_later(esdp); + ASSERT(esdp); + + lop->func = later_func; + lop->data = later_data; + lop->later = later; + lop->next = NULL; + if (!esdp->aux_work_data.later_op.last) + esdp->aux_work_data.later_op.first = lop; + else + esdp->aux_work_data.later_op.last->next = lop; + esdp->aux_work_data.later_op.last = lop; + set_aux_work_flags_wakeup_nob(esdp->ssi, + ERTS_SSI_AUX_WORK_THR_PRGR_LATER_OP); + return later; +} + #endif /* ERTS_SMP */ void @@ -1396,31 +1474,24 @@ erts_schedule_thr_prgr_later_op(void (*later_func)(void *), #ifndef ERTS_SMP later_func(later_data); #else - ErtsSchedulerData *esdp; - ErtsAuxWorkData *awdp; - int request_wakeup = 1; - - esdp = erts_get_scheduler_data(); - ASSERT(esdp); - awdp = &esdp->aux_work_data; + ErtsSchedulerData *esdp = erts_get_scheduler_data(); + ErtsThrPrgrVal later = enqueue_later_op(esdp, later_func, later_data, lop); + haw_thr_prgr_wakeup(&esdp->aux_work_data, later); +#endif +} - lop->func = later_func; - lop->data = later_data; - lop->later = erts_thr_progress_later(esdp); - lop->next = NULL; - if (!awdp->later_op.last) - awdp->later_op.first = lop; - else { - ErtsThrPrgrLaterOp *last = awdp->later_op.last; - last->next = lop; - if (erts_thr_progress_equal(last->later, lop->later)) - request_wakeup = 0; - } - awdp->later_op.last = lop; - set_aux_work_flags_wakeup_nob(awdp->ssi, - ERTS_SSI_AUX_WORK_THR_PRGR_LATER_OP); - if (request_wakeup) - erts_thr_progress_wakeup(esdp, lop->later); +void +erts_schedule_thr_prgr_later_cleanup_op(void (*later_func)(void *), + void *later_data, + ErtsThrPrgrLaterOp *lop, + UWord size) +{ +#ifndef ERTS_SMP + later_func(later_data); +#else + ErtsSchedulerData *esdp = erts_get_scheduler_data(); + ErtsThrPrgrVal later = enqueue_later_op(esdp, later_func, later_data, lop); + haw_thr_prgr_later_cleanup_op_wakeup(&esdp->aux_work_data, later, size); #endif } @@ -4358,6 +4429,25 @@ erts_sched_set_busy_wait_threshold(char *str) return 0; } + +int +erts_sched_set_wake_cleanup_threshold(char *str) +{ + if (sys_strcmp(str, "very_lazy") == 0) + thr_prgr_later_cleanup_op_threshold = ERTS_THR_PRGR_LATER_CLEANUP_OP_THRESHOLD_VERY_LAZY; + else if (sys_strcmp(str, "lazy") == 0) + thr_prgr_later_cleanup_op_threshold = ERTS_THR_PRGR_LATER_CLEANUP_OP_THRESHOLD_LAZY; + else if (sys_strcmp(str, "medium") == 0) + thr_prgr_later_cleanup_op_threshold = ERTS_THR_PRGR_LATER_CLEANUP_OP_THRESHOLD_MEDIUM; + else if (sys_strcmp(str, "eager") == 0) + thr_prgr_later_cleanup_op_threshold = ERTS_THR_PRGR_LATER_CLEANUP_OP_THRESHOLD_EAGER; + else if (sys_strcmp(str, "very_eager") == 0) + thr_prgr_later_cleanup_op_threshold = ERTS_THR_PRGR_LATER_CLEANUP_OP_THRESHOLD_VERY_EAGER; + else + return EINVAL; + return 0; +} + static void init_aux_work_data(ErtsAuxWorkData *awdp, ErtsSchedulerData *esdp, char *dawwp) { @@ -4365,10 +4455,13 @@ init_aux_work_data(ErtsAuxWorkData *awdp, ErtsSchedulerData *esdp, char *dawwp) awdp->esdp = esdp; awdp->ssi = esdp ? esdp->ssi : NULL; #ifdef ERTS_SMP + awdp->latest_wakeup = ERTS_THR_PRGR_VAL_FIRST; awdp->misc.thr_prgr = ERTS_THR_PRGR_VAL_WAITING; awdp->dd.thr_prgr = ERTS_THR_PRGR_VAL_WAITING; awdp->dd.completed_callback = NULL; awdp->dd.completed_arg = NULL; + awdp->later_op.thr_prgr = ERTS_THR_PRGR_VAL_FIRST; + awdp->later_op.size = 0; awdp->later_op.first = NULL; awdp->later_op.last = NULL; #endif diff --git a/erts/emulator/beam/erl_process.h b/erts/emulator/beam/erl_process.h index 6d1032c292..3d3579fa7e 100644 --- a/erts/emulator/beam/erl_process.h +++ b/erts/emulator/beam/erl_process.h @@ -430,6 +430,7 @@ typedef struct { ErtsSchedulerSleepInfo *ssi; #ifdef ERTS_SMP ErtsThrPrgrVal current_thr_prgr; + ErtsThrPrgrVal latest_wakeup; #endif struct { int ix; @@ -444,6 +445,8 @@ typedef struct { void (*completed_arg)(void *); } dd; struct { + ErtsThrPrgrVal thr_prgr; + UWord size; ErtsThrPrgrLaterOp *first; ErtsThrPrgrLaterOp *last; } later_op; @@ -1298,10 +1301,15 @@ ERTS_GLB_INLINE int erts_proclist_is_last(ErtsProcList *list, int erts_sched_set_wakeup_other_thresold(char *str); int erts_sched_set_wakeup_other_type(char *str); int erts_sched_set_busy_wait_threshold(char *str); +int erts_sched_set_wake_cleanup_threshold(char *); void erts_schedule_thr_prgr_later_op(void (*)(void *), void *, ErtsThrPrgrLaterOp *); +void erts_schedule_thr_prgr_later_cleanup_op(void (*)(void *), + void *, + ErtsThrPrgrLaterOp *, + UWord); #if defined(ERTS_SMP) && defined(ERTS_ENABLE_LOCK_CHECK) int erts_dbg_check_halloc_lock(Process *p); diff --git a/erts/emulator/beam/erl_ptab.c b/erts/emulator/beam/erl_ptab.c index 87beeafa1a..5bbc71c659 100644 --- a/erts/emulator/beam/erl_ptab.c +++ b/erts/emulator/beam/erl_ptab.c @@ -421,6 +421,7 @@ erts_ptab_init_table(ErtsPTab *ptab, void (*release_element)(void *), ErtsPTabElementCommon *invalid_element, int size, + UWord element_size, char *name) { size_t tab_sz; @@ -443,6 +444,7 @@ erts_ptab_init_table(ErtsPTab *ptab, bits = erts_fit_in_bits_int32((Sint32) size - 1); } + ptab->r.o.element_size = element_size; ptab->r.o.max = size; tab_sz = ERTS_ALC_CACHE_LINE_ALIGN_SIZE(size*sizeof(erts_smp_atomic_t)); @@ -670,9 +672,10 @@ erts_ptab_delete_element(ErtsPTab *ptab, } if (ptab->r.o.release_element) - erts_schedule_thr_prgr_later_op(ptab->r.o.release_element, - (void *) ptab_el, - &ptab_el->u.release); + erts_schedule_thr_prgr_later_cleanup_op(ptab->r.o.release_element, + (void *) ptab_el, + &ptab_el->u.release, + ptab->r.o.element_size); } /* diff --git a/erts/emulator/beam/erl_ptab.h b/erts/emulator/beam/erl_ptab.h index 8a130f42a3..7fa1251900 100644 --- a/erts/emulator/beam/erl_ptab.h +++ b/erts/emulator/beam/erl_ptab.h @@ -109,6 +109,7 @@ typedef struct { ErtsPTabElementCommon *invalid_element; Eterm invalid_data; void (*release_element)(void *); + UWord element_size; } ErtsPTabReadOnlyData; typedef struct { @@ -177,6 +178,7 @@ void erts_ptab_init_table(ErtsPTab *ptab, void (*release_element)(void *), ErtsPTabElementCommon *invalid_element, int size, + UWord element_size, char *name); int erts_ptab_new_element(ErtsPTab *ptab, ErtsPTabElementCommon *ptab_el, diff --git a/erts/emulator/beam/erl_thr_progress.c b/erts/emulator/beam/erl_thr_progress.c index 1292cb0678..cf5e3dc012 100644 --- a/erts/emulator/beam/erl_thr_progress.c +++ b/erts/emulator/beam/erl_thr_progress.c @@ -418,7 +418,7 @@ erts_thr_progress_pre_init(void) { intrnl = NULL; erts_tsd_key_create(&erts_thr_prgr_data_key__); - init_nob(&erts_thr_prgr__.current, 0); + init_nob(&erts_thr_prgr__.current, ERTS_THR_PRGR_VAL_FIRST); } void diff --git a/erts/emulator/beam/erl_thr_progress.h b/erts/emulator/beam/erl_thr_progress.h index 1416aa6166..1aeecf2b06 100644 --- a/erts/emulator/beam/erl_thr_progress.h +++ b/erts/emulator/beam/erl_thr_progress.h @@ -108,6 +108,8 @@ struct ErtsThrPrgrLaterOp_ { #ifdef ERTS_SMP +/* ERTS_THR_PRGR_VAL_FIRST should only be used when initializing... */ +#define ERTS_THR_PRGR_VAL_FIRST ((ErtsThrPrgrVal) 0) #define ERTS_THR_PRGR_VAL_WAITING (~((ErtsThrPrgrVal) 0)) #define ERTS_THR_PRGR_INVALID (~((ErtsThrPrgrVal) 0)) diff --git a/erts/emulator/beam/io.c b/erts/emulator/beam/io.c index a9771de7ad..7cadd4aaad 100644 --- a/erts/emulator/beam/io.c +++ b/erts/emulator/beam/io.c @@ -2651,11 +2651,17 @@ void erts_init_io(int port_tab_size, int port_tab_size_ignore_files) { ErlDrvEntry** dp; + UWord common_element_size; erts_smp_rwmtx_opt_t drv_list_rwmtx_opts = ERTS_SMP_RWMTX_OPT_DEFAULT_INITER; drv_list_rwmtx_opts.type = ERTS_SMP_RWMTX_TYPE_EXTREMELY_FREQUENT_READ; drv_list_rwmtx_opts.lived = ERTS_SMP_RWMTX_LONG_LIVED; + 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 */ #ifdef ERTS_SMP + common_element_size += sizeof(erts_mtx_t); + init_xports_list_alloc(); #endif @@ -2684,6 +2690,7 @@ void erts_init_io(int port_tab_size, NULL, (ErtsPTabElementCommon *) &erts_invalid_port.common, port_tab_size, + common_element_size, /* Doesn't need to be excact */ "port_table"); erts_smp_atomic_init_nob(&erts_bytes_out, 0); diff --git a/erts/etc/common/erlexec.c b/erts/etc/common/erlexec.c index 9d674a7c65..31d9b2e0ad 100644 --- a/erts/etc/common/erlexec.c +++ b/erts/etc/common/erlexec.c @@ -125,6 +125,7 @@ static char *pluss_val_switches[] = { "cl", "ct", "tbt", + "wct", "wt", "ws", "ss", diff --git a/erts/etc/unix/run_erl.c b/erts/etc/unix/run_erl.c index 910be3dce8..a3bcdb85d9 100644 --- a/erts/etc/unix/run_erl.c +++ b/erts/etc/unix/run_erl.c @@ -682,7 +682,7 @@ static void pass_on(pid_t childpid) } } - if (!got_some && wfd && buf[0] == '\022') { + if (!got_some && wfd && buf[0] == '\014') { char wbuf[30]; int wlen = sn_printf(wbuf,sizeof(wbuf),"[run_erl v%u-%u]\n", RUN_ERL_HI_VER, RUN_ERL_LO_VER); diff --git a/erts/etc/unix/to_erl.c b/erts/etc/unix/to_erl.c index 754b349338..094006c5fd 100644 --- a/erts/etc/unix/to_erl.c +++ b/erts/etc/unix/to_erl.c @@ -353,7 +353,7 @@ int main(int argc, char **argv) * at the start of every new to_erl-session. */ - if (write(wfd, "\022", 1) < 0) { + if (write(wfd, "\014", 1) < 0) { fprintf(stderr, "Error in writing ^R to FIFO.\n"); } diff --git a/erts/preloaded/ebin/erl_prim_loader.beam b/erts/preloaded/ebin/erl_prim_loader.beam Binary files differindex a4ddae3d5d..aee6f631cf 100644 --- a/erts/preloaded/ebin/erl_prim_loader.beam +++ b/erts/preloaded/ebin/erl_prim_loader.beam diff --git a/erts/preloaded/ebin/erlang.beam b/erts/preloaded/ebin/erlang.beam Binary files differindex 684659cca1..ea80d47eb6 100644 --- a/erts/preloaded/ebin/erlang.beam +++ b/erts/preloaded/ebin/erlang.beam diff --git a/erts/preloaded/ebin/erts_internal.beam b/erts/preloaded/ebin/erts_internal.beam Binary files differindex 74c08fa4c9..6b2593e427 100644 --- a/erts/preloaded/ebin/erts_internal.beam +++ b/erts/preloaded/ebin/erts_internal.beam diff --git a/erts/preloaded/ebin/init.beam b/erts/preloaded/ebin/init.beam Binary files differindex 143d5b18b9..7fe1685ef3 100644 --- a/erts/preloaded/ebin/init.beam +++ b/erts/preloaded/ebin/init.beam diff --git a/erts/preloaded/ebin/otp_ring0.beam b/erts/preloaded/ebin/otp_ring0.beam Binary files differindex a6b2fdb985..57dafb9ce7 100644 --- a/erts/preloaded/ebin/otp_ring0.beam +++ b/erts/preloaded/ebin/otp_ring0.beam diff --git a/erts/preloaded/ebin/prim_file.beam b/erts/preloaded/ebin/prim_file.beam Binary files differindex 9460f1da6f..7558a713ae 100644 --- a/erts/preloaded/ebin/prim_file.beam +++ b/erts/preloaded/ebin/prim_file.beam diff --git a/erts/preloaded/ebin/prim_inet.beam b/erts/preloaded/ebin/prim_inet.beam Binary files differindex 8c47d8b611..f836473cb4 100644 --- a/erts/preloaded/ebin/prim_inet.beam +++ b/erts/preloaded/ebin/prim_inet.beam diff --git a/erts/preloaded/ebin/prim_zip.beam b/erts/preloaded/ebin/prim_zip.beam Binary files differindex e6f3995b50..35acb59096 100644 --- a/erts/preloaded/ebin/prim_zip.beam +++ b/erts/preloaded/ebin/prim_zip.beam diff --git a/erts/preloaded/ebin/zlib.beam b/erts/preloaded/ebin/zlib.beam Binary files differindex cdbaa43d5d..c100d1755b 100644 --- a/erts/preloaded/ebin/zlib.beam +++ b/erts/preloaded/ebin/zlib.beam diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl index 8e4a471a82..a88a8831ea 100644 --- a/erts/preloaded/src/erlang.erl +++ b/erts/preloaded/src/erlang.erl @@ -1905,11 +1905,11 @@ setelement(_Index, _Tuple1, _Value) -> Function :: atom(), Args :: [term()], Options :: [Option], - Option :: link | monitor | {priority, Level} + Option :: link | monitor + | {priority, Level :: priority_level()} | {fullsweep_after, Number :: non_neg_integer()} | {min_heap_size, Size :: non_neg_integer()} - | {min_bin_vheap_size, VSize :: non_neg_integer()}, - Level :: low | normal | high. + | {min_bin_vheap_size, VSize :: non_neg_integer()}. spawn_opt(_Tuple) -> erlang:nif_error(undefined). @@ -2244,11 +2244,11 @@ spawn_monitor(M, F, A) -> -spec spawn_opt(Fun, Options) -> pid() | {pid(), reference()} when Fun :: function(), Options :: [Option], - Option :: link | monitor | {priority, Level} + Option :: link | monitor + | {priority, Level :: priority_level()} | {fullsweep_after, Number :: non_neg_integer()} | {min_heap_size, Size :: non_neg_integer()} - | {min_bin_vheap_size, VSize :: non_neg_integer()}, - Level :: low | normal | high. + | {min_bin_vheap_size, VSize :: non_neg_integer()}. spawn_opt(F, O) when erlang:is_function(F) -> spawn_opt(erlang, apply, [F, []], O); spawn_opt({M,F}=MF, O) when erlang:is_atom(M), erlang:is_atom(F) -> @@ -2262,11 +2262,11 @@ spawn_opt(F, O) -> Node :: node(), Fun :: function(), Options :: [Option], - Option :: link | monitor | {priority, Level} + Option :: link | monitor + | {priority, Level :: priority_level()} | {fullsweep_after, Number :: non_neg_integer()} | {min_heap_size, Size :: non_neg_integer()} - | {min_bin_vheap_size, VSize :: non_neg_integer()}, - Level :: low | normal | high. + | {min_bin_vheap_size, VSize :: non_neg_integer()}. spawn_opt(N, F, O) when N =:= erlang:node() -> spawn_opt(F, O); spawn_opt(N, F, O) when erlang:is_function(F) -> @@ -2354,11 +2354,11 @@ spawn_link(N,M,F,A) -> Function :: atom(), Args :: [term()], Options :: [Option], - Option :: link | monitor | {priority, Level} + Option :: link | monitor + | {priority, Level :: priority_level()} | {fullsweep_after, Number :: non_neg_integer()} | {min_heap_size, Size :: non_neg_integer()} - | {min_bin_vheap_size, VSize :: non_neg_integer()}, - Level :: low | normal | high. + | {min_bin_vheap_size, VSize :: non_neg_integer()}. spawn_opt(M, F, A, Opts) -> case catch erlang:spawn_opt({M,F,A,Opts}) of {'EXIT',{Reason,_}} -> @@ -2374,11 +2374,11 @@ spawn_opt(M, F, A, Opts) -> Function :: atom(), Args :: [term()], Options :: [Option], - Option :: link | monitor | {priority, Level} + Option :: link | monitor + | {priority, Level :: priority_level()} | {fullsweep_after, Number :: non_neg_integer()} | {min_heap_size, Size :: non_neg_integer()} - | {min_bin_vheap_size, VSize :: non_neg_integer()}, - Level :: low | normal | high. + | {min_bin_vheap_size, VSize :: non_neg_integer()}. spawn_opt(N, M, F, A, O) when N =:= erlang:node(), erlang:is_atom(M), erlang:is_atom(F), erlang:is_list(A), erlang:is_list(O) -> diff --git a/lib/common_test/doc/src/ct_run.xml b/lib/common_test/doc/src/ct_run.xml index d871908952..c87c765ae7 100644 --- a/lib/common_test/doc/src/ct_run.xml +++ b/lib/common_test/doc/src/ct_run.xml @@ -113,9 +113,9 @@ [-muliply_timetraps Multiplier] [-scale_timetraps] [-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc] - [-repeat N [-force_stop]] | - [-duration HHMMSS [-force_stop]] | - [-until [YYMoMoDD]HHMMSS [-force_stop]] + [-repeat N] | + [-duration HHMMSS [-force_stop [skip_rest]]] | + [-until [YYMoMoDD]HHMMSS [-force_stop [skip_rest]]] [-basic_html] [-ct_hooks CTHModule1 CTHOpts1 and CTHModule2 CTHOpts2 and .. CTHModuleN CTHOptsN] @@ -149,9 +149,9 @@ [-muliply_timetraps Multiplier] [-scale_timetraps] [-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc] - [-repeat N [-force_stop]] | - [-duration HHMMSS [-force_stop]] | - [-until [YYMoMoDD]HHMMSS [-force_stop]] + [-repeat N] | + [-duration HHMMSS [-force_stop [skip_rest]]] | + [-until [YYMoMoDD]HHMMSS [-force_stop [skip_rest]]] [-basic_html] [-ct_hooks CTHModule1 CTHOpts1 and CTHModule2 CTHOpts2 and .. CTHModuleN CTHOptsN] diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml index 35f89153d3..afaed29626 100644 --- a/lib/common_test/doc/src/run_test_chapter.xml +++ b/lib/common_test/doc/src/run_test_chapter.xml @@ -174,7 +174,7 @@ <item><c><![CDATA[-repeat <n>]]></c>, tells Common Test to repeat the tests n times (see below).</item> <item><c><![CDATA[-duration <time>]]></c>, tells Common Test to repeat the tests for duration of time (see below).</item> <item><c><![CDATA[-until <stop_time>]]></c>, tells Common Test to repeat the tests until stop_time (see below).</item> - <item><c>-force_stop</c>, on timeout, the test run will be aborted when current test job is finished (see below).</item> + <item><c>-force_stop [skip_rest]</c>, on timeout, the test run will be aborted when current test job is finished. If <c>skip_rest</c> is provided the rest of the test cases in the current test job will be skipped (see below).</item> <item><c><![CDATA[-decrypt_key <key>]]></c>, provides a decryption key for <seealso marker="config_file_chapter#encrypted_config_files">encrypted configuration files</seealso>.</item> <item><c><![CDATA[-decrypt_file <key_file>]]></c>, points out a file containing a decryption key for @@ -1273,6 +1273,7 @@ <item><c>-duration DurTime ({duration,DurTime})</c>, where <c>DurTime</c> is the duration, see below.</item> <item><c>-until StopTime ({until,StopTime})</c>, where <c>StopTime</c> is finish time, see below.</item> <item><c>-force_stop ({force_stop,true})</c></item> + <item><c>-force_stop skip_rest ({force_stop,skip_rest})</c></item> </list> <p>The duration time, <c>DurTime</c>, is specified as <c>HHMMSS</c>. Example: <c>-duration 012030</c> or <c>{duration,"012030"}</c>, means the tests will @@ -1283,12 +1284,16 @@ Example: <c>-until 071001120000</c> or <c>{until,"071001120000"}</c>, which means the tests will be executed and (if time allows) repeated, until 12 o'clock on the 1st of Oct 2007.</p> - <p>When timeout occurs, Common Test will never abort the test run immediately, since + <p>When timeout occurs, Common Test will never abort the ongoing test case, since this might leave the system under test in an undefined, and possibly bad, state. - Instead Common Test will finish the current test job, or the complete test - run, before stopping. The latter is the default behaviour. The <c>force_stop</c> - flag/option tells Common Test to stop as soon as the current test job is finished. - Note that since Common Test always finishes off the current test job or test session, + Instead Common Test will by default finish the current test + run before stopping. If the <c>force_stop</c> flag is + given, Common Test will stop as soon as the current test job + is finished, and if the <c>force_stop</c> flag is given with + <c>skip_rest</c> Common Test will only complete the current + test case and skip the rest of the tests in the test job. + Note that since Common Test always finishes off at least the + current test case, the time specified with <c>duration</c> or <c>until</c> is never definitive!</p> <p>Log files from every single repeated test run is saved in normal Common Test fashion (see above). @@ -1312,6 +1317,18 @@ <p>Example 2:</p> <pre> + $ ct_run -dir $TEST_ROOT/to1 $TEST_ROOT/to2 -duration 001000 -forces_stop skip_rest</pre> + <p>Here the same test run as in Example 1, but with the + <c>force_stop</c> flag set to <c>skip_rest</c>. If the timeout + occurs while executing tests in directory to1, the rest of the + test cases in to1 will be skipped and then the test will be + aborted without running the tests in to2 another time. If the + timeout occurs while executing tests in directory to2, then the + rest of the test cases in to2 will be skipped and then the test + will be aborted.</p> + + <p>Example 3:</p> + <pre> $ date Fri Sep 28 15:00:00 MEST 2007 @@ -1321,7 +1338,7 @@ Common Test will finish the entire test run before stopping (i.e. the to1 and to2 test will always both be executed in the same test run).</p> - <p>Example 3:</p> + <p>Example 4:</p> <pre> $ ct_run -dir $TEST_ROOT/to1 $TEST_ROOT/to2 -repeat 5</pre> <p>Here the test run, including both the to1 and the to2 test, will be repeated 5 times.</p> diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl index 04a95a53fa..e6732f7fc7 100644 --- a/lib/common_test/src/ct.erl +++ b/lib/common_test/src/ct.erl @@ -153,7 +153,7 @@ run(TestDirs) -> %%% {auto_compile,Bool} | {create_priv_dir,CreatePrivDir} | %%% {multiply_timetraps,M} | {scale_timetraps,Bool} | %%% {repeat,N} | {duration,DurTime} | {until,StopTime} | -%%% {force_stop,Bool} | {decrypt,DecryptKeyOrFile} | +%%% {force_stop,ForceStop} | {decrypt,DecryptKeyOrFile} | %%% {refresh_logs,LogDir} | {logopts,LogOpts} | %%% {verbosity,VLevels} | {basic_html,Bool} | %%% {ct_hooks, CTHs} | {enable_builtin_hooks,Bool} | @@ -184,6 +184,7 @@ run(TestDirs) -> %%% N = integer() %%% DurTime = string(HHMMSS) %%% StopTime = string(YYMoMoDDHHMMSS) | string(HHMMSS) +%%% ForceStop = skip_rest | Bool %%% DecryptKeyOrFile = {key,DecryptKey} | {file,DecryptFile} %%% DecryptKey = string() %%% DecryptFile = string() diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl index 5fe4eaf511..b92fe1555f 100644 --- a/lib/common_test/src/ct_framework.erl +++ b/lib/common_test/src/ct_framework.erl @@ -64,38 +64,46 @@ init_tc(Mod,Func,Config) -> ok end, - case ct_util:get_testdata(curr_tc) of - {Suite,{suite0_failed,{require,Reason}}} -> - {skip,{require_failed_in_suite0,Reason}}; - {Suite,{suite0_failed,_}=Failure} -> - {skip,Failure}; + case Func=/=end_per_suite + andalso Func=/=end_per_group + andalso ct_util:get_testdata(skip_rest) of + true -> + {skip,"Repeated test stopped by force_stop option"}; _ -> - ct_util:update_testdata(curr_tc, - fun(undefined) -> - [{Suite,Func}]; - (Running) -> - [{Suite,Func}|Running] - end, [create]), - case ct_util:read_suite_data({seq,Suite,Func}) of - undefined -> - init_tc1(Mod,Suite,Func,Config); - Seq when is_atom(Seq) -> - case ct_util:read_suite_data({seq,Suite,Seq}) of - [Func|TCs] -> % this is the 1st case in Seq - %% make sure no cases in this seq are - %% marked as failed from an earlier execution - %% in the same suite - lists:foreach( - fun(TC) -> - ct_util:save_suite_data({seq,Suite,TC}, - Seq) - end, TCs); - _ -> - ok - end, - init_tc1(Mod,Suite,Func,Config); - {failed,Seq,BadFunc} -> - {skip,{sequence_failed,Seq,BadFunc}} + case ct_util:get_testdata(curr_tc) of + {Suite,{suite0_failed,{require,Reason}}} -> + {skip,{require_failed_in_suite0,Reason}}; + {Suite,{suite0_failed,_}=Failure} -> + {skip,Failure}; + _ -> + ct_util:update_testdata(curr_tc, + fun(undefined) -> + [{Suite,Func}]; + (Running) -> + [{Suite,Func}|Running] + end, [create]), + case ct_util:read_suite_data({seq,Suite,Func}) of + undefined -> + init_tc1(Mod,Suite,Func,Config); + Seq when is_atom(Seq) -> + case ct_util:read_suite_data({seq,Suite,Seq}) of + [Func|TCs] -> % this is the 1st case in Seq + %% make sure no cases in this seq are + %% marked as failed from an earlier execution + %% in the same suite + lists:foreach( + fun(TC) -> + ct_util:save_suite_data( + {seq,Suite,TC}, + Seq) + end, TCs); + _ -> + ok + end, + init_tc1(Mod,Suite,Func,Config); + {failed,Seq,BadFunc} -> + {skip,{sequence_failed,Seq,BadFunc}} + end end end. diff --git a/lib/common_test/src/ct_repeat.erl b/lib/common_test/src/ct_repeat.erl index a47309c6ee..f4d9949776 100644 --- a/lib/common_test/src/ct_repeat.erl +++ b/lib/common_test/src/ct_repeat.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2012. All Rights Reserved. +%% Copyright Ericsson AB 2007-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -23,7 +23,7 @@ %%% start flags (or equivalent ct:run_test/1 options) are supported: %%% -until <StopTime>, StopTime = YYMoMoDDHHMMSS | HHMMSS %%% -duration <DurTime>, DurTime = HHMMSS -%%% -force_stop +%%% -force_stop [skip_rest] %%% -repeat <N>, N = integer()</p> -module(ct_repeat). @@ -62,12 +62,15 @@ loop_test(If,Args) when is_list(Args) -> io:format("\nCommon Test: " "Will repeat tests for ~s.\n\n",[ts(Secs)]), TPid = - case lists:keymember(force_stop,1,Args) of - true -> + case proplists:get_value(force_stop,Args) of + False when False==false; False==undefined -> + undefined; + ForceStop -> CtrlPid = self(), - spawn(fun() -> stop_after(CtrlPid,Secs) end); - false -> - undefined + spawn( + fun() -> + stop_after(CtrlPid,Secs,ForceStop) + end) end, Args1 = [{loop_info,[{stop_time,Secs,StopTime,1}]} | Args], loop(If,stop_time,0,Secs,StopTime,Args1,TPid,[]) @@ -212,7 +215,7 @@ get_stop_time(until,[Y1,Y2,Mo1,Mo2,D1,D2,H1,H2,Mi1,Mi2,S1,S2]) -> list_to_integer([S1,S2])}, calendar:datetime_to_gregorian_seconds({Date,Time}); -get_stop_time(until,Time) -> +get_stop_time(until,Time=[_,_,_,_,_,_]) -> get_stop_time(until,"000000"++Time); get_stop_time(duration,[H1,H2,Mi1,Mi2,S1,S2]) -> @@ -227,10 +230,17 @@ cancel(Pid) -> %% After Secs, abort will make the test_server finish the current %% job, then empty the job queue and stop. -stop_after(_CtrlPid,Secs) -> +stop_after(_CtrlPid,Secs,ForceStop) -> timer:sleep(Secs*1000), + case ForceStop of + SkipRest when SkipRest==skip_rest; SkipRest==["skip_rest"] -> + ct_util:set_testdata({skip_rest,true}); + _ -> + ok + end, test_server_ctrl:abort(). + %% Callback from ct_run to print loop info to system log. log_loop_info(Args) -> case lists:keysearch(loop_info,1,Args) of @@ -259,11 +269,11 @@ log_loop_info(Args) -> io_lib:format("Test time remaining: ~w secs (~w%)\n", [Secs,trunc((Secs/Secs0)*100)]), LogStr4 = - case lists:keymember(force_stop,1,Args) of - true -> - io_lib:format("force_stop is enabled",[]); - _ -> - "" + case proplists:get_value(force_stop,Args) of + False when False==false; False==undefined -> + ""; + ForceStop -> + io_lib:format("force_stop is set to: ~w",[ForceStop]) end, ct_logs:log("Test loop info",LogStr1++LogStr2++LogStr3++LogStr4,[]) end. diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index 49f00429ae..57cfab532e 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -771,9 +771,9 @@ script_usage() -> "\n\t[-scale_timetraps]" "\n\t[-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]" "\n\t[-basic_html]" - "\n\t[-repeat N [-force_stop]] |" - "\n\t[-duration HHMMSS [-force_stop]] |" - "\n\t[-until [YYMoMoDD]HHMMSS [-force_stop]]\n\n"), + "\n\t[-repeat N] |" + "\n\t[-duration HHMMSS [-force_stop [skip_rest]]] |" + "\n\t[-until [YYMoMoDD]HHMMSS [-force_stop [skip_rest]]]\n\n"), io:format("Run tests using test specification:\n\n" "\tct_run -spec TestSpec1 TestSpec2 .. TestSpecN" "\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]" @@ -795,9 +795,9 @@ script_usage() -> "\n\t[-scale_timetraps]" "\n\t[-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]" "\n\t[-basic_html]" - "\n\t[-repeat N [-force_stop]] |" - "\n\t[-duration HHMMSS [-force_stop]] |" - "\n\t[-until [YYMoMoDD]HHMMSS [-force_stop]]\n\n"), + "\n\t[-repeat N] |" + "\n\t[-duration HHMMSS [-force_stop [skip_rest]]] |" + "\n\t[-until [YYMoMoDD]HHMMSS [-force_stop [skip_rest]]]\n\n"), io:format("Refresh the HTML index files:\n\n" "\tct_run -refresh_logs [LogDir]" "[-logdir LogDir] " @@ -2933,6 +2933,8 @@ opts2args(EnvStartOpts) -> []; ({create_priv_dir,PD}) when is_atom(PD) -> [{create_priv_dir,[atom_to_list(PD)]}]; + ({force_stop,skip_rest}) -> + [{force_stop,["skip_rest"]}]; ({force_stop,true}) -> [{force_stop,[]}]; ({force_stop,false}) -> diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile index a9ebd8f1d3..94569fa87f 100644 --- a/lib/common_test/test/Makefile +++ b/lib/common_test/test/Makefile @@ -38,6 +38,7 @@ MODULES= \ ct_groups_spec_SUITE \ ct_sequence_1_SUITE \ ct_repeat_1_SUITE \ + ct_repeat_testrun_SUITE \ ct_testspec_1_SUITE \ ct_testspec_2_SUITE \ ct_testspec_3_SUITE \ diff --git a/lib/common_test/test/ct_repeat_testrun_SUITE.erl b/lib/common_test/test/ct_repeat_testrun_SUITE.erl new file mode 100644 index 0000000000..7ec384c932 --- /dev/null +++ b/lib/common_test/test/ct_repeat_testrun_SUITE.erl @@ -0,0 +1,378 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%%%------------------------------------------------------------------- +%%% File: ct_repeat_test_SUITE +%%% +%%% Description: +%%% Test different options for repeating test runs: +%%% -repeat N +%%% -duration T [-force_stop [skip_rest]] +%%% -until T [-force_stop [skip_rest]] +%%% +%%%------------------------------------------------------------------- +-module(ct_repeat_testrun_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("common_test/include/ct_event.hrl"). + +-define(eh, ct_test_support_eh). +-define(skip_reason, "Repeated test stopped by force_stop option"). +-define(skipped, {skipped, ?skip_reason}). + + +%% Timers used in this test. +%% Each test suite consists of +%% +%% [tc1,tc2,{group,g,[tc1,tc2]},tc2] +%% +%% In r1_SUITE tc1 has a sleep of 10 sec - all other test cases just +%% return ok. +%% +%% => One complete test run of two suites r1_SUITE + r2_SUITE is at +%% least 20 seconds (10 sec for each r1_SUITE:tc1) +%% +-define(t1,30). % time shall expire during second run of r1_SUITE +-define(t2,6). % time shall expire during first run of tc1 +-define(t3,16). % time shall expire during second run of tc1 + + +%%-------------------------------------------------------------------- +%% TEST SERVER CALLBACK FUNCTIONS +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% Description: Since Common Test starts another Test Server +%% instance, the tests need to be performed on a separate node (or +%% there will be clashes with logging processes etc). +%%-------------------------------------------------------------------- +init_per_suite(Config0) -> + Config = ct_test_support:init_per_suite(Config0), + DataDir = ?config(data_dir, Config), + Suite1 = filename:join([DataDir,"a_test","r1_SUITE"]), + Suite2 = filename:join([DataDir,"b_test","r2_SUITE"]), + Opts0 = ct_test_support:get_opts(Config), + Opts1 = Opts0 ++ [{suite,Suite1},{testcase,tc2},{label,timing1}], + Opts2 = Opts0 ++ [{suite,Suite2},{testcase,tc2},{label,timing2}], + + %% Make sure both suites are compiled + {1,0,{0,0}} = ct_test_support:run(ct,run_test,[Opts1],Config), + {1,0,{0,0}} = ct_test_support:run(ct,run_test,[Opts2],Config), + + %% Time the shortest testcase to use for offset + {T0,{1,0,{0,0}}} = timer:tc(ct_test_support,run,[ct,run_test,[Opts1],Config]), + + %% -2 is to ensure we hit inside the target test case and not after +% T = round(T0/1000000)-2, + T=0, + [{offset,T}|Config]. + +end_per_suite(Config) -> + ct_test_support:end_per_suite(Config). + +init_per_testcase(TestCase, Config) -> + ct_test_support:init_per_testcase(TestCase, Config). + +end_per_testcase(TestCase, Config) -> + ct_test_support:end_per_testcase(TestCase, Config). + +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> + [ + repeat_n, + duration, + duration_force_stop, + duration_force_stop_skip_rest, + duration_force_stop_skip_rest_group, + until, + until_force_stop, + until_force_stop_skip_rest, + until_force_stop_skip_rest_group + ]. + +%%-------------------------------------------------------------------- +%% TEST CASES +%%-------------------------------------------------------------------- + +%%%----------------------------------------------------------------- +%%% +repeat_n(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Dirs = filelib:wildcard(filename:join(DataDir,"*")), + {Opts,ERPid} = setup([{dir,Dirs}, + {label,repeat_n}, + {repeat,2}], + Config), + ok = execute(repeat_n, Opts, ERPid, Config). + +duration(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Dirs = filelib:wildcard(filename:join(DataDir,"*")), + {Opts,ERPid} = setup([{dir,Dirs}, + {label,duration}, + {duration,duration_str(?t1,2,Config)}], + Config), + ok = execute(duration, Opts, ERPid, Config). + +duration_force_stop(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Dirs = filelib:wildcard(filename:join(DataDir,"*")), + {Opts,ERPid} = setup([{dir,Dirs}, + {label,duration_force_stop}, + {duration,duration_str(?t1,2,Config)}, + {force_stop,true}], + Config), + ok = execute(duration_force_stop, Opts, ERPid, Config). + +duration_force_stop_skip_rest(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Dirs = filelib:wildcard(filename:join(DataDir,"*")), + {Opts,ERPid} = setup([{dir,Dirs}, + {label,duration_force_stop_skip_rest}, + {duration,duration_str(?t2,1,Config)}, + {force_stop,skip_rest}], + Config), + ok = execute(duration_force_stop_skip_rest, Opts, ERPid, Config). + +duration_force_stop_skip_rest_group(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Dirs = filelib:wildcard(filename:join(DataDir,"*")), + {Opts,ERPid} = setup([{dir,Dirs}, + {label,duration_force_stop_skip_rest_group}, + {duration,duration_str(?t3,1,Config)}, + {force_stop,skip_rest}], + Config), + ok = execute(duration_force_stop_skip_rest_group, Opts, ERPid, Config). + +until(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Dirs = filelib:wildcard(filename:join(DataDir,"*")), + {Opts,ERPid} = setup([{dir,Dirs}, + {label,until}], + Config), + ExecuteFun = + fun() -> + [_,_] = ct_test_support:run_ct_run_test( + Opts++[{until,until_str(?t1,2,Config)}],Config), + 0 = ct_test_support:run_ct_script_start( + Opts++[{until,until_str(?t1,2,Config)}],Config) + end, + ok = execute(ExecuteFun, until, Opts, ERPid, Config). + +until_force_stop(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Dirs = filelib:wildcard(filename:join(DataDir,"*")), + {Opts,ERPid} = setup([{dir,Dirs}, + {label,until_force_stop}, + {force_stop,true}], + Config), + ExecuteFun = + fun() -> + [_,_] = ct_test_support:run_ct_run_test( + Opts++[{until,until_str(?t1,2,Config)}],Config), + 0 = ct_test_support:run_ct_script_start( + Opts++[{until,until_str(?t1,2,Config)}],Config) + end, + ok = execute(ExecuteFun, until_force_stop, Opts, ERPid, Config). + +until_force_stop_skip_rest(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Dirs = filelib:wildcard(filename:join(DataDir,"*")), + {Opts,ERPid} = setup([{dir,Dirs}, + {label,until_force_stop_skip_rest}, + {force_stop,skip_rest}], + Config), + ExecuteFun = + fun() -> + [_] = ct_test_support:run_ct_run_test( + Opts++[{until,until_str(?t2,1,Config)}],Config), + 1 = ct_test_support:run_ct_script_start( + Opts++[{until,until_str(?t2,1,Config)}],Config) + end, + ok = execute(ExecuteFun, until_force_stop_skip_rest, + Opts, ERPid, Config). + +until_force_stop_skip_rest_group(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Dirs = filelib:wildcard(filename:join(DataDir,"*")), + {Opts,ERPid} = setup([{dir,Dirs}, + {label,until_force_stop_skip_rest_group}, + {force_stop,skip_rest}], + Config), + ExecuteFun = + fun() -> + [_] = ct_test_support:run_ct_run_test( + Opts++[{until,until_str(?t3,1,Config)}],Config), + 0 = ct_test_support:run_ct_script_start( + Opts++[{until,until_str(?t3,1,Config)}],Config) + end, + ok = execute(ExecuteFun, + until_force_stop_skip_rest_group, + Opts, ERPid, Config). + + +%%%----------------------------------------------------------------- +%%% HELP FUNCTIONS +%%%----------------------------------------------------------------- + +setup(Test, Config) -> + Opts0 = ct_test_support:get_opts(Config), + Level = ?config(trace_level, Config), + EvHArgs = [{cbm,ct_test_support},{trace_level,Level}], + Opts = Opts0 ++ [{event_handler,{?eh,EvHArgs}}|Test], + ERPid = ct_test_support:start_event_receiver(Config), + {Opts,ERPid}. + +%% Execute test, first with ct:run_test, then with ct:script_start +execute(Name, Opts, ERPid, Config) -> + ExecuteFun = fun() -> ok = ct_test_support:run(Opts, Config) end, + execute(ExecuteFun, Name, Opts, ERPid, Config). + +execute(ExecuteFun, Name, Opts, ERPid, Config) -> + ExecuteFun(), + Events = ct_test_support:get_events(ERPid, Config), + + ct_test_support:log_events(Name, + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), + + TestEvents = events_to_check(Name), + ct_test_support:verify_events(TestEvents, Events, Config). + +reformat(Events, EH) -> + ct_test_support:reformat(Events, EH). + +%% N is the expected number of repeats +until_str(Secs0,N,Config) -> + Offset = ?config(offset,Config), + Secs = Secs0 + N*Offset, + Now = calendar:datetime_to_gregorian_seconds(calendar:local_time()), + {{Y,Mo,D},{H,M,S}} = calendar:gregorian_seconds_to_datetime(Now+Secs), + lists:flatten(io_lib:format("~2..0w~2..0w~2..0w~2..0w~2..0w~2..0w", + [Y rem 100, Mo, D, H, M, S])). + +%% N is the expected number of repeats +duration_str(Secs0,N,Config) -> + Offset = ?config(offset,Config), + Secs = Secs0 + N*Offset, + "0000" ++ lists:flatten(io_lib:format("~2..0w",[Secs])). + +%%%----------------------------------------------------------------- +%%% TEST EVENTS +%%%----------------------------------------------------------------- +%% 2 tests (ct:run_test + script_start) is default +events_to_check(C) when C==repeat_n; C==duration; C==until -> + dupl(4, start_logging() ++ all_succ() ++ stop_logging()); +events_to_check(C) when C==duration_force_stop; C==until_force_stop -> + dupl(2, start_logging() ++ + all_succ() ++ + stop_logging() ++ + start_logging() ++ + all_succ(r1_SUITE) ++ + stop_logging()); +events_to_check(C) when C==duration_force_stop_skip_rest; + C==until_force_stop_skip_rest -> + dupl(2, start_logging() ++ skip_first_tc1(r1_SUITE) ++ stop_logging()); +events_to_check(C) when C==duration_force_stop_skip_rest_group; + C==until_force_stop_skip_rest_group -> + dupl(2, start_logging() ++ skip_tc1_in_group(r1_SUITE) ++ stop_logging()). + +dupl(N,List) -> + lists:flatten(lists:duplicate(N,List)). + +start_logging() -> + [{?eh,start_logging,{'DEF','RUNDIR'}}]. +stop_logging() -> + [{?eh,stop_logging,[]}]. + + +all_succ() -> + all_succ(r1_SUITE) ++ all_succ(r2_SUITE). + +all_succ(Suite) -> + [{?eh,tc_start,{Suite,init_per_suite}}, + {?eh,tc_done,{Suite,init_per_suite,ok}}, + {?eh,tc_start,{Suite,tc1}}, + {?eh,tc_done,{Suite,tc1,ok}}, + {?eh,test_stats,{'_',0,{0,0}}}, + {?eh,tc_start,{Suite,tc2}}, + {?eh,tc_done,{Suite,tc2,ok}}, + {?eh,test_stats,{'_',0,{0,0}}}, + [{?eh,tc_start,{Suite,{init_per_group,g,[]}}}, + {?eh,tc_done,{Suite,{init_per_group,g,[]},ok}}, + {?eh,tc_start,{Suite,tc1}}, + {?eh,tc_done,{Suite,tc1,ok}}, + {?eh,test_stats,{'_',0,{0,0}}}, + {?eh,tc_start,{Suite,tc2}}, + {?eh,tc_done,{Suite,tc2,ok}}, + {?eh,test_stats,{'_',0,{0,0}}}, + {?eh,tc_start,{Suite,{end_per_group,g,[]}}}, + {?eh,tc_done,{Suite,{end_per_group,g,[]},ok}}], + {?eh,tc_start,{Suite,tc2}}, + {?eh,tc_done,{Suite,tc2,ok}}, + {?eh,test_stats,{'_',0,{0,0}}}, + {?eh,tc_start,{Suite,end_per_suite}}, + {?eh,tc_done,{Suite,end_per_suite,ok}}]. + +skip_first_tc1(Suite) -> + [{?eh,tc_start,{Suite,init_per_suite}}, + {?eh,tc_done,{Suite,init_per_suite,ok}}, + {?eh,tc_start,{Suite,tc1}}, + {?eh,tc_done,{Suite,tc1,ok}}, + {?eh,test_stats,{'_',0,{0,0}}}, + {?eh,tc_done,{Suite,tc2,?skipped}}, + {?eh,test_stats,{'_',0,{1,0}}}, + {?eh,tc_done,{Suite,{init_per_group,g,[]},?skipped}}, + {?eh,tc_auto_skip,{Suite,tc1,?skip_reason}}, + {?eh,test_stats,{'_',0,{1,1}}}, + {?eh,tc_auto_skip,{Suite,tc2,?skip_reason}}, + {?eh,test_stats,{'_',0,{1,2}}}, + {?eh,tc_auto_skip,{Suite,end_per_group,?skip_reason}}, + {?eh,tc_done,{Suite,tc2,?skipped}}, + {?eh,test_stats,{'_',0,{2,2}}}, + {?eh,tc_start,{Suite,end_per_suite}}, + {?eh,tc_done,{Suite,end_per_suite,ok}}]. + + +skip_tc1_in_group(Suite) -> + [{?eh,tc_start,{Suite,init_per_suite}}, + {?eh,tc_done,{Suite,init_per_suite,ok}}, + {?eh,tc_start,{Suite,tc1}}, + {?eh,tc_done,{Suite,tc1,ok}}, + {?eh,test_stats,{'_',0,{0,0}}}, + {?eh,tc_start,{Suite,tc2}}, + {?eh,tc_done,{Suite,tc2,ok}}, + {?eh,test_stats,{'_',0,{0,0}}}, + [{?eh,tc_start,{Suite,{init_per_group,g,[]}}}, + {?eh,tc_done,{Suite,{init_per_group,g,[]},ok}}, + {?eh,tc_start,{Suite,tc1}}, + {?eh,tc_done,{Suite,tc1,ok}}, + {?eh,test_stats,{'_',0,{0,0}}}, + {?eh,tc_done,{Suite,tc2,?skipped}}, + {?eh,test_stats,{'_',0,{1,0}}}, + {?eh,tc_start,{Suite,{end_per_group,g,[]}}}, + {?eh,tc_done,{Suite,{end_per_group,g,[]},ok}}], + {?eh,tc_done,{Suite,tc2,?skipped}}, + {?eh,test_stats,{'_',0,{2,0}}}, + {?eh,tc_start,{Suite,end_per_suite}}, + {?eh,tc_done,{Suite,end_per_suite,ok}}]. diff --git a/lib/common_test/test/ct_repeat_testrun_SUITE_data/a_test/r1_SUITE.erl b/lib/common_test/test/ct_repeat_testrun_SUITE_data/a_test/r1_SUITE.erl new file mode 100644 index 0000000000..3fd5943691 --- /dev/null +++ b/lib/common_test/test/ct_repeat_testrun_SUITE_data/a_test/r1_SUITE.erl @@ -0,0 +1,75 @@ +%%-------------------------------------------------------------------- +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%---------------------------------------------------------------------- +%% File: r1_SUITE.erl +%% +%% Description: +%% +%% +%% @author Support +%% @doc +%% @end +%%---------------------------------------------------------------------- +%%---------------------------------------------------------------------- +-module(r1_SUITE). +-include_lib("common_test/include/ct.hrl"). + +-compile(export_all). + +%% Default timetrap timeout (set in init_per_testcase). +-define(default_timeout, ?t:seconds(30)). + +all() -> + testcases() ++ [{group,g}, tc2]. + +groups() -> + [{g,testcases()}]. + +testcases() -> + [tc1,tc2]. + +init_per_suite(Config) -> + Config. + +end_per_suite(Config) -> + Config. + +init_per_group(_, Config) -> + Config. + +end_per_group(_Group, Config) -> + Config. + +init_per_testcase(_Case, Config) -> + Dog = test_server:timetrap(?default_timeout), + [{watchdog, Dog}|Config]. + +end_per_testcase(_Case, Config) -> + Dog=?config(watchdog, Config), + test_server:timetrap_cancel(Dog), + ok. + +%%%----------------------------------------------------------------- +%%% Test cases +tc1(_Config) -> + timer:sleep(10000), + ok. + +tc2(_Config) -> + ok. diff --git a/lib/common_test/test/ct_repeat_testrun_SUITE_data/b_test/r2_SUITE.erl b/lib/common_test/test/ct_repeat_testrun_SUITE_data/b_test/r2_SUITE.erl new file mode 100644 index 0000000000..dc9abc2863 --- /dev/null +++ b/lib/common_test/test/ct_repeat_testrun_SUITE_data/b_test/r2_SUITE.erl @@ -0,0 +1,75 @@ +%%-------------------------------------------------------------------- +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%---------------------------------------------------------------------- +%% File: r2_SUITE.erl +%% +%% Description: +%% +%% +%% @author Support +%% @doc +%% @end +%%---------------------------------------------------------------------- +%%---------------------------------------------------------------------- +-module(r2_SUITE). +-include_lib("common_test/include/ct.hrl"). + +-compile(export_all). + +%% Default timetrap timeout (set in init_per_testcase). +-define(default_timeout, ?t:seconds(30)). + +all() -> + testcases() ++ [{group,g}, tc2]. + +groups() -> + [{g,testcases()}]. + +testcases() -> + [tc1,tc2]. + +init_per_suite(Config) -> + Config. + +end_per_suite(Config) -> + Config. + +init_per_group(_, Config) -> + Config. + +end_per_group(_Group, Config) -> + Config. + +init_per_testcase(_Case, Config) -> + Dog = test_server:timetrap(?default_timeout), + [{watchdog, Dog}|Config]. + +end_per_testcase(_Case, Config) -> + Dog=?config(watchdog, Config), + test_server:timetrap_cancel(Dog), + ok. + +%%%----------------------------------------------------------------- +%%% Test cases +tc1(_Config) -> + %% timer:sleep(3000), + ok. + +tc2(_Config) -> + ok. diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl index 5e109e98e9..70dd087358 100644 --- a/lib/common_test/test/ct_test_support.erl +++ b/lib/common_test/test/ct_test_support.erl @@ -29,7 +29,8 @@ -export([init_per_suite/1, init_per_suite/2, end_per_suite/1, init_per_testcase/2, end_per_testcase/2, write_testspec/2, write_testspec/3, - run/2, run/3, run/4, get_opts/1, wait_for_ct_stop/1]). + run/2, run/3, run/4, run_ct_run_test/2, run_ct_script_start/2, + get_opts/1, wait_for_ct_stop/1]). -export([handle_event/2, start_event_receiver/1, get_events/2, verify_events/3, verify_events/4, reformat/2, log_events/4, @@ -224,9 +225,15 @@ get_opts(Config) -> %%%----------------------------------------------------------------- %%% run(Opts, Config) when is_list(Opts) -> + %% use ct interface + CtRunTestResult=run_ct_run_test(Opts,Config), + %% use run_test interface (simulated) + ExitStatus=run_ct_script_start(Opts,Config), + check_result(CtRunTestResult,ExitStatus,Opts). + +run_ct_run_test(Opts,Config) -> CTNode = proplists:get_value(ct_node, Config), Level = proplists:get_value(trace_level, Config), - %% use ct interface test_server:format(Level, "~n[RUN #1] Calling ct:run_test(~p) on ~p~n", [Opts, CTNode]), CtRunTestResult = rpc:call(CTNode, ct, run_test, [Opts]), @@ -242,7 +249,11 @@ run(Opts, Config) when is_list(Opts) -> timer:sleep(5000), undefined = rpc:call(CTNode, erlang, whereis, [ct_util_server]) end, - %% use run_test interface (simulated) + CtRunTestResult. + +run_ct_script_start(Opts, Config) -> + CTNode = proplists:get_value(ct_node, Config), + Level = proplists:get_value(trace_level, Config), Opts1 = [{halt_with,{?MODULE,ct_test_halt}} | Opts], test_server:format(Level, "Saving start opts on ~p: ~p~n", [CTNode, Opts1]), @@ -253,27 +264,38 @@ run(Opts, Config) when is_list(Opts) -> ExitStatus = rpc:call(CTNode, ct_run, script_start, []), test_server:format(Level, "[RUN #2] Got exit status value ~p~n", [ExitStatus]), - case {CtRunTestResult,ExitStatus} of - {{_Ok,Failed,{_UserSkipped,_AutoSkipped}},1} when Failed > 0 -> - ok; - {{_Ok,0,{_UserSkipped,AutoSkipped}},ExitStatus} when AutoSkipped > 0 -> - case proplists:get_value(exit_status, Opts1) of - ignore_config when ExitStatus == 1 -> - {error,{wrong_exit_status,ExitStatus}}; - _ -> - ok - end; - {{error,_}=Error,ExitStatus} -> - if ExitStatus /= 2 -> - {error,{wrong_exit_status,ExitStatus}}; - ExitStatus == 2 -> - Error - end; - {{_Ok,0,{_UserSkipped,_AutoSkipped}},0} -> - ok; - Unexpected -> - {error,{unexpected_return_value,Unexpected}} - end. + ExitStatus. + +check_result({_Ok,Failed,{_UserSkipped,_AutoSkipped}},1,_Opts) + when Failed > 0 -> + ok; +check_result({_Ok,0,{_UserSkipped,AutoSkipped}},ExitStatus,Opts) + when AutoSkipped > 0 -> + case proplists:get_value(exit_status, Opts) of + ignore_config when ExitStatus == 1 -> + {error,{wrong_exit_status,ExitStatus}}; + _ -> + ok + end; +check_result({error,_}=Error,2,_Opts) -> + Error; +check_result({error,_},ExitStatus,_Opts) -> + {error,{wrong_exit_status,ExitStatus}}; +check_result({_Ok,0,{_UserSkipped,_AutoSkipped}},0,_Opts) -> + ok; +check_result(CtRunTestResult,ExitStatus,Opts) + when is_list(CtRunTestResult) -> % repeated testruns + try check_result(sum_testruns(CtRunTestResult,0,0,0,0),ExitStatus,Opts) + catch _:_ -> + {error,{unexpected_return_value,{CtRunTestResult,ExitStatus}}} + end; +check_result(CtRunTestResult,ExitStatus,_Opts) -> + {error,{unexpected_return_value,{CtRunTestResult,ExitStatus}}}. + +sum_testruns([{O,F,{US,AS}}|T],Ok,Failed,UserSkipped,AutoSkipped) -> + sum_testruns(T,Ok+O,Failed+F,UserSkipped+US,AutoSkipped+AS); +sum_testruns([],Ok,Failed,UserSkipped,AutoSkipped) -> + {Ok,Failed,{UserSkipped,AutoSkipped}}. run(M, F, A, Config) -> run({M,F,A}, [], Config). diff --git a/lib/stdlib/doc/src/sys.xml b/lib/stdlib/doc/src/sys.xml index 073faf2df2..a177b80739 100644 --- a/lib/stdlib/doc/src/sys.xml +++ b/lib/stdlib/doc/src/sys.xml @@ -211,18 +211,87 @@ <p>Gets the status of the process.</p> <p>The value of <c><anno>Misc</anno></c> varies for different types of processes. For example, a <c>gen_server</c> process returns - the callback module's state, and a <c>gen_fsm</c> process - returns information such as its current state name. Callback - modules for <c>gen_server</c> and <c>gen_fsm</c> can also - customise the value of <c><anno>Misc</anno></c> by exporting - a <c>format_status/2</c> function that contributes - module-specific information; - see <seealso marker="gen_server#Module:format_status/2">gen_server:format_status/2</seealso> - and <seealso marker="gen_fsm#Module:format_status/2">gen_fsm:format_status/2</seealso> + the callback module's state, a <c>gen_fsm</c> process + returns information such as its current state name and state data, + and a <c>gen_event</c> process returns information about each of its + registered handlers. Callback modules for <c>gen_server</c>, + <c>gen_fsm</c>, and <c>gen_event</c> can also customise the value + of <c><anno>Misc</anno></c> by exporting a <c>format_status/2</c> + function that contributes module-specific information; + see <seealso marker="gen_server#Module:format_status/2">gen_server:format_status/2</seealso>, + <seealso marker="gen_fsm#Module:format_status/2">gen_fsm:format_status/2</seealso>, and + <seealso marker="gen_event#Module:format_status/2">gen_event:format_status/2</seealso> for more details.</p> </desc> </func> <func> + <name name="get_state" arity="1"/> + <name name="get_state" arity="2"/> + <fsummary>Get the state of the process</fsummary> + <desc> + <p>Gets the state of the process.</p> + <note> + <p>These functions are intended only to help with debugging. They are provided for + convenience, allowing developers to avoid having to create their own state extraction + functions and also avoid having to interactively extract state from the return values of + <c><seealso marker="get_status/1">get_status/1</seealso></c> or + <c><seealso marker="get_status/2">get_status/2</seealso></c> while debugging.</p> + </note> + <p>The value of <c><anno>State</anno></c> varies for different types of + processes. For a <c>gen_server</c> process, the returned <c><anno>State</anno></c> + is simply the callback module's state. For a <c>gen_fsm</c> process, + <c><anno>State</anno></c> is the tuple <c>{CurrentStateName, CurrentStateData}</c>. + For a <c>gen_event</c> process, <c><anno>State</anno></c> a list of tuples, + where each tuple corresponds to an event handler registered in the process and contains + <c>{Module, Id, HandlerState}</c>, where <c>Module</c> is the event handler's module name, + <c>Id</c> is the handler's ID (which is the value <c>false</c> if it was registered without + an ID), and <c>HandlerState</c> is the handler's state.</p> + <p>To obtain more information about a process, including its state, see + <seealso marker="get_status/1">get_status/1</seealso> and + <seealso marker="get_status/2">get_status/2</seealso>.</p> + </desc> + </func> + <func> + <name name="replace_state" arity="2"/> + <name name="replace_state" arity="3"/> + <fsummary>Replace the state of the process</fsummary> + <desc> + <p>Replaces the state of the process, and returns the new state.</p> + <note> + <p>These functions are intended only to help with debugging, and they should not be + be called from normal code. They are provided for convenience, allowing developers + to avoid having to create their own custom state replacement functions.</p> + </note> + <p>The <c><anno>StateFun</anno></c> function provides a new state for the process. + The <c><anno>State</anno></c> argument and <c><anno>NewState</anno></c> return value + of <c><anno>StateFun</anno></c> vary for different types of processes. For a + <c>gen_server</c> process, <c><anno>State</anno></c> is simply the callback module's + state, and <c><anno>NewState</anno></c> is a new instance of that state. For a + <c>gen_fsm</c> process, <c><anno>State</anno></c> is the tuple + <c>{CurrentStateName, CurrentStateData}</c>, and <c><anno>NewState</anno></c> + is a similar tuple that may contain a new state name, new state data, or both. + For a <c>gen_event</c> process, <c><anno>State</anno></c> is the tuple + <c>{Module, Id, HandlerState}</c> where <c>Module</c> is the event handler's module name, + <c>Id</c> is the handler's ID (which is the value <c>false</c> if it was registered without + an ID), and <c>HandlerState</c> is the handler's state. <c><anno>NewState</anno></c> is a + similar tuple where <c>Module</c> and <c>Id</c> shall have the same values as in + <c><anno>State</anno></c> but the value of <c>HandlerState</c> may be different. Returning + a <c><anno>NewState</anno></c> whose <c>Module</c> or <c>Id</c> values differ from those of + <c><anno>State</anno></c> will result in the event handler's state remaining unchanged. For a + <c>gen_event</c> process, <c><anno>StateFun</anno></c> is called once for each event handler + registered in the <c>gen_event</c> process.</p> + <p>If a <c><anno>StateFun</anno></c> function decides not to effect any change in process + state, then regardless of process type, it may simply return its <c><anno>State</anno></c> + argument.</p> + <p>If a <c><anno>StateFun</anno></c> function crashes or throws an exception, then + for <c>gen_server</c> and <c>gen_fsm</c> processes, the original state of the process is + unchanged. For <c>gen_event</c> processes, a crashing or failing <c><anno>StateFun</anno></c> + function means that only the state of the particular event handler it was working on when it + failed or crashed is unchanged; it can still succeed in changing the states of other event + handlers registered in the same <c>gen_event</c> process.</p> + </desc> + </func> + <func> <name name="install" arity="2"/> <name name="install" arity="3"/> <fsummary>Install a debug function in the process</fsummary> diff --git a/lib/stdlib/src/gen_event.erl b/lib/stdlib/src/gen_event.erl index 2b8ba86909..bfebf29080 100644 --- a/lib/stdlib/src/gen_event.erl +++ b/lib/stdlib/src/gen_event.erl @@ -229,6 +229,24 @@ wake_hib(Parent, ServerName, MSL, Debug) -> fetch_msg(Parent, ServerName, MSL, Debug, Hib) -> receive + {system, From, get_state} -> + States = [{Mod,Id,State} || #handler{module=Mod, id=Id, state=State} <- MSL], + sys:handle_system_msg(get_state, From, Parent, ?MODULE, Debug, + {States, [ServerName, MSL, Hib]}, Hib); + {system, From, {replace_state, StateFun}} -> + {NMSL, NStates} = + lists:unzip([begin + Cur = {Mod,Id,State}, + try + NState = {Mod,Id,NS} = StateFun(Cur), + {HS#handler{state=NS}, NState} + catch + _:_ -> + {HS, Cur} + end + end || #handler{module=Mod, id=Id, state=State}=HS <- MSL]), + sys:handle_system_msg(replace_state, From, Parent, ?MODULE, Debug, + {NStates, [ServerName, NMSL, Hib]}, Hib); {system, From, Req} -> sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug, [ServerName, MSL, Hib],Hib); diff --git a/lib/stdlib/src/gen_fsm.erl b/lib/stdlib/src/gen_fsm.erl index e480e2ac11..d9411e58cf 100644 --- a/lib/stdlib/src/gen_fsm.erl +++ b/lib/stdlib/src/gen_fsm.erl @@ -422,6 +422,17 @@ wake_hib(Parent, Name, StateName, StateData, Mod, Debug) -> decode_msg(Msg,Parent, Name, StateName, StateData, Mod, Time, Debug, Hib) -> case Msg of + {system, From, get_state} -> + Misc = [Name, StateName, StateData, Mod, Time], + sys:handle_system_msg(get_state, From, Parent, ?MODULE, Debug, + {{StateName, StateData}, Misc}, Hib); + {system, From, {replace_state, StateFun}} -> + State = {StateName, StateData}, + NState = {NStateName, NStateData} = try StateFun(State) + catch _:_ -> State end, + NMisc = [Name, NStateName, NStateData, Mod, Time], + sys:handle_system_msg(replace_state, From, Parent, ?MODULE, Debug, + {NState, NMisc}, Hib); {system, From, Req} -> sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug, [Name, StateName, StateData, Mod, Time], Hib); diff --git a/lib/stdlib/src/gen_server.erl b/lib/stdlib/src/gen_server.erl index 04308a51b7..9c4b95acf6 100644 --- a/lib/stdlib/src/gen_server.erl +++ b/lib/stdlib/src/gen_server.erl @@ -372,6 +372,13 @@ wake_hib(Parent, Name, State, Mod, Debug) -> decode_msg(Msg, Parent, Name, State, Mod, Time, Debug, Hib) -> case Msg of + {system, From, get_state} -> + sys:handle_system_msg(get_state, From, Parent, ?MODULE, Debug, + {State, [Name, State, Mod, Time]}, Hib); + {system, From, {replace_state, StateFun}} -> + NState = try StateFun(State) catch _:_ -> State end, + sys:handle_system_msg(replace_state, From, Parent, ?MODULE, Debug, + {NState, [Name, NState, Mod, Time]}, Hib); {system, From, Req} -> sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug, [Name, State, Mod, Time], Hib); diff --git a/lib/stdlib/src/sys.erl b/lib/stdlib/src/sys.erl index 2d6287814e..bffeb44179 100644 --- a/lib/stdlib/src/sys.erl +++ b/lib/stdlib/src/sys.erl @@ -21,6 +21,8 @@ %% External exports -export([suspend/1, suspend/2, resume/1, resume/2, get_status/1, get_status/2, + get_state/1, get_state/2, + replace_state/2, replace_state/3, change_code/4, change_code/5, log/2, log/3, trace/2, trace/3, statistics/2, statistics/3, log_to_file/2, log_to_file/3, no_debug/1, no_debug/2, @@ -97,6 +99,32 @@ get_status(Name) -> send_system_msg(Name, get_status). | (Misc :: term()). get_status(Name, Timeout) -> send_system_msg(Name, get_status, Timeout). +-spec get_state(Name) -> State when + Name :: name(), + State :: term(). +get_state(Name) -> send_system_msg(Name, get_state). + +-spec get_state(Name, Timeout) -> State when + Name :: name(), + Timeout :: timeout(), + State :: term(). +get_state(Name, Timeout) -> send_system_msg(Name, get_state, Timeout). + +-spec replace_state(Name, StateFun) -> NewState when + Name :: name(), + StateFun :: fun((State :: term()) -> NewState :: term()), + NewState :: term(). +replace_state(Name, StateFun) -> + send_system_msg(Name, {replace_state, StateFun}). + +-spec replace_state(Name, StateFun, Timeout) -> NewState when + Name :: name(), + StateFun :: fun((State :: term()) -> NewState :: term()), + Timeout :: timeout(), + NewState :: term(). +replace_state(Name, StateFun, Timeout) -> + send_system_msg(Name, {replace_state, StateFun}, Timeout). + -spec change_code(Name, Module, OldVsn, Extra) -> 'ok' | {error, Reason} when Name :: name(), Module :: module(), @@ -362,6 +390,10 @@ do_cmd(_, suspend, _Parent, _Mod, Debug, Misc) -> {suspended, ok, Debug, Misc}; do_cmd(_, resume, _Parent, _Mod, Debug, Misc) -> {running, ok, Debug, Misc}; +do_cmd(SysState, get_state, _Parent, _Mod, Debug, {State, Misc}) -> + {SysState, State, Debug, Misc}; +do_cmd(SysState, replace_state, _Parent, _Mod, Debug, {State, Misc}) -> + {SysState, State, Debug, Misc}; do_cmd(SysState, get_status, Parent, Mod, Debug, Misc) -> Res = get_status(SysState, Parent, Mod, Debug, Misc), {SysState, Res, Debug, Misc}; diff --git a/lib/stdlib/test/gen_event_SUITE.erl b/lib/stdlib/test/gen_event_SUITE.erl index 5c51e12e35..6be5a299b6 100644 --- a/lib/stdlib/test/gen_event_SUITE.erl +++ b/lib/stdlib/test/gen_event_SUITE.erl @@ -26,13 +26,14 @@ delete_handler/1, swap_handler/1, swap_sup_handler/1, notify/1, sync_notify/1, call/1, info/1, hibernate/1, call_format_status/1, call_format_status_anon/1, - error_format_status/1]). + error_format_status/1, get_state/1, replace_state/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [start, {group, test_all}, hibernate, - call_format_status, call_format_status_anon, error_format_status]. + call_format_status, call_format_status_anon, error_format_status, + get_state, replace_state]. groups() -> [{test_all, [], @@ -956,3 +957,45 @@ error_format_status(Config) when is_list(Config) -> ?line ok = gen_event:stop(Pid), process_flag(trap_exit, OldFl), ok. + +get_state(suite) -> + []; +get_state(doc) -> + ["Test that sys:get_state/1,2 return the gen_event state"]; +get_state(Config) when is_list(Config) -> + {ok, Pid} = gen_event:start({local, my_dummy_handler}), + State1 = self(), + ok = gen_event:add_handler(my_dummy_handler, dummy1_h, [State1]), + [{dummy1_h,false,State1}] = sys:get_state(Pid), + [{dummy1_h,false,State1}] = sys:get_state(Pid, 5000), + State2 = {?MODULE, self()}, + ok = gen_event:add_handler(my_dummy_handler, {dummy1_h,id}, [State2]), + Result1 = sys:get_state(Pid), + [{dummy1_h,false,State1},{dummy1_h,id,State2}] = lists:sort(Result1), + Result2 = sys:get_state(Pid, 5000), + [{dummy1_h,false,State1},{dummy1_h,id,State2}] = lists:sort(Result2), + ok = gen_event:stop(Pid), + ok. + +replace_state(suite) -> + []; +replace_state(doc) -> + ["Test that replace_state/2,3 replace the gen_event state"]; +replace_state(Config) when is_list(Config) -> + {ok, Pid} = gen_event:start({local, my_dummy_handler}), + State1 = self(), + ok = gen_event:add_handler(my_dummy_handler, dummy1_h, [State1]), + [{dummy1_h,false,State1}] = sys:get_state(Pid), + NState1 = "replaced", + Replace1 = fun({dummy1_h,false,_}=S) -> setelement(3,S,NState1) end, + [{dummy1_h,false,NState1}] = sys:replace_state(Pid, Replace1), + [{dummy1_h,false,NState1}] = sys:get_state(Pid), + NState2 = "replaced again", + Replace2 = fun({dummy1_h,false,_}=S) -> setelement(3,S,NState2) end, + [{dummy1_h,false,NState2}] = sys:replace_state(Pid, Replace2, 5000), + [{dummy1_h,false,NState2}] = sys:get_state(Pid), + %% verify no change in state if replace function crashes + Replace3 = fun(_) -> exit(fail) end, + [{dummy1_h,false,NState2}] = sys:replace_state(Pid, Replace3), + [{dummy1_h,false,NState2}] = sys:get_state(Pid), + ok. diff --git a/lib/stdlib/test/gen_fsm_SUITE.erl b/lib/stdlib/test/gen_fsm_SUITE.erl index a637a8543b..fd15838b7d 100644 --- a/lib/stdlib/test/gen_fsm_SUITE.erl +++ b/lib/stdlib/test/gen_fsm_SUITE.erl @@ -31,7 +31,7 @@ -export([shutdown/1]). --export([ sys1/1, call_format_status/1, error_format_status/1]). +-export([ sys1/1, call_format_status/1, error_format_status/1, get_state/1, replace_state/1]). -export([hibernate/1,hiber_idle/3,hiber_wakeup/3,hiber_idle/2,hiber_wakeup/2]). @@ -66,7 +66,7 @@ groups() -> start8, start9, start10, start11, start12]}, {abnormal, [], [abnormal1, abnormal2]}, {sys, [], - [sys1, call_format_status, error_format_status]}]. + [sys1, call_format_status, error_format_status, get_state, replace_state]}]. init_per_suite(Config) -> Config. @@ -413,6 +413,40 @@ error_format_status(Config) when is_list(Config) -> process_flag(trap_exit, OldFl), ok. +get_state(Config) when is_list(Config) -> + State = self(), + {ok, Pid} = gen_fsm:start(?MODULE, {state_data, State}, []), + {idle, State} = sys:get_state(Pid), + {idle, State} = sys:get_state(Pid, 5000), + stop_it(Pid), + + %% check that get_state can handle a name being an atom (pid is + %% already checked by the previous test) + {ok, Pid2} = gen_fsm:start({local, gfsm}, gen_fsm_SUITE, {state_data, State}, []), + {idle, State} = sys:get_state(gfsm), + {idle, State} = sys:get_state(gfsm, 5000), + stop_it(Pid2), + ok. + +replace_state(Config) when is_list(Config) -> + State = self(), + {ok, Pid} = gen_fsm:start(?MODULE, {state_data, State}, []), + {idle, State} = sys:get_state(Pid), + NState1 = "replaced", + Replace1 = fun({StateName, _}) -> {StateName, NState1} end, + {idle, NState1} = sys:replace_state(Pid, Replace1), + {idle, NState1} = sys:get_state(Pid), + NState2 = "replaced again", + Replace2 = fun({idle, _}) -> {state0, NState2} end, + {state0, NState2} = sys:replace_state(Pid, Replace2, 5000), + {state0, NState2} = sys:get_state(Pid), + %% verify no change in state if replace function crashes + Replace3 = fun(_) -> error(fail) end, + {state0, NState2} = sys:replace_state(Pid, Replace3), + {state0, NState2} = sys:get_state(Pid), + stop_it(Pid), + ok. + %% Hibernation hibernate(suite) -> []; hibernate(Config) when is_list(Config) -> diff --git a/lib/stdlib/test/gen_server_SUITE.erl b/lib/stdlib/test/gen_server_SUITE.erl index dffeadb423..3b6a3f38bc 100644 --- a/lib/stdlib/test/gen_server_SUITE.erl +++ b/lib/stdlib/test/gen_server_SUITE.erl @@ -32,7 +32,7 @@ spec_init_local_registered_parent/1, spec_init_global_registered_parent/1, otp_5854/1, hibernate/1, otp_7669/1, call_format_status/1, - error_format_status/1, call_with_huge_message_queue/1 + error_format_status/1, get_state/1, replace_state/1, call_with_huge_message_queue/1 ]). % spawn export @@ -57,6 +57,7 @@ all() -> spec_init_local_registered_parent, spec_init_global_registered_parent, otp_5854, hibernate, otp_7669, call_format_status, error_format_status, + get_state, replace_state, call_with_huge_message_queue]. groups() -> @@ -1033,6 +1034,51 @@ error_format_status(Config) when is_list(Config) -> process_flag(trap_exit, OldFl), ok. +%% Verify that sys:get_state correctly returns gen_server state +%% +get_state(suite) -> + []; +get_state(doc) -> + ["Test that sys:get_state/1,2 return the gen_server state"]; +get_state(Config) when is_list(Config) -> + State = self(), + {ok, _Pid} = gen_server:start_link({local, get_state}, + ?MODULE, {state,State}, []), + State = sys:get_state(get_state), + State = sys:get_state(get_state, 5000), + {ok, Pid} = gen_server:start_link(?MODULE, {state,State}, []), + State = sys:get_state(Pid), + State = sys:get_state(Pid, 5000), + ok. + +%% Verify that sys:replace_state correctly replaces gen_server state +%% +replace_state(suite) -> + []; +replace_state(doc) -> + ["Test that sys:replace_state/1,2 replace the gen_server state"]; +replace_state(Config) when is_list(Config) -> + State = self(), + {ok, _Pid} = gen_server:start_link({local, replace_state}, + ?MODULE, {state,State}, []), + State = sys:get_state(replace_state), + NState1 = "replaced", + Replace1 = fun(_) -> NState1 end, + NState1 = sys:replace_state(replace_state, Replace1), + NState1 = sys:get_state(replace_state), + {ok, Pid} = gen_server:start_link(?MODULE, {state,NState1}, []), + NState1 = sys:get_state(Pid), + Suffix = " again", + NState2 = NState1 ++ Suffix, + Replace2 = fun(S) -> S ++ Suffix end, + NState2 = sys:replace_state(Pid, Replace2, 5000), + NState2 = sys:get_state(Pid, 5000), + %% verify no change in state if replace function crashes + Replace3 = fun(_) -> throw(fail) end, + NState2 = sys:replace_state(Pid, Replace3), + NState2 = sys:get_state(Pid, 5000), + ok. + %% Test that the time for a huge message queue is not %% significantly slower than with an empty message queue. call_with_huge_message_queue(Config) when is_list(Config) -> diff --git a/lib/stdlib/test/io_proto_SUITE.erl b/lib/stdlib/test/io_proto_SUITE.erl index e16ba55481..76a8109a8d 100644 --- a/lib/stdlib/test/io_proto_SUITE.erl +++ b/lib/stdlib/test/io_proto_SUITE.erl @@ -147,8 +147,7 @@ unicode_prompt(Config) when is_list(Config) -> %% And one with oldshell ?line rtnode([{putline,""}, {putline, "2."}, - {getline_re, ".*2."}, - {getline, "2"}, + {getline_re, ".*2$"}, {putline, "shell:prompt_func({io_proto_SUITE,uprompt})."}, {getline_re, ".*default"}, {putline, "io:get_line('')."}, @@ -263,8 +262,7 @@ setopts_getopts(Config) when is_list(Config) -> %% And one with oldshell ?line rtnode([{putline,""}, {putline, "2."}, - {getline_re, ".*2."}, - {getline, "2"}, + {getline_re, ".*2$"}, {putline, "lists:keyfind(binary,1,io:getopts())."}, {getline_re, ".*{binary,false}"}, {putline, "io:get_line('')."}, @@ -467,8 +465,7 @@ unicode_options(Config) when is_list(Config) -> end, ?line rtnode([{putline,""}, {putline, "2."}, - {getline_re, ".*2."}, - {getline, "2"}, + {getline_re, ".*2$"}, {putline, "lists:keyfind(encoding,1,io:getopts())."}, {getline_re, ".*{encoding,latin1}"}, {putline, "io:format(\"~ts~n\",[[1024]])."}, @@ -701,8 +698,7 @@ binary_options(Config) when is_list(Config) -> old -> ok; new -> - ?line rtnode([{putline,""}, - {putline, "2."}, + ?line rtnode([{putline, "2."}, {getline, "2"}, {putline, "lists:keyfind(binary,1,io:getopts())."}, {getline, "{binary,false}"}, @@ -720,10 +716,8 @@ binary_options(Config) when is_list(Config) -> ],[]) end, %% And one with oldshell - ?line rtnode([{putline,""}, - {putline, "2."}, - {getline_re, ".*2."}, - {getline, "2"}, + ?line rtnode([{putline, "2."}, + {getline_re, ".*2$"}, {putline, "lists:keyfind(binary,1,io:getopts())."}, {getline_re, ".*{binary,false}"}, {putline, "io:get_line('')."}, diff --git a/lib/test_server/src/test_server_gl.erl b/lib/test_server/src/test_server_gl.erl index 766a4537a2..2e4f223811 100644 --- a/lib/test_server/src/test_server_gl.erl +++ b/lib/test_server/src/test_server_gl.erl @@ -197,7 +197,7 @@ handle_info({io_request,From,ReplyAs,Req}=IoReq, St) -> From ! {io_reply,ReplyAs,ok} catch _:_ -> - {io_reply,ReplyAs,{error,arguments}} + From ! {io_reply,ReplyAs,{error,arguments}} end, {noreply,St}; handle_info({structured_io,ClientPid,{Detail,Str}}, St) -> diff --git a/lib/test_server/test/test_server_SUITE.erl b/lib/test_server/test/test_server_SUITE.erl index 1a2fc632da..cf1df6df34 100644 --- a/lib/test_server/test/test_server_SUITE.erl +++ b/lib/test_server/test/test_server_SUITE.erl @@ -104,7 +104,7 @@ test_server_SUITE(Config) -> % rpc:call(Node,dbg, tpl,[test_server_ctrl,x]), run_test_server_tests("test_server_SUITE", [{test_server_SUITE,skip_case7,"SKIPPED!"}], - 38, 1, 30, 19, 9, 1, 11, 2, 25, Config). + 39, 1, 31, 20, 9, 1, 11, 2, 26, Config). test_server_parallel01_SUITE(Config) -> run_test_server_tests("test_server_parallel01_SUITE", [], diff --git a/lib/test_server/test/test_server_SUITE_data/test_server_SUITE.erl b/lib/test_server/test/test_server_SUITE_data/test_server_SUITE.erl index fc2adcd651..6c50efa712 100644 --- a/lib/test_server/test/test_server_SUITE_data/test_server_SUITE.erl +++ b/lib/test_server/test/test_server_SUITE_data/test_server_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2012. All Rights Reserved. +%% Copyright Ericsson AB 1997-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -38,7 +38,8 @@ conf_init/1, check_new_conf/1, conf_cleanup/1, check_old_conf/1, conf_init_fail/1, start_stop_node/1, cleanup_nodes_init/1, check_survive_nodes/1, cleanup_nodes_fin/1, - commercial/1]). + commercial/1, + io_invalid_data/1]). -export([dummy_function/0,dummy_function/1,doer/1]). @@ -47,7 +48,7 @@ all(suite) -> [config, comment, timetrap, timetrap_cancel, multiply_timetrap, init_per_s, init_per_tc, end_per_tc, timeconv, msgs, capture, timecall, do_times, skip_cases, - commercial, + commercial, io_invalid_data, {conf, conf_init, [check_new_conf], conf_cleanup}, check_old_conf, {conf, conf_init_fail,[conf_member_skip],conf_cleanup_skip}, @@ -497,4 +498,8 @@ commercial(Config) when is_list(Config) -> true -> {comment,"Commercial build"} end. - +io_invalid_data(Config) when is_list(Config) -> + ok = io:put_chars("valid: " ++ [42]), + %% OTP-10991 caused this to hang and produce a timetrap timeout: + {'EXIT',{badarg,_}} = (catch io:put_chars("invalid: " ++ [42.0])), + ok. diff --git a/system/doc/top/Makefile b/system/doc/top/Makefile index 37466fa2d9..20ef9fe781 100644 --- a/system/doc/top/Makefile +++ b/system/doc/top/Makefile @@ -38,13 +38,6 @@ INFO_FILES = ../../README ../../COPYRIGHT PR.template TOPDOCDIR=. -TOP_HTML_GEN_FILES = \ - $(HTMLDIR)/incompatible.html \ - $(HTMLDIR)/highlights.html - -TOP_HTML_FILES = \ - $(TOP_HTML_GEN_FILES) - include ../installation_guide/xmlfiles.mk include ../system_principles/xmlfiles.mk include ../embedded/xmlfiles.mk @@ -229,7 +222,7 @@ $(TOP_PDF_FILE): $(XML_FILES) pdf: $(TOP_PDF_FILE) -html: $(INDEX_FILES) $(TOP_HTML_FILES) \ +html: $(INDEX_FILES) \ $(MAN_INDEX) $(JAVASCRIPT) debug opt: @@ -237,7 +230,7 @@ debug opt: clean: rm -rf ../html/js rm -f PR.template - rm -f $(INDEX_FILES) $(TOP_HTML_FILES) $(MAN_INDEX) + rm -f $(INDEX_FILES) $(MAN_INDEX) rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo) rm -f $(INDEX_SCRIPT) $(GLOSSARY_SCRIPT) \ $(JAVASCRIPT_BUILD_SCRIPT) @@ -256,11 +249,10 @@ release_docs_spec: docs $(INSTALL_DIR) $(RELSYSDIR)/pdf $(INSTALL_DATA) \ $(TOP_PDF_FILE) $(RELSYSDIR)/pdf - $(INSTALL_DATA) $(TOP_HTML_FILES) $(RELSYSDIR) $(INSTALL_DIR) $(RELSYSDIR)/js $(INSTALL_DATA) \ $(JAVASCRIPT) $(RELSYSDIR)/js - $(INSTALL_DATA) $(INDEX_FILES) $(MAN_INDEX) $(TOP_HTML_FILES) $(RELSYSDIR) + $(INSTALL_DATA) $(INDEX_FILES) $(MAN_INDEX) $(RELSYSDIR) $(INSTALL_DIR) $(RELSYSDIR)/docbuild $(INSTALL_DATA) $(INDEX_SCRIPT) $(MAN_INDEX_SCRIPT) $(JAVASCRIPT_BUILD_SCRIPT) \ $(INDEX_SRC) $(MAN_INDEX_SRC) $(JAVASCRIPT_BUILD_SCRIPT_SRC) \ diff --git a/system/doc/top/highlights.xml b/system/doc/top/highlights.xml deleted file mode 100644 index 251bb1a47d..0000000000 --- a/system/doc/top/highlights.xml +++ /dev/null @@ -1,110 +0,0 @@ -<?xml version="1.0" encoding="latin1" ?> -<!DOCTYPE chapter SYSTEM "chapter.dtd"> - -<chapter> - <header> - <copyright> - <year>2006</year><year>2010</year> - <holder>Ericsson AB. All Rights Reserved.</holder> - </copyright> - <legalnotice> - 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. - - </legalnotice> - - <title>Highlights</title> - <prepared></prepared> - <docno></docno> - <date></date> - <rev>A</rev> - <file>highlights.xml</file> - </header> - <p>This document lists some highlights of Erlang 5.8/OTP R14A - (unpatched), compared to the previous version Erlang 5.7.1/OTP R13B, - with focus on things not already released as R13B patches.</p> - <p>Note: This document was compiled at the time when R14A was released - and does not list any features introduced in R14 patches.</p> - - <section> - <title>Erlang Language and Run-time System</title> - <section> - <title>New NIF features</title> - <list type="bulleted"> - <item><p>Send messages from a NIF, or from thread created by NIF, to - any local process (enif_send)</p></item> - - <item><p>Store terms between NIF calls (enif_alloc_env, - enif_make_copy)</p></item> - - <item><p>Create binary terms with user defined memory management - (enif_make_resource_binary)</p></item> - - <item><p>Some incompatible changes made to the API. For more - information see the warning text in erl_nif(3).</p></item> - </list> - </section> - <section> - <title>Receive optimizations</title> - <p>Receive statements that can only read out a newly created - reference are now specially optimized so that it will execute - in constant time regardless of the number of messages in the - receive queue for the process. That optimization will benefit - calls to gen_server:call(). (See gen:do_call/4 for an example - of a receive statement that will be optimized.)</p> - </section> - <section> - <title>Improved eprof</title> - <p>eprof has been reimplemented with support in the Erlang - virtual machine and is now both faster (i.e. slows down the - code being measured less) and scales much better. In - measurements we saw speed-ups compared to the old eprof - ranging from 6 times (for sequential code that only uses one - scheduler/core) up to 84 times (for parallel code that uses 8 - cores).</p> - - <p>Note: The API for the eprof has been cleaned up and extended. - See the documentation.</p> - </section> - </section> - <section> - <title>New features in Existing Applications</title> - - <section> - <title>Common_test</title> - - <p> - It is now possible for the user to provide specific callback - modules that handle test configuration data, so that data on - arbitray form can be accessed (e.g. by reading files or by - communicating with a configuration server process). Two - default callback modules have been introduced in Common Test: - ct_config_plain and ct_config_xml. The former is used to - handle the traditional Common Test configuration files (with - terms on key-value tuple form) and the latter to handle - configuration data on XML representation. - </p> - <p> - The run_test script has been replaced by a program (with the - same name) which can be executed without explicit - installation. The start flags are the same as for the legacy - start script. - </p> - </section> - <section> - <title>STDLIB</title> - <p> - The module binary from EEP31 (and EEP9) is implemented. - </p> - </section> - </section> -</chapter> - diff --git a/system/doc/top/incompatible.xml b/system/doc/top/incompatible.xml deleted file mode 100644 index e09fa7272f..0000000000 --- a/system/doc/top/incompatible.xml +++ /dev/null @@ -1,271 +0,0 @@ -<?xml version="1.0" encoding="latin1" ?> -<!DOCTYPE chapter SYSTEM "chapter.dtd"> - -<chapter> - <header> - <copyright> - <year>2006</year><year>2010</year> - <holder>Ericsson AB. All Rights Reserved.</holder> - </copyright> - <legalnotice> - 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. - - </legalnotice> - - <title>Potential Incompatibilities</title> - <prepared></prepared> - <docno></docno> - <date></date> - <rev></rev> - <file>incompatible.xml</file> - </header> - <p>This document contains a list of potential incompatibilities - between Erlang 5.8/OTP R14A and Erl 5.7.5/OTP R13B04, - and is an extract from the release notes for the respective applications.</p> - - <section> - <title>Tools</title> - <list type="bulleted"> - <item> - <p> - Xref has been updated to use the re module instead of the - deprecated regexp module.</p> - <p>*** INCOMPATIBILITY with R12B ***</p> - <p> - Own Id: OTP-8472</p> - </item> - <item> - <p>eprof has been reimplemented with support in the Erlang - virtual machine and is now both faster (i.e. slows down the - code being measured less) and scales much better. In - measurements we saw speed-ups compared to the old eprof - ranging from 6 times (for sequential code that only uses one - scheduler/core) up to 84 times (for parallel code that uses 8 - cores).</p> - - <p>Note: The API for the eprof has been cleaned up and extended. - See the documentation.</p> - <p> - *** POTENTIAL INCOMPATIBILITY ***</p> - <p> - Own Id: OTP-8706</p> - </item> - </list> - </section> - - <section> - <title>Odbc</title> - <list type="bulleted"> - <item> - <p> - Now supports SQL_TYPE_TIMESTAMP on the format {{YY, MM, DD}, - {HH, MM, SS}}. Thanks to Juhani Ränkimies. - </p> - <p> - *** POTENTIAL INCOMPATIBILITY ***</p> - <p> - Own Id: OTP-8511</p> - </item> - </list> - </section> - - <section> - <title>Ssh</title> - <list type="bulleted"> - <item> - <p> - The configuration parameter ip_v6_disabled is now available, - which makes it possible for the user to alter the IP version - SSH shall use.</p> - <p> - *** POTENTIAL INCOMPATIBILITY ***</p> - <p> - Own Id: OTP-8535</p> - </item> - <item> - <p> - Removed deprecated modules (ssh_ssh, ssh_sshd and ssh_cm) and - functions (ssh_sftp:connect and ssh_sftp:stop).</p> - <p> - *** POTENTIAL INCOMPATIBILITY ***</p> - <p> - Own Id: OTP-8596</p> - </item> - <item> - <p> - Aligned error message with used version (SSH_FX_FAILURE vs - SSH_FX_NOT_A_DIRECTORY, the latter introduced in version 6).</p> - <p> - *** POTENTIAL INCOMPATIBILITY ***</p> - <p> - Own Id: OTP-8644</p> - </item> - </list> - </section> - - <section> - <title>ERTS</title> - <list> - <item> - <p>Some incompatible changes made to the NIF API. For more - information see the warning text in erl_nif(3).</p> - <p> - *** POTENTIAL INCOMPATIBILITY ***</p> - <p> - Own Id: OTP-8555</p> - </item> - </list> - </section> - - <section> - <title>STDLIB</title> - <list type="bulleted"> - <item> - <p>When defining macros the closing right parenthesis before the - dot is now mandatory.</p> - <p> - *** POTENTIAL INCOMPATIBILITY ***</p> - <p> - Own Id: OTP-8562</p> - </item> - </list> - </section> - - <section> - <title>Compiler and STDLIB</title> - <list type="bulleted"> - <item> - <p> - Local and imported functions now override the auto-imported - BIFs when the names clash. The pre R14 behaviour was that - auto-imported BIFs would override local functions. To avoid - that old programs change behaviour, the following will - generate an error:</p> - - <list type="bulleted"> - <item>Doing a call without explicit module name to a local function - having a name clashing with the name of an auto-imported BIF - that was present (and auto-imported) before OTP R14A</item> - - <item>Explicitly importing a function having a name clashing with - the name of an autoimported BIF that was present (and - autoimported) before OTP R14A</item> - - <item>Using any form of the old compiler directive nowarn_bif_clash</item> - </list> - - <p>If the BIF was added or auto-imported in OTP R14A or later, - overriding it with an import or a local function will only - result in a warning.</p> - - <p>To resolve clashes, you can either use the explicit module - name erlang to call the BIF, or you can remove the - auto-import of that specific BIF by using the new compiler - directive -compile({no_auto_import,[F/A]})., which makes all - calls to the local or imported function without explicit - module name pass without warnings or errors.</p> - - <p>The change makes it possible to add auto-imported BIFs - without breaking or silently changing old code in the future. - However some current code ingeniously utilizing the old - behaviour or the nowarn_bif_clash compiler directive, might - need changing to be accepted by the compiler.</p> - <p> - *** POTENTIAL INCOMPATIBILITY ***</p> - <p> - Own Id: OTP-8579</p> - </item> - <item> - <p> - The recently added BIFs erlang:min/2, erlang:max/2 and - erlang:port_command/3 are now auto-imported (as they were - originally intended to be). Due to the recent compiler change - (OTP-8579), the only impact on old code defining it's own - min/2, max/2 or port_command/3 functions will be a warning, - the local functions will still be used. The warning can be - removed by using - -compile({no_auto_import,[min/2,max/2,port_command/3]}). in - the source file.</p> - <p> - *** POTENTIAL INCOMPATIBILITY ***</p> - <p> - Own Id: OTP-8669</p> - </item> - <item> - <p> - Now, binary_to_term/2 is auto-imported. This will cause a - compile warning if and only if a module has got a local - function with that name.</p> - <p> - *** POTENTIAL INCOMPATIBILITY ***</p> - <p> - Own Id: OTP-8671</p> - </item> - </list> - </section> - - <section> - <title>Erl_interface</title> - <list type="bulleted"> - <item> - <p>Compact IEEE 754 double encoding in external binary format - for ei</p> - - <p>Implement the compact IEEE 754 double encoding in external - binary format for ei. Encoding for ei now always produces the - NEW_FLOAT_EXT format. Decoding and term printing handle both - the old ERL_FLOAT_EXT encoding and the new NEW_FLOAT_EXT - encoding.</p> - - <p>Legacy erl_interface code also handles the new encoding, but - still produces the ERL_FLOAT_EXT encoding by default.</p> - - <p>Also enable the DFLAG_NEW_FLOATS distribution flag.</p> - - <p>ei_get_type() will return ERL_FLOAT_EXT regardless if the - external format is encoded with ERL_FLOAT_EXT or - NEW_FLOAT_EXT for doubles.</p> - - <p>Reduce the number of copies of the code for encoding and - decoding doubles throughout ei and erl_interface by instead - calling the ei encoding and decoding functions wherever - possible.</p> - - <p>Restore commented-out float tests in ei_decode_SUITE and - ei_encode_SUITE in lib/erl_interface/test. Modify them to - make them match the style of other tests in the same suites.</p> - - <p>These changes are based on an ei float patch from Serge - Aleynikov originally submitted against R12B-2 in July 2008 - and reworked by Steve Vinoski May 2010.</p> - <p> - *** POTENTIAL INCOMPATIBILITY ***</p> - <p> - Own Id: OTP-8684</p> - </item> - </list> - </section> - <section> - <title>Debugger and STDLIB</title> - <list type="bulleted"> - <item> - <p> The predefined builtin type tid() has been removed. Instead, - ets:tid() should be used.</p> - <p> - *** POTENTIAL INCOMPATIBILITY ***</p> - <p> - Own Id: OTP-8687</p> - </item> - </list> - </section> -</chapter> - |