tao

tao

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

Showing Posts 1 to 9

kokolegorille

kokolegorille

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?

kokolegorille

kokolegorille

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}}
tao

tao OP

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:

idi527

idi527

:waving_hand:

{: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
tao

tao OP

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.

idi527

idi527

--- 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).

tao

tao OP

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.

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?

idi527

idi527

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

tao

tao OP

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:
— All posts loaded —

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
dli
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app? Looking for hints regarding: Addi...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
bottlenecked
Hi all, I wanted to ask how the community is dealing with post-release steps. Today we have Ecto migrations, which make sure that the db...
New
rahultumpala
Hello, I have an Elixir backend that implements a custom protocol over TCP. I want to load test the backend and assess the performance o...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews