timer(3)

πŸ’– timer(3) β€” the Erlang timer module, explained with love πŸ’–

okay bestie, pull up a chair, grab your iced coffee, because we're doing timer(3) and honestly? it's giving time management queen. ✨


🌸 NAME 🌸

timer β€” timer functions. that's it, that's the whole vibe. πŸŽ€


✨ DESCRIPTION ✨

so this module gives you a bunch of genuinely useful functions that are all about time. and unless the docs say otherwise, time is always measured in milliseconds, okay? milliseconds. not seconds, not "a little bit", ms. πŸ•

and here's the cute part: all the timer functions return immediately, no matter how much work some other process is out there doing. they're not gonna block you, they're not gonna make you wait, they're just gonna hand you a little receipt and go. πŸ’…

when a timer function succeeds, it gives you back a value containing a timer reference, and we call that a TRef. you take that TRef and hand it to cancel/1, and boom β€” you can cancel whatever action you asked for. one thing though: a TRef is an Erlang term and its contents must not be changed. don't touch it, don't be weird about it, just hold it and pass it along. πŸ™ˆ

also, lowkey important: the time-outs are not exact. they're at least as long as you requested. so if you ask for 1000ms, you get 1000ms or more, never less. no cap.

now, real talk: creating timers with erlang:send_after/3 and erlang:start_timer/3 is more efficient than using the timers this module provides. but don't cry, because in OTP 25 this timer module got a glow-up 🌟 β€” it's more efficient now and less likely to get overloaded. if you wanna read the deep lore, check the Timer Module section in the Efficiency Guide.

and if you want the general 411 on timers in Erlang, go read the Timers section of Time and Time Correction in Erlang in the ERTS User's guide. πŸ“š

πŸ¦‹ Examples πŸ¦‹

Example 1

okay so here's how you print "Hello World!" after 5 seconds:

1> timer:apply_after(5000, io, format, ["~nHello World!~n", []]).
{ok,TRef}
Hello World!

iconic. 5 seconds, then boom, hello world. πŸ‘‘

Example 2

and here's a process doing a thing, except if it doesn't finish in time β€” it gets killed:

Pid = spawn(mod, fun, [foo, bar]),
%% If pid is not finished in 10 seconds, kill him
{ok, R} = timer:kill_after(timer:seconds(10), Pid),
%% We change our mind...
timer:cancel(R),

see how it sets the kill timer, then immediately changes its mind and cancels? main character energy, honestly. πŸ’…

🧚 Notes on timer behavior 🧚

okay so a timer can always be removed by calling cancel/1. always. periodt.

an interval timer β€” that's a timer you made by evaluating any of these bad boys: apply_interval/2, apply_interval/3, apply_interval/4, apply_repeatedly/2, apply_repeatedly/3, apply_repeatedly/4, send_interval/2, and send_interval/3 β€” is linked to the process it performs its task for. linked. they're bonded like that. 🫢

a one-shot timer β€” that's a timer made by evaluating any of apply_after/2, apply_after/3, apply_after/4, send_after/2, send_after/3, exit_after/2, exit_after/3, kill_after/1, and kill_after/2 β€” is not linked to any process. so the only ways it goes away are: it reaches its time-out, or you explicitly remove it with a call to cancel/1. that's it. no lurking, no leftover ghosts. πŸ‘»

now here's a big one, so listen up bestie. the functions you give to apply_after/2, apply_after/3, apply_interval/2, apply_interval/3, apply_repeatedly/2, and apply_repeatedly/3 β€” or the ones denoted by Module, Function and Arguments given to apply_after/4, apply_interval/4, and apply_repeatedly/4 β€” get executed in a freshly-spawned process. which means if you call self/0 inside one of those functions, you get the Pid of that process, not the process that called timer:apply_*. this trips everyone up, okay, you're not alone. 😭

Example

so the intention here is super relatable: set a timer to run a function after 1 second, that function does a fictional task, and then it wants to tell the process that set the timer that it's done, by sending it a done message.

if you use self/0 inside the timed function, it does not work the way you want. the task gets done, sure, but the done message gets sent to the wrong process and it's just... lost. gone. into the void. πŸ’€

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

but this version? this version calls self/0 in the process that sets the timer, assigns it to a variable, and then that variable is what the function uses to send the done message to. works exactly like you wanted. 🌈

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

and there's another option too β€” just 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

three ways, take your pick, all valid. ✨


πŸŽ€ DATA TYPES πŸŽ€

  • time() β€” time in milliseconds. that's the whole deal. πŸ’œ
  • -opaque tref() β€” a timer reference. it's opaque bestie, don't peek inside, just hold it. πŸ™ˆ

πŸ‘‘ FUNCTIONS πŸ‘‘

apply_after(Time, Function) -> {ok, TRef} | {error, Reason}

when
    Time :: time(),
    Function :: fun(() -> _),
    TRef :: tref(),
    Reason :: term().

evaluates spawn(erlang, apply, [Function, []]) after Time milliseconds. ⏰

apply_after(Time, Function, Arguments) -> {ok, TRef} | {error, Reason}

