How to gracefully kill gen_servers after their job is done?

I have two GenServers, A and B, where A is long-running, spawns a bunch of Bs, which perform a bunch of tasks and then are no longer needed.


  • A spawns B.
  • B calls A.
  • A casts a message back to B, possibly minutes later.
  • B performs some task on the information cast by A, then needs to be killed.

So I want B to be killed either after it gets the response from A, or after a timeout. This leads to two questions:

  1. To kill B after receiving the cast from A, is it simply enough to return {:stop, :normal, state} instead of {:noreply, state}?

  2. For timing out B in case of no reply, I am returning {:ok, state, @timeout} from init (where @timeout is some timespan). I also need to add timeout to all my replies, correct? (e.g: {:noreply, state, @timeout}).



I assume B does a cast to A as the 2nd step. Or is there something else
going on that needs that to be synchronous? A return value??

When you want to stop B {:stop, :normal, state} is enough.

Still assuming that B casts to A. After doing that you could do a
{:noreply, state, timeout}. If you don’t receive anything the timeout will
happen and you then need to deal with that in handle_info/2, where you can
kill B. Do note that the timeout is cancelled whenever a message is
If that does not suit you, you have to manage timers yourself. It sounds
like you need that, but it is hard to tell from your description.


If you want a dead-simple timeout that applies to the whole lifetime of a genserver you can also do something like this:

def init(_) do
  :timer.send_after(600_000, :job_timeout)

def handle_info(:job_timeout, state) do
  {:stop, :timeout, state}

Basically you send yourself a :job_timeout message 600 seconds after it starts. This timer won’t be reset by incoming messages. The only downside I know of is that :timer has a single process that sends all of the messages so if you get up to millions of jobs it can become a bottleneck.


Process.send_after uses a different mechanism and would be probably the right choice here.

I wish we knew more about the actual underlying problem, it might help with feedback.


You can use :erlang.send_after/3/4 or :erlang.start_timer/3/4 which are handled internally in the machine and are much more efficient.

EDIT: This is probably the same as Process.send_after.


Yeah, Process.send_after is just erlang.send_after but it swaps the arguments around to be more in line with the noun first elixir convention. IE pid |> Process.send_after(:msg, 5_000)