From 849a2ed2db2bd54422ec9284468f80cc86978974 Mon Sep 17 00:00:00 2001 From: Rickard Green Date: Mon, 11 May 2015 17:56:03 +0200 Subject: Timer fixes, documentation, and test cases --- erts/doc/src/erlang.xml | 249 ++++++++++++++++++---- erts/emulator/beam/erl_alloc.c | 16 ++ erts/emulator/beam/erl_hl_timer.c | 66 +++--- erts/emulator/beam/time.c | 60 +++--- erts/emulator/test/after_SUITE.erl | 29 ++- erts/emulator/test/system_info_SUITE.erl | 31 +++ erts/emulator/test/timer_bif_SUITE.erl | 346 +++++++++++++++++++++++++------ erts/preloaded/ebin/erlang.beam | Bin 101588 -> 101812 bytes erts/preloaded/src/erlang.erl | 41 ++-- 9 files changed, 657 insertions(+), 181 deletions(-) diff --git a/erts/doc/src/erlang.xml b/erts/doc/src/erlang.xml index ba5f80a9c1..6ca57566aa 100644 --- a/erts/doc/src/erlang.xml +++ b/erts/doc/src/erlang.xml @@ -536,29 +536,69 @@ - + Cancel a timer -

Cancels a timer, where TimerRef was returned by - either - erlang:send_after/3 - or - erlang:start_timer/3. - If the timer is there to be removed, the function returns - the time in milliseconds left until the timer would have expired, - otherwise false (which means that TimerRef was - never a timer, that it has already been cancelled, or that it - has already delivered its message).

+

Cancels a timer. TimerRef needs to refer to + a timer that was created by either + erlang:send_after(), + or erlang:start_timer().

+

Currently available Options:

+ + {async, Async} + +

Asynchronous request for cancellation. Async + defaults to false. That is the operation will be + performed synchronously. When Async is set to + true the cancel operation will be performed + asynchronously. That is, cancel_timer() will send + a request for cancellation to the timer service that + manages the timer, and then return ok.

+ {info, Info} + +

Request information about the Result of the + cancellation. Info defaults to true. That + is information will be given. When Info is set to + false no information about the result of the cancel + operation will be given. When the operation is performed + synchronously the Result will returned from + cancel_timer(). When the operation is performed + asynchronously, a message on the form + {cancel_timer, TimerRef, Result} + will be sent to the caller of cancel_timer() when + the operation has been performed.

+
+

When the Result equals false a timer + corresponding to TimerRef could not be found. This + can be either because the timer had expired, been canceled, or because + TimerRef do not correspond to a timer. When the + Result is an integer, it represents + the time in milli seconds left before the timer will expire.

+

The timer service that manages the timer may be co-located + with another scheduler than the scheduler that the calling process + is executing on. In this case communication with the timer + service will be performed using asynchronous signals. If the calling + process is in critical path and can do other things while waiting + for the result of this operation, you want to use the {async, true} + option.

See also - erlang:send_after/3, - erlang:start_timer/3, + erlang:send_after/4, + erlang:start_timer/4, and - erlang:read_timer/1.

+ erlang:read_timer/2.

Note: Cancelling a timer does not guarantee that the message has not already been delivered to the message queue.

- + + + Cancel a timer + +

Cancels a timer. The same as calling + erlang:cancel_timer(TimerRef, + [{async, false}, {info, true}]).

+
+
Check if a module has old code @@ -4505,23 +4545,54 @@ os_prompt% - - Number of milliseconds remaining for a timer - -

TimerRef is a timer reference returned by - erlang:send_after/3 - or - erlang:start_timer/3. - If the timer is active, the function returns the time in - milliseconds left until the timer will expire, otherwise - false (which means that TimerRef was never a - timer, that it has been cancelled, or that it has already - delivered its message).

+ + Read the state of a timer + +

Read the state of a timer. TimerRef + needs to refer to a timer that was created by either + erlang:send_after(), + or erlang:start_timer().

+

Currently available Options:

+ + {async, Async} + +

Asynchronous request. Async defaults to false. That + is the operation will be performed synchronously, and the Result + will returned from read_timer(). When Async is set to + true, read_timer() will send a request for the + Result to a timer service that manages the timer and then + return ok. A message on the format + {read_timer, TimerRef, Result} + will be sent to the caller of read_timer() when + the operation has been processed.

+
+

When the Result equals false a timer + corresponding to TimerRef could not be found. This + can be either because the timer had expired, been canceled, or because + TimerRef do not correspond to a timer. When the + Result is an integer, it represents + the time in milli seconds left before the timer will expire.

+

The timer service that manages the timer may be co-located + with another scheduler than the scheduler that the calling process + is executing on. In this case communication with the timer + service will be performed using asynchronous signals. If the calling + process is in critical path and can do other things while waiting + for the result of this operation, you want to use the {async, true} + option.

See also - erlang:send_after/3, - erlang:start_timer/3, + erlang:send_after/4, + erlang:start_timer/4, and - erlang:cancel_timer/1.

+ erlang:cancel_timer/2.

+
+
+ + + Read the state of a timer + +

Read the state of a timer. The same as calling + erlang:read_timer(TimerRef, + [{async, false}]).

@@ -4669,6 +4740,63 @@ true + + + Start a timer + +

Starts a timer. When the timer expires, the message + Msg will be sent to + Dest.

+

If Dest is a pid() it has to + be a pid() of a local process, dead or alive.

+

Currently available Options:

+ + {abs, Abs} + +

Absolute timeout. When Abs is false + the Time value will be interpreted + as a time in milli-seconds relative current + Erlang + monotonic time. When Abs is true the + Time value will be interpreted as an absolute + Erlang monotonic time of milli second time unit. Abs + defaults to false.

+
+
+

The absolute time when the timer is set to expire needs + to be in the range between + erlang:system_info(start_time) + and + erlang:system_info(end_time). + If a negative relative time is specified the time is not + allowed to be negative.

+

If Dest is an atom(), it is supposed to be the name of + a registered process. The process referred to by the name is + looked up at the time of delivery. No error is given if + the name does not refer to a process.

+

If Dest is a pid(), the timer will be automatically + canceled if the process referred to by the pid() is not alive, + or when the process exits. This feature was introduced in + erts version 5.4.11. Note that timers will not be + automatically canceled when Dest is an atom().

+

