Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:24
erlang
2331-Modernize-and-improve-the-timer-module.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 2331-Modernize-and-improve-the-timer-module.patch of Package erlang
From 06cacbca485bbd4dc64e24d7e9fe743fb0a3610b Mon Sep 17 00:00:00 2001 From: Maria-12648430 <maria-12648430@hnc-agency.org> Date: Fri, 7 May 2021 13:31:16 +0200 Subject: [PATCH] Modernize and improve the timer module Remove most of the timer management overhead from the single timer server process, and decentralize the actual process of timing, to make the timer server more efficient and less prone to becoming overloaded. The outward behavior of the timer module is unchanged. - Start the timer server under kernel_sup instead of kernel_safe_sup. - Move argument validations out of the timer server and into the API functions. - One-shot send timers are using erlang:send_after and erlang:cancel_timer in the client process, without going through the timer server, when the destination is a pid on the local node. - The timer server does not do any actual timing any more but only executes actions and reschedules interval timers. Instead, erlang:start_timer is used for the timing. - The timer table is not named any more, an ets:tid() is carried in a state variable instead. - Processes related to interval timers are monitored (vs linked) by the timer server in order to cancel them when the related process dies. - timer:sleep/1 now accepts arbitrarily high integer values. Integer values above the limit of what is allowed in the after clause of a receive expression are split up. - Document that send_after and send_interval accept {RegName, Node} as message destinations. - The function get_status/0 that was only used in tests has been removed, the tests now use sys:get_state instead. - Test return values. --- lib/stdlib/doc/src/timer.xml | 33 +- lib/stdlib/src/timer.erl | 693 ++++++++++-------- lib/stdlib/test/timer_SUITE.erl | 10 +- lib/stdlib/test/timer_simple_SUITE.erl | 317 ++++++-- system/doc/efficiency_guide/commoncaveats.xml | 15 +- 5 files changed, 681 insertions(+), 387 deletions(-) diff --git a/lib/stdlib/doc/src/timer.xml b/lib/stdlib/doc/src/timer.xml index 56342b94be..8eb3079a63 100644 --- a/lib/stdlib/doc/src/timer.xml +++ b/lib/stdlib/doc/src/timer.xml @@ -50,7 +50,9 @@ <p>Creating timers using <seemfa marker="erts:erlang#send_after/3">erlang:send_after/3</seemfa> and <seemfa marker="erts:erlang#start_timer/3">erlang:start_timer/3</seemfa> - is much more efficient than using the timers provided by this module. See + is more efficient than using the timers provided by this module. However, + the timer module has been improved in OTP 25, making it more efficient and + less susceptible to being overloaded. See <seeguide marker="system/efficiency_guide:commoncaveats#timer-module">the Timer Module section in the Efficiency Guide</seeguide>.</p> </description> @@ -116,8 +118,9 @@ <c>exit_after(<anno>Time</anno>, self(), <anno>Reason1</anno>)</c>.</p> <p><c>exit_after/3</c> sends an exit signal with reason - <c><anno>Reason1</anno></c> to - pid <c><anno>Pid</anno></c>. Returns <c>{ok, <anno>TRef</anno>}</c> + <c><anno>Reason1</anno></c> to <c><anno>Target</anno></c>, + which can be a local process identifier or an atom of a registered + name. Returns <c>{ok, <anno>TRef</anno>}</c> or <c>{error, <anno>Reason2</anno>}</c>.</p> </desc> </func> @@ -149,7 +152,7 @@ <p><c>kill_after/1</c> is the same as <c>exit_after(<anno>Time</anno>, self(), kill)</c>.</p> <p><c>kill_after/2</c> is the same as - <c>exit_after(<anno>Time</anno>, <anno>Pid</anno>, kill)</c>.</p> + <c>exit_after(<anno>Time</anno>, <anno>Target</anno>, kill)</c>.</p> </desc> </func> @@ -190,15 +193,16 @@ <func> <name name="send_after" arity="2" since=""/> <name name="send_after" arity="3" since=""/> - <fsummary>Send <c>Message</c> to <c>Pid</c> after a specified + <fsummary>Send <c>Message</c> to <c>Destination</c> after a specified <c>Time</c>.</fsummary> <desc> <taglist> <tag><c>send_after/3</c></tag> <item> - <p>Evaluates <c><anno>Pid</anno> ! <anno>Message</anno></c> after - <c><anno>Time</anno></c> milliseconds. (<c><anno>Pid</anno></c> - can also be an atom of a registered name.)</p> + <p>Evaluates <c><anno>Destination</anno> ! <anno>Message</anno></c> after + <c><anno>Time</anno></c> milliseconds. (<c><anno>Destination</anno></c> can + be a remote or local process identifier, an atom of a registered name or + a tuple <c>{RegName, Node}</c> for a registered name at another node.)</p> <p>Returns <c>{ok, <anno>TRef</anno>}</c> or <c>{error, <anno>Reason</anno>}</c>.</p> <p>See also @@ -223,10 +227,11 @@ <taglist> <tag><c>send_interval/3</c></tag> <item> - <p>Evaluates <c><anno>Pid</anno> ! <anno>Message</anno></c> + <p>Evaluates <c><anno>Destination</anno> ! <anno>Message</anno></c> repeatedly after <c><anno>Time</anno></c> milliseconds. - (<c><anno>Pid</anno></c> can also be - an atom of a registered name.)</p> + (<c><anno>Destination</anno></c> can be a remote or local process + identifier, an atom of a registered name or a tuple <c>{RegName, Node}</c> + for a registered name at another node.)</p> <p>Returns <c>{ok, <anno>TRef</anno>}</c> or <c>{error, <anno>Reason</anno>}</c>.</p> </item> @@ -249,6 +254,11 @@ or suspends the process forever if <c><anno>Time</anno></c> is the atom <c>infinity</c>. Naturally, this function does <em>not</em> return immediately.</p> + <note> + <p>Before OTP 25, <c>timer:sleep/1</c> did not accept integer + timeout values greater than <c>16#ffffffff</c>, that is, <c>2^32-1</c>. + Since OTP 25, arbitrarily high integer values are accepted.</p> + </note> </desc> </func> @@ -350,4 +360,3 @@ timer:cancel(R), <seemfa marker="#cancel/1"><c>cancel/1</c></seemfa>.</p> </section> </erlref> - diff --git a/lib/stdlib/src/timer.erl b/lib/stdlib/src/timer.erl index df10790ea0..8ae3ef03d7 100644 --- a/lib/stdlib/src/timer.erl +++ b/lib/stdlib/src/timer.erl @@ -26,129 +26,198 @@ cancel/1, sleep/1, tc/1, tc/2, tc/3, now_diff/2, seconds/1, minutes/1, hours/1, hms/3]). --export([start_link/0, start/0, - handle_call/3, handle_info/2, +-export([start_link/0, start/0, + handle_call/3, handle_info/2, init/1, code_change/3, handle_cast/2, terminate/2]). -%% internal exports for test purposes only --export([get_status/0]). - -%% types which can be used by other modules +%% Types which can be used by other modules -export_type([tref/0]). -%% Max --define(MAX_TIMEOUT, 16#0800000). --define(TIMER_TAB, timer_tab). --define(INTERVAL_TAB, timer_interval_tab). +%% Max value for a receive's after clause. +-define(MAX_RECEIVE_AFTER, 16#ffffffff). + +%% Validations +-define(valid_time(T), is_integer(T), T >= 0). +-define(valid_mfa(M, F, A), is_atom(M), is_atom(F), is_list(A)). %% %% Time is in milliseconds. %% --opaque tref() :: {integer(), reference()}. +-opaque tref() :: {type(), reference()}. +-type type() :: 'once' | 'interval' | 'instant' | 'send_local'. -type time() :: non_neg_integer(). %% %% Interface functions %% -spec apply_after(Time, Module, Function, Arguments) -> - {'ok', TRef} | {'error', Reason} when - Time :: time(), - Module :: module(), - Function :: atom(), - Arguments :: [term()], - TRef :: tref(), - Reason :: term(). - -apply_after(Time, M, F, A) -> - req(apply_after, {Time, {M, F, A}}). - --spec send_after(Time, Pid, Message) -> {'ok', TRef} | {'error', Reason} when - Time :: time(), - Pid :: pid() | (RegName :: atom()), - Message :: term(), - TRef :: tref(), - Reason :: term(). -send_after(Time, Pid, Message) -> - req(apply_after, {Time, {?MODULE, send, [Pid, Message]}}). - --spec send_after(Time, Message) -> {'ok', TRef} | {'error', Reason} when - Time :: time(), - Message :: term(), - TRef :: tref(), - Reason :: term(). + {'ok', TRef} | {'error', Reason} + when Time :: time(), + Module :: module(), + Function :: atom(), + Arguments :: [term()], + TRef :: tref(), + Reason :: term(). +apply_after(0, M, F, A) + when ?valid_mfa(M, F, A) -> + do_apply({M, F, A}), + {ok, {instant, make_ref()}}; +apply_after(Time, M, F, A) + when ?valid_time(Time), + ?valid_mfa(M, F, A) -> + req(apply_once, {system_time(), Time, {M, F, A}}); +apply_after(_Time, _M, _F, _A) -> + {error, badarg}. + +-spec send_after(Time, Destination, Message) -> {'ok', TRef} | {'error', Reason} + when Time :: time(), + Destination :: pid() | (RegName :: atom()) | {RegName :: atom(), Node :: node()}, + Message :: term(), + TRef :: tref(), + Reason :: term(). +send_after(0, PidOrRegName, Message) + when is_pid(PidOrRegName); + is_atom(PidOrRegName) -> + PidOrRegName ! Message, + {ok, {instant, make_ref()}}; +send_after(0, {RegName, Node} = Dest, Message) + when is_atom(RegName), + is_atom(Node) -> + Dest ! Message, + {ok, {instant, make_ref()}}; +send_after(Time, Pid, Message) + when ?valid_time(Time), + is_pid(Pid), + node(Pid) =:= node() -> + TRef = erlang:send_after(Time, Pid, Message), + {ok, {send_local, TRef}}; +send_after(Time, Pid, Message) + when is_pid(Pid) -> + apply_after(Time, ?MODULE, send, [Pid, Message]); +send_after(Time, RegName, Message) + when is_atom(RegName) -> + apply_after(Time, ?MODULE, send, [RegName, Message]); +send_after(Time, {RegName, Node} = Dest, Message) + when is_atom(RegName), + is_atom(Node) -> + apply_after(Time, ?MODULE, send, [Dest, Message]); +send_after(_Time, _PidOrRegName, _Message) -> + {error, badarg}. + +-spec send_after(Time, Message) -> {'ok', TRef} | {'error', Reason} + when Time :: time(), + Message :: term(), + TRef :: tref(), + Reason :: term(). send_after(Time, Message) -> send_after(Time, self(), Message). --spec exit_after(Time, Pid, Reason1) -> {'ok', TRef} | {'error', Reason2} when - Time :: time(), - Pid :: pid() | (RegName :: atom()), - TRef :: tref(), - Reason1 :: term(), - Reason2 :: term(). +-spec exit_after(Time, Target, Reason1) -> {'ok', TRef} | {'error', Reason2} + when Time :: time(), + Target :: pid() | (RegName :: atom()), + TRef :: tref(), + Reason1 :: term(), + Reason2 :: term(). exit_after(Time, Pid, Reason) -> - req(apply_after, {Time, {erlang, exit, [Pid, Reason]}}). + apply_after(Time, erlang, exit, [Pid, Reason]). --spec exit_after(Time, Reason1) -> {'ok', TRef} | {'error', Reason2} when - Time :: time(), - TRef :: tref(), - Reason1 :: term(), - Reason2 :: term(). +-spec exit_after(Time, Reason1) -> {'ok', TRef} | {'error', Reason2} + when Time :: time(), + TRef :: tref(), + Reason1 :: term(), + Reason2 :: term(). exit_after(Time, Reason) -> exit_after(Time, self(), Reason). --spec kill_after(Time, Pid) -> {'ok', TRef} | {'error', Reason2} when - Time :: time(), - Pid :: pid() | (RegName :: atom()), - TRef :: tref(), - Reason2 :: term(). +-spec kill_after(Time, Target) -> {'ok', TRef} | {'error', Reason2} + when Time :: time(), + Target :: pid() | (RegName :: atom()), + TRef :: tref(), + Reason2 :: term(). kill_after(Time, Pid) -> exit_after(Time, Pid, kill). --spec kill_after(Time) -> {'ok', TRef} | {'error', Reason2} when - Time :: time(), - TRef :: tref(), - Reason2 :: term(). +-spec kill_after(Time) -> {'ok', TRef} | {'error', Reason2} + when Time :: time(), + TRef :: tref(), + Reason2 :: term(). kill_after(Time) -> exit_after(Time, self(), kill). -spec apply_interval(Time, Module, Function, Arguments) -> - {'ok', TRef} | {'error', Reason} when - Time :: time(), - Module :: module(), - Function :: atom(), - Arguments :: [term()], - TRef :: tref(), - Reason :: term(). -apply_interval(Time, M, F, A) -> - req(apply_interval, {Time, self(), {M, F, A}}). - --spec send_interval(Time, Pid, Message) -> - {'ok', TRef} | {'error', Reason} when - Time :: time(), - Pid :: pid() | (RegName :: atom()), - Message :: term(), - TRef :: tref(), - Reason :: term(). -send_interval(Time, Pid, Message) -> - req(apply_interval, {Time, Pid, {?MODULE, send, [Pid, Message]}}). - --spec send_interval(Time, Message) -> {'ok', TRef} | {'error', Reason} when - Time :: time(), - Message :: term(), - TRef :: tref(), - Reason :: term(). + {'ok', TRef} | {'error', Reason} + when Time :: time(), + Module :: module(), + Function :: atom(), + Arguments :: [term()], + TRef :: tref(), + Reason :: term(). +apply_interval(Time, M, F, A) + when ?valid_time(Time), + ?valid_mfa(M, F, A) -> + req(apply_interval, {system_time(), Time, self(), {M, F, A}}); +apply_interval(_Time, _M, _F, _A) -> + {error, badarg}. + +-spec send_interval(Time, Destination, Message) -> {'ok', TRef} | {'error', Reason} + when Time :: time(), + Destination :: pid() | (RegName :: atom()) | {RegName :: atom(), Node :: node()}, + Message :: term(), + TRef :: tref(), + Reason :: term(). +send_interval(Time, Pid, Message) + when ?valid_time(Time), + is_pid(Pid) -> + req(apply_interval, {system_time(), Time, Pid, {?MODULE, send, [Pid, Message]}}); +send_interval(Time, RegName, Message) + when ?valid_time(Time), + is_atom(RegName) -> + req(apply_interval, {system_time(), Time, RegName, {?MODULE, send, [RegName, Message]}}); +send_interval(Time, Dest = {RegName, Node}, Message) + when ?valid_time(Time), + is_atom(RegName), + is_atom(Node) -> + req(apply_interval, {system_time(), Time, Dest, {?MODULE, send, [Dest, Message]}}); +send_interval(_Time, _Pid, _Message) -> + {error, badarg}. + +-spec send_interval(Time, Message) -> {'ok', TRef} | {'error', Reason} + when Time :: time(), + Message :: term(), + TRef :: tref(), + Reason :: term(). send_interval(Time, Message) -> send_interval(Time, self(), Message). --spec cancel(TRef) -> {'ok', 'cancel'} | {'error', Reason} when - TRef :: tref(), - Reason :: term(). -cancel(BRef) -> - req(cancel, BRef). - --spec sleep(Time) -> 'ok' when - Time :: timeout(). +-spec cancel(TRef) -> {'ok', 'cancel'} | {'error', Reason} + when TRef :: tref(), + Reason :: term(). +cancel({instant, Ref}) + when is_reference(Ref) -> + {ok, cancel}; +cancel({send_local, Ref}) + when is_reference(Ref) -> + _ = erlang:cancel_timer(Ref), + {ok, cancel}; +cancel({once, Ref} = TRef) + when is_reference(Ref) -> + req(cancel, TRef); +cancel({interval, Ref} = TRef) + when is_reference(Ref) -> + req(cancel, TRef); +cancel(_TRef) -> + {error, badarg}. + +-spec sleep(Time) -> 'ok' + when Time :: timeout(). +sleep(T) + when is_integer(T), + T > ?MAX_RECEIVE_AFTER -> + receive + after ?MAX_RECEIVE_AFTER -> + sleep(T - ?MAX_RECEIVE_AFTER) + end; sleep(T) -> receive after T -> ok @@ -157,10 +226,10 @@ sleep(T) -> %% %% Measure the execution time (in microseconds) for Fun(). %% --spec tc(Fun) -> {Time, Value} when - Fun :: function(), - Time :: integer(), - Value :: term(). +-spec tc(Fun) -> {Time, Value} + when Fun :: function(), + Time :: integer(), + Value :: term(). tc(F) -> T1 = erlang:monotonic_time(), Val = F(), @@ -171,11 +240,11 @@ tc(F) -> %% %% Measure the execution time (in microseconds) for Fun(Args). %% --spec tc(Fun, Arguments) -> {Time, Value} when - Fun :: function(), - Arguments :: [term()], - Time :: integer(), - Value :: term(). +-spec tc(Fun, Arguments) -> {Time, Value} + when Fun :: function(), + Arguments :: [term()], + Time :: integer(), + Value :: term(). tc(F, A) -> T1 = erlang:monotonic_time(), Val = apply(F, A), @@ -186,12 +255,12 @@ tc(F, A) -> %% %% Measure the execution time (in microseconds) for an MFA. %% --spec tc(Module, Function, Arguments) -> {Time, Value} when - Module :: module(), - Function :: atom(), - Arguments :: [term()], - Time :: integer(), - Value :: term(). +-spec tc(Module, Function, Arguments) -> {Time, Value} + when Module :: module(), + Function :: atom(), + Arguments :: [term()], + Time :: integer(), + Value :: term(). tc(M, F, A) -> T1 = erlang:monotonic_time(), Val = apply(M, F, A), @@ -203,250 +272,245 @@ tc(M, F, A) -> %% Calculate the time difference (in microseconds) of two %% erlang:now() timestamps, T2-T1. %% --spec now_diff(T2, T1) -> Tdiff when - T1 :: erlang:timestamp(), - T2 :: erlang:timestamp(), - Tdiff :: integer(). +-spec now_diff(T2, T1) -> Tdiff + when T1 :: erlang:timestamp(), + T2 :: erlang:timestamp(), + Tdiff :: integer(). now_diff({A2, B2, C2}, {A1, B1, C1}) -> ((A2-A1)*1000000 + B2-B1)*1000000 + C2-C1. %% -%% Convert seconds, minutes etc. to milliseconds. +%% Convert seconds, minutes etc. to milliseconds. %% --spec seconds(Seconds) -> MilliSeconds when - Seconds :: non_neg_integer(), - MilliSeconds :: non_neg_integer(). +-spec seconds(Seconds) -> MilliSeconds + when Seconds :: non_neg_integer(), + MilliSeconds :: non_neg_integer(). seconds(Seconds) -> 1000*Seconds. --spec minutes(Minutes) -> MilliSeconds when - Minutes :: non_neg_integer(), - MilliSeconds :: non_neg_integer(). + +-spec minutes(Minutes) -> MilliSeconds + when Minutes :: non_neg_integer(), + MilliSeconds :: non_neg_integer(). minutes(Minutes) -> 1000*60*Minutes. --spec hours(Hours) -> MilliSeconds when - Hours :: non_neg_integer(), - MilliSeconds :: non_neg_integer(). + +-spec hours(Hours) -> MilliSeconds + when Hours :: non_neg_integer(), + MilliSeconds :: non_neg_integer(). hours(Hours) -> 1000*60*60*Hours. --spec hms(Hours, Minutes, Seconds) -> MilliSeconds when - Hours :: non_neg_integer(), - Minutes :: non_neg_integer(), - Seconds :: non_neg_integer(), - MilliSeconds :: non_neg_integer(). + +-spec hms(Hours, Minutes, Seconds) -> MilliSeconds + when Hours :: non_neg_integer(), + Minutes :: non_neg_integer(), + Seconds :: non_neg_integer(), + MilliSeconds :: non_neg_integer(). hms(H, M, S) -> hours(H) + minutes(M) + seconds(S). -%% +%% %% Start/init functions %% -%% Start is only included because of backward compatibility! -spec start() -> 'ok'. start() -> - ensure_started(). + {ok, _Pid} = do_start(), + ok. + +do_start() -> + case + supervisor:start_child( + kernel_sup, + #{ + id => timer_server, + start => {?MODULE, start_link, []}, + restart => permanent, + shutdown => 1000, + type => worker, + modules => [?MODULE] + } + ) + of + {ok, Pid} -> + {ok, Pid}; + {ok, Pid, _} -> + {ok, Pid}; + {error, {already_started, Pid}} -> + {ok, Pid}; + Error -> + Error + end. -spec start_link() -> {'ok', pid()} | {'error', term()}. start_link() -> - gen_server:start_link({local, timer_server}, ?MODULE, [], []). + gen_server:start_link({local, timer_server}, ?MODULE, [], []). --spec init([]) -> {'ok', [], 'infinity'}. +-spec init([]) -> {'ok', ets:tid()}. init([]) -> process_flag(trap_exit, true), - ?TIMER_TAB = ets:new(?TIMER_TAB, [named_table,ordered_set,protected]), - ?INTERVAL_TAB = ets:new(?INTERVAL_TAB, [named_table,protected]), - {ok, [], infinity}. - --spec ensure_started() -> 'ok'. -ensure_started() -> - case whereis(timer_server) of - undefined -> - C = {timer_server, {?MODULE, start_link, []}, permanent, 1000, - worker, [?MODULE]}, - _ = supervisor:start_child(kernel_safe_sup, C), - ok; - _ -> ok - end. + Tab = ets:new(?MODULE, []), + {ok, Tab}. %% server calls +%% Try sending a call. If it fails with reason noproc, +%% try starting the timer server and try once again. req(Req, Arg) -> - SysTime = system_time(), - ensure_started(), - gen_server:call(timer_server, {Req, Arg, SysTime}, infinity). + try + maybe_req(Req, Arg) + catch + exit:{noproc, _} -> + {ok, _Pid} = do_start(), + maybe_req(Req, Arg) + end. -%% -%% handle_call(Request, From, Timers) -> -%% {reply, Response, Timers, Timeout} -%% -%% Time and Timeout is in milliseconds. Started is in microseconds. -%% --type timers() :: term(). % XXX: refine? - --spec handle_call(term(), term(), timers()) -> - {'reply', term(), timers(), timeout()} | {'noreply', timers(), timeout()}. -handle_call({apply_after, {Time, Op}, Started}, _From, _Ts) - when is_integer(Time), Time >= 0 -> - BRef = {Started + 1000*Time, make_ref()}, - Timer = {BRef, timeout, Op}, - ets:insert(?TIMER_TAB, Timer), - Timeout = timer_timeout(system_time()), - {reply, {ok, BRef}, [], Timeout}; -handle_call({apply_interval, {Time, To, MFA}, Started}, _From, _Ts) - when is_integer(Time), Time >= 0 -> - %% To must be a pid or a registered name - case get_pid(To) of - Pid when is_pid(Pid) -> - catch link(Pid), - SysTime = system_time(), - Ref = make_ref(), - BRef1 = {interval, Ref}, - Interval = Time*1000, - BRef2 = {Started + Interval, Ref}, - Timer = {BRef2, {repeat, Interval, Pid}, MFA}, - ets:insert(?INTERVAL_TAB, {BRef1,BRef2,Pid}), - ets:insert(?TIMER_TAB, Timer), - Timeout = timer_timeout(SysTime), - {reply, {ok, BRef1}, [], Timeout}; - _ -> - {reply, {error, badarg}, [], next_timeout()} - end; -handle_call({cancel, BRef = {_Time, Ref}, _}, _From, Ts) - when is_reference(Ref) -> - delete_ref(BRef), - {reply, {ok, cancel}, Ts, next_timeout()}; -handle_call({cancel, _BRef, _}, _From, Ts) -> - {reply, {error, badarg}, Ts, next_timeout()}; -handle_call({apply_after, _, _}, _From, Ts) -> - {reply, {error, badarg}, Ts, next_timeout()}; -handle_call({apply_interval, _, _}, _From, Ts) -> - {reply, {error, badarg}, Ts, next_timeout()}; -handle_call(_Else, _From, Ts) -> % Catch anything else - {noreply, Ts, next_timeout()}. - --spec handle_info(term(), timers()) -> {'noreply', timers(), timeout()}. -handle_info(timeout, Ts) -> % Handle timeouts - Timeout = timer_timeout(system_time()), - {noreply, Ts, Timeout}; -handle_info({'EXIT', Pid, _Reason}, Ts) -> % Oops, someone died - pid_delete(Pid), - {noreply, Ts, next_timeout()}; -handle_info(_OtherMsg, Ts) -> % Other Msg's - {noreply, Ts, next_timeout()}. - --spec handle_cast(term(), timers()) -> {'noreply', timers(), timeout()}. -handle_cast(_Req, Ts) -> % Not predicted but handled - {noreply, Ts, next_timeout()}. - --spec terminate(term(), _State) -> 'ok'. -terminate(_Reason, _State) -> +maybe_req(Req, Arg) -> + gen_server:call(timer_server, {Req, Arg}, infinity). + +%% Call handling. +-spec handle_call(term(), term(), Tab) -> + {'reply', term(), Tab} | {'noreply', Tab} when + Tab :: ets:tid(). +%% Start a one-shot timer. +handle_call({apply_once, {Started, Time, MFA}}, _From, Tab) -> + Timeout = Started + Time, + Reply = try + erlang:start_timer( + Timeout, + self(), + {apply_once, MFA}, + [{abs, true}] + ) + of + SRef -> + ets:insert(Tab, {SRef, SRef}), + {ok, {once, SRef}} + catch + error:badarg -> + {error, badarg} + end, + {reply, Reply, Tab}; +%% Start an interval timer. +handle_call({apply_interval, {Started, Time, Pid, MFA}}, _From, Tab) -> + NextTimeout = Started + Time, + TRef = monitor(process, Pid), + Reply = try + erlang:start_timer( + NextTimeout, + self(), + {apply_interval, NextTimeout, Time, TRef, MFA}, + [{abs, true}] + ) + of + SRef -> + ets:insert(Tab, {TRef, SRef}), + {ok, {interval, TRef}} + catch + error:badarg -> + demonitor(TRef, [flush]), + {error, badarg} + end, + {reply, Reply, Tab}; +%% Cancel a one-shot timer. +handle_call({cancel, {once, TRef}}, _From, Tab) -> + _ = remove_timer(TRef, Tab), + {reply, {ok, cancel}, Tab}; +%% Cancel an interval timer. +handle_call({cancel, {interval, TRef}}, _From, Tab) -> + _ = case remove_timer(TRef, Tab) of + true -> + demonitor(TRef, [flush]); + false -> + ok + end, + {reply, {ok, cancel}, Tab}; +%% Unexpected. +handle_call(_Req, _From, Tab) -> + {noreply, Tab}. + +%% Info handling. +-spec handle_info(term(), Tab) -> {'noreply', Tab} + when Tab :: ets:tid(). +%% One-shot timer timeout. +handle_info({timeout, TRef, {apply_once, MFA}}, Tab) -> + case ets:take(Tab, TRef) of + [{TRef, _SRef}] -> + do_apply(MFA); + [] -> + ok + end, + {noreply, Tab}; +%% Interval timer timeout. +handle_info({timeout, _, {apply_interval, CurTimeout, Time, TRef, MFA}}, Tab) -> + case ets:member(Tab, TRef) of + true -> + NextTimeout = CurTimeout + Time, + SRef = erlang:start_timer( + NextTimeout, + self(), + {apply_interval, NextTimeout, Time, TRef, MFA}, + [{abs, true}] + ), + ets:update_element(Tab, TRef, {2, SRef}), + do_apply(MFA); + false -> + ok + end, + {noreply, Tab}; +%% A process related to an interval timer died. +handle_info({'DOWN', TRef, process, _Pid, _Reason}, Tab) -> + _ = remove_timer(TRef, Tab), + {noreply, Tab}; +%% Unexpected. +handle_info(_Req, Tab) -> + {noreply, Tab}. + +%% Cast handling. +-spec handle_cast(term(), Tab) -> {'noreply', Tab} + when Tab :: ets:tid(). +%% Unexpected. +handle_cast(_Req, Tab) -> + {noreply, Tab}. + +-spec terminate(term(), _Tab) -> 'ok'. +terminate(_Reason, _Tab) -> ok. -spec code_change(term(), State, term()) -> {'ok', State}. -code_change(_OldVsn, State, _Extra) -> +code_change(_OldVsn, Tab, _Extra) -> %% According to the man for gen server no timer can be set here. - {ok, State}. - -%% -%% timer_timeout(SysTime) -%% -%% Apply and remove already timed-out timers. A timer is a tuple -%% {Time, BRef, Op, MFA}, where Time is in microseconds. -%% Returns {Timeout, Timers}, where Timeout is in milliseconds. -%% -timer_timeout(SysTime) -> - case ets:first(?TIMER_TAB) of - '$end_of_table' -> - infinity; - {Time, _Ref} when Time > SysTime -> - Timeout = (Time - SysTime + 999) div 1000, - %% Returned timeout must fit in a small int - erlang:min(Timeout, ?MAX_TIMEOUT); - Key -> - case ets:lookup(?TIMER_TAB, Key) of - [{Key, timeout, MFA}] -> - ets:delete(?TIMER_TAB,Key), - do_apply(MFA), - timer_timeout(SysTime); - [{{Time, Ref}, Repeat = {repeat, Interv, To}, MFA}] -> - ets:delete(?TIMER_TAB,Key), - NewTime = Time + Interv, - %% Update the interval entry (last in table) - ets:insert(?INTERVAL_TAB,{{interval,Ref},{NewTime,Ref},To}), - do_apply(MFA), - ets:insert(?TIMER_TAB, {{NewTime, Ref}, Repeat, MFA}), - timer_timeout(SysTime) - end - end. - -%% -%% delete_ref -%% - -delete_ref(BRef = {interval, _}) -> - case ets:lookup(?INTERVAL_TAB, BRef) of - [{_, BRef2, _Pid}] -> - ets:delete(?INTERVAL_TAB, BRef), - ets:delete(?TIMER_TAB, BRef2); - _ -> % TimerReference does not exist, do nothing - ok - end; -delete_ref(BRef) -> - ets:delete(?TIMER_TAB, BRef). - -%% -%% pid_delete -%% - --spec pid_delete(pid()) -> 'ok'. -pid_delete(Pid) -> - IntervalTimerList = - ets:select(?INTERVAL_TAB, - [{{'_', '_','$1'}, - [{'==','$1',Pid}], - ['$_']}]), - lists:foreach(fun({IntKey, TimerKey, _ }) -> - ets:delete(?INTERVAL_TAB, IntKey), - ets:delete(?TIMER_TAB, TimerKey) - end, IntervalTimerList). - -%% Calculate time to the next timeout. Returned timeout must fit in a -%% small int. - --spec next_timeout() -> timeout(). -next_timeout() -> - case ets:first(?TIMER_TAB) of - '$end_of_table' -> - infinity; - {Time, _} -> - erlang:min(positive((Time - system_time() + 999) div 1000), ?MAX_TIMEOUT) + {ok, Tab}. + +%% Remove a timer. +remove_timer(TRef, Tab) -> + case ets:take(Tab, TRef) of + [{TRef, SRef}] -> + ok = erlang:cancel_timer(SRef, [{async, true}, {info, false}]), + true; + [] -> % TimerReference does not exist, do nothing + false end. %% Help functions -do_apply({M,F,A}) -> - case {M, F, A} of - {?MODULE, send, A} -> - %% If send op. send directly, (faster than spawn) - catch send(A); - {erlang, exit, [Name, Reason]} -> - catch exit(get_pid(Name), Reason); - _ -> - %% else spawn process with the operation - catch spawn(M,F,A) - end. - -positive(X) -> - erlang:max(X, 0). +%% If send op. send directly (faster than spawn) +do_apply({?MODULE, send, A}) -> + catch send(A); +%% If exit op. resolve registered name +do_apply({erlang, exit, [Name, Reason]}) -> + catch exit(get_pid(Name), Reason); +do_apply({M,F,A}) -> + catch spawn(M, F, A). -%% -%% system_time() -> time in microseconds -%% +%% Get current time in milliseconds, +%% ceil'ed to the next millisecond. system_time() -> - erlang:monotonic_time(1000000). + (erlang:monotonic_time(microsecond) + 999) div 1000. send([Pid, Msg]) -> Pid ! Msg. +%% Resolve a registered name. get_pid(Name) when is_pid(Name) -> Name; get_pid(undefined) -> @@ -455,22 +519,3 @@ get_pid(Name) when is_atom(Name) -> get_pid(whereis(Name)); get_pid(_) -> undefined. - -%% -%% get_status() -> -%% {{TimerTabName,TotalNumTimers},{IntervalTabName,NumIntervalTimers}} -%% -%% This function is for test purposes only; it is used by the test suite. -%% There is a small possibility that there is a mismatch of one entry -%% between the 2 tables if this call is made when the timer server is -%% in the middle of a transaction - --spec get_status() -> - {{?TIMER_TAB,non_neg_integer()},{?INTERVAL_TAB,non_neg_integer()}}. - -get_status() -> - Info1 = ets:info(?TIMER_TAB), - {size,TotalNumTimers} = lists:keyfind(size, 1, Info1), - Info2 = ets:info(?INTERVAL_TAB), - {size,NumIntervalTimers} = lists:keyfind(size, 1, Info2), - {{?TIMER_TAB,TotalNumTimers},{?INTERVAL_TAB,NumIntervalTimers}}. diff --git a/lib/stdlib/test/timer_SUITE.erl b/lib/stdlib/test/timer_SUITE.erl index 9062cbae80..84b008f1d3 100644 --- a/lib/stdlib/test/timer_SUITE.erl +++ b/lib/stdlib/test/timer_SUITE.erl @@ -132,7 +132,7 @@ big_loop(C, N, Pids) -> %%Pids2=Pids1, %% wait a little while - timer:sleep(rand:uniform(200)*3), + ok = timer:sleep(rand:uniform(200)*3), %% spawn zero, one or two nrev to get some load ;-/ Pids3 = start_nrev(Pids2, rand:uniform(100)), @@ -168,7 +168,7 @@ s_a_t(C, TimeOut) -> a_t(C, TimeOut) -> start_watchdog(self(), TimeOut), Start = system_time(), - timer:send_after(TimeOut, self(), now), + {ok, _} = timer:send_after(TimeOut, self(), now), receive now -> Stop = system_time(), @@ -202,12 +202,12 @@ i_wait(Start, Prev, Times, TimeOut, Times, Ref, C) -> interval -> Now = system_time(), report_interval(C, {final,Times}, Start, Prev, Now, TimeOut), - timer:cancel(Ref), + {ok, cancel} = timer:cancel(Ref), exit(done); watchdog -> Now = system_time(), report_interval(C, {final,Times}, Start, Prev, Now, TimeOut), - timer:cancel(Ref), + {ok, cancel} = timer:cancel(Ref), ok = io:format("Internal watchdog timeout (i), not good!!~n", []), exit(done) @@ -358,7 +358,7 @@ system_time() -> %% ------------------------------------------------------- %% do_nrev(Sleep) -> - timer:sleep(Sleep), + ok = timer:sleep(Sleep), test(1000,"abcdefghijklmnopqrstuvxyz1234"), exit(done). diff --git a/lib/stdlib/test/timer_simple_SUITE.erl b/lib/stdlib/test/timer_simple_SUITE.erl index 1a582ae95a..889d859a04 100644 --- a/lib/stdlib/test/timer_simple_SUITE.erl +++ b/lib/stdlib/test/timer_simple_SUITE.erl @@ -21,25 +21,43 @@ -module(timer_simple_SUITE). +%% This test suite matches on opaque tref() return values from the *_after +%% and *_interval functions, namely {instant, _}, {send_local, _}, {once, _} +%% and {interval, _}. +%% If the implementation changes, the test suite has to be changed accordingly. + %% external -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, init_per_testcase/2, - apply_after/1, + apply_after1/1, + apply_after2/1, + apply_after_invalid_args/1, send_after1/1, send_after2/1, send_after3/1, + send_after4/1, + send_after_invalid_args/1, exit_after1/1, exit_after2/1, + exit_after3/1, kill_after1/1, kill_after2/1, - apply_interval/1, + kill_after3/1, + apply_interval1/1, + apply_interval_invalid_args/1, send_interval1/1, send_interval2/1, send_interval3/1, send_interval4/1, + send_interval_invalid_args/1, cancel1/1, cancel2/1, + cancel3/1, + cancel4/1, + cancel_invalid_args/1, + sleep1/1, + sleep2/1, tc/1, unique_refs/1, timer_perf/1]). @@ -60,15 +78,107 @@ suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,10}}]. -all() -> - [apply_after, send_after1, send_after2, send_after3, - exit_after1, exit_after2, kill_after1, kill_after2, - apply_interval, send_interval1, send_interval2, - send_interval3, send_interval4, cancel1, cancel2, tc, - unique_refs, timer_perf]. +all() -> + [ + {group, apply_after}, + {group, send_after}, + {group, exit_after}, + {group, kill_after}, + {group, apply_interval}, + {group, send_interval}, + {group, cancel}, + {group, sleep}, + {group, misc} + ]. groups() -> - []. + [ + { + apply_after, + [], + [ + apply_after1, + apply_after2, + apply_after_invalid_args + ] + }, + { + send_after, + [], + [ + send_after1, + send_after2, + send_after3, + send_after4, + send_after_invalid_args + ] + }, + { + exit_after, + [], + [ + exit_after1, + exit_after2, + exit_after3 + ] + }, + { + kill_after, + [], + [ + kill_after1, + kill_after2, + kill_after3 + ] + }, + { + apply_interval, + [], + [ + apply_interval1, + apply_interval_invalid_args + ] + }, + { + send_interval, + [], + [ + send_interval1, + send_interval2, + send_interval3, + send_interval4, + send_interval_invalid_args + ] + }, + { + cancel, + [], + [ + cancel1, + cancel2, + cancel3, + cancel4, + cancel_invalid_args + ] + }, + { + sleep, + [], + [ + sleep1, + sleep2 + ] + }, + { + misc, + [], + [ + tc, + unique_refs, + timer_perf + ] + } + ]. init_per_suite(Config) -> Config. @@ -84,24 +194,37 @@ end_per_group(_GroupName, Config) -> init_per_testcase(_, Config) when is_list(Config) -> - timer:start(), + ok = timer:start(), Config. %% Testing timer interface!! -%% Test of apply_after, with sending of message. -apply_after(Config) when is_list(Config) -> - timer:apply_after(500, ?MODULE, send, [self(), ok_apply]), +%% Test of apply_after with time = 0, with sending of message. +apply_after1(Config) when is_list(Config) -> + {ok, {instant, _}} = timer:apply_after(0, ?MODULE, send, [self(), ok_apply]), + ok = get_mess(1000, ok_apply). + +%% Test of apply_after with time = 500, with sending of message. +apply_after2(Config) when is_list(Config) -> + {ok, {once, _}} = timer:apply_after(500, ?MODULE, send, [self(), ok_apply]), ok = get_mess(1000, ok_apply). +%% Test that apply_after rejects invalid arguments. +apply_after_invalid_args(Config) when is_list(Config) -> + {error, badarg} = timer:apply_after(-1, foo, bar, []), + {error, badarg} = timer:apply_after(0, "foo", bar, []), + {error, badarg} = timer:apply_after(0, foo, "bar", []), + {error, badarg} = timer:apply_after(0, foo, bar, baz), + ok. + %% Test of send_after with time = 0. send_after1(Config) when is_list(Config) -> - timer:send_after(0, ok_send1), + {ok, {instant, _}} = timer:send_after(0, ok_send1), ok = get_mess(1000, ok_send1). %% Test of send_after with time = 500. send_after2(Config) when is_list(Config) -> - timer:send_after(500, self(), ok_send2), + {ok, {send_local, _}} = timer:send_after(500, self(), ok_send2), ok = get_mess(2000, ok_send2). %% Test of send_after with time = 500, with receiver a registered @@ -109,15 +232,32 @@ send_after2(Config) when is_list(Config) -> send_after3(Config) when is_list(Config) -> Name = list_to_atom(pid_to_list(self())), register(Name, self()), - timer:send_after(500, Name, ok_send3), + {ok, {once, _}} = timer:send_after(500, Name, ok_send3), ok = get_mess(2000, ok_send3), unregister(Name). +%% Test that send_after works if the destination is a registered +%% name which gets registered after the timer is started. +send_after4(Config) when is_list(Config) -> + Name = list_to_atom(pid_to_list(self())), + {ok, {once, _}} = timer:send_after(500, Name, ok_send4), + register(Name, self()), + ok = get_mess(2000, ok_send4), + unregister(Name). + +%% Test that send_after rejects invalid arguments. +send_after_invalid_args(Config) when is_list(Config) -> + {error, badarg} = timer:send_after(-1, test), + {error, badarg} = timer:send_after(-1, self(), test), + {error, badarg} = timer:send_after(-1, ?MODULE, test), + {error, badarg} = timer:send_after(0, "", test), + ok. + %% Test of exit_after with time = 1000. exit_after1(Config) when is_list(Config) -> process_flag(trap_exit, true), Pid = spawn_link(?MODULE, forever, []), - timer:exit_after(1000, Pid, exit_test1), + {ok, {once, _}} = timer:exit_after(1000, Pid, exit_test1), ok = get_mess(5000, {'EXIT', Pid, exit_test1}). %% Test of exit_after with time = 1000. The process to exit is the @@ -127,14 +267,25 @@ exit_after2(Config) when is_list(Config) -> Pid = spawn_link(?MODULE, forever, []), Name = list_to_atom(pid_to_list(Pid)), register(Name, Pid), - timer:exit_after(1000, Name, exit_test2), + {ok, {once, _}} = timer:exit_after(1000, Name, exit_test2), ok = get_mess(2000, {'EXIT', Pid, exit_test2}). +%% Test of exit_after for sending an exit to self. +exit_after3(Config) when is_list(Config) -> + process_flag(trap_exit, true), + Pid = spawn_link( + fun () -> + {ok, {once, _}} = timer:exit_after(1000, exit_test3), + forever() + end + ), + ok = get_mess(2000, {'EXIT', Pid, exit_test3}). + %% Test of kill_after with time = 1000. kill_after1(Config) when is_list(Config) -> process_flag(trap_exit, true), Pid = spawn_link(?MODULE, forever, []), - timer:kill_after(1000, Pid), + {ok, {once, _}} = timer:kill_after(1000, Pid), ok = get_mess(2000, {'EXIT', Pid, killed}). %% Test of kill_after with time = 1000. The process to exit is the @@ -144,25 +295,44 @@ kill_after2(Config) when is_list(Config) -> Pid = spawn_link(?MODULE, forever, []), Name = list_to_atom(pid_to_list(Pid)), register(Name, Pid), - timer:kill_after(1000, Name), + {ok, {once, _}} = timer:kill_after(1000, Name), + ok = get_mess(2000, {'EXIT', Pid, killed}). + +%% Test of kill_after for self-killing. +kill_after3(Config) when is_list(Config) -> + process_flag(trap_exit, true), + Pid = spawn_link( + fun () -> + {ok, {once, _}} = timer:kill_after(1000), + forever() + end + ), ok = get_mess(2000, {'EXIT', Pid, killed}). %% Test of apply_interval by sending messages. Receive %% 3 messages, cancel the timer, and check that we do %% not get any more messages. -apply_interval(Config) when is_list(Config) -> +apply_interval1(Config) when is_list(Config) -> {ok, Ref} = timer:apply_interval(1000, ?MODULE, send, [self(), apply_int]), ok = get_mess(1500, apply_int, 3), - timer:cancel(Ref), + {ok, cancel} = timer:cancel(Ref), nor = get_mess(1000, apply_int). +%% Test that apply_interval rejects invalid arguments. +apply_interval_invalid_args(Config) when is_list(Config) -> + {error, badarg} = timer:apply_interval(-1, foo, bar, []), + {error, badarg} = timer:apply_interval(0, "foo", bar, []), + {error, badarg} = timer:apply_interval(0, foo, "bar", []), + {error, badarg} = timer:apply_interval(0, foo, bar, baz), + ok. + %% Test of send_interval/2. Receive 5 messages, cancel the timer, and %% check that we do not get any more messages. send_interval1(Config) when is_list(Config) -> {ok, Ref} = timer:send_interval(1000, send_int), ok = get_mess(1500, send_int, 5), - timer:cancel(Ref), + {ok, cancel} = timer:cancel(Ref), nor = get_mess(1000, send_int). % We should receive only five %% Test of send_interval/3. Receive 2 messages, cancel the timer, and @@ -170,7 +340,7 @@ send_interval1(Config) when is_list(Config) -> send_interval2(Config) when is_list(Config) -> {ok, Ref} = timer:send_interval(1000, self(), send_int2), ok = get_mess(1500, send_int2, 2), - timer:cancel(Ref), + {ok, cancel} = timer:cancel(Ref), nor = get_mess(1000, send_int2). % We should receive only two %% Test of send_interval/3. Receive 2 messages, cancel the timer, and @@ -182,32 +352,97 @@ send_interval3(Config) when is_list(Config) -> register(Name, self()), {ok, Ref} = timer:send_interval(1000, Name, send_int3), ok = get_mess(1500, send_int3, 2), - timer:cancel(Ref), + {ok, cancel} = timer:cancel(Ref), nor = get_mess(1000, send_int3), % We should receive only two unregister(Name). %% Test that send interval stops sending msg when the receiving %% process terminates. send_interval4(Config) when is_list(Config) -> - timer:send_interval(500, one_time_only), + {ok, {interval, Ref}} = timer:send_interval(500, one_time_only), receive one_time_only -> ok end, - timer_server ! {'EXIT', self(), normal}, % Should remove the timer - timer:send_after(600, send_intv_ok), + timer_server ! {'DOWN', Ref, process, self(), test}, + {ok, {send_local, _}} = timer:send_after(600, send_intv_ok), send_intv_ok = receive Msg -> Msg end. -%% Test that we can cancel a timer. +%% Test that send_interval rejects invalid arguments. +send_interval_invalid_args(Config) when is_list(Config) -> + {error, badarg} = timer:send_interval(-1, test), + {error, badarg} = timer:send_interval(-1, self(), test), + {error, badarg} = timer:send_interval(-1, ?MODULE, test), + {error, badarg} = timer:send_interval(0, "", test), + ok. + +%% Test that we can cancel a send-once timer. cancel1(Config) when is_list(Config) -> {ok, Ref} = timer:send_after(1000, this_should_be_canceled), - timer:cancel(Ref), + {ok, cancel} = timer:cancel(Ref), nor = get_mess(2000, this_should_be_canceled). % We should rec 0 msgs -%% Test cancel/1 with bad argument. +%% Test that we can cancel an apply-once timer. cancel2(Config) when is_list(Config) -> - {error, badarg} = timer:cancel(no_reference). + {ok, Ref} = timer:apply_after(1000, erlang, send, [self(), this_should_be_canceled]), + {ok, cancel} = timer:cancel(Ref), + nor = get_mess(2000, this_should_be_canceled). + +%% Test that we can cancel a send-interval timer. +cancel3(Config) when is_list(Config) -> + {ok, Ref} = timer:send_interval(500, one_time_only), + receive + one_time_only -> ok + end, + {ok, cancel} = timer:cancel(Ref), + {ok, {send_local, _}} = timer:send_after(600, send_intv_ok), + send_intv_ok = receive + Msg -> Msg + end. + +%% Test that we can cancel an apply-interval timer. +cancel4(Config) when is_list(Config) -> + {ok, Ref} = timer:apply_interval(500, erlang, send, [self(), one_time_only]), + receive + one_time_only -> ok + end, + {ok, cancel} = timer:cancel(Ref), + {ok, {send_local, _}} = timer:send_after(600, send_intv_ok), + send_intv_ok = receive + Msg -> Msg + end. + +%% Test that cancel rejects invalid arguments. +cancel_invalid_args(Config) when is_list(Config) -> + {error, badarg} = timer:cancel(no_reference), + {error, badarg} = timer:cancel({foo, make_ref()}), + {error, badarg} = timer:cancel({once, foo}), + {error, badarg} = timer:cancel({interval, foo}), + {error, badarg} = timer:cancel({instant, foo}), + ok. + +%% Test that sleep pauses the calling process for +%% at least the given time. +sleep1(Config) when is_list(Config) -> + T0 = erlang:monotonic_time(millisecond), + ok = timer:sleep(1000), + T1 = erlang:monotonic_time(millisecond), + true = T1 - T0 >= 1000, + ok. + +%% Test that sleep accepts times >(2^32)-1, which is +%% the maximum time for the after clause of a receive +%% operation, at the time of this writing. +sleep2(Config) when is_list(Config) -> + process_flag(trap_exit, true), + Pid = spawn_link( + fun () -> + {ok, {once, _}} = timer:kill_after(1000), + ok = timer:sleep(16#ffffffff+1) + end + ), + ok = get_mess(2000, {'EXIT', Pid, killed}). %% Test sleep/1 and tc/3. tc(Config) when is_list(Config) -> @@ -220,7 +455,7 @@ tc(Config) when is_list(Config) -> end, %% tc/2 - {Res2, ok} = timer:tc(fun(T) -> timer:sleep(T) end, [500]), + {Res2, ok} = timer:tc(fun(T) -> ok = timer:sleep(T) end, [500]), ok = if Res2 < 500*1000 -> {too_early, Res2}; % Too early Res2 > 800*1000 -> {too_late, Res2}; % Too much time @@ -228,7 +463,7 @@ tc(Config) when is_list(Config) -> end, %% tc/1 - {Res3, ok} = timer:tc(fun() -> timer:sleep(500) end), + {Res3, ok} = timer:tc(fun() -> ok = timer:sleep(500) end), ok = if Res3 < 500*1000 -> {too_early, Res3}; % Too early Res3 > 800*1000 -> {too_late, Res3}; % Too much time @@ -293,19 +528,19 @@ set_and_cancel_one_shots(0) -> set_and_cancel_one_shots(N) -> {ok, Ref} = timer:send_after(7000, self(), kalle), %% Cancel twice - timer:cancel(Ref), - timer:cancel(Ref), + {ok, cancel} = timer:cancel(Ref), + {ok, cancel} = timer:cancel(Ref), set_and_cancel_one_shots(N-1). cancel([T| Ts]) -> - timer:cancel(T), + {ok, cancel} = timer:cancel(T), cancel(Ts); cancel([]) -> ok. num_timers() -> - {{_, TotalTimers},{_, _IntervalTimers}} = timer:get_status(), - TotalTimers. + Tab = sys:get_state(timer_server), + ets:info(Tab, size). receive_nisse() -> receive @@ -326,7 +561,7 @@ get_mess(Time, Mess, N) -> end. forever() -> - timer:sleep(1000), + ok = timer:sleep(1000), forever(). @@ -395,7 +630,7 @@ timer(apply, Mod, T, Pid) -> Pid ! {self(), ok, (After-Before) div 1000, T} after T*3 + 300 -> % Watch dog io:format("WARNING TIMER WATCHDOG timed out: ~w ~n", [T]), - timer:cancel(Ref), + {ok, cancel} = timer:cancel(Ref), Pid ! {self(), watch_dog_timed_out} end. diff --git a/system/doc/efficiency_guide/commoncaveats.xmlsrc b/system/doc/efficiency_guide/commoncaveats.xmlsrc index a0ed5f5b0d..9b1e7d0504 100644 --- a/system/doc/efficiency_guide/commoncaveats.xmlsrc +++ b/system/doc/efficiency_guide/commoncaveats.xmlsrc @@ -39,11 +39,16 @@ marker="erts:erlang#send_after/3">erlang:send_after/3</seemfa> and <seemfa marker="erts:erlang#start_timer/3">erlang:start_timer/3</seemfa>, - is much more efficient than using the timers provided by the - <seeerl marker="stdlib:timer">timer</seeerl> module in STDLIB. - The <c>timer</c> module uses a separate process to manage the timers. - That process can easily become overloaded if many processes - create and cancel timers frequently.</p> + is more efficient than using the timers provided by the + <seeerl marker="stdlib:timer">timer</seeerl> module in STDLIB.</p> + <p>The <c>timer</c> module uses a separate process to manage the timers. + Before OTP 25, this management overhead was substantial and increasing + with the number of timers, especially when they were short-lived, so the + timer server process could easily become overloaded and unresponsive. + In OTP 25, the timer module was improved by removing most of the management + overhead and the resulting performance penalty. Still, the timer server + remains a single process, and it may at some point become a bottleneck + of an application.</p> <p>The functions in the <c>timer</c> module that do not manage timers (such as <c>timer:tc/3</c> or <c>timer:sleep/1</c>), do not call the -- 2.26.2
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor