Understanding "the process is not alive or there's no process currently associated with the given name"

Hi!

I’m writing an Elixir application with a DynamicSupervisor. Once the application has started, I’d like to add some children to this dynamic supervisor, but I receive an error when calling start_child():

** (Mix) Could not start application backend: exited in: Backend.Application.start(:normal, [])
    ** (EXIT) exited in: GenServer.call(Backend.Crawler.CrawlerSupervisor, {:start_child, {{Backend.Crawler.Server, :start_link, [[domain: "mastodon.social"]]}, :permanent, 5000, :worker, [Backend.Crawler.Server]}}, :infinity)
        ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started

The same error occurs when I run through the steps one at a time in iex.

I’m confused by this error because I know that the dynamic supervisor CrawlerSupervisor is alive (I can see it in the supervision tree). The GenServer I’m trying to start, Backend.Crawler.Server, does exist – but why would it need to be alive before I call start_child? Can someone clear this up for me?

Here’s the full code, if it’s helpful.

The application

defmodule Backend.Application do
  @moduledoc false

  use Application
  alias Backend.Crawler.CrawlerSupervisor
  alias Backend.{Instance, Repo}
  import Ecto.Query

  def start(_type, _args) do
    children = [
      Backend.Repo,
      BackendWeb.Endpoint,
      CrawlerSupervisor
    ]

    opts = [strategy: :one_for_one, name: Backend.Supervisor]

    case Supervisor.start_link(children, opts) do
      ok = {:ok, _pid} ->
        start_instance_servers()
        ok

      other ->
        other
    end
  end

  defp start_instance_servers() do
    domains = ["mastodon.social"]
    domains
    |> Enum.each(fn domain -> CrawlerSupervisor.start_child(domain) end)
  end
end

The dynamic supervisor

defmodule Backend.Crawler.CrawlerSupervisor do
  use DynamicSupervisor
  alias Backend.Crawler.Server

  def start_link(_init_arg) do
    DynamicSupervisor.start_link(__MODULE__, name: __MODULE__)
  end

  @impl true
  def init(_opts) do
    DynamicSupervisor.init(strategy: :one_for_one)
  end

  def start_child(domain) do
    spec = {Server, domain: domain}
    DynamicSupervisor.start_child(__MODULE__, spec)
  end
end

The GenServer child process

defmodule Backend.Crawler.Server do
  use GenServer
  import Backend.Crawler.Util
  alias Backend.Crawler.Crawler

  # Client
  def start_link(domain) do
    GenServer.start_link(__MODULE__, domain, name: domain)
  end

  # Server
  @impl true
  def init(domain) do
    schedule_crawl()
    {:ok, domain}
  end

  @impl true
  def handle_cast(:crawl, state) do
    Crawler.run(state)
    schedule_crawl()
    {:noreply, state}
  end

  defp schedule_crawl() do
    interval = get_config(:crawl_interval_mins) * 60_000
    Process.send_after(self(), :crawl, interval)
  end
end

Those are the kind of spec I am passing to dynamic supervisor when starting a new child…

    spec = %{
      id: name,
      start: {Worker, :start_link, [args]},
      restart: :temporary,
      type: :worker
    }

For You, it would probably be

    spec = %{
      id: domain,
      start: {Server, :start_link, [domain]},
      restart: :permanent,
      type: :worker
    }

BTW: Are You sure You want a permanent restart with dynamic children?

I also do register worker name in Registry…

  alias Registry.Games, as: RegGames
  ...
  def start_link(args) do
    name = args.uuid
    GenServer.start_link(__MODULE__, args, name: via_tuple(name))
  end
  ...
  defp via_tuple(name), do: {:via, Registry, {RegGames, name}}

I think that the code in my initial post is just shorthand for the same – see this section in the Elixir docs. But with your change, I get the same error: the only difference is that instead of [[domain: "mastodon.social"]] the worker receives the argument ["mastodon.social"]. Based on this, I tried using

spec = %{
    id: domain,
    start: {Server, :start_link, domain},
    restart: :permanent,
    type: :worker
}

Now the application starts successfully, but the worker doesn’t actually start! Rather, the call to DynamicSupervisor.start_child/2 returns

