Skip to content

Commit

Permalink
Merge pull request erlang#7649 from juhlig/timer_apply_ext
Browse files Browse the repository at this point in the history
Add `apply_*` functions to the `timer`module that accept anonymous functions

OTP-18808
  • Loading branch information
bjorng authored Oct 18, 2023
2 parents 29e0b1f + 4e97963 commit 1a3d53c
Show file tree
Hide file tree
Showing 3 changed files with 319 additions and 43 deletions.
147 changes: 141 additions & 6 deletions lib/stdlib/doc/src/timer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,31 @@
</datatypes>

<funcs>
<func>
<name name="apply_after" arity="2" since="OTP @OTP-18808@"/>
<fsummary>Spawn a process evaluating <c>erlang:apply(Function, [])</c>
after a specified <c>Time</c>.</fsummary>
<desc>
<p>Evaluates <c>spawn(erlang, apply, [<anno>Function</anno>,
[]])</c> after <c><anno>Time</anno></c> milliseconds.</p>
<p>Returns <c>{ok, <anno>TRef</anno>}</c> or
<c>{error, <anno>Reason</anno>}</c>.</p>
</desc>
</func>

<func>
<name name="apply_after" arity="3" since="OTP @OTP-18808@"/>
<fsummary>Spawn a process evaluating <c>erlang:apply(Function, Arguments)</c>
after a specified <c>Time</c>.</fsummary>
<desc>
<p>Evaluates <c>spawn(erlang, apply, [<anno>Function</anno>,
<anno>Arguments</anno>])</c> after <c><anno>Time</anno></c>
milliseconds.</p>
<p>Returns <c>{ok, <anno>TRef</anno>}</c> or
<c>{error, <anno>Reason</anno>}</c>.</p>
</desc>
</func>

<func>
<name name="apply_after" arity="4" since=""/>
<fsummary>Spawn a process evaluating <c>Module:Function(Arguments)</c>
Expand All @@ -82,6 +107,34 @@
</desc>
</func>

<func>
<name name="apply_interval" arity="2" since="OTP @OTP-18808@"/>
<fsummary>Spawn a process evaluating <c>erlang:apply(Function, [])</c>
repeatedly at intervals of <c>Time</c>.</fsummary>
<desc>
<p>Evaluates <c>spawn(erlang, apply, [<anno>Function</anno>,
[]])</c> repeatedly at intervals of
<c><anno>Time</anno></c>, irrespective of whether a previously
spawned process has finished or not.</p>
<p>Returns <c>{ok, <anno>TRef</anno>}</c> or
<c>{error, <anno>Reason</anno>}</c>.</p>
</desc>
</func>

<func>
<name name="apply_interval" arity="3" since="OTP @OTP-18808@"/>
<fsummary>Spawn a process evaluating <c>erlang:apply(Function, Arguments)</c>
repeatedly at intervals of <c>Time</c>.</fsummary>
<desc>
<p>Evaluates <c>spawn(erlang, apply, [<anno>Function</anno>,
<anno>Arguments</anno>])</c> repeatedly at intervals of
<c><anno>Time</anno></c>, irrespective of whether a previously
spawned process has finished or not.</p>
<p>Returns <c>{ok, <anno>TRef</anno>}</c> or
<c>{error, <anno>Reason</anno>}</c>.</p>
</desc>
</func>

<func>
<name name="apply_interval" arity="4" since=""/>
<fsummary>Spawn a process evaluating <c>Module:Function(Arguments)</c>
Expand Down Expand Up @@ -111,6 +164,34 @@
</desc>
</func>

<func>
<name name="apply_repeatedly" arity="2" since="OTP @OTP-18808@"/>
<fsummary>Spawn a process evaluating <c>erlang:apply(Function, [])</c>
repeatedly at intervals of <c>Time</c>.</fsummary>
<desc>
<p>Evaluates <c>spawn(erlang, apply, [<anno>Function</anno>,
[]])</c> repeatedly at intervals of
<c><anno>Time</anno></c>, waiting for the spawned process to
finish before starting the next.</p>
<p>Returns <c>{ok, <anno>TRef</anno>}</c> or
<c>{error, <anno>Reason</anno>}</c>.</p>
</desc>
</func>

<func>
<name name="apply_repeatedly" arity="3" since="OTP @OTP-18808@"/>
<fsummary>Spawn a process evaluating <c>erlang:apply(Function, Arguments)</c>
repeatedly at intervals of <c>Time</c>.</fsummary>
<desc>
<p>Evaluates <c>spawn(erlang, apply, [<anno>Function</anno>,
<anno>Arguments</anno>])</c> repeatedly at intervals of
<c><anno>Time</anno></c>, waiting for the spawned process to
finish before starting the next.</p>
<p>Returns <c>{ok, <anno>TRef</anno>}</c> or
<c>{error, <anno>Reason</anno>}</c>.</p>
</desc>
</func>

<func>
<name name="apply_repeatedly" arity="4" since="OTP 26.0"/>
<fsummary>Spawn a process evaluating <c>Module:Function(Arguments)</c>
Expand Down Expand Up @@ -405,22 +486,76 @@ timer:cancel(R),

