defmodule EQC.Pulse.Task.Supervisor do @compile {:parse_transform, :pulse_instrument} @compile {:pulse_replace_module, [{:supervisor, :pulse_supervisor}]} alias EQC.Pulse.Supervisor, as: Supervisor @moduledoc """ A tasks supervisor. This module defines a supervisor which can be used to dynamically supervise tasks. Behind the scenes, this module is implemented as a `:simple_one_for_one` supervisor where the workers are temporary (i.e. they are not restarted after they die). The functions in this module allow tasks to be spawned and awaited from a supervisor, similar to the functions defined in the `Task` module. ## Name Registration A `Task.Supervisor` is bound to the same name registration rules as a `GenServer`. Read more about it in the `GenServer` docs. """ @doc """ Starts a new supervisor. The supported options are: * `:name` - used to register a supervisor name, the supported values are described under the `Name Registration` section in the `GenServer` module docs; * `:restart` - the restart strategy, may be `:temporary` (the default), `:transient` or `:permanent`. Check `Supervisor.Spec` for more info. Defaults to temporary as most tasks can't be effectively restarted after a crash; * `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown or an integer indicating the timeout value, defaults to 5000 milliseconds; * `:max_restarts` and `:max_seconds` - as specified in `Supervisor.Spec.supervise/2`; """ @spec start_link(Supervisor.options) :: Supervisor.on_start def start_link(opts \\ []) do import Supervisor.Spec {restart, opts} = Keyword.pop(opts, :restart, :temporary) {shutdown, opts} = Keyword.pop(opts, :shutdown, 5000) children = [worker(EQC.Pulse.Task.Supervised, [], restart: restart, shutdown: shutdown)] Supervisor.start_link(children, [strategy: :simple_one_for_one] ++ opts) end @doc """ Starts a task that can be awaited on. The `supervisor` must be a reference as defined in `Task.Supervisor`. For more information on tasks, check the `Task` module. """ @spec async(Supervisor.supervisor, fun) :: Task.t def async(supervisor, fun) do async(supervisor, :erlang, :apply, [fun, []]) end @doc """ Starts a task that can be awaited on. The `supervisor` must be a reference as defined in `Task.Supervisor`. For more information on tasks, check the `Task` module. """ @spec async(Supervisor.supervisor, module, atom, [term]) :: Task.t def async(supervisor, module, fun, args) do args = [self, get_info(self), {module, fun, args}] {:ok, pid} = Supervisor.start_child(supervisor, args) ref = Process.monitor(pid) send pid, {self(), ref} %Task{pid: pid, ref: ref} end @doc """ Terminates the child with the given `pid`. """ @spec terminate_child(Supervisor.supervisor, pid) :: :ok def terminate_child(supervisor, pid) when is_pid(pid) do :supervisor.terminate_child(supervisor, pid) end @doc """ Returns all children pids. """ @spec children(Supervisor.supervisor) :: [pid] def children(supervisor) do :supervisor.which_children(supervisor) |> Enum.map(&elem(&1, 1)) end @doc """ Starts a task as child of the given `supervisor`. Note that the spawned process is not linked to the caller, but only to the supervisor. This command is useful in case the task needs to perform side-effects (like I/O) and does not need to report back to the caller. """ @spec start_child(Supervisor.supervisor, fun) :: {:ok, pid} def start_child(supervisor, fun) do start_child(supervisor, :erlang, :apply, [fun, []]) end @doc """ Starts a task as child of the given `supervisor`. Similar to `start_child/2` except the task is specified by the given `module`, `fun` and `args`. """ @spec start_child(Supervisor.supervisor, module, atom, [term]) :: {:ok, pid} def start_child(supervisor, module, fun, args) do Supervisor.start_child(supervisor, [get_info(self), {module, fun, args}]) end defp get_info(self) do {node(), case Process.info(self, :registered_name) do {:registered_name, []} -> self() {:registered_name, name} -> name end} end end