{:error,
 {:invalid_mfa, {Backend.Crawler.Server, :start_link, "mastodon.social"}}}

This makes me think that the problem lies in my worker module, Backend.Crawler.Server. It seems like the start_link function causes the initial error if it’s passed a list?

Oh, and re:

BTW: Are You sure You want a permanent restart with dynamic children?

Yes – I don’t know the complete list of workers up front, but once a worker is started, I want it to stick around :slight_smile:

:wave:

{:error,
 {:invalid_mfa, {Backend.Crawler.Server, :start_link, "mastodon.social"}}}

mfa (module/functions/args) needs to be {atom, atom, list}. So you need to pass
{Backend.Crawler.Server, :start_link, ["mastodon.social"]}

In your OP, you probably need to change the start_link function since it receives a keyword list from your supervisor:

defmodule Backend.Crawler.Server do
  use GenServer
  import Backend.Crawler.Util
  alias Backend.Crawler.Crawler

  # Client
  def start_link(domain: domain) do # <---
    GenServer.start_link(__MODULE__, domain, name: String.to_atom(domain)) # <-- names can only be atoms unless you use registries, I'd suggest to use a registry for processes created by dynamic supervisors
  end

You also need to replace handle_cast with handle_info for these section to work:

  def handle_info(:crawl, state) do # <---
    Crawler.run(state)
    schedule_crawl()
    {:noreply, state}
  end

  defp schedule_crawl() do
    interval = get_config(:crawl_interval_mins) * 60_000
    Process.send_after(self(), :crawl, interval) # <-- these messages are handled in `handle_info` callbacks
  end

Thank you! I followed this advice, but I’m getting the same error as in the initial post.

I made a minimal app here that reproduces the problem: https://gitlab.com/taobojlen/dynamic-supervisor-demo/tree/master/lib/dynamic_supervisor_demo

Running mix phx.server throws the error in question.

--- a/lib/dynamic_supervisor_demo/application.ex
+++ b/lib/dynamic_supervisor_demo/application.ex
@@ -13,20 +13,16 @@ defmodule DynamicSupervisorDemo.Application do
       DynamicSupervisorDemo.Repo,
       # Start the endpoint when the application starts
       DynamicSupervisorDemoWeb.Endpoint,
-      CrawlerSupervisor
+      CrawlerSupervisor,
       # Starts a worker by calling: DynamicSupervisorDemo.Worker.start_link(arg)
       # {DynamicSupervisorDemo.Worker, arg},
+      {Task, fn -> start_instance_servers() end}
     ]

     # See https://hexdocs.pm/elixir/Supervisor.html
     # for other strategies and supported options
     opts = [strategy: :one_for_one, name: DynamicSupervisorDemo.Supervisor]
-    case Supervisor.start_link(children, opts) do
-      ok = {:ok, _pid} ->
-        start_instance_servers()
-        ok
-      other -> other
-    end
+    Supervisor.start_link(children, opts)
   end

Seems like the the CrawlerSupervisor wasn’t yet started when you were calling it right after Supervisor.start_link(children, opts).

Interesting – with these changes, I still receive the same error.

Before calling start_instance_servers(), the process tree (minus the Phoenix web part) looks like so:

<0.301.0> is the CrawlerSupervisor. I don’t know why its name isn’t shown here, but it can be seen from its state.

Screenshot%20from%202019-07-07%2016-47-28

So the supervisor is running, but start_instance_servers() still fails.

Could the problem be that the the call to CrawlerSupervisor.start_child_server() isn’t attached to the actually-running supervisor?

Seems like it wasn’t named since DynamicSupervisor.start_link/2,3 accepts init_arg as the second argument and option list – which includes :name – as the third argument:

DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__) from

https://hexdocs.pm/elixir/master/DynamicSupervisor.html?#module-module-based-supervisors

2 Likes

Amazing – thank you so much, that did it!

A lot of take-away points from this for a new Elixir dev like me.

  • Be really careful about the types of function arguments.
  • The name of a supervisor matters – it doesn’t seem like you can refer to it just by its module name if it isn’t named correctly.
  • Use a registry for dynamic supervisors :slight_smile:
2 Likes