NamedSupervisedServer behaviour (named_supervised_server v0.1.5)

A behaviour module for starting supervised GenServer that is named as __MODULE__ by default or you can supply different name.

With the use of NamedSupervisedServer, you can avoid the need to add start_link/1 function with name: __MODULE__ to your module every time you write a new one. And if you need your own start_link/1 function, you can override it.

Compatible with PartitionSupervisor. Partition number added at the end of process name, :partition argument passed down to process init.

Installation

If available in Hex, the package can be installed by adding named_supervised_server to your list of dependencies in mix.exs:

def deps do
  [
    {:named_supervised_server, "~> 0.1"}
  ]
end

Usage

  • Add use NamedSupervisedServer to your module.
  • See Examples.

use NamedSupervisedServer

When you use NamedSupervisedServer, the NamedSupervisedServer module will:

  • set @behaviour NamedSupervisedServer,
  • add use GenServer,
  • pass options passed to use NamedSupervisedServer to use GenServer,
  • define a start_link/1 function with name: __MODULE__, so your module does not need to define it if you don't want a custom one.

Usage with PartitionSupervisor

You must use with_arguments with :partition key:

with_arguments: fn [opts], partition ->
  [Keyword.put(opts, :partition, partition)]
end}

Like this:

children = [
  {PartitionSupervisor,
  child_spec: {MyNamedServer, my_arg: "Hello named server!"},
  name: MyNamedServerPartitionSupervisor,
  with_arguments: fn [opts], partition ->
    [Keyword.put(opts, :partition, partition)]
  end},
]

{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)

See Examples.

Callbacks

The following callbacks may be implemented by modules that use the NamedSupervisedServer behavior:

  • start_link/1: Starts a GenServer process linked to the current process. This function is often used to start the server as part of a supervision tree.

start_link/1 callback should return one of the following values:

  • {:ok, pid}: The server was successfully created and initialized.

  • :ignore: The server should be ignored, and no further action is taken.

  • {:error, {:already_started, pid}}: A process with the specified name already exists.

  • {:error, reason}: The initialization of the server failed for the given reason.

  • {:stop, reason}: The process is terminated due to the provided reason.

Options

start_link/1 callback accept the following option:

  • :name (optional): Used for name registration as described in the "Name Registration" section in the documentation for GenServer.

Examples

Basic usage

iex> defmodule MyServer do
...>  use NamedSupervisedServer
...>
...>  @impl GenServer
...>  def init(arg) do
...>    {:ok, arg}
...>  end
...>end
...># Starting a supervised process
...>{:ok, sup} = Supervisor.start_link([MyServer], strategy: :one_for_one)
...>{_id, child, _type, _modules} = hd(Supervisor.which_children(sup))
...>assert Process.whereis(MyServer) == child

With GenServer options

iex> defmodule TransientServer do
...>  use NamedSupervisedServer, restart: :transient, shutdown: 10_000
...>
...>  @impl GenServer
...>  def init(arg) do
...>    {:ok, arg}
...>  end
...>end
...># Starting a supervised process
...>{:ok, sup} = Supervisor.start_link([TransientServer], strategy: :one_for_one)
...>{:ok, chldspec} = :supervisor.get_childspec(sup, TransientServer)
...>assert chldspec.restart == :transient
...>assert chldspec.shutdown == 10_000
...>{_id, child, _type, _modules} = hd(Supervisor.which_children(sup))
...>assert Process.whereis(TransientServer) == child

Using a different name for the process

iex>defmodule NamedServer do
...>  use NamedSupervisedServer
...>
...>  @impl GenServer
...>  def init(arg) do
...>    {:ok, arg}
...>  end
...>
...>  @impl GenServer
...>  def handle_call(:get_my_arg, _from, state) do
...>    {:reply, {:ok, state[:my_arg]}, state}
...>  end
...>
...>  def get_my_arg(pid) do
...>    GenServer.call(pid, :get_my_arg)
...>  end
...>end
...>
...>children = [
...>  {NamedServer, my_arg: "Hello named server!", name: :my_named_server}
...>]
...># Starting a named supervised process
...>{:ok, _} = Supervisor.start_link(children, strategy: :one_for_one)
...>
...># Access the named process using its registered name
...>{:ok, "Hello named server!"} = NamedServer.get_my_arg(:my_named_server)

With PartitionSupervisor

iex>defmodule MyNamedServer do
...>  use NamedSupervisedServer
...>
...>  @impl GenServer
...>  def init(arg) do
...>    {:ok, arg}
...>  end
...>
...>  @impl GenServer
...>  def handle_call(:get_my_arg, _from, state) do
...>    {:reply, {:ok, state[:my_arg]}, state}
...>  end
...>
...>  @impl GenServer
...>  def handle_call(:get_partition, _from, state) do
...>    {:reply, {:ok, state[:partition]}, state}
...>  end
...>
...>  def get_my_arg(pid) do
...>    GenServer.call(pid, :get_my_arg)
...>  end
...>
...>  def get_partition(pid) do
...>    GenServer.call(pid, :get_partition)
...>  end
...>end
...>
...>children = [
...>  {PartitionSupervisor,
...>   child_spec: {MyNamedServer, my_arg: "Hello named server!", name: :my_named_server},
...>   name: MyNamedServerPartitionSupervisor,
...>   with_arguments: fn [opts], partition ->
...>     [Keyword.put(opts, :partition, partition)]
...>   end},
...>]
...># Starting a named supervised process
...>{:ok, _} = Supervisor.start_link(children, strategy: :one_for_one)
...>
...># Access the named process using its registered name
...>{:ok, "Hello named server!"} = MyNamedServer.get_my_arg(:my_named_server0)
...># Partition number of process
...>{:ok, 0} = MyNamedServer.get_partition(:my_named_server0)
iex>defmodule CustomServer do
...>  use NamedSupervisedServer
...>
...>  # Override start_link/1 to provide additional options
...>  @impl NamedSupervisedServer
...>  def start_link(args) when is_list(args) do
...>    GenServer.start_link(__MODULE__, args, name: :custom_server)
...>  end
...>
...>  @impl GenServer
...>  def init(arg) do
...>    {:ok, arg}
...>  end
...>end
...>
...># Starting a custom supervised GenServer process
...>{:ok, sup} = Supervisor.start_link([CustomServer], strategy: :one_for_one)
...>{_id, child, _type, _modules} = hd(Supervisor.which_children(sup))
...>assert Process.whereis(:custom_server) == child

References

Summary

Callbacks

Starts a NamedSupervisedServer process linked to the current process that is named as __MODULE__ by default or you can supply different name.

Callbacks

start_link(args)

@callback start_link(args) :: GenServer.on_start() when args: list()

Starts a NamedSupervisedServer process linked to the current process that is named as __MODULE__ by default or you can supply different name.

This is often used to start the NamedSupervisedServer as part of a supervision tree.

Once the server is started, the init/1 function of the given 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.

Note that a NamedSupervisedServer started with start_link/1 is linked to the parent process and will exit in case of crashes from the parent. The NamedSupervisedServer will also exit due to the :normal reasons in case it is configured to trap exits in the init/1 callback.

Options

  • :name (optional): Used for name registration as described in the "Name Registration" section in the documentation for GenServer.

Return values

If the server is successfully created and initialized, this function returns {:ok, pid}, where pid is the PID of the server. If a process with the specified server name already exists, this function returns {:error, {:already_started, pid}} with the PID of that process.

If the init/1 callback fails with reason, this function returns {:error, reason}. Otherwise, if it returns {:stop, reason} or :ignore, the process is terminated and this function returns {:error, reason} or :ignore, respectively.