<p>An interval timer, that is, a timer created by evaluating any of the
functions
<seemfa marker="#apply_interval/2"><c>apply_interval/2</c></seemfa>,
<seemfa marker="#apply_interval/3"><c>apply_interval/3</c></seemfa>,
<seemfa marker="#apply_interval/4"><c>apply_interval/4</c></seemfa>,
<seemfa marker="#send_interval/3"><c>send_interval/3</c></seemfa>, and
<seemfa marker="#send_interval/2"><c>send_interval/2</c></seemfa>
<seemfa marker="#apply_repeatedly/2"><c>apply_repeatedly/2</c></seemfa>,
<seemfa marker="#apply_repeatedly/3"><c>apply_repeatedly/3</c></seemfa>,
<seemfa marker="#apply_repeatedly/4"><c>apply_repeatedly/4</c></seemfa>,
<seemfa marker="#send_interval/2"><c>send_interval/2</c></seemfa>, and
<seemfa marker="#send_interval/3"><c>send_interval/3</c></seemfa>
is linked to the process to which the timer performs its task.</p>

<p>A one-shot timer, that is, a timer created by evaluating any of the
functions
<seemfa marker="#apply_after/2"><c>apply_after/2</c></seemfa>,
<seemfa marker="#apply_after/3"><c>apply_after/3</c></seemfa>,
<seemfa marker="#apply_after/4"><c>apply_after/4</c></seemfa>,
<seemfa marker="#send_after/3"><c>send_after/3</c></seemfa>,
<seemfa marker="#send_after/2"><c>send_after/2</c></seemfa>,
<seemfa marker="#exit_after/3"><c>exit_after/3</c></seemfa>,
<seemfa marker="#send_after/3"><c>send_after/3</c></seemfa>,
<seemfa marker="#exit_after/2"><c>exit_after/2</c></seemfa>,
<seemfa marker="#kill_after/2"><c>kill_after/2</c></seemfa>, and
<seemfa marker="#kill_after/1"><c>kill_after/1</c></seemfa>
<seemfa marker="#exit_after/3"><c>exit_after/3</c></seemfa>,
<seemfa marker="#kill_after/1"><c>kill_after/1</c></seemfa>, and
<seemfa marker="#kill_after/2"><c>kill_after/2</c></seemfa>
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
<seemfa marker="#cancel/1"><c>cancel/1</c></seemfa>.</p>

<p>The functions given to
<seemfa marker="#apply_after/2"><c>apply_after/2</c></seemfa>,
<seemfa marker="#apply_after/3"><c>apply_after/3</c></seemfa>,
<seemfa marker="#apply_interval/2"><c>apply_interval/2</c></seemfa>,
<seemfa marker="#apply_interval/3"><c>apply_interval/3</c></seemfa>,
<seemfa marker="#apply_repeatedly/2"><c>apply_repeatedly/2</c></seemfa>, and
<seemfa marker="#apply_repeatedly/3"><c>apply_repeatedly/3</c></seemfa>,
or denoted by <c>Module</c>, <c>Function</c> and <c>Arguments</c> given to
<seemfa marker="#apply_after/4"><c>apply_after/4</c></seemfa>,
<seemfa marker="#apply_interval/4"><c>apply_interval/4</c></seemfa>, and
<seemfa marker="#apply_repeatedly/4"><c>apply_repeatedly/4</c></seemfa>
are executed in a freshly-spawned process, and therefore calls to <c>self()</c>
in those functions will return the Pid of this process, which is different
from the process that called <c>timer:apply_*</c>.</p>
<p><em>Example</em></p>
<p>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 <c>done</c> message.</p>
<p>Using <c>self()</c> <em>inside</em> the timed function, the code below does
not work as intended. The task gets done, but the <c>done</c> message gets
sent to the wrong process and is lost.</p>
<pre>
1> <input>timer:apply_after(1000, fun() -> do_something(), self() ! done end).</input>
{ok,TRef}
2> <input>receive done -> done after 5000 -> timeout end.</input>
%% ... 5s pass...
timeout</pre>
<p>The code below calls <c>self()</c> in the process which sets the timer and
assigns it to a variable, which is then used in the function to send the
<c>done</c> message to, and so works as intended.</p>
<pre>
1> <input>Target = self()</input>
&lt;0.82.0&gt;
2> <input>timer:apply_after(1000, fun() -> do_something(), Target ! done end).</input>
{ok,TRef}
3> <input>receive done -> done after 5000 -> timeout end.</input>
%% ... 1s passes...
done</pre>
<p>Another option is to pass the message target as a parameter to the function.</p>
<pre>
1> <input>timer:apply_after(1000, fun(Target) -> do_something(), Target ! done end, [self()]).</input>
{ok,TRef}
2> <input>receive done -> done after 5000 -> timeout end.</input>
%% ... 1s passes...
done</pre>
</section>
</erlref>
92 changes: 85 additions & 7 deletions lib/stdlib/src/timer.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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]).
Expand All @@ -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.
Expand All @@ -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(),
Expand All @@ -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}.
Expand Down Expand Up @@ -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(),
Expand All @@ -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(),
Expand All @@ -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}.
Expand Down
Loading

0 comments on commit 1a3d53c

Please sign in to comment.