Process Monitoring and Linking

Process Monitoring and Linking

In the last lesson, you kept state inside a process with a receive loop. Quick reminder: a process waits for messages with receive and often recurses to stay alive. Today, you will build on that by making your process aware of other processes’ life cycles. This is where monitoring (and, conceptually, linking) comes in.

  • Monitoring lets one process watch another and receive a message when it exits.
  • Linking ties processes together so a crash can propagate. We will mention linking for context but focus on monitoring in code.

Monitoring Two Workers and Handling DOWN Messages

defmodule MonitorExample do
  def unstable_worker do
    Process.sleep(1000)
    # Simulate a failure without raising an exception to avoid error logs
    IO.puts "Unstable worker finished abnormally"
    exit(:abnormal)
  end

  def stable_worker do
    Process.sleep(2000)
    IO.puts "Stable worker completed"
  end

  def monitor_workers do
    pid1 = spawn(fn -> __MODULE__.unstable_worker() end)
    pid2 = spawn(fn -> __MODULE__.stable_worker() end)
    
    ref1 = Process.monitor(pid1)
    ref2 = Process.monitor(pid2)
    
    wait_for_completion(ref1, ref2)
  end

  defp wait_for_completion(ref1, ref2) do
    receive do
      {:DOWN, ^ref1, :process, _pid, reason} when not is_nil(ref1) ->
        IO.puts "Process 1 died: #{inspect(reason)}"
        wait_for_completion(nil, ref2)
      
      {:DOWN, ^ref2, :process, _pid, _reason} when not is_nil(ref2) ->
        IO.puts "Process 2 completed"
    end
  end
end

MonitorExample.monitor_workers()

Explanation:

  • unstable_worker sleeps for 1s, prints a message, then calls exit(:abnormal). This simulates a failure without raising an exception (so you avoid noisy error logs).
  • stable_worker sleeps for 2s and finishes normally.
  • monitor_workers spawns both workers and sets up monitors: ref1 = Process.monitor(pid1) and ref2 = Process.monitor(pid2). A monitor is one-way: if the target process exits, you receive a {:DOWN, ref, :process, pid, reason} message. Your monitoring process does not crash because of it.
  • wait_for_completion/2 listens for those :DOWN messages:
    • The first clause matches the DOWN for ref1 and prints the exit reason (expected :abnormal here). It then recurses with ref1 set to nil to avoid matching it again.
    • The second clause matches the DOWN for ref2 and prints a completion message.
  • The final line starts the whole flow. Expected order: after ~1s, you’ll see the “abnormally” message and “Process 1 died: :abnormal,” and after ~2s, you’ll see “Stable worker completed” and “Process 2 completed.”

About linking (briefly): linking connects processes so that a crash in one can bring down the linked process unless you trap exits. Monitoring is safer for observers because it only sends a message on exit and does not crash the observer.

Summary and Next Steps

You now know how to:

  • Spawn workers and monitor them with Process.monitor/1.
  • React to {:DOWN, ref, :process, pid, reason} messages in a receive loop.
  • Distinguish monitoring (one-way notification) from linking (bidirectional failure propagation).

This is the basis for building supervisor-like behavior and making your systems fault-aware. Ready to make it stick? Head to the practice section and put monitoring into action.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal