defmodule SupervisedServer do @moduledoc """ A behaviour module for starting supervised GenServer processes. With the use of `SupervisedServer`, you can avoid the need to add `c:start_link/1` function to your module every time you write a new one. And if you need your own `c:start_link/1` function, you can override it. ## Usage * Add `use SupervisedServer` to your module. * View [`Examples`](#module-examples). > #### `use SupervisedServer` {: .info} > > When you `use SupervisedServer`, the SupervisedServer module will: > * set `@behaviour SupervisedServer`, > * add `use GenServer`, > * pass options passed to `use SupervisedServer` to `use GenServer`, > * define a `c:start_link/1` function, so your module does not need to define it > if you don't want a custom one. ## Callbacks The following callbacks may be implemented by modules that use the `SupervisedServer` behavior: - `c: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. `c: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 `c: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 ```elixir iex> defmodule MyServer do ...> use SupervisedServer ...> ...> @impl GenServer ...> def init(arg) do ...> {:ok, arg} ...> end ...>end ...># Starting a supervised process ...>{:ok, _} = Supervisor.start_link([MyServer], strategy: :one_for_one) ``` ### Additional options ```elixir iex> defmodule TransientServer do ...> use SupervisedServer, 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 ``` ### Naming the process ```elixir iex>defmodule NamedServer do ...> use SupervisedServer ...> ...> @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) ``` ### Custom start_link/1 ```elixir iex>defmodule CustomServer do ...> use SupervisedServer ...> ...> # Override start_link/1 to provide additional options ...> @impl SupervisedServer ...> 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, _} = Supervisor.start_link([CustomServer], strategy: :one_for_one) ...>pid = Process.whereis(:custom_server) ...>true = is_pid(pid) ``` ## References * `GenServer` * `Supervisor` """ @doc """ Starts a `SupervisedServer` process linked to the current process. This is often used to start the `SupervisedServer` 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 `SupervisedServer` started with `c:start_link/1` is linked to the parent process and will exit in case of crashes from the parent. The `SupervisedServer` 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. """ @callback start_link(args) :: GenServer.on_start() when args: list @doc false @spec __using__(opts :: keyword) :: Macro.t() defmacro __using__(opts) do quote location: :keep, bind_quoted: [opts: opts] do @behaviour SupervisedServer use GenServer, Macro.escape(opts) @doc false @impl SupervisedServer def start_link(args) when is_list(args) do {name, init_args} = Keyword.pop(args, :name) case name do nil -> GenServer.start_link(__MODULE__, init_args) _ -> GenServer.start_link(__MODULE__, init_args, name: name) end end defoverridable start_link: 1 end end end