Iex won't drop into iex> prompt

I’m working on bringing an old app back to life and one of the things I’ve noticed as I’ve plugged things back in is that when I try to launch an iex session, I see debug messages as various components start (mostly GenServers), but I never get to the iex prompt. I’m just running the usual iex -S mix

I can debug this by commenting out the problematic GenServers, but I was wondering if anyone had some ideas “life-hacks” that might give me something more specific to focus on. Like… do these problematic genservers need to be wrapped in their own supervised process? Are they somehow bogarting the current iex process? I feel like the Process.send_after/3 calls are causing the current process to wait instead of the genserver process…

I think I found the cause, but I’m not sure I follow why one variant hangs.

This is the variant that hangs. Notice that it makes an HTTP call from directly within the init. Then it calls Process.send_after/3. When this is started in my app’s supervision tree, iex starts, but it never drops into the iex> shell.

def init(%{http_client: http_client} = state) do
    data = http_client.get_stuff()
    schedule_refresh()
    {:ok, Map.put(state, :data, data)}
end

def handle_info(:refresh, %{http_client: http_client} = state) do
    data = http_client.get_stuff()
    schedule_refresh()
    {:noreply, Map.merge(state, %{data: data})}
end

defp schedule_refresh,
    do: Process.send_after(self(), :refresh, @refresh_delay + :rand.uniform(20))

Compare with this refactor. This removes the duplicated code. This one seems to work fine: iex starts up as expected – I have to hit enter but then I drop into the iex> shell as expected.

def init(state) do
     send(self(), :refresh)
    {:ok, state}
 end

def handle_info(:refresh, state = %{http_client: http_client}) do
    data = http_client.get_stuff()
    schedule_refresh()
    {:noreply, Map.merge(state, %{data: data})}
end

defp schedule_refresh,
    do: Process.send_after(self(), :refresh, @refresh_delay + :rand.uniform(20))

Can someone explain why the first variant hangs? Is it because the current process (the iex session in my case) is waiting for a response from the start_link function (which is ultimately handled by the init function)?

1 Like

Don’t take my response as a given, as I am only just starting to use processes in this way in my own work.

However, I think that you are correct in that start_link is waiting. i.e. data = http_client.get_stuff(), whereas in your refactored code the call to handle_info does not expect a response, regardless of whether it succeeds.

The GenServer start_link doc (GenServer — Elixir v1.12.3), I think confirms what you have thought, specifically the following paragraph:

Once the server is started, the init/1 function of the given module is called with init_arg as its argument to initialize the server. To ensure a synchronized start-up procedure, this function does not return until init/1 has returned.

I noted that there is an option to add a timeout to start_link, but I think your refactor works far better (not that I am an expert).

As You have noticed, it’s not good to block the init… and for this, there is handle_continue.

https://elixirschool.com/blog/til-genserver-handle-continue/

3 Likes