Station 03Open the adventure map
Messages and timeouts
Receive and reply by hand first. Then uncover what GenServer does for us.
When concurrent replies can be late or out of order and the service process can exit, how do we make sure each caller gets its own result?
The second request received the first request's late reply
After the first request times out, its late reply remains in the caller's mailbox. The second request has no unique identifier and matches that old reply; a service exit is also mistaken for an ordinary timeout.
- A complete log of requests, references, and replies
- Messages still present in the caller's mailbox after timeout
- A timeline showing whether monitor DOWN or timeout happened first
The problem it solves
Write one message loop by hand so you can see what GenServer adds. Give every request a number and return that number with the reply. A timeout only stops waiting; it does not pull a message back.
You will be able to
Give every request a unique reference and return the same number with its reply
Explain that links and monitors both watch process exits, but differ in direction and reaction
Recognize three common problems: processes waiting on each other, late replies, and a growing mailbox
- Use Elixir or Erlang pattern matching to recognize a message
- Know that every BEAM process has its own mailbox
See what it does before how it is written
A request reference pairs the reply, a monitor reference reports exit, and a timeout still does not recall a sent message.
# Prepare one reference for process exit and another for the reply
def call(server, request, timeout \ 1_000) do
monitor_ref = Process.monitor(server)
request_ref = make_ref()
# Tell the server both the return address and request number
send(server, {:call, self(), request_ref, request})
receive do
# A normal reply must match the request reference
{:reply, ^request_ref, response} ->
# Stop watching and remove a DOWN message that may already be waiting
Process.demonitor(monitor_ref, [:flush])
{:ok, response}
# A monitor reports exit in one direction; the caller chooses the result
{:DOWN, ^monitor_ref, :process, ^server, reason} ->
{:error, {:server_down, reason}}
after
# A timeout stops waiting; it does not pull back the request
timeout ->
Process.demonitor(monitor_ref, [:flush])
{:error, :timeout}
end
endWhy this shape?
Why carry a request reference in every reply?
One caller can receive late, out-of-order, or unrelated messages. A reference states which request this answer belongs to.
A timeout is not cancellation. Late replies, server exits, and monitor cleanup still need policy.
Coming from Java, Python, or JavaScript
Java
- Familiar starting point
- Future, CompletableFuture, and executors.
- What BEAM changes
- A reply is an ordinary mailbox message paired by a reference; a monitor separately reports exit.
- False friend
- A waiting timeout rarely proves that remote work was cancelled.
Python
- Familiar starting point
- await, Future, and Queue.
- What BEAM changes
receiveselects by pattern, leaving unmatched messages in place.- False friend
- Selective receive can rescan a mailbox; growth still needs a bound.
JavaScript
- Familiar starting point
- Promise resolve or reject.
- What BEAM changes
- Request and reply are an explicit message protocol, not a Promise created by the VM.
- False friend
- A reply may still arrive after timeout.
Make a reply arrive late
Let the server reply after 1.5 seconds while the client waits only 0.5 seconds. See where the reply goes after the timeout.
- 01
Write a small service that waits 1.5 seconds before replying to a request
- 02
Make a call that is willing to wait only 500 milliseconds
- 03
After two seconds, run
Process.info(self(), :messages)and inspect your mailbox
# Show messages still waiting in the current IEx process mailbox
Process.info(self(), :messages)- The call first returns
{:error, :timeout} - The server may still finish its work, and the late reply may appear in the caller's mailbox
Remove the reference and send two requests that finish at different speeds. See whether replies can be matched to the wrong request.
Stopping the wait does not stop the server's work. A reference can pair the correct reply.
One experiment shows only a few arrival orders. It cannot tell us the right timeout for a real service.
Key ideas in this code
reference
A ticket generated by BEAM that is extremely unlikely to repeat. It often answers, “Which request does this reply belong to?”
link
A two-way exit connection between processes. By default, an abnormal exit signal travels along a link.
monitor
A one-way watching relationship. When the watched process exits, the watcher receives DOWN but does not automatically exit.
Design patterns used here
Correlation Identifier
Give every request a unique reference and accept only a reply with that reference.
Request-Reply
Define the requester, receiver, and reply-message protocol explicitly.
Timeout
Limit waiting while recognizing that a late message can still arrive and must be handled.
After the caller reaches its timeout, what usually happens to the message already sent to the server?
Build a small key-value service
Implement put, get, and delete. Give synchronous requests references, and separate a timeout from a server exit.
Hint 1Take the first step
Write every incoming and outgoing message shape on paper before writing the loop.
Hint 2Make it a little smaller
While waiting for a reply, also handle a DOWN message in the same receive.
Hint 3You are close
After a normal reply, decide when to use demonitor to stop a watch you no longer need.
Hint 4Work backward from the finish
Choose one success signal and write the smallest test for it. If the computer cannot show the result, rewrite the signal as something you can truly observe.
Requests do not confuse each other even when replies arrive out of order
The caller separates “waited too long” from “the service exited”
A test truly creates and checks a late reply
Remember three things
- 1
A timeout means stop waiting. It does not automatically cancel work that has begun.
- 2
A reference is like a receipt number that keeps requests and replies paired.
- 3
A link makes a two-way exit connection. A monitor only watches and sends a message, leaving the watcher to choose the next step.