Running out of Ammo
When you can’t bang!
This story (like the last one) was also inspired by the curiosity of my students at Cali, Colombia. The question, this time, is: When and why does the bang (!) operator fail?
Sometimes you have infinite bullets
What we were discussing with my students those days was one of the core concepts of Erlang: Message Passing. Message Passing in Erlang is achieved using the send operator (i.e. !, a.k.a. bang!). The first time I introduced them to bang, I used a Pid on its left side. Just like this…
1> Pid = self().
<0.65.0>
2> Pid ! {a, message}.
{a,message}
3>
In that scenario (when the expression to the left of the bang is a Pid), bang never fails. Even if the Pid represents a dead process…
3> 1 / 0.
** exception error: an error occurred when evaluating an arithmetic expression
in operator '/'/2
called as 1 / 0
4> erlang:is_process_alive(Pid).
false
5> Pid ! {another, message}.
{another,message}
6>
Sometimes you just can’t shoot
But Pids are not the only valid expressions on the left side of bang. You can also have atoms or tuples. For example…
6> register(my, self()).
true
7> my ! {third, message}.
{third,message}
8> {my, node()} ! {fourth, message}.
{fourth,message}
9> flush().
Shell got {third,message}
Shell got {fourth,message}
ok
10>
In this scenarios, bang might fail if there is no process registered with that name…
10> no_proc ! {fifth, message}.
** exception error: bad argument
in operator !/2
called as no_proc ! {fifth,message}
11>
RTFM!
All these things are very clear in the docs:
Expr1
must evaluate to a pid, a registered name (atom), or a tuple{Name, Node}
.Name
is an atom andNode
is a node name, also an atom.· If
Expr1
evaluates to a name, but this name is not registered, abadarg
run-time error occurs.· Sending a message to a pid never fails, even if the pid identifies a non-existing process.
· Distributed message sending, that is, if
Expr1
evaluates to a tuple{Name, Node}
(or a pid located at another node), also never fails.
Yeah, but…
Now, we could’ve just stopped there.
As you might have guessed, we didn’t do that. We asked ourselves…
If sending a message to a registered process fails with
badarg
when there is no process registered with that name…
1. Can a process be registered with a name but not be alive? In other words: bang failing means that the process is not alive?
2. How can I check if there is a process registered with that name before using bang?
Question #1: Can I register dead processes?
For question #1 we had to check something first. Can a process be dead and also registered with a name? We tried in 2 ways:
1> Pid = self().
<0.65.0>
2> 1 / 0.
** exception error: an error occurred when evaluating an arithmetic expression
in operator '/'/2
called as 1 / 0
3> register(my, Pid).
** exception error: bad argument
in function register/2
called as register(my,<0.65.0>)
4>
First we tried to use erlang:register/2
with a process that was already dead and we couldn’t. Then we tried to register the process, then kill it…
4> register(my, self()).
true
5> 1 / 0.
** exception error: an error occurred when evaluating an arithmetic expression
in operator '/'/2
called as 1 / 0
6> my ! message.
** exception error: bad argument
in operator !/2
called as my ! message
7>
So it failed, but did it fail because the process was dead or because it was not registered at the time of sending the message? That brings us back to our second question…
Question #2: How can I prevent badarg from happening?
Now, how can I find out if there is a process registered with the name I’m about to use before actually using bang on it?
The quick and most accurate answer is: you can’t. Due to the concurrent nature of Erlang, between the moment when you check if the process is there and the moment when you do send the message, the process might have died (or a new process might have been registered with that name.
Nevertheless, there is a way to just check if there is a process registered with a name in your node and find what that process is as a bonus: whereis/1.
7> whereis(my).
undefined
8> register(my, self()).
true
9> whereis(my).
<0.75.0>
10> 1 / 0.
** exception error: an error occurred when evaluating an arithmetic expression
in operator '/'/2
called as 1 / 0
11> whereis(my).
undefined
12>
If whereis/1
returns undefined
, you know that there is no process registered to that name in your node. But, as stated above, you should not write code like the following one…
case whereis(my) of
undefined -> do_nothing;
_ -> my ! message
end
You’re buying yourself a ticket to race conditions land right there, since after whereis
, but before bang, the process called my
might have terminated. This example is a little bit contrived (and it can be easily improved by actually using the result from whereis(my)
), but sadly it’s not unheard of. In any case, a safer way to achieve the same result is something like…
try my ! message
catch
_:badarg -> do_nothing
end
But we hate try…catch!
Here is a trick if you don’t really like to go case’ing or try…catch’ing every message like that. Remember what the docs said before?
· Distributed message sending, that is, if
Expr1
evaluates to a tuple{Name, Node}
(or a pid located at another node), also never fails.
With that in mind, look what you can do instead:
12> my ! message.
** exception error: bad argument
in operator !/2
called as my ! message
13> {my, node()} ! message.
message
14>
Using tuple syntax with your own node you’re effectively sending messages to a process called my if it exists and discarding them if the process is not there. Exactly what would have happened if you’ve used Pids instead.
Bonus Track
I want to close this article with another cool thing we found at Cali. When talking about these things, and not actually knowing how Erlang VM works internally for bang, but considering the scenarios in which bang fails, we tried to imagine what goes on inside the VM there.
The possible errors
First of all, consider this: When bang fails, it always fails with badarg
. It fails that way when you use an atom for an unregistered process but it also fails if you use something else, check it out…
1> x ! message.
** exception error: bad argument
in operator !/2
called as x ! message
2> "a list" ! message.
** exception error: bad argument
in operator !/2
called as "a list" ! message
3>
With a little bit of imagination, we might think that, when you use an atom, Erlang VM is converting that atom to a Pid and then executing the original version of bang on it. If it fails to turn the atom into a Pid, then of course badarg
occurs. In other words, looks like Erlang VM is reducing this…
1> x ! message.
…into this…
1> whereis(x) ! message.
Then, when whereis returns undefined
, bang fails.
Wait a second…
Yeah, right… but undefined
is also an atom. How is the Erlang VM not falling in an infinite recursive loop right there? Well maybe it has a special clause for undefined
. That way it can avoid treating it as any other atom. But then… what if I register a process as undefined
?
Let’s try it, shall we?
3> register(undefined, self()).
** exception error: bad argument
in function register/2
called as register(undefined,<0.69.0>)
4>
Alright, we can’t do it. It’s actually clearly documented:
badarg
IfRegName
is the atomundefined
.
Well played, Erlang/OTP Team. Well played, indeed!