Station 03Open the adventure map
00Set up your toolsSetup01The starting lineSetup02Processes and mailboxesR1An Elixir data pipelineOptional reviewR2Read ErlangOptional review03Messages and timeouts04OTP rules for messages05A supervision tree that restarts06Put a limit on concurrency07When a BEAM node disappearsX1Agree on shared BEAM termsBilingual extensionX2Two-language partnersBilingual extension08A reliable job runner
Home/BEAM mainline/Station 03
03
ConcurrencyIntermediate explorationElixirErlangBEAM

Messages and timeouts

Receive and reply by hand first. Then uncover what GenServer does for us.

4checkpoints
About 6 hours · Try 4 sessionssplit it into sessions
QUESTION · ONE PROBLEM FOR THIS STATION

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?

Start at the scene

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.

Evidence you can observe
  • 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
Why this station matters

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.

After this station

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

Before you start
  • Use Elixir or Erlang pattern matching to recognize a message
  • Know that every BEAM process has its own mailbox
One job, two ways to write it

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.

Current code
Elixir
# 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
end
DESIGN DECISION

Why this shape?

Why carry a request reference in every reply?

Choice

One caller can receive late, out-of-order, or unrelated messages. A reference states which request this answer belongs to.

Cost and boundary

A timeout is not cancellation. Late replies, server exits, and monitor cleanup still need policy.

A FAMILIAR POINT OF VIEW

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
receive selects 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.
LAB
Hands on

Make a reply arrive late

About 15–25 minutes

Let the server reply after 1.5 seconds while the client waits only 0.5 seconds. See where the reply goes after the timeout.

  1. 01

    Write a small service that waits 1.5 seconds before replying to a request

  2. 02

    Make a call that is willing to wait only 500 milliseconds

  3. 03

    After two seconds, run Process.info(self(), :messages) and inspect your mailbox

Copy into the terminal and press Enter
# Show messages still waiting in the current IEx process mailbox
Process.info(self(), :messages)
What you should see
  • The call first returns {:error, :timeout}
  • The server may still finish its work, and the late reply may appear in the caller's mailbox
Break it on purpose

Remove the reference and send two requests that finish at different speeds. See whether replies can be matched to the wrong request.

What this shows

Stopping the wait does not stop the server's work. A reference can pair the correct reply.

What this does not show yet

One experiment shows only a few arrival orders. It cannot tell us the right timeout for a real service.

Meet the words

Key ideas in this code

01

reference

A ticket generated by BEAM that is extremely unlikely to repeat. It often answers, “Which request does this reply belong to?”

02

link

A two-way exit connection between processes. By default, an abnormal exit signal travels along a link.

03

monitor

A one-way watching relationship. When the watched process exits, the watcher receives DOWN but does not automatically exit.

Name the shape in the code

Design patterns used here

01

Correlation Identifier

Give every request a unique reference and accept only a reply with that reference.

02

Request-Reply

Define the requester, receiver, and reply-message protocol explicitly.

03

Timeout

Limit waiting while recognizing that a late message can still arrive and must be handled.

Think it through

After the caller reaches its timeout, what usually happens to the message already sent to the server?

Your turn

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.

Ready to move on when
  • 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

Take these with you

Remember three things

  1. 1

    A timeout means stop waiting. It does not automatically cancel work that has begun.

  2. 2

    A reference is like a receipt number that keeps requests and replies paired.

  3. 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.

Read a little more

Visit the original sources

ProcessErlang concurrent programming
Station completeYou ran the experiment and thought through the answer. Save this station.