"receive do ..." frequency and rejecting -- 2 questions

  1. How often does OTP poll a mailbox of a GenServer? In other words, if I send a message to a GenServer, how fast will it arrive to receive do?

Can that be set up?

  1. Is there any way to reject or flush all messages from a mailbox waiting to be processed by receive do without actually receiving them? Or rather the ones I wish to reject given some condition.

If on the local node then the sending process puts it into the mailbox of the receiver immediately.

If remote, then it is however long it takes to transmit it over the network.

Once in the mailbox then the system registers the receiving process as needing a timeslice, which might happen immediately (depends on system load and back-pressure conditions and such).

Nope, the BEAM has no way of controlling network conditions so it does not give you such a false security. :slight_smile:

You can flush all of them out with flush(), to flush specific ones that you do not want then just do something like:

def flush_what_i_do_not_want() do
  receive do
    {:pattern, :blah} -> :more
    i when is_integer(i) -> :more
    {:add, :more, :patterns, :here} -> :more
  after 0 -> :done
  end
  |> case do
    :more -> flush_what_i_do_not_want()
    :done -> :done
  end
end

Or if you want to ā€˜flushā€™ all that do not match a condition, that is more tricky, usually better to just handle all the ones you want then match out what you do not care about via _ and ignore those, but if you really really want to remove the ones you do not want but keeping a matched set then you can do this, but be forwarned that a message ā€˜couldā€™ come in during the time you are processing them so order would be changed a bit, and it completely removes the BEAM back-pressure system:

def flush_all_non_matching(), do: flush_all_non_matching(make_ref())
def flush_all_non_matching(ref) do
  receive do
    ^ref -> :done
    {:pattern, :blah} = msg -> send(self(), msg); :more
    i = msg when is_integer(i) -> send(self(), msg); :more
    {:add, :more, :patterns, :here} = msg -> send(self(), msg); :more
    _ -> :more # Flush non-matching
  after 0 -> throw "Should not happen unless someone screwed with messages during this time"
  end
  |> case do
    :more -> flush_all_non_matching()
    :done -> :done
  end
end

And of course you could wrap those up in macros for ease-of-use.

Honestly though, if you are doing the above you really should refactor your code.

2 Likes

My understanding of how the BEAM works is limited, but I donā€™t think it makes sense to think of this in terms of polling. As I understand it when a process hits a receive block it will first check to see if a matching message already exists. If not the process gets removed from the active pool of processes. At this point it will consume memory, but no CPU. Another process can send it a message which basically puts the message contents into the memory of the target process, and moves it back into the active pool for further evaluation.

Each process in the live pool gets ~2000 ā€œreductionsā€ before being paused and the next process given a turn. A reduction loosely corresponds to a function call.

1 Like

(specifically to the part of OP asking about using receive do in GenServer)

In the GenServer docs, it says not to use receive do.

Therefore, you should never call your own ā€œreceiveā€ inside the GenServer callbacks as doing so will cause the GenServer to misbehave.

What happens if I go against this advice? Iā€™ve noticed a few applications in Erlang that do use it and was wondering how Iā€™d implement the same pattern short of wrapping the receive do block in a process.

The reason youā€™re cautioned against having custom receive do blocks in a gen server is that the generic gen server code is already doing this for you. Arbitrary messages will show up in handle_info, and the specially tagged genserver messages like cast and all will show up in handle_call/cast. If you start manually receiving these youā€™re intercepting them before the genserver code can do its job.

1 Like

There are a few cases that it is fine, but in general it just means be careful about the matched messages in it so you do not mis-route something that should have gone through the GenServer. In general it is best to use handle_info for non-genserver messages.

Yep this. :slight_smile:

But matching specific messages is often fine, like if you do a GenServer.call from within a GenServer, it will be ā€˜receivingā€™ the specific message, so that is fine. :slight_smile:

3 Likes

There are a few cases that it is fine, but in general it just means be careful about the matched messages in it so you do not mis-route something that should have gone through the GenServer. In general it is best to use handle_info for non-genserver messages.

Sweet, thanks for the insight! In my case, the messages are tagged to avoid mis-routing.

def flush_what_i_do_not_want() do
  receive do
    {:pattern, :blah} -> :more
    i when is_integer(i) -> :more
    {:add, :more, :patterns, :here} -> :more
  after 0 -> :done
  end
  |> case do
    :more -> flush_what_i_do_not_want()
    :done -> :done
  end
end

FtLoG, really? :lol:

def flush_what_i_do_not_want() do
  receive do
    {:pattern, :blah} -> flush_what_i_do_not_want()
    i when is_integer(i) -> flush_what_i_do_not_want()
    {:add, :more, :patterns, :here} -> flush_what_i_do_not_want()
  after 0 -> :done
  end
end

Just stick to recursion ā€¦ pipes have their place - I donā€™t think this is one of the them :icon_biggrin:

Lol, true, my original example before it was simplified had more cases so it was useful, not as useful after it was simplified. ^.^;

1 Like

Haha, Iā€™ve always missed that. Anyway I do it all the time.

If we really want to get technical, anytime you use GenServer.call you enter into a receive block.

Whatā€™s really dangerous in a GenServer is entering into a receive loop or matching everything.

1 Like

Yes, the BEAM does not poll. When a process is doing a receive and runs out of messages it is put in a waiting state. Then when a new message arrives, or the receive times out, the process is put in the run-queue and when its turn comes that new message or timeout is processed. So yes, processes waiting for messages only take memory and do not use CPU time.

This handling is actually critical as in most system very many, if not most, processes are just sitting waiting for messages.

1 Like