## Wednesday, November 10, 2010

If you worked with Erlang you've probably heard about selective receive. But do you actually know how it works? I want to post here an excerpt from Joe Armstrong's book Programming Erlang where he explains how it works exactly (Section 8.6, p.153):

receive    Pattern1 [when Guard1] -> Expressions1;    Pattern2 [when Guard2] -> Expressions2;    ...after    Time -> ExpressionTimeoutend

1. When we enter a receive statement, we start a timer (but only if an after section is present in the expression).
2. Take the first message in the mailbox and try to match it against Pattern1, Pattern2, and so on. If the match succeeds, the message is removed from the mailbox, and the expressions following the pattern are evaluated.
3. If none of the patterns in the receive statement matches the first message in the mailbox, then the first message is removed from the mailbox and put into a "save queue." The second message in the mailbox is then tried. This procedure is repeated until a matching message is found or until all the messages in the mailbox have been examined.
4. If none of the messages in the mailbox matches, then the process is suspended and will be rescheduled for execution the next time a new message is put in the mailbox. Note that when a new message arrives, the messages in the save queue are not rematched; only the new message is matched.
5. As soon as a message has been matched, then all messages that have been put into the save queue are reentered into the mailbox in the order in which they arrived at the process. If a timer was set, it is cleared.
6. If the timer elapses when we are waiting for a message, then evaluate the expressions ExpressionsTimeout and put any saved messages back into the mailbox in the order in which they arrived at the process.

Did you notice the concept of "save queue"? That's what many people are not aware of. Let's play with various scenarios and see the mailbox and save queue in action.

The first scenario is simple, nothing to test there in regards to mailbox. The second one is also straightforward:

1> self() ! a.a2> process_info(self()). ... {message_queue_len,1}, {messages,[a]}, ...3> receive a -> 1; b -> 2 end.14> process_info(self()). ... {message_queue_len,0}, {messages,[]}, ...

You send a message to the shell, you see it in the process mailbox, then you receive it by matching, after which the queue is empty. Standard queue behaviour.

Now let's test scenario 3,5:

1> self() ! c, self() ! d, self() ! a.a2> process_info(self()). ... {message_queue_len,3}, {messages,[c,d,a]}, ...3> receive a -> 1; b -> 2 end.14> process_info(self()). ... {message_queue_len,2}, {messages,[c,d]}, ...

Again, no surprises. Actually, this example demonstrates what people think when they hear about selective receive. Unfortunately we don't see what happened internally between lines 3 and 4. We are going to investigate it now by testing scenario 3,4.

This time start the shell in distributed mode so that we can connect to it later from the remote shell.

(foo@bar)1> register(shell, self()).true(foo@bar)2> shell ! c, shell ! d.d(foo@bar)3> process_info(whereis(shell)). ... {current_function,{erl_eval,do_apply,5}}, ... {message_queue_len,2}, {messages,[c,d]}, ...(foo@bar)4> receive a -> 1; b -> 2 end.

At this moment the shell is suspended - we are exactly at step 4. Go to remote shell, and type the following:

(foo@bar)1> process_info(whereis(shell)). ... {current_function,{erl_eval,receive_clauses,6}}, ... {message_queue_len,0}, {messages,[]}, ...

That's interesting: no messages in the mailbox. As Joe said, they are in the save queue. Now send a matching message:

(foo@bar)2> shell ! a.a

Go back to initial shell, which should be resumed now, and check the mailbox again:

1(foo@bar)5> process_info(whereis(shell)). ... {current_function,{erl_eval,do_apply,5}}, ... {message_queue_len,2}, {messages,[c,d]}, ...

That's what we saw in the previous test, but now you know what happens behind the scenes: messages are moved from the mailbox to the save queue and then back to the mailbox after the matching message arrives.

Now you should understand better how selective receive works. Next time you explore your Erlang process, keep in mind the save queue and disappearing and reappearing messages.