From 4e97963da88fce3e00de85f49ceea396e8bc0634 Mon Sep 17 00:00:00 2001 From: Jan Uhlig Date: Mon, 16 Oct 2023 14:37:01 +0200 Subject: [PATCH] Add apply_* functions to the timer module that accept functions --- lib/stdlib/doc/src/timer.xml | 147 ++++++++++++++++++++++++- lib/stdlib/src/timer.erl | 92 ++++++++++++++-- lib/stdlib/test/timer_simple_SUITE.erl | 123 ++++++++++++++++----- 3 files changed, 319 insertions(+), 43 deletions(-) diff --git a/lib/stdlib/doc/src/timer.xml b/lib/stdlib/doc/src/timer.xml index 75706f7b2dff..90542bc6b9bd 100644 --- a/lib/stdlib/doc/src/timer.xml +++ b/lib/stdlib/doc/src/timer.xml @@ -69,6 +69,31 @@ + + + Spawn a process evaluating erlang:apply(Function, []) + after a specified Time. + +

Evaluates spawn(erlang, apply, [Function, + []]) after Time milliseconds.

+

Returns {ok, TRef} or + {error, Reason}.

+
+
+ + + + Spawn a process evaluating erlang:apply(Function, Arguments) + after a specified Time. + +

Evaluates spawn(erlang, apply, [Function, + Arguments]) after Time + milliseconds.

+

Returns {ok, TRef} or + {error, Reason}.

+
+
+ Spawn a process evaluating Module:Function(Arguments) @@ -82,6 +107,34 @@ + + + Spawn a process evaluating erlang:apply(Function, []) + repeatedly at intervals of Time. + +

Evaluates spawn(erlang, apply, [Function, + []]) repeatedly at intervals of + Time, irrespective of whether a previously + spawned process has finished or not.

+

Returns {ok, TRef} or + {error, Reason}.

+
+
+ + + + Spawn a process evaluating erlang:apply(Function, Arguments) + repeatedly at intervals of Time. + +

Evaluates spawn(erlang, apply, [Function, + Arguments]) repeatedly at intervals of + Time, irrespective of whether a previously + spawned process has finished or not.

+

Returns {ok, TRef} or + {error, Reason}.

+
+
+ Spawn a process evaluating Module:Function(Arguments) @@ -111,6 +164,34 @@ + + + Spawn a process evaluating erlang:apply(Function, []) + repeatedly at intervals of Time. + +

Evaluates spawn(erlang, apply, [Function, + []]) repeatedly at intervals of + Time, waiting for the spawned process to + finish before starting the next.

+

Returns {ok, TRef} or + {error, Reason}.

+
+
+ + + + Spawn a process evaluating erlang:apply(Function, Arguments) + repeatedly at intervals of Time. + +

Evaluates spawn(erlang, apply, [Function, + Arguments]) repeatedly at intervals of + Time, waiting for the spawned process to + finish before starting the next.

+

Returns {ok, TRef} or + {error, Reason}.

+
+
+ Spawn a process evaluating Module:Function(Arguments) @@ -405,22 +486,76 @@ timer:cancel(R),

An interval timer, that is, a timer created by evaluating any of the functions + apply_interval/2, + apply_interval/3, apply_interval/4, - send_interval/3, and - send_interval/2 + apply_repeatedly/2, + apply_repeatedly/3, + apply_repeatedly/4, + send_interval/2, and + send_interval/3 is linked to the process to which the timer performs its task.

A one-shot timer, that is, a timer created by evaluating any of the functions + apply_after/2, + apply_after/3, apply_after/4, - send_after/3, send_after/2, - exit_after/3, + send_after/3, exit_after/2, - kill_after/2, and - kill_after/1 + exit_after/3, + kill_after/1, and + kill_after/2 is not linked to any process. Hence, such a timer is removed only when it reaches its time-out, or if it is explicitly removed by a call to cancel/1.

+ +

The functions given to + apply_after/2, + apply_after/3, + apply_interval/2, + apply_interval/3, + apply_repeatedly/2, and + apply_repeatedly/3, + or denoted by Module, Function and Arguments given to + apply_after/4, + apply_interval/4, and + apply_repeatedly/4 + are executed in a freshly-spawned process, and therefore calls to self() + in those functions will return the Pid of this process, which is different + from the process that called timer:apply_*.

+

Example

+

