Catherine Zeta-Jones, Tom Hanks, Kumar Pallana — The Terminal (2004)

The Code

Let me introduce you to Jaime:

Jaime is a gen_server. It basically carries things in his tray and eventually delivers them all together. The tricky part is that, if he trips with something, he’ll throw the last thing he put on his tray. But he will do it so efficiently that if you want, you can totally grab that thing before it… you know… hits the floor or something.

Most of Jaime’s code is your regular gen_server’s code. The main part is in handle_call. Let’s check its three clauses one by one…

handle_call(deliver, _From, Tray) -> {reply, Tray, []};

The first one is a classic one: The server gets a deliver message and returns the whole state (in this case the Tray) and in the same process alters the state so that the new Tray becomes empty. Let’s give it a try on the console…

1> jaime:start().

{ok,<0.68.0>}

2> jaime:deliver().

[]

3> jaime:carry(coke).

ok

4> jaime:carry(pasta).

ok

5> jaime:deliver().

[pasta,coke]

6> jaime:deliver().

[]

7>

Everything looks quite right. Let’s check the next clause…

handle_call(trip, _From, []) -> throw(empty_tray);

This is an edge case. The requirements stated that, in case of tripping, Jaime should throw the most recent item on his Tray. What should Jaime do if there are no items on it is not specified. The proper thing to do would’ve been to just not add a clause for that, but let’s imagine that we wanted to provide a clearer exception in this situation and not just a function_clause. Let’s see what happens…

1> jaime:start().

{ok,<0.73.0>}

2> jaime:trip(). =ERROR REPORT==== 2-Aug-2016::08:17:58 ===

** Generic server jaime terminating

** Last message in was trip

** When Server state == []

** Reason for termination ==

** {bad_return_value,empty_tray}

** exception exit: {{bad_return_value,empty_tray},

{gen_server,call,[jaime,trip]}}

in function gen_server:call/2 (gen_server.erl, line 204)

3>

Ok, so… From the perspective of the caller, we get the empty_tray exception somewhere. Also the server crashes, which was expected. But there is one curious thing: We don’t simply get an empty_tray exception, don’t we? It’s actually a bad_return_value. Before diving into what this means, let’s look at the last function clause:

handle_call(trip, _From, [Stuff|Tray]) ->

throw({reply, Stuff, Tray}).

This one is weird. Jaime is actually throwing an exception. But the exception looks a lot like a proper handle_call response. Let’s see what happens if we try to use it…

1> jaime:start().

{ok,<0.68.0>}

2> jaime:carry(coke).

ok

3> jaime:carry(pasta).

ok

4> jaime:trip().

pasta

5> jaime:deliver().

[coke]

6>

Toro the Bull expected an exception

Wait a second! There are absolutely no traces of any exception there. The gen_server just acted as if the exception Jaime threw was a regular callback response.