Are erlang: send_after / 3 and timer: send_after / 3 designed to behave differently?

I want to send a message to the process after a delay and have found erlang:send_after/4 .

If you look at the docs , then this was exactly what I wanted:

erlang:send_after(Time, Dest, Msg, Options) -> TimerRef

Starts a timer. When the timer expires, the Msg message is sent to the process identified by Dest.

However, it does not seem to work when the assignment is executed on another node - this tells me that one of the arguments is bad.

 1> P = spawn(' node@host ', module, function, [Arg]). <10585.83.0> 2> erlang:send_after(1000, P, {123}). ** exception error: bad argument in function erlang:send_after/3 called as erlang:send_after(1000,<10585.83.0>,{123}) 

Executing the same function with timer:send_after/3 looks fine:

 1> P = spawn(' node@host ', module, function, [Arg]). <10101.10.0> 2> timer:send_after(1000, P, {123}). {ok,{-576458842589535,#Ref<0.1843049418.1937244161.31646>}} 

And the docs for timer:send_after/3 says pretty much the same as the erlang version:

send_after(Time, Pid, Message) -> {ok, TRef} | {error, Reason}

Evaluates Pid! Message in milliseconds of time.

So the question is, why do these two functions, which at first glance do the same thing, behave differently? Is erlang:send_after broken or advertised incorrectly? Or maybe timer:send_after not doing what I think?

+5
source share
2 answers

TL DR

Your assumption is true: they are designed for the same, but implemented in different ways.

discussion

Things in the timer module such as timer:send_after/2,3 work through gen_server, which defines this as a service. Like any other service, this one can be overloaded if you assign it a really huge number of tasks (timers for tracking).

erlang:send_after/3,4 , on the other hand, is a BIF implemented as NIF. If you have a ton of timers, this is definitely the way to go. In most programs, you will not notice the difference.

There is actually a note in the Erlang Performance Guide :

3.1 Timer module

Creating timers using erlang: send_after / 3 and erlang: start_timer / 3 is much more efficient than using timers provided by the timer module in STDLIB. The timer module uses a separate process to manage timers. This process can easily be overloaded if many processes often create and cancel timers (especially when using the SMP emulator).

Functions in the timer module that do not control timers (such as timer: tc / 3 or timer: sleep / 1) do not invoke the timer server process and are therefore harmless.

Workaround

A workaround to increase the efficiency of BIF without limiting the same node is to have your own process that does nothing but wait for a message to be sent to another node:

 -module(foo_forward). -export([send_after/3, cancel/1]). % Obviously this is an example only. You would want to write this to % be compliant with proc_lib, write a proper init/N and integrate with % OTP. Note that this snippet is missing the OTP service functions. start() -> spawn(fun() -> loop(self(), [], none) end). send_after(Time, Dest, Message) -> erlang:send_after(Time, self(), {forward, Dest, Message}). loop(Parent, Debug, State) -> receive {forward, Dest, Message} -> Dest ! Message, loop(Parent, Debug, State); {system, From, Request} -> sys:handle_msg(Request, From, Parent, ?MODULE, Debug, State); Unexpected -> ok = log(warning, "Received message: ~tp", [Unexpected]), loop(Parent, Debug, State) end. 

The above example is a bit superficial, but hopefully it reflects the gist. It should be possible to get the efficiency of BIF erlang:send_after/3,4 but still manage to send messages through the nodes, and also give you the opportunity to cancel the message using erlang:cancel_timer/1

But why?

The riddle (and error) is that erlang:send_after/3,4 does not want to work between nodes. The above example looks somewhat strange, since the first assignment for P was Pid <10101.10.0> , but the <10101.10.0> call was reported as <10585.83.0> - clearly not the same.

At the moment I do not know why erlang:send_after/3,4 does not work, but I can say with confidence that the mechanism of work between them is not the same. I will look at this, but I imagine that the BIF version actually does some fun things at runtime to increase efficiency and, as a result, signals the target process by directly updating its mailbox, instead of actually sending an Erlang message to more High Erlang. Erlang level.

Maybe it’s good that we have both, but this should be clearly noted in the documents, and this obviously is not (I just checked).

+5
source

There is some difference in the timeout order if you have many timers. The example below shows that erlang: send_after does not guarantee order, and timer: send_after does.

 1> A = lists:seq(1,10). [1,2,3,4,5,6,7,8,9,10] 2> [erlang:send_after(100, self(), X) || X <- A]. ... 3> flush(). Shell got 2 Shell got 3 Shell got 4 Shell got 5 Shell got 6 Shell got 7 Shell got 8 Shell got 9 Shell got 10 Shell got 1 ok 4> [timer:send_after(100, self(), X) || X <- A]. ... 5> flush(). Shell got 1 Shell got 2 Shell got 3 Shell got 4 Shell got 5 Shell got 6 Shell got 7 Shell got 8 Shell got 9 Shell got 10 ok 
0
source

Source: https://habr.com/ru/post/1272709/


All Articles