In the following example, the intention is to set a timer to execute a + function after 1 second, which performs a fictional task, and then wants + to inform the process which set the timer about its completion, by sending + it a done message.

+

Using self() inside the timed function, the code below does + not work as intended. The task gets done, but the done message gets + sent to the wrong process and is lost.

+
+1> timer:apply_after(1000, fun() -> do_something(), self() ! done end).
+{ok,TRef}
+2> receive done -> done after 5000 -> timeout end.
+%% ... 5s pass...
+timeout
+

The code below calls self() in the process which sets the timer and + assigns it to a variable, which is then used in the function to send the + done message to, and so works as intended.

+
+1> Target = self()
+<0.82.0>
+2> timer:apply_after(1000, fun() -> do_something(), Target ! done end).
+{ok,TRef}
+3> receive done -> done after 5000 -> timeout end.
+%% ... 1s passes...
+done
+

Another option is to pass the message target as a parameter to the function.

+
+1> timer:apply_after(1000, fun(Target) -> do_something(), Target ! done end, [self()]).
+{ok,TRef}
+2> receive done -> done after 5000 -> timeout end.
+%% ... 1s passes...
+done
diff --git a/lib/stdlib/src/timer.erl b/lib/stdlib/src/timer.erl index 5f1d2eb6b705..c02fc1842748 100644 --- a/lib/stdlib/src/timer.erl +++ b/lib/stdlib/src/timer.erl @@ -19,10 +19,11 @@ %% -module(timer). --export([apply_after/4, +-export([apply_after/2, apply_after/3, apply_after/4, send_after/3, send_after/2, exit_after/3, exit_after/2, kill_after/2, kill_after/1, - apply_interval/4, apply_repeatedly/4, + apply_interval/2, apply_interval/3, apply_interval/4, + apply_repeatedly/2, apply_repeatedly/3, apply_repeatedly/4, send_interval/3, send_interval/2, cancel/1, sleep/1, tc/1, tc/2, tc/3, tc/4, now_diff/2, seconds/1, minutes/1, hours/1, hms/3]). @@ -40,7 +41,9 @@ %% 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)). +-define(valid_apply(F), is_function(F, 0)). +-define(valid_apply(F, A), is_list(A), is_function(F, length(A))). +-define(valid_apply(M, F, A), is_atom(M), is_atom(F), is_list(A)). %% %% Time is in milliseconds. @@ -52,6 +55,31 @@ %% %% Interface functions %% +-spec apply_after(Time, Function) -> + {'ok', TRef} | {'error', Reason} + when Time :: time(), + Function :: fun(() -> _), + TRef :: tref(), + Reason :: term(). +apply_after(Time, F) + when ?valid_apply(F) -> + apply_after(Time, erlang, apply, [F, []]); +apply_after(_Time, _F) -> + {error, badarg}. + +-spec apply_after(Time, Function, Arguments) -> + {'ok', TRef} | {'error', Reason} + when Time :: time(), + Function :: fun((...) -> _), + Arguments :: [term()], + TRef :: tref(), + Reason :: term(). +apply_after(Time, F, A) + when ?valid_apply(F, A) -> + apply_after(Time, erlang, apply, [F, A]); +apply_after(_Time, _F, _A) -> + {error, badarg}. + -spec apply_after(Time, Module, Function, Arguments) -> {'ok', TRef} | {'error', Reason} when Time :: time(), @@ -61,12 +89,12 @@ TRef :: tref(), Reason :: term(). apply_after(0, M, F, A) - when ?valid_mfa(M, F, A) -> + when ?valid_apply(M, F, A) -> _ = do_apply({M, F, A}, false), {ok, {instant, make_ref()}}; apply_after(Time, M, F, A) when ?valid_time(Time), - ?valid_mfa(M, F, A) -> + ?valid_apply(M, F, A) -> req(apply_once, {system_time(), Time, {M, F, A}}); apply_after(_Time, _M, _F, _A) -> {error, badarg}. @@ -146,6 +174,31 @@ kill_after(Time, Pid) -> kill_after(Time) -> exit_after(Time, self(), kill). +-spec apply_interval(Time, Function) -> + {'ok', TRef} | {'error', Reason} + when Time :: time(), + Function :: fun(() -> _), + TRef :: tref(), + Reason :: term(). +apply_interval(Time, F) + when ?valid_apply(F) -> + apply_interval(Time, erlang, apply, [F, []]); +apply_interval(_Time, _F) -> + {error, badarg}. + +-spec apply_interval(Time, Function, Arguments) -> + {'ok', TRef} | {'error', Reason} + when Time :: time(), + Function :: fun((...) -> _), + Arguments :: [term()], + TRef :: tref(), + Reason :: term(). +apply_interval(Time, F, A) + when ?valid_apply(F, A) -> + apply_interval(Time, erlang, apply, [F, A]); +apply_interval(_Time, _F, _A) -> + {error, badarg}. + -spec apply_interval(Time, Module, Function, Arguments) -> {'ok', TRef} | {'error', Reason} when Time :: time(), @@ -156,11 +209,36 @@ kill_after(Time) -> Reason :: term(). apply_interval(Time, M, F, A) when ?valid_time(Time), - ?valid_mfa(M, F, A) -> + ?valid_apply(M, F, A) -> req(apply_interval, {system_time(), Time, self(), {M, F, A}}); apply_interval(_Time, _M, _F, _A) -> {error, badarg}. +-spec apply_repeatedly(Time, Function) -> + {'ok', TRef} | {'error', Reason} + when Time :: time(), + Function :: fun(() -> _), + TRef :: tref(), + Reason :: term(). +apply_repeatedly(Time, F) + when ?valid_apply(F) -> + apply_repeatedly(Time, erlang, apply, [F, []]); +apply_repeatedly(_Time, _F) -> + {error, badarg}. + +-spec apply_repeatedly(Time, Function, Arguments) -> + {'ok', TRef} | {'error', Reason} + when Time :: time(), + Function :: fun((...) -> _), + Arguments :: [term()], + TRef :: tref(), + Reason :: term(). +apply_repeatedly(Time, F, A) + when ?valid_apply(F, A) -> + apply_repeatedly(Time, erlang, apply, [F, A]); +apply_repeatedly(_Time, _F, _A) -> + {error, badarg}. + -spec apply_repeatedly(Time, Module, Function, Arguments) -> {'ok', TRef} | {'error', Reason} when Time :: time(), @@ -171,7 +249,7 @@ apply_interval(_Time, _M, _F, _A) -> Reason :: term(). apply_repeatedly(Time, M, F, A) when ?valid_time(Time), - ?valid_mfa(M, F, A) -> + ?valid_apply(M, F, A) -> req(apply_repeatedly, {system_time(), Time, self(), {M, F, A}}); apply_repeatedly(_Time, _M, _F, _A) -> {error, badarg}. diff --git a/lib/stdlib/test/timer_simple_SUITE.erl b/lib/stdlib/test/timer_simple_SUITE.erl index 761689dc5114..339ac7af913c 100644 --- a/lib/stdlib/test/timer_simple_SUITE.erl +++ b/lib/stdlib/test/timer_simple_SUITE.erl @@ -240,14 +240,20 @@ init_per_testcase(_, Config) when is_list(Config) -> %% Test of apply_after with time = 0, with sending of message. apply_after1(Config) when is_list(Config) -> Msg = make_ref(), - {ok, {instant, _}} = timer:apply_after(0, ?MODULE, send, [self(), Msg]), - ok = get_mess(1000, Msg). + {ok, {instant, _}} = timer:apply_after(0, ?MODULE, send, [self(), {Msg, 1}]), + {ok, {instant, _}} = timer:apply_after(0, fun erlang:send/2, [self(), {Msg, 2}]), + Self = self(), + {ok, {instant, _}} = timer:apply_after(0, fun() -> Self ! {Msg, 3} end), + ok = get_messes(1000, Msg, [1, 2, 3]). %% Test of apply_after with time = 500, with sending of message. apply_after2(Config) when is_list(Config) -> Msg = make_ref(), - {ok, {once, _}} = timer:apply_after(500, ?MODULE, send, [self(), Msg]), - ok = get_mess(1000, Msg). + {ok, {once, _}} = timer:apply_after(500, ?MODULE, send, [self(), {Msg, 1}]), + {ok, {once, _}} = timer:apply_after(500, fun erlang:send/2, [self(), {Msg, 2}]), + Self = self(), + {ok, {once, _}} = timer:apply_after(500, fun() -> Self ! {Msg, 3} end), + ok = get_messes(1000, Msg, [1, 2, 3]). %% Test that a request starts the timer server if it is not running. apply_after3(Config) when is_list(Config) -> @@ -270,6 +276,14 @@ apply_after4(Config) when is_list(Config) -> %% Test that apply_after rejects invalid arguments. apply_after_invalid_args(Config) when is_list(Config) -> + {error, badarg} = timer:apply_after(-1, fun() -> ok end), + {error, badarg} = timer:apply_after(0, foo), + {error, badarg} = timer:apply_after(0, fun(_X) -> ok end), + {error, badarg} = timer:apply_after(-1, fun(_X) -> ok end, [foo]), + {error, badarg} = timer:apply_after(0, foo, []), + {error, badarg} = timer:apply_after(0, fun(_X) -> ok end, []), + {error, badarg} = timer:apply_after(0, fun(_X) -> ok end, [foo, bar]), + {error, badarg} = timer:apply_after(0, fun(_X) -> ok end, foo), {error, badarg} = timer:apply_after(-1, foo, bar, []), {error, badarg} = timer:apply_after(0, "foo", bar, []), {error, badarg} = timer:apply_after(0, foo, "bar", []), @@ -415,11 +429,17 @@ kill_after3(Config) when is_list(Config) -> %% not get any more messages. apply_interval1(Config) when is_list(Config) -> Msg = make_ref(), - {ok, Ref} = timer:apply_interval(1000, ?MODULE, send, - [self(), Msg]), - ok = get_mess(1500, Msg, 3), - {ok, cancel} = timer:cancel(Ref), - nor = get_mess(1000, Msg). + {ok, Ref1} = timer:apply_interval(1000, ?MODULE, send, + [self(), {Msg, 1}]), + {ok, Ref2} = timer:apply_interval(1000, fun erlang:send/2, + [self(), {Msg, 2}]), + Self = self(), + {ok, Ref3} = timer:apply_interval(1000, fun() -> Self ! {Msg, 3} end), + ok = get_messes(1500, Msg, [1, 2, 3], 3), + {ok, cancel} = timer:cancel(Ref1), + {ok, cancel} = timer:cancel(Ref2), + {ok, cancel} = timer:cancel(Ref3), + nor = get_messes(1000, Msg, [1, 2, 3]). %% Test apply_interval with the execution time of the action %% longer than the timer interval. The timer should not wait for @@ -427,19 +447,32 @@ apply_interval1(Config) when is_list(Config) -> %% previously started action is still running. apply_interval2(Config) when is_list(Config) -> Msg = make_ref(), + Fn = fun(P, Idx) -> + P ! {Msg, Idx}, + receive after 1000 -> ok end + end, + {ok, Ref1} = timer:apply_interval(500, erlang, apply, + [Fn, [self(), 1]]), + {ok, Ref2} = timer:apply_interval(500, Fn, [self(), 2]), Self = self(), - {ok, Ref} = timer:apply_interval(500, erlang, apply, - [fun() -> - Self ! Msg, - receive after 1000 -> ok end - end, []]), + {ok, Ref3} = timer:apply_interval(500, fun() -> Fn(Self, 3) end), receive after 1800 -> ok end, - {ok, cancel} = timer:cancel(Ref), - ok = get_mess(1000, Msg, 3), - nor = get_mess(1000, Msg). + {ok, cancel} = timer:cancel(Ref1), + {ok, cancel} = timer:cancel(Ref2), + {ok, cancel} = timer:cancel(Ref3), + ok = get_messes(1000, Msg, [1, 2, 3], 3), + nor = get_messes(1000, Msg, [1, 2, 3]). %% Test that apply_interval rejects invalid arguments. apply_interval_invalid_args(Config) when is_list(Config) -> + {error, badarg} = timer:apply_interval(-1, fun() -> ok end), + {error, badarg} = timer:apply_interval(0, foo), + {error, badarg} = timer:apply_interval(0, fun(_X) -> ok end), + {error, badarg} = timer:apply_interval(-1, fun(_X) -> ok end, [foo]), + {error, badarg} = timer:apply_interval(0, foo, []), + {error, badarg} = timer:apply_interval(0, fun(_X) -> ok end, []), + {error, badarg} = timer:apply_interval(0, fun(_X) -> ok end, [foo, bar]), + {error, badarg} = timer:apply_interval(0, fun(_X) -> ok end, foo), {error, badarg} = timer:apply_interval(-1, foo, bar, []), {error, badarg} = timer:apply_interval(0, "foo", bar, []), {error, badarg} = timer:apply_interval(0, foo, "bar", []), @@ -453,11 +486,17 @@ apply_interval_invalid_args(Config) when is_list(Config) -> %% interval, this should behave the same as apply_interval. apply_repeatedly1(Config) when is_list(Config) -> Msg = make_ref(), - {ok, Ref} = timer:apply_repeatedly(1000, ?MODULE, send, - [self(), Msg]), - ok = get_mess(1500, Msg, 3), - {ok, cancel} = timer:cancel(Ref), - nor = get_mess(1000, Msg). + {ok, Ref1} = timer:apply_repeatedly(1000, ?MODULE, send, + [self(), {Msg, 1}]), + {ok, Ref2} = timer:apply_repeatedly(1000, fun erlang:send/2, + [self(), {Msg, 2}]), + Self = self(), + {ok, Ref3} = timer:apply_repeatedly(1000, fun() -> Self ! {Msg, 3} end), + ok = get_messes(1500, Msg, [1, 2, 3], 3), + {ok, cancel} = timer:cancel(Ref1), + {ok, cancel} = timer:cancel(Ref2), + {ok, cancel} = timer:cancel(Ref3), + nor = get_messes(1000, Msg, [1, 2, 3]). %% Test apply_repeatedly with the execution time of the action %% longer than the timer interval. The timer should wait for @@ -465,19 +504,33 @@ apply_repeatedly1(Config) when is_list(Config) -> %% has completed. apply_repeatedly2(Config) when is_list(Config) -> Msg = make_ref(), + Fn = fun(P, I) -> + P ! {Msg, I}, + receive after 1000 -> ok end + end, Self = self(), - {ok, Ref} = timer:apply_repeatedly(1, erlang, apply, - [fun() -> - Self ! Msg, - receive after 1000 -> ok end - end, []]), + {ok, Ref1} = timer:apply_repeatedly(1, erlang, apply, + [Fn, [self(), 1]]), + {ok, Ref2} = timer:apply_repeatedly(1, Fn, [self(), 2]), + Self = self(), + {ok, Ref3} = timer:apply_repeatedly(1, fun() -> Fn(Self, 3) end), receive after 2500 -> ok end, - {ok, cancel} = timer:cancel(Ref), - ok = get_mess(1000, Msg, 3), - nor = get_mess(1000, Msg). + {ok, cancel} = timer:cancel(Ref1), + {ok, cancel} = timer:cancel(Ref2), + {ok, cancel} = timer:cancel(Ref3), + ok = get_messes(1000, Msg, [1, 2, 3], 3), + nor = get_messes(1000, Msg, [1, 2, 3]). %% Test that apply_repeatedly rejects invalid arguments. apply_repeatedly_invalid_args(Config) when is_list(Config) -> + {error, badarg} = timer:apply_repeatedly(-1, fun() -> ok end), + {error, badarg} = timer:apply_repeatedly(0, foo), + {error, badarg} = timer:apply_repeatedly(0, fun(_X) -> ok end), + {error, badarg} = timer:apply_repeatedly(-1, fun(_X) -> ok end, [foo]), + {error, badarg} = timer:apply_repeatedly(0, foo, []), + {error, badarg} = timer:apply_repeatedly(0, fun(_X) -> ok end, []), + {error, badarg} = timer:apply_repeatedly(0, fun(_X) -> ok end, [foo, bar]), + {error, badarg} = timer:apply_repeatedly(0, fun(_X) -> ok end, foo), {error, badarg} = timer:apply_repeatedly(-1, foo, bar, []), {error, badarg} = timer:apply_repeatedly(0, "foo", bar, []), {error, badarg} = timer:apply_repeatedly(0, foo, "bar", []), @@ -781,6 +834,16 @@ get_mess(Time, Mess, N) -> nor % Not Received end. +get_messes(Time, Mess, Indexes) -> get_messes(Time, Mess, Indexes, 1). +get_messes(Time, Mess, Indexes, N) -> get_messes1(Time, Mess, lists:append(lists:duplicate(N, Indexes))). +get_messes1(_, _, []) -> ok; +get_messes1(Time, Mess, Indexes) -> + receive + {Mess, Index} -> get_messes1(Time, Mess, lists:delete(Index, Indexes)) + after Time -> + nor + end. + forever() -> ok = timer:sleep(1000), forever().