Await an OTP process to exit
Asked Answered
N

2

5

Suppose you have an OTP process whose completion you want to synchronously wait on (where "completion" may be a normal exit or a crash, stop, etc.).

Suppose further that for business reasons, you can't spawn this process with Task.async/1 or related Task utilities—it has to be a "normal" process not dependent on Task.await/2.

Is there a better way to do this than simply intermittently polling Process.alive?/1? This strikes me as the sort of thing there's probably an established pattern for, but for the life of me I can't find it.

def await(pid, timeout) when timeout <= 0 do
  if Process.alive?(pid) do
    Process.exit(pid, :kill) # (or however you want to handle timeouts)
  end
end

@await_sleep_ms 500
def await(pid, timeout) do
  # Is there a better way to do this?
  if Process.alive?(pid) do
    :timer.sleep(@await_sleep_ms)
    await(pid, subtract_timeout(timeout, @await_sleep_ms))
  end
end
Naples answered 15/7, 2021 at 16:26 Comment(2)
Do you know whether the process is trapping exits? In any case, Process.monitor/1 and receive/1 with a timeout: :infinity and pattern-matching PID in message handler is a way to go.Multure
Aha! Process.monitor/1 is exactly what I was looking for!Naples
Z
4

The Process.monitor/1 function monitors the given process from the calling process. Combining this with receive, you can react to your process mailbox.

defmodule Await do
  def spawn do
    Kernel.spawn(fn -> :timer.sleep(2000) end)
  end
  
  def await_down(pid) do
    ref = Process.monitor(pid)
    receive do
      {:DOWN, ^ref, :process, ^pid, _reason} = message -> {:ok, message}
    after
      3000 -> {:error, :timeout}
    end
  end
end

pid = Await.spawn()
# => #PID<0.332.0>

Await.await_down(pid)
# => {:ok, {:DOWN, #Reference<0.4083290149.3661365255.180715>, :process, #PID<0.332.0>, :normal}}

Note the pattern matching inside the receive block, to ensure not only the message is from your process, but that is comes from that specific monitoring.

Zoraidazorana answered 15/7, 2021 at 18:1 Comment(0)
S
3

It looks like Process.monitor/1 might be what you're looking for? Example from the docs:

pid = spawn(fn -> 1 + 2 end)
#=> #PID<0.118.0>
Process.monitor(pid)
#=> #Reference<0.906660723.3006791681.40191>
Process.exit(pid, :kill)
#=> true
receive do
  msg -> msg
end
#=> {:DOWN, #Reference<0.906660723.3006791681.40191>, :process, #PID<0.118.0>, :noproc}
Stabilizer answered 15/7, 2021 at 16:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.