when
    Time :: time(),
    Function :: fun((...) -> _),
    Arguments :: [term()],
    TRef :: tref(),
    Reason :: term().

evaluates spawn(erlang, apply, [Function, Arguments]) after Time milliseconds. same energy, now with Arguments. πŸ’…

apply_after(Time, Module, Function, Arguments) -> {ok, TRef} | {error, Reason}

when
    Time :: time(),
    Module :: module(),
    Function :: atom(),
    Arguments :: [term()],
    TRef :: tref(),
    Reason :: term().

evaluates spawn(Module, Function, Arguments) after Time milliseconds. the Module + Function + Arguments flavor, chef's kiss. πŸ“

apply_interval(Time, Function) -> {ok, TRef} | {error, Reason}

when
    Time :: time(),
    Function :: fun(() -> _),
    TRef :: tref(),
    Reason :: term().

evaluates spawn(erlang, apply, [Function, []]) repeatedly at intervals of Time, and it does not care whether a previously spawned process has finished or not. it just keeps going. no thoughts, head empty, spawn again. πŸ”

apply_interval(Time, Function, Arguments) -> {ok, TRef} | {error, Reason}

when
    Time :: time(),
    Function :: fun((...) -> _),
    Arguments :: [term()],
    TRef :: tref(),
    Reason :: term().

evaluates spawn(erlang, apply, [Function, Arguments]) repeatedly at intervals of Time, irrespective of whether a previously spawned process has finished or not. yeah, same "irrespective" energy. ✨

apply_interval(Time, Module, Function, Arguments) -> {ok, TRef} | {error, Reason}

when
    Time :: time(),
    Module :: module(),
    Function :: atom(),
    Arguments :: [term()],
    TRef :: tref(),
    Reason :: term().

evaluates spawn(Module, Function, Arguments) repeatedly at intervals of Time, irrespective of whether a previously spawned process has finished or not.

⚠️ WARNING: if the execution time of the spawned process is, on average, greater than the given Time, then multiple such processes will run at the same time. and if you've got long execution times, short intervals, and many interval timers running, this may even lead to exceeding the number of allowed processes. as an extreme example, consider [timer:apply_interval(1, timer, sleep, [1000]) || _ <- lists:seq(1, 1000)] β€” that's 1,000 interval timers executing a process that takes 1s to complete, started in intervals of 1ms, which would result in 1,000,000 processes running at the same time, far more than a node started with default settings allows (see the System Limits section in the Efficiency Guide). bestie do NOT do this. πŸ˜­πŸ’€

apply_repeatedly(Time, Function) -> {ok, TRef} | {error, Reason}

when
    Time :: time(),
    Function :: fun(() -> _),
    TRef :: tref(),
    Reason :: term().

evaluates spawn(erlang, apply, [Function, []]) repeatedly at intervals of Time, except this one is polite β€” it waits for the spawned process to finish before starting the next. see the difference? that's the whole thing. 🫢

apply_repeatedly(Time, Function, Arguments) -> {ok, TRef} | {error, Reason}

when
    Time :: time(),
    Function :: fun((...) -> _),
    Arguments :: [term()],
    TRef :: tref(),
    Reason :: term().

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

apply_repeatedly(Time, Module, Function, Arguments) -> {ok, TRef} | {error, Reason}

when
    Time :: time(),
    Module :: module(),
    Function :: atom(),
    Arguments :: [term()],
    TRef :: tref(),
    Reason :: term().

evaluates spawn(Module, Function, Arguments) repeatedly at intervals of Time, waiting for the spawned process to finish before starting the next.

and here's the nuance, okay: if the execution time of the spawned process is greater than the given Time, the next process is spawned immediately after the one currently running has finished. but assuming the execution times of the spawned processes performing the applies are, on average, smaller than Time, the amount of applies made over a large amount of time will be the same even if some individual execution times are larger than Time. the system will try to catch up as soon as possible. for example, if one apply takes 2.5*Time, the following two applies will be made immediately one after the other in sequence. it's catching up, it's doing its best, let it cook. πŸ”₯

cancel(TRef) -> {ok, cancel} | {error, Reason}

when TRef :: tref(), Reason :: term().

cancels a previously requested time-out. TRef is a unique timer reference returned by the related timer function. πŸŽ€

returns {ok, cancel}, or {error, Reason} when TRef is not a timer reference. that's your vibe check right there β€” if it's not a real timer ref, you get an error, no cap. πŸ’…


more coming bestie, don't go anywhere πŸ¦‹

✨ exit_after/3 ✨

exit_after(Time, Target, Reason1) -> {ok, TRef} | {error, Reason2}
                    when
                        Time :: time(),
                        Target :: pid() | (RegName :: atom()),
                        TRef :: tref(),
                        Reason1 :: term(),
                        Reason2 :: term().

Okay so this one sends an exit signal with reason Reason1 to Target, bestie. Target can be a local process identifier or an atom of a registered name. It's giving directed exit energy, so use it with intention. πŸ’…


✨ hms/3 ✨