See also + erlang:send_timer/4, + erlang:cancel_timer/2, + and + erlang:read_timer/2.

+

Failure: badarg if the arguments does not satisfy + the requirements specified above.

+
+
+ + + Start a timer + +

Starts a timer. The same as calling + erlang:send_after(Time, + Dest, Msg, [{abs, false}]).

+
+
0 <= Time <= 4294967295 @@ -4690,9 +4818,9 @@ true automatically canceled when Dest is an atom.

See also erlang:start_timer/3, - erlang:cancel_timer/1, + erlang:cancel_timer/2, and - erlang:read_timer/1.

+ erlang:read_timer/2.

Failure: badarg if the arguments does not satisfy the requirements specified above.

@@ -5100,15 +5228,35 @@ true
- - 0 <= Time <= 4294967295 + Start a timer -

Starts a timer which will send the message - {timeout, TimerRef, Msg} to Dest - after Time milliseconds.

-

If Dest is a pid() it has to be a pid() of a local process, dead or alive.

-

The Time value can, in the current implementation, not be greater than 4294967295.

+

Starts a timer. When the timer expires, the message + {timeout, TimerRef, Msg} + will be sent to Dest.

+

If Dest is a pid() it has to + be a pid() of a local process, dead or alive.

+

Currently available Options:

+ + {abs, Abs} + +

Absolute timeout. When Abs is false + the Time value will be interpreted + as a time in milli-seconds relative current + Erlang + monotonic time. When Abs is true the + Time value will be interpreted as an absolute + Erlang monotonic time of milli second time unit. Abs + defaults to false.

+
+
+

The absolute time when the timer is set to expire needs + to be in the range between + erlang:system_info(start_time) + and + erlang:system_info(end_time). + If a negative relative time is specified the time is not + allowed to be negative.

If Dest is an atom(), it is supposed to be the name of a registered process. The process referred to by the name is looked up at the time of delivery. No error is given if @@ -5119,14 +5267,23 @@ true erts version 5.4.11. Note that timers will not be automatically canceled when Dest is an atom().

See also - erlang:send_after/3, - erlang:cancel_timer/1, + erlang:send_after/4, + erlang:cancel_timer/2, and - erlang:read_timer/1.

+ erlang:read_timer/2.

Failure: badarg if the arguments does not satisfy the requirements specified above.

+ + + Start a timer + +

Starts a timer. The same as calling + erlang:start_timer(Time, + Dest, Msg, [{abs, false}]).

+
+
Information about context switches @@ -6236,6 +6393,14 @@ ok (i.e. system_info(dynamic_trace) returns dtrace or systemtap).

+ end_time +

The last Erlang monotonic + time in native + time unit that + can be represented internally in the current Erlang runtime system + instance. The time between the + start time and + the end time is at least a quarter of a millennium.

elib_malloc

