View Source Starting and stopping processes

The ProcessHub library provides a way to start and stop processes dynamically. The processes can be started under a hub and can be stopped at any time.

ProcessHub can be used with any process as long as it returns {:ok, pid} when started. We recommend using GenServer for the processes as it provides a lot of features out of the box.

Process identification

The :id of child specification is used to uniquely identify the process in the hub and can be used to stop the process or query its pid. The :id has to be either an atom or a string. For systems that create lots of dynamic processes, it is recommended to use strings as :id because atoms are not garbage collected.

The :id has to be unique within the hub. If a process with the same :id is started again it may result in an unexpected behavior unless the uniqueness is checked before starting the process using the :check_existing option.

Starting processes

Processes can be started dynamically using the ProcessHub.start_children/3 or ProcessHub.start_child/3 functions. The latter is a convenience function to start a single process and wraps the former.

Some examples of starting processes are:

Starting multiple processes

iex> ProcessHub.start_children(:my_hub, [
  %{id: "process1", start: {MyProcess, :start_link, []}},
  %{id: "process2", start: {MyProcess, :start_link, []}}
])
{:ok, :start_initiated}

Starting a single process

child_spec = %{id: "process_id", start: {MyProcess, :start_link, []}}
ProcessHub.start_child(:my_hub, child_spec)
{:ok, :start_initiated}

Starting processes statically

Processes can also be started statically under the supervision tree. This is useful when the processes are known at compile time.

defmodule MyApp.Application do
  use Application

  def start(_type, _args) do
    child_specs = [
        %{
            id: :my_process_1,
            start: {MyProcess, :start_link, []}
        },
        %{
            id: :my_process_2,
            start: {MyProcess, :start_link, []}
        }
    ]

    children = [
      ProcessHub.child_spec(%ProcessHub{
        hub_id: :my_hub,
        child_specs: child_specs
      })
    ]

    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

See ProcessHub.init_opts/0 for more information on the options that can be passed to the start functions.

Stoping processes

Processes can be stopped dynamically using the ProcessHub.stop_child/3 function or ProcessHub.stop_children/3 function.

Some examples of stopping processes are:

ProcessHub.stop_child(:my_hub, "child_id")
{:ok, :stop_initiated}
ProcessHub.stop_children(:my_hub, ["child_id1", "child_id2"])
{:ok, :stop_initiated}

See ProcessHub.stop_opts/0 for more information on the options that can be passed to the stop functions.

Self stopping processes

Processes can also stop themselves by sending exit signals to the linked processes (supervisor). This is useful when the process has to stop itself based on some condition.

For processes to be eligible for self-stopping, they have to be started with a :restart option set to :transient

If the :restart option is anything other than :transient and the stop code is not either shutdownor normal the supervisor will try to restart the process when it stops.

This will also update the registry accordingly for all the nodes in the cluster. Keep in mind that the operation is asynchronous.

Example self-stopping GenServer process

child_spec1 = %{
      id: :self_shutdown_1,
      start: {Test.Helper.TestServer, :start_link, [%{name: :self_shutdown_1}]},
      restart: :transient # Set the restart option to transient
    }

ProcessHub.start_child(:my_hub, child_spec)

# In the process module
defmodule MyProcess do
  use GenServer

  def start_link(_) do
    GenServer.start_link(__MODULE__, nil)
  end

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

  def handle_info(msg, state) do
    {:stop, :normal, state} # Return the stop tuple
  end
end

Asynchronous operations

All operations by default are asynchronous and can be made synchronous by passing the async_wait: true option. The same options works for starting and stopping processes.

Example of synchronous operation

ProcessHub.start_children(
    :my_hub, 
    [child_spec1], 
    async_wait: true # Add this option to be able to wait for the operation to complete
) |> ProcessHub.await() # Wait for the operation to complete
{:ok, {:my_child, [{:mynode, #PID<0.123.0>}]}}