hms(Hours, Minutes, Seconds) -> MilliSeconds
             when
                 Hours :: non_neg_integer(),
                 Minutes :: non_neg_integer(),
                 Seconds :: non_neg_integer(),
                 MilliSeconds :: time().

Returns the number of milliseconds in Hours + Minutes + Seconds. Math but make it timer-coded. ✨


✨ hours/1 ✨

hours(Hours) -> MilliSeconds
               when Hours :: non_neg_integer(), MilliSeconds :: time().

Returns the number of milliseconds in Hours. Lowkey the easiest conversion ever. πŸ•°οΈ


✨ minutes/1 ✨

minutes(Minutes) -> MilliSeconds
                 when
                     Minutes :: non_neg_integer(),
                     MilliSeconds :: time().

Returns the number of milliseconds in Minutes. No cap, just multiplication with extra steps. πŸ’–


✨ now_diff/2 ✨

now_diff(T2, T1) -> Tdiff
                  when
                      T1 :: erlang:timestamp(),
                      T2 :: erlang:timestamp(),
                      Tdiff :: integer().

Calculates the time difference Tdiff = T2 - T1 in microseconds, where T1 and T2 are time-stamp tuples on the same format as returned from erlang:timestamp/0 or os:timestamp/0. Time math slay. πŸ¦‹


✨ seconds/1 ✨

seconds(Seconds) -> MilliSeconds
                 when
                     Seconds :: non_neg_integer(),
                     MilliSeconds :: time().

Returns the number of milliseconds in Seconds. Convert and serve. πŸ’…


✨ send_after/3 ✨

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().

Evaluates Destination ! Message after Time milliseconds.

Destination can be a remote or local process identifier, an atom of a registered name or a tuple {RegName, Node} for a registered name at another node.

See also the Timer Module section in the Efficiency Guide. It's giving scheduled message. πŸ“¬


✨ send_interval/3 ✨

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().

Evaluates Destination ! Message repeatedly after Time milliseconds.

Destination can be a remote or local process identifier, an atom of a registered name or a tuple {RegName, Node} for a registered name at another node. It's the recurring message vibe. πŸ”


✨ sleep/1 ✨

sleep(Time) -> ok when Time :: time() | infinity.

Suspends the process calling this function for Time milliseconds and then returns ok, or suspends the process forever if Time is the atom infinity. Naturally, this function does not return immediately. It's nap time for your process. 😴

NOTE: Before OTP 25, timer:sleep/1 did not accept integer timeout values greater than 16#ffffffff, that is, 2^32-1. Since OTP 25, arbitrarily high integer values are accepted. Glow-up alert. ✨

✨ start/0 ✨

start() -> ok.

Starts the timer server.

Normally, the server does not need to be started explicitly. It is started dynamically if it is needed. This is useful during development, but in a target system the server is to be started explicitly. Use configuration parameters for Kernel for this. Server behavior, but make it fashion. πŸ‘‘


✨ tc/2 ✨

tc(Fun, Arguments) -> {Time, Value}
            when
                Fun :: fun((...) -> term()),
                Arguments :: [term()],
                Time :: integer(),
                Value :: term();
        (Fun, TimeUnit) -> {Time, Value}
            when
                Fun :: fun(() -> term()),
                TimeUnit :: erlang:time_unit(),
                Time :: integer(),
                Value :: term().

Measures the execution time of Fun.

Equivalent to tc(Fun, Arguments, microsecond) if called as tc(Fun, Arguments).

Measures the execution time of Fun in TimeUnit if called as tc(Fun, TimeUnit). Added in OTP 26.0. Benchmarking era. ⏱️


✨ tc/3 ✨

tc(Module, Function, Arguments) -> {Time, Value}
            when
                Module :: module(),
                Function :: atom(),
                Arguments :: [term()],
                Time :: integer(),
                Value :: term();
        (Fun, Arguments, TimeUnit) -> {Time, Value}
            when
                Fun :: fun((...) -> term()),
                Arguments :: [term()],
                TimeUnit :: erlang:time_unit(),
                Time :: integer(),
                Value :: term().

Measures the execution time of Fun or apply(Module, Function, Arguments).

Equivalent to tc(Module, Function, Arguments, microsecond) if called as tc(Module, Function, Arguments).

Equivalent to tc(erlang, apply, [Fun, Arguments], TimeUnit) if called as tc(Fun, Arguments, TimeUnit). Added in OTP 26.0 Flexible timing, we love to see it. πŸ’œ


✨ tc/4 ✨

tc(Module, Function, Arguments, TimeUnit) -> {Time, Value}
            when
                Module :: module(),
                Function :: atom(),
                Arguments :: [term()],
                TimeUnit :: erlang:time_unit(),
                Time :: integer(),
                Value :: term().

Evaluates apply(Module, Function, Arguments) and measures the elapsed real time as reported by erlang:monotonic_time/0.

Returns {Time, Value}, where Time is the elapsed real time in the specified TimeUnit, and Value is what is returned from the apply. Precision timing, periodt. πŸ”₯


Shoutout to Ericsson AB for stdlib 8.0.4 and this timer(3) page. That's the footer vibe, bestie. πŸ’–

πŸ“¦ where this page comes from