This option will be removed in a future release. diff --git a/erts/emulator/beam/erl_alloc.c b/erts/emulator/beam/erl_alloc.c index 71902c2f9f..7d099997d8 100644 --- a/erts/emulator/beam/erl_alloc.c +++ b/erts/emulator/beam/erl_alloc.c @@ -2331,6 +2331,22 @@ erts_memory(int *print_to_p, void *print_to_arg, void *proc, Eterm earg) &size.processes_used, fi, ERTS_ALC_T_MSG_REF); + add_fix_values(&size.processes, + &size.processes_used, + fi, + ERTS_ALC_T_LL_PTIMER); + add_fix_values(&size.processes, + &size.processes_used, + fi, + ERTS_ALC_T_HL_PTIMER); + add_fix_values(&size.processes, + &size.processes_used, + fi, + ERTS_ALC_T_BIF_TIMER); + add_fix_values(&size.processes, + &size.processes_used, + fi, + ERTS_ALC_T_ABIF_TIMER); } if (want.atom || want.atom_used) { diff --git a/erts/emulator/beam/erl_hl_timer.c b/erts/emulator/beam/erl_hl_timer.c index 2245799819..f1806d25be 100644 --- a/erts/emulator/beam/erl_hl_timer.c +++ b/erts/emulator/beam/erl_hl_timer.c @@ -850,8 +850,6 @@ hl_timer_destroy(ErtsHLTimer *tmr) if (!(roflgs & ERTS_TMR_ROFLG_BIF_TMR)) erts_free(ERTS_ALC_T_HL_PTIMER, tmr); else { - if (tmr->btm.bp) - free_message_buffer(tmr->btm.bp); if (roflgs & ERTS_TMR_ROFLG_PRE_ALC) bif_timer_pre_free(tmr); else if (roflgs & ERTS_TMR_ROFLG_ABIF_TMR) @@ -898,10 +896,6 @@ schedule_hl_timer_destroy(ErtsHLTimer *tmr, Uint32 roflgs) * Message buffer can be dropped at * once... */ - if (tmr->btm.bp) { - free_message_buffer(tmr->btm.bp); - tmr->btm.bp = NULL; - } size = sizeof(ErtsHLTimer); } @@ -1121,6 +1115,7 @@ hlt_bif_timer_timeout(ErtsHLTimer *tmr, Uint32 roflgs) { ErtsProcLocks proc_locks = ERTS_PROC_LOCKS_MSG_SEND; Process *proc; + int queued_message = 0; int dec_refc = 0; Uint32 is_reg_name = (roflgs & ERTS_TMR_ROFLG_REG_NAME); ERTS_HLT_ASSERT(roflgs & ERTS_TMR_ROFLG_BIF_TMR); @@ -1159,6 +1154,7 @@ hlt_bif_timer_timeout(ErtsHLTimer *tmr, Uint32 roflgs) #endif ); erts_smp_proc_unlock(proc, ERTS_PROC_LOCKS_MSG_SEND); + queued_message = 1; proc_locks &= ~ERTS_PROC_LOCKS_MSG_SEND; tmr->btm.bp = NULL; if (tmr->btm.proc_tree.parent != ERTS_HLT_PFIELD_NOT_IN_TABLE) { @@ -1172,6 +1168,8 @@ hlt_bif_timer_timeout(ErtsHLTimer *tmr, Uint32 roflgs) if (dec_refc) hl_timer_pre_dec_refc(tmr); } + if (!queued_message && tmr->btm.bp) + free_message_buffer(tmr->btm.bp); } static ERTS_INLINE void @@ -1689,6 +1687,8 @@ setup_bif_timer(Process *c_p, ErtsMonotonicTime timeout_pos, rcvr, ERTS_PROC_LOCK_BTM, ERTS_P2P_FLG_INC_REFC); if (!proc) { + if (tmr->btm.bp) + free_message_buffer(tmr->btm.bp); hlt_delete_timer(esdp, tmr); hl_timer_destroy(tmr); } @@ -1721,6 +1721,9 @@ cancel_bif_timer(ErtsHLTimer *tmr) if (state != ERTS_TMR_STATE_ACTIVE) return 0; + if (tmr->btm.bp) + free_message_buffer(tmr->btm.bp); + res = -1; roflgs = tmr->head.roflgs; @@ -1834,18 +1837,25 @@ access_sched_local_btm(Process *c_p, Eterm pid, if (!async) hsz += REF_THING_SIZE; else { - if (is_non_value(tref)) + if (is_non_value(tref) || proc != c_p) hsz += REF_THING_SIZE; hsz += 1; /* upgrade to 3-tuple */ } if (time_left > (Sint64) MAX_SMALL) hsz += ERTS_SINT64_HEAP_SIZE(time_left); - hp = erts_alloc_message_heap(hsz, - &bp, - &ohp, - proc, - &proc_locks); + if (proc == c_p) { + bp = NULL; + ohp = NULL; + hp = HAlloc(c_p, hsz); + } + else { + hp = erts_alloc_message_heap(hsz, + &bp, + &ohp, + proc, + &proc_locks); + } #ifdef ERTS_HLT_DEBUG hp_end = hp + hsz; @@ -1871,7 +1881,7 @@ access_sched_local_btm(Process *c_p, Eterm pid, } else { Eterm tag = cancel ? am_cancel_timer : am_read_timer; - if (is_value(tref)) + if (is_value(tref) && proc == c_p) ref = tref; else { write_ref_thing(hp, @@ -1987,8 +1997,6 @@ try_access_sched_remote_btm(ErtsSchedulerData *esdp, } else { Eterm tag, res, msg; - ErlOffHeap *ohp; - ErlHeapFragment* bp; Uint hsz; Eterm *hp; ErtsProcLocks proc_locks = ERTS_PROC_LOCK_MAIN; @@ -1997,11 +2005,7 @@ try_access_sched_remote_btm(ErtsSchedulerData *esdp, if (time_left > (Sint64) MAX_SMALL) hsz += ERTS_SINT64_HEAP_SIZE(time_left); - hp = erts_alloc_message_heap(hsz, - &bp, - &ohp, - c_p, - &proc_locks); + hp = HAlloc(c_p, hsz); if (cancel) tag = am_cancel_timer; else @@ -2016,7 +2020,7 @@ try_access_sched_remote_btm(ErtsSchedulerData *esdp, msg = TUPLE3(hp, tag, tref, res); - erts_queue_message(c_p, &proc_locks, bp, + erts_queue_message(c_p, &proc_locks, NULL, msg, NIL #ifdef USE_VM_PROBES , NIL @@ -2166,7 +2170,7 @@ parse_bif_timer_options(Eterm option_list, int *async, int *info, if (async) *async = 0; if (info) - *info = 0; + *info = 1; if (abs) *abs = 0; if (accessor) @@ -2235,13 +2239,19 @@ exit_cancel_bif_timer(ErtsHLTimer *tmr, void *vesdp) tmr->btm.proc_tree.parent = ERTS_HLT_PFIELD_NOT_IN_TABLE; if (sid == (Uint32) esdp->no) { - if (state == ERTS_TMR_STATE_ACTIVE) + if (state == ERTS_TMR_STATE_ACTIVE) { + if (tmr->btm.bp) + free_message_buffer(tmr->btm.bp); hlt_delete_timer(esdp, tmr); + } hl_timer_dec_refc(tmr, roflgs); } else { - if (state == ERTS_TMR_STATE_ACTIVE) + if (state == ERTS_TMR_STATE_ACTIVE) { + if (tmr->btm.bp) + free_message_buffer(tmr->btm.bp); queue_canceled_timer(esdp, sid, (ErtsTimer *) tmr); + } else hl_timer_dec_refc(tmr, roflgs); } @@ -2415,7 +2425,7 @@ BIF_RETTYPE send_after_3(BIF_ALIST_3) tres = parse_timeout_pos(ERTS_PROC_GET_SCHDATA(BIF_P), BIF_ARG_1, NULL, 0, &timeout_pos, &short_time); if (tres != 0) - BIF_ERROR(BIF_P, tres < 0 ? BADARG : SYSTEM_LIMIT); + BIF_ERROR(BIF_P, BADARG); return setup_bif_timer(BIF_P, timeout_pos, short_time, BIF_ARG_2, BIF_ARG_2, BIF_ARG_3, 0); @@ -2433,7 +2443,7 @@ BIF_RETTYPE send_after_4(BIF_ALIST_4) tres = parse_timeout_pos(ERTS_PROC_GET_SCHDATA(BIF_P), BIF_ARG_1, NULL, abs, &timeout_pos, &short_time); if (tres != 0) - BIF_ERROR(BIF_P, tres < 0 ? BADARG : SYSTEM_LIMIT); + BIF_ERROR(BIF_P, BADARG); return setup_bif_timer(BIF_P, timeout_pos, short_time, BIF_ARG_2, accessor, BIF_ARG_3, 0); @@ -2447,7 +2457,7 @@ BIF_RETTYPE start_timer_3(BIF_ALIST_3) tres = parse_timeout_pos(ERTS_PROC_GET_SCHDATA(BIF_P), BIF_ARG_1, NULL, 0, &timeout_pos, &short_time); if (tres != 0) - BIF_ERROR(BIF_P, tres < 0 ? BADARG : SYSTEM_LIMIT); + BIF_ERROR(BIF_P, BADARG); return setup_bif_timer(BIF_P, timeout_pos, short_time, BIF_ARG_2, BIF_ARG_2, BIF_ARG_3, !0); @@ -2465,7 +2475,7 @@ BIF_RETTYPE start_timer_4(BIF_ALIST_4) tres = parse_timeout_pos(ERTS_PROC_GET_SCHDATA(BIF_P), BIF_ARG_1, NULL, abs, &timeout_pos, &short_time); if (tres != 0) - BIF_ERROR(BIF_P, tres < 0 ? BADARG : SYSTEM_LIMIT); + BIF_ERROR(BIF_P, BADARG); return setup_bif_timer(BIF_P, timeout_pos, short_time, BIF_ARG_2, accessor, BIF_ARG_3, !0); diff --git a/erts/emulator/beam/time.c b/erts/emulator/beam/time.c index ea19d8b362..8bffdedb2b 100644 --- a/erts/emulator/beam/time.c +++ b/erts/emulator/beam/time.c @@ -135,39 +135,40 @@ struct ErtsTimerWheel_ { ErtsMonotonicTime next_timeout_time; }; -/* get the time (in units of TIW_ITIME) to the next timeout, - or -1 if there are no timeouts */ - static ERTS_INLINE ErtsMonotonicTime -find_next_timeout(ErtsTimerWheel *tiw, - ErtsMonotonicTime curr_time, - ErtsMonotonicTime max_search_time) +find_next_timeout(ErtsSchedulerData *esdp, + ErtsTimerWheel *tiw, + int search_all, + ErtsMonotonicTime curr_time, /* When !search_all */ + ErtsMonotonicTime max_search_time) /* When !search_all */ { int start_ix, tiw_pos_ix; ErtsTWheelTimer *p; - int true_min_timeout; + int true_min_timeout = 0; ErtsMonotonicTime min_timeout, min_timeout_pos, slot_timeout_pos; - if (tiw->true_next_timeout_time) - return tiw->next_timeout_time; - if (tiw->nto == 0) { /* no timeouts in wheel */ - true_min_timeout = 0; - min_timeout_pos = ERTS_MONOTONIC_TO_CLKTCKS(curr_time + ERTS_MONOTONIC_DAY); + if (!search_all) + min_timeout_pos = tiw->pos; + else { + curr_time = erts_get_monotonic_time(esdp); + tiw->pos = min_timeout_pos = ERTS_MONOTONIC_TO_CLKTCKS(curr_time); + } + min_timeout_pos += ERTS_MONOTONIC_TO_CLKTCKS(ERTS_MONOTONIC_DAY); goto found_next; } - slot_timeout_pos = tiw->pos; - min_timeout_pos = ERTS_MONOTONIC_TO_CLKTCKS(curr_time + max_search_time); + slot_timeout_pos = min_timeout_pos = tiw->pos; + if (search_all) + min_timeout_pos += ERTS_MONOTONIC_TO_CLKTCKS(ERTS_MONOTONIC_DAY); + else + min_timeout_pos = ERTS_MONOTONIC_TO_CLKTCKS(curr_time + max_search_time); start_ix = tiw_pos_ix = (int) (tiw->pos & (ERTS_TIW_SIZE-1)); do { - slot_timeout_pos++; - if (slot_timeout_pos >= min_timeout_pos) { - true_min_timeout = 0; + if (++slot_timeout_pos >= min_timeout_pos) break; - } p = tiw->w[tiw_pos_ix]; @@ -269,24 +270,16 @@ remove_timer(ErtsTimerWheel *tiw, ErtsTWheelTimer *p) p->slot = ERTS_TWHEEL_SLOT_INACTIVE; -#if 0 - p->next = NULL; - p->prev = NULL; -#endif - tiw->nto--; } ErtsMonotonicTime erts_check_next_timeout_time(ErtsSchedulerData *esdp) { - /* - * Called before a scheduler is about to wait. We wont - * check more than 10 minutes into the future. - */ - return find_next_timeout(esdp->timer_wheel, - erts_get_monotonic_time(esdp), - ERTS_SEC_TO_MONOTONIC(10*60)); + ErtsTimerWheel *tiw = esdp->timer_wheel; + if (tiw->true_next_timeout_time) + return tiw->next_timeout_time; + return find_next_timeout(esdp, tiw, 1, 0, 0); } #ifndef ERTS_TW_DEBUG @@ -330,14 +323,11 @@ timeout_timer(ErtsTWheelTimer *p) { ErlTimeoutProc timeout; void *arg; -#if 0 - p->next = NULL; - p->prev = NULL; -#endif p->slot = ERTS_TWHEEL_SLOT_INACTIVE; timeout = p->u.func.timeout; arg = p->u.func.arg; (*timeout)(arg); + ASSERT_NO_LOCKED_LOCKS; } void @@ -508,7 +498,7 @@ erts_bump_timers(ErtsTimerWheel *tiw, ErtsMonotonicTime curr_time) tiw->next_timeout_time = curr_time + ERTS_MONOTONIC_DAY; /* Search at most two seconds ahead... */ - (void) find_next_timeout(tiw, curr_time, ERTS_SEC_TO_MONOTONIC(2)); + (void) find_next_timeout(NULL, tiw, 0, curr_time, ERTS_SEC_TO_MONOTONIC(2)); } Uint diff --git a/erts/emulator/test/after_SUITE.erl b/erts/emulator/test/after_SUITE.erl index 699a1436ce..c855481489 100644 --- a/erts/emulator/test/after_SUITE.erl +++ b/erts/emulator/test/after_SUITE.erl @@ -27,7 +27,8 @@ init_per_group/2,end_per_group/2, t_after/1, receive_after/1, receive_after_big/1, receive_after_errors/1, receive_var_zero/1, receive_zero/1, - multi_timeout/1, receive_after_32bit/1]). + multi_timeout/1, receive_after_32bit/1, + receive_after_blast/1]). -export([init_per_testcase/2, end_per_testcase/2]). @@ -40,7 +41,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [t_after, receive_after, receive_after_big, receive_after_errors, receive_var_zero, receive_zero, - multi_timeout, receive_after_32bit]. + multi_timeout, receive_after_32bit, receive_after_blast]. groups() -> []. @@ -244,4 +245,26 @@ recv_after_32bit(I, T) when I rem 2 =:= 0 -> receive after T -> exit(timeout) end; recv_after_32bit(_, _) -> receive after 16#ffffFFFF -> exit(timeout) end. - + +blaster() -> + receive + {go, TimeoutTime} -> + Tmo = TimeoutTime - erlang:monotonic_time(milli_seconds), + receive after Tmo -> ok end + end. + +spawn_blasters(0) -> + []; +spawn_blasters(N) -> + [spawn_monitor(fun () -> blaster() end)|spawn_blasters(N-1)]. + +receive_after_blast(Config) when is_list(Config) -> + PMs = spawn_blasters(10000), + TimeoutTime = erlang:monotonic_time(milli_seconds) + 5000, + lists:foreach(fun ({P, _}) -> P ! {go, TimeoutTime} end, PMs), + lists:foreach(fun ({P, M}) -> + receive + {'DOWN', M, process, P, normal} -> + ok + end + end, PMs). diff --git a/erts/emulator/test/system_info_SUITE.erl b/erts/emulator/test/system_info_SUITE.erl index 0fd6696536..e3ac2d5d83 100644 --- a/erts/emulator/test/system_info_SUITE.erl +++ b/erts/emulator/test/system_info_SUITE.erl @@ -264,6 +264,37 @@ memory_test(_Config) -> []), cmp_memory(MWs, "unlink procs"), + mem_workers_call(MWs, + fun () -> + lists:foreach( + fun (P) -> + Tmr = erlang:start_timer(1 bsl 34, + P, + hello), + Tmrs = case get('BIF_TMRS') of + undefined -> []; + Rs -> Rs + end, + true = is_reference(Tmr), + put('BIF_TMRS', [Tmr|Tmrs]) + end, Ps) + end, + []), + cmp_memory(MWs, "start BIF timer procs"), + + mem_workers_call(MWs, + fun () -> + lists:foreach(fun (Tmr) -> + true = is_reference(Tmr), + true = is_integer(erlang:cancel_timer(Tmr)) + end, get('BIF_TMRS')), + put('BIF_TMRS', undefined), + garbage_collect() + end, + []), + erts_debug:set_internal_state(wait, deallocations), + cmp_memory(MWs, "cancel BIF timer procs"), + DMs = mem_workers_call(MWs, fun () -> lists:map(fun (P) -> diff --git a/erts/emulator/test/timer_bif_SUITE.erl b/erts/emulator/test/timer_bif_SUITE.erl index 3701dd8437..d406456f98 100644 --- a/erts/emulator/test/timer_bif_SUITE.erl +++ b/erts/emulator/test/timer_bif_SUITE.erl @@ -26,11 +26,17 @@ cancel_timer_1/1, start_timer_big/1, send_after_big/1, start_timer_e/1, send_after_e/1, cancel_timer_e/1, - read_timer_trivial/1, read_timer/1, - cleanup/1, evil_timers/1, registered_process/1]). + read_timer_trivial/1, read_timer/1, read_timer_async/1, + cleanup/1, evil_timers/1, registered_process/1, same_time_yielding/1, + same_time_yielding_with_cancel/1, same_time_yielding_with_cancel_other/1, + same_time_yielding_with_cancel_other_accessor/1, auto_cancel_yielding/1]). -include_lib("test_server/include/test_server.hrl"). +-define(SHORT_TIMEOUT, 5000). %% Bif timers as short as this may be pre-allocated +-define(TIMEOUT_YIELD_LIMIT, 100). +-define(AUTO_CANCEL_YIELD_LIMIT, 100). + init_per_testcase(_Case, Config) -> ?line Dog=test_server:timetrap(test_server:seconds(30)), case catch erts_debug:get_internal_state(available_internal_state) of @@ -45,6 +51,7 @@ end_per_testcase(_Case, Config) -> ok. init_per_suite(Config) -> + erts_debug:set_internal_state(available_internal_state, true), Config. end_per_suite(_Config) -> @@ -56,8 +63,12 @@ all() -> [start_timer_1, send_after_1, send_after_2, cancel_timer_1, start_timer_e, send_after_e, cancel_timer_e, start_timer_big, send_after_big, - read_timer_trivial, read_timer, cleanup, evil_timers, - registered_process]. + read_timer_trivial, read_timer, read_timer_async, + cleanup, evil_timers, registered_process, + same_time_yielding, same_time_yielding_with_cancel, + same_time_yielding_with_cancel_other, + same_time_yielding_with_cancel_other_accessor, + auto_cancel_yielding]. groups() -> []. @@ -162,7 +173,7 @@ cancel_timer_1(Config) when is_list(Config) -> start_timer_e(doc) -> ["Error cases for start_timer/3"]; start_timer_e(Config) when is_list(Config) -> ?line {'EXIT', _} = (catch erlang:start_timer(-4, self(), hej)), - ?line {'EXIT', _} = (catch erlang:start_timer(4728472847827482, + ?line {'EXIT', _} = (catch erlang:start_timer(1 bsl 64, self(), hej)), ?line {'EXIT', _} = (catch erlang:start_timer(4.5, self(), hej)), @@ -180,7 +191,7 @@ send_after_e(doc) -> ["Error cases for send_after/3"]; send_after_e(suite) -> []; send_after_e(Config) when is_list(Config) -> ?line {'EXIT', _} = (catch erlang:send_after(-4, self(), hej)), - ?line {'EXIT', _} = (catch erlang:send_after(4728472847827482, + ?line {'EXIT', _} = (catch erlang:send_after(1 bsl 64, self(), hej)), ?line {'EXIT', _} = (catch erlang:send_after(4.5, self(), hej)), @@ -213,44 +224,79 @@ read_timer_trivial(Config) when is_list(Config) -> read_timer(doc) -> ["Test that read_timer/1 seems to return the correct values."]; read_timer(suite) -> []; read_timer(Config) when is_list(Config) -> - ?line Big = 1 bsl 31, - ?line R = erlang:send_after(Big, self(), hej_hopp), + process_flag(scheduler, 1), + Big = 1 bsl 31, + R = erlang:send_after(Big, self(), hej_hopp), + + receive after 200 -> ok end, % Delay and clear reductions. + Left = erlang:read_timer(R), + Left2 = erlang:cancel_timer(R), + case Left == Left2 of + true -> ok; + false -> Left = Left2 + 1 + end, + false = erlang:read_timer(R), - ?line receive after 200 -> ok end, % Delay and clear reductions. - ?line Left = erlang:read_timer(R), - ?line Left = erlang:cancel_timer(R), - ?line false = erlang:read_timer(R), + case Big - Left of + Diff when Diff >= 200, Diff < 10000 -> + ok; + _Diff -> + test_server:fail({big, Big, Left}) + end, + process_flag(scheduler, 0), + ok. - ?line case Big - Left of - Diff when Diff >= 200, Diff < 10000 -> - ok; - _Diff -> - test_server:fail({big, Big, Left}) - end, +read_timer_async(doc) -> ["Test that read_timer/1 seems to return the correct values."]; +read_timer_async(suite) -> []; +read_timer_async(Config) when is_list(Config) -> + process_flag(scheduler, 1), + Big = 1 bsl 33, + R = erlang:send_after(Big, self(), hej_hopp), + + %% Access from another scheduler + process_flag(scheduler, erlang:system_info(schedulers_online)), + + receive after 200 -> ok end, % Delay and clear reductions. + ok = erlang:read_timer(R, [{async, true}]), + ok = erlang:cancel_timer(R, [{async, true}, {info, true}]), + ok = erlang:read_timer(R, [{async, true}]), + + {read_timer, R, Left} = receive_one(), + {cancel_timer, R, Left2} = receive_one(), + case Left == Left2 of + true -> ok; + false -> Left = Left2 + 1 + end, + {read_timer, R, false} = receive_one(), + + case Big - Left of + Diff when Diff >= 200, Diff < 10000 -> + ok; + _Diff -> + test_server:fail({big, Big, Left}) + end, + process_flag(scheduler, 0), ok. cleanup(doc) -> []; cleanup(suite) -> []; cleanup(Config) when is_list(Config) -> - {skipped, "Test needs to be UPDATED for new timer implementation"}. - -cleanup_test(Config) when is_list(Config) -> ?line Mem = mem(), %% Timer on dead process ?line P1 = spawn(fun () -> ok end), ?line wait_until(fun () -> process_is_cleaned_up(P1) end), - ?line T1 = erlang:start_timer(10000, P1, "hej"), - ?line T2 = erlang:send_after(10000, P1, "hej"), + ?line T1 = erlang:start_timer(?SHORT_TIMEOUT*2, P1, "hej"), + ?line T2 = erlang:send_after(?SHORT_TIMEOUT*2, P1, "hej"), receive after 1000 -> ok end, ?line Mem = mem(), ?line false = erlang:read_timer(T1), ?line false = erlang:read_timer(T2), ?line Mem = mem(), %% Process dies before timeout - ?line P2 = spawn(fun () -> receive after 500 -> ok end end), - ?line T3 = erlang:start_timer(10000, P2, "hej"), - ?line T4 = erlang:send_after(10000, P2, "hej"), - ?line true = Mem < mem(), + ?line P2 = spawn(fun () -> receive after (?SHORT_TIMEOUT div 10) -> ok end end), + ?line T3 = erlang:start_timer(?SHORT_TIMEOUT*2, P2, "hej"), + ?line T4 = erlang:send_after(?SHORT_TIMEOUT*2, P2, "hej"), + ?line true = mem_larger_than(Mem), ?line true = is_integer(erlang:read_timer(T3)), ?line true = is_integer(erlang:read_timer(T4)), ?line wait_until(fun () -> process_is_cleaned_up(P2) end), @@ -259,21 +305,22 @@ cleanup_test(Config) when is_list(Config) -> ?line false = erlang:read_timer(T4), ?line Mem = mem(), %% Cancel timer - ?line P3 = spawn(fun () -> receive after 20000 -> ok end end), - ?line T5 = erlang:start_timer(10000, P3, "hej"), - ?line T6 = erlang:send_after(10000, P3, "hej"), - ?line true = Mem < mem(), + ?line P3 = spawn(fun () -> receive after ?SHORT_TIMEOUT*4 -> ok end end), + ?line T5 = erlang:start_timer(?SHORT_TIMEOUT*2, P3, "hej"), + ?line T6 = erlang:send_after(?SHORT_TIMEOUT*2, P3, "hej"), + ?line true = mem_larger_than(Mem), ?line true = is_integer(erlang:cancel_timer(T5)), ?line true = is_integer(erlang:cancel_timer(T6)), ?line false = erlang:read_timer(T5), ?line false = erlang:read_timer(T6), ?line exit(P3, kill), + ?line wait_until(fun () -> process_is_cleaned_up(P3) end), ?line Mem = mem(), %% Timeout ?line Ref = make_ref(), - ?line T7 = erlang:start_timer(500, self(), Ref), - ?line T8 = erlang:send_after(500, self(), Ref), - ?line true = Mem < mem(), + ?line T7 = erlang:start_timer(?SHORT_TIMEOUT+1, self(), Ref), + ?line T8 = erlang:send_after(?SHORT_TIMEOUT+1, self(), Ref), + ?line true = mem_larger_than(Mem), ?line true = is_integer(erlang:read_timer(T7)), ?line true = is_integer(erlang:read_timer(T8)), ?line receive {timeout, T7, Ref} -> ok end, @@ -423,15 +470,12 @@ evil_recv_timeouts(TOs, N, M) -> registered_process(doc) -> []; registered_process(suite) -> []; registered_process(Config) when is_list(Config) -> - {skipped, "Test needs to be UPDATED for new timer implementation"}. - -registered_process_test(Config) when is_list(Config) -> ?line Mem = mem(), %% Cancel - ?line T1 = erlang:start_timer(500, ?MODULE, "hej"), - ?line T2 = erlang:send_after(500, ?MODULE, "hej"), + ?line T1 = erlang:start_timer(?SHORT_TIMEOUT+1, ?MODULE, "hej"), + ?line T2 = erlang:send_after(?SHORT_TIMEOUT+1, ?MODULE, "hej"), ?line undefined = whereis(?MODULE), - ?line true = Mem < mem(), + ?line true = mem_larger_than(Mem), ?line true = is_integer(erlang:cancel_timer(T1)), ?line true = is_integer(erlang:cancel_timer(T2)), ?line false = erlang:read_timer(T1), @@ -439,10 +483,10 @@ registered_process_test(Config) when is_list(Config) -> ?line Mem = mem(), %% Timeout register after start ?line Ref1 = make_ref(), - ?line T3 = erlang:start_timer(500, ?MODULE, Ref1), - ?line T4 = erlang:send_after(500, ?MODULE, Ref1), + ?line T3 = erlang:start_timer(?SHORT_TIMEOUT+1, ?MODULE, Ref1), + ?line T4 = erlang:send_after(?SHORT_TIMEOUT+1, ?MODULE, Ref1), ?line undefined = whereis(?MODULE), - ?line true = Mem < mem(), + ?line true = mem_larger_than(Mem), ?line true = is_integer(erlang:read_timer(T3)), ?line true = is_integer(erlang:read_timer(T4)), ?line true = register(?MODULE, self()), @@ -451,9 +495,9 @@ registered_process_test(Config) when is_list(Config) -> ?line Mem = mem(), %% Timeout register before start ?line Ref2 = make_ref(), - ?line T5 = erlang:start_timer(500, ?MODULE, Ref2), - ?line T6 = erlang:send_after(500, ?MODULE, Ref2), - ?line true = Mem < mem(), + ?line T5 = erlang:start_timer(?SHORT_TIMEOUT+1, ?MODULE, Ref2), + ?line T6 = erlang:send_after(?SHORT_TIMEOUT+1, ?MODULE, Ref2), + ?line true = mem_larger_than(Mem), ?line true = is_integer(erlang:read_timer(T5)), ?line true = is_integer(erlang:read_timer(T6)), ?line receive {timeout, T5, Ref2} -> ok end, @@ -462,19 +506,135 @@ registered_process_test(Config) when is_list(Config) -> ?line true = unregister(?MODULE), ?line ok. -mem() -> - TSrvs = erts_internal:get_bif_timer_servers(), - lists:foldl(fun (Tab, Sz) -> - case lists:member(ets:info(Tab, owner), TSrvs) of - true -> - ets:info(Tab, memory) + Sz; - false -> - Sz - end - end, - 0, - ets:all())*erlang:system_info({wordsize,external}). - +same_time_yielding(Config) when is_list(Config) -> + Mem = mem(), + SchdlrsOnln = erlang:system_info(schedulers_online), + Tmo = erlang:monotonic_time(milli_seconds) + 3000, + Tmrs = lists:map(fun (I) -> + process_flag(scheduler, (I rem SchdlrsOnln) + 1), + erlang:start_timer(Tmo, self(), hej, [{abs, true}]) + end, + lists:seq(1, (?TIMEOUT_YIELD_LIMIT*3+1)*SchdlrsOnln)), + true = mem_larger_than(Mem), + lists:foreach(fun (Tmr) -> receive {timeout, Tmr, hej} -> ok end end, Tmrs), + Done = erlang:monotonic_time(milli_seconds), + true = Done >= Tmo, + case erlang:system_info(build_type) of + opt -> true = Done < Tmo + 200; + _ -> true = Done < Tmo + 1000 + end, + Mem = mem(), + ok. + +same_time_yielding_with_cancel(Config) when is_list(Config) -> + same_time_yielding_with_cancel_test(false, false). + +same_time_yielding_with_cancel_other(Config) when is_list(Config) -> + same_time_yielding_with_cancel_test(true, false). + +same_time_yielding_with_cancel_other_accessor(Config) when is_list(Config) -> + same_time_yielding_with_cancel_test(true, true). + +do_cancel_tmrs(Tmo, Tmrs, Tester) -> + BeginCancel = erlang:convert_time_unit(Tmo, + milli_seconds, + micro_seconds) - 100, + busy_wait_until(fun () -> + erlang:monotonic_time(micro_seconds) >= BeginCancel + end), + lists:foreach(fun (Tmr) -> + erlang:cancel_timer(Tmr, + [{async, true}, + {info, true}]) + end, Tmrs), + case Tester == self() of + true -> ok; + false -> forward_msgs(Tester) + end. + +same_time_yielding_with_cancel_test(Other, Accessor) -> + Mem = mem(), + SchdlrsOnln = erlang:system_info(schedulers_online), + Tmo = erlang:monotonic_time(milli_seconds) + 3000, + Tester = self(), + Cancelor = case Other of + false -> + Tester; + true -> + spawn(fun () -> + receive + {timers, Tmrs} -> + do_cancel_tmrs(Tmo, Tmrs, Tester) + end + end) + end, + Opts = case Accessor of + false -> [{abs, true}]; + true -> [{accessor, Cancelor}, {abs, true}] + end, + Tmrs = lists:map(fun (I) -> + process_flag(scheduler, (I rem SchdlrsOnln) + 1), + erlang:start_timer(Tmo, self(), hej, Opts) + end, + lists:seq(1, (?TIMEOUT_YIELD_LIMIT*3+1)*SchdlrsOnln)), + true = mem_larger_than(Mem), + case Other of + false -> + do_cancel_tmrs(Tmo, Tmrs, Tester); + true -> + Cancelor ! {timers, Tmrs} + end, + {Tmos, Cncls} = lists:foldl(fun (Tmr, {T, C}) -> + receive + {timeout, Tmr, hej} -> + receive + {cancel_timer, Tmr, Info} -> + false = Info, + {T+1, C} + end; + {cancel_timer, Tmr, false} -> + receive + {timeout, Tmr, hej} -> + {T+1, C} + end; + {cancel_timer, Tmr, TimeLeft} -> + true = is_integer(TimeLeft), + {T, C+1} + end + end, + {0, 0}, + Tmrs), + io:format("Timeouts: ~p Cancels: ~p~n", [Tmos, Cncls]), + Mem = mem(), + case Other of + true -> exit(Cancelor, bang); + false -> ok + end, + {comment, + "Timeouts: " ++ integer_to_list(Tmos) ++ " Cancels: " + ++ integer_to_list(Cncls)}. + +auto_cancel_yielding(Config) when is_list(Config) -> + Mem = mem(), + SchdlrsOnln = erlang:system_info(schedulers_online), + P = spawn(fun () -> + lists:foreach( + fun (I) -> + process_flag(scheduler, (I rem SchdlrsOnln)+1), + erlang:start_timer((1 bsl 28)+I*10, self(), hej) + end, + lists:seq(1, + ((?AUTO_CANCEL_YIELD_LIMIT*3+1) + *SchdlrsOnln))), + receive after infinity -> ok end + end), + true = mem_larger_than(Mem), + exit(P, bang), + wait_until(fun () -> process_is_cleaned_up(P) end), + receive after 1000 -> ok end, + Mem = mem(), + ok. + process_is_cleaned_up(P) when is_pid(P) -> undefined == erts_debug:get_internal_state({process_status, P}). @@ -484,6 +644,19 @@ wait_until(Pred) when is_function(Pred) -> _ -> receive after 50 -> ok end, wait_until(Pred) end. +busy_wait_until(Pred) when is_function(Pred) -> + case catch Pred() of + true -> ok; + _ -> busy_wait_until(Pred) + end. + +forward_msgs(To) -> + receive + Msg -> + To ! Msg + end, + forward_msgs(To). + get(Time, Msg) -> receive Msg -> @@ -566,5 +739,58 @@ type(X) when is_port(X) -> {port, node(X)}; type(X) when is_binary(X) -> binary; type(X) when is_atom(X) -> atom; type(_) -> unknown. - + +mem_larger_than(no_fix_alloc) -> + true; +mem_larger_than(Mem) -> + mem() > Mem. + +mem() -> + erts_debug:set_internal_state(wait, deallocations), + erts_debug:set_internal_state(wait, deallocations), + case mem_get() of + {-1, -1} -> no_fix_alloc; + {A, U} -> io:format("mem = ~p ~p~n", [A, U]), U + end. + +mem_get() -> + % Bif timer memory + Ref = make_ref(), + erlang:system_info({memory_internal, Ref, [fix_alloc]}), + mem_recv(erlang:system_info(schedulers), Ref, {0, 0}). + +mem_recv(0, _Ref, AU) -> + AU; +mem_recv(N, Ref, AU) -> + receive + {Ref, _, IL} -> + mem_recv(N-1, Ref, mem_parse_ilists(IL, AU)) + end. + + +mem_parse_ilists([], AU) -> + AU; +mem_parse_ilists([I|Is], AU) -> + mem_parse_ilists(Is, mem_parse_ilist(I, AU)). + +mem_parse_ilist({fix_alloc, false}, _) -> + {-1, -1}; +mem_parse_ilist({fix_alloc, _, IDL}, {A, U}) -> + case lists:keyfind(fix_types, 1, IDL) of + {fix_types, TL} -> + {ThisA, ThisU} = mem_get_btm_aus(TL, 0, 0), + {ThisA + A, ThisU + U}; + {fix_types, Mask, TL} -> + {ThisA, ThisU} = mem_get_btm_aus(TL, 0, 0), + {(ThisA + A) band Mask , (ThisU + U) band Mask} + end. + +mem_get_btm_aus([], A, U) -> + {A, U}; +mem_get_btm_aus([{BtmType, BtmA, BtmU} | Types], + A, U) when BtmType == bif_timer; + BtmType == accessor_bif_timer -> + mem_get_btm_aus(Types, BtmA+A, BtmU+U); +mem_get_btm_aus([_|Types], A, U) -> + mem_get_btm_aus(Types, A, U). diff --git a/erts/preloaded/ebin/erlang.beam b/erts/preloaded/ebin/erlang.beam index ce61199567..aa9d8ae4fe 100644 Binary files a/erts/preloaded/ebin/erlang.beam and b/erts/preloaded/ebin/erlang.beam differ diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl index 6aea2c08e4..ba7878bd2c 100644 --- a/erts/preloaded/src/erlang.erl +++ b/erts/preloaded/src/erlang.erl @@ -425,19 +425,23 @@ call_on_load_function(_P1) -> erlang:nif_error(undefined). %% cancel_timer/1 --spec erlang:cancel_timer(TimerRef) -> Time | false when +-spec erlang:cancel_timer(TimerRef) -> Result when TimerRef :: reference(), - Time :: non_neg_integer(). + Time :: non_neg_integer(), + Result :: Time | false. cancel_timer(_TimerRef) -> erlang:nif_error(undefined). %% cancel_timer/2 --spec erlang:cancel_timer(TimerRef, Options) -> Time | false | ok when +-spec erlang:cancel_timer(TimerRef, Options) -> Result | ok when TimerRef :: reference(), - Option :: {async, boolean()} | {info, boolean()}, + Async :: boolean(), + Info :: boolean(), + Option :: {async, Async} | {info, Info}, Options :: [Option], - Time :: non_neg_integer(). + Time :: non_neg_integer(), + Result :: Time | false. cancel_timer(_TimerRef, _Options) -> erlang:nif_error(undefined). @@ -1478,17 +1482,22 @@ raise(_Class, _Reason, _Stacktrace) -> erlang:nif_error(undefined). %% read_timer/1 --spec erlang:read_timer(TimerRef) -> non_neg_integer() | false when - TimerRef :: reference(). +-spec erlang:read_timer(TimerRef) -> Result when + TimerRef :: reference(), + Time :: non_neg_integer(), + Result :: Time | false. read_timer(_TimerRef) -> erlang:nif_error(undefined). %% read_timer/2 --spec erlang:read_timer(TimerRef, Options) -> non_neg_integer() | false | ok when +-spec erlang:read_timer(TimerRef, Options) -> Result | ok when TimerRef :: reference(), - Option :: {async, boolean()}, - Options :: [Option]. + Async :: boolean(), + Option :: {async, Async}, + Options :: [Option], + Time :: non_neg_integer(), + Result :: Time | false. read_timer(_TimerRef, _Options) -> erlang:nif_error(undefined). @@ -1547,7 +1556,8 @@ send_after(_Time, _Dest, _Msg) -> Dest :: pid() | atom(), Msg :: term(), Options :: [Option], - Option :: {abs, boolean()} | {accessor, pid()}, + Abs :: boolean(), + Option :: {abs, Abs}, %% | {accessor, Accessor} undocumented feature for now, TimerRef :: reference(). send_after(_Time, _Dest, _Msg, _Options) -> @@ -1634,7 +1644,8 @@ start_timer(_Time, _Dest, _Msg) -> Dest :: pid() | atom(), Msg :: term(), Options :: [Option], - Option :: {abs, boolean()} | {accessor, pid()}, + Abs :: boolean(), + Option :: {abs, Abs}, %% | {accessor, Accessor} undocumented feature for now, TimerRef :: reference(). start_timer(_Time, _Dest, _Msg, _Options) -> @@ -3589,7 +3600,11 @@ blocks_size([], Acc) -> get_fix_proc([{ProcType, A1, U1}| Rest], {A0, U0}) when ProcType == proc; ProcType == monitor_sh; ProcType == nlink_sh; - ProcType == msg_ref -> + ProcType == msg_ref; + ProcType == ll_ptimer; + ProcType == hl_ptimer; + ProcType == bif_timer; + ProcType == accessor_bif_timer -> get_fix_proc(Rest, {A0+A1, U0+U1}); get_fix_proc([_|Rest], Acc) -> get_fix_proc(Rest, Acc); -- cgit v1.2.3