简体   繁体   中英

Add/remove child from supervisor children list at runtime in Elixir

I know how to create a supervisor with an initial list of children.

defmodule TweetProcesser.DummySupervisor do
  use Supervisor

  def start_link(opts) do
    Supervisor.start_link(__MODULE__, :ok, opts)
  end

  @impl true
  def init(:ok) do
    children = [
      Supervisor.child_spec({TweetProcesser.Worker, []}, id: :my_worker_1),
      Supervisor.child_spec({TweetProcesser.Worker, []}, id: :my_worker_2),
      Supervisor.child_spec({TweetProcesser.Worker, []}, id: :my_worker_3),
      Supervisor.child_spec({TweetProcesser.Worker, []}, id: :my_worker_4),
      Supervisor.child_spec({TweetProcesser.Worker, []}, id: :my_worker_5)
    ]

    opts = [strategy: :one_for_one, name: TweetProcesser.WorkerSupervisor]

    Supervisor.init(children, opts)
  end
end

But how can I make the functionality for adding new children or removing children from this list at runtime? In such way that other actors could call these functions while running in order to add or remove children.

A possible approach would be to use a DynamicSupervisor, followed by a one-off worker that gives the supervisor it's initial children.

Assuming this is the bare skeleton code for you processes

defmodule TweetProcesser.Worker do
  use GenServer

  def start_link() do
    GenServer.start_link(__MODULE__, [], [])
  end

  def init(_) do
    {:ok, nil}
  end
end

defmodule StartInitialChildren do
  use Task

  def start_link([]) do
    Task.start_link(fn ->
      [:my_worker_1, :my_worker_2, :my_worker_3, :my_worker_4, :my_worker_5]
      |> Enum.each(fn id ->
        spec = %{id: id, start: {TweetProcesser.Worker, :start_link, []}}
        {:ok, _pid} = DynamicSupervisor.start_child(TweetProcesser.DummySupervisor, spec)
      end)
    end)
  end
end

You can then add this to your application children

defmodule MyApp.Application do
  # ...
  def start(_, _) do
    def children = [
      # ...
      {DynamicSupervisor, name: TweetProcesser.DummySupervisor, strategy: :one_for_one},
      StartInitialChildren
      # ...
    ]
  end
  #...
end

What happens is, your dynamic supervisor will start with no children.

Then the one off task will run, and start the 5 children you want it to start with.

After that, you can use the same line of code to start more children.

spec = %{id: some_id, start: {TweetProcesser.Worker, :start_link, []}}
DynamicSupervisor.start_child(TweetProcesser.DummySupervisor, spec)

Of course, you can make it nicer by defining helper functions, but to get the base functionality going, this should do the trick.

Also note that, as others here have said, you should probably avoid atoms as ids when you're starting processes dynamically, as they don't get cleaned up fully and there's an overall limit on the system.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM