Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elixir Supervisors — How do you name a Supervised Task

I'm really struggling with Elixir supervisors and figuring out how to name them so that I can use them. Basically, I'm just trying to start a supervised Task which I can send messages to.

So I have the following:

defmodule Run.Command do
  def start_link do
    Task.start_link(fn ->
      receive do
        {:run, cmd} -> System.cmd(cmd, [])
      end
    end)
  end
end

with the project entry point as:

defmodule Run do
  use Application

  # See http://elixir-lang.org/docs/stable/elixir/Application.html
  # for more information on OTP Applications
  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    children = [
      # Define workers and child supervisors to be supervised
      worker(Run.Command, [])
    ]

    # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: Run.Command]
    Supervisor.start_link(children, opts)
  end
end

At this point, I don't even feel confident that I'm using the right thing (Task specifically). Basically, all I want is to spawn a process or task or GenServer or whatever is right when the application starts that I can send messages to which will in essence do a System.cmd(cmd, opts). I want this task or process to be supervised. When I send it a {:run, cmd, opts} message such as {:run, "mv", ["/file/to/move", "/move/to/here"]} I want it to spawn a new task or process to execute that command. For my use, I don't even need to ever get back the response from the task, I just need it to execute. Any guidance on where to go would be helpful. I've read through the getting started guide but honestly it left me more confused because when I try to do what is done it never turns out as it does in the application.

Thanks for your patience.

like image 639
kkirsche Avatar asked Jul 14 '15 01:07

kkirsche


1 Answers

I would just use a GenServer, set up like the following:

defmodule Run do
  use Application

  def start(_, _) do
    import Supervisor.Spec, warn: false

    children = [worker(Run.Command, [])]
    Supervisor.start_link(children, strategy: :one_for_one)
  end
end

defmodule Run.Command do
  use GenServer

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

  def run(cmd, opts) when is_list(opts), do: GenServer.call(__MODULE__, {:run, cmd, opts})
  def run(cmd, _), do: GenServer.call(__MODULE__, {:run, cmd, []})

  def handle_call({:run, cmd, opts}, _from, state) do
    {:reply, System.cmd(cmd, opts), state}
  end
  def handle_call(request, from, state), do: super(request, from, state)
end

You can then send the running process a command to execute like so:

# If you want the result
{contents, _} = Run.Command.run("cat", ["path/to/some/file"])
# If not, just ignore it
Run.Command.run("cp", ["path/to/source", "path/to/destination"])

Basically we're creating a "singleton" process (only one process can be registered with a given name, and we're registering the Run.Command process with the name of the module, so any consecutive calls to start_link while the process is running will fail. However, this makes it easy to set up an API (the run function) which can transparently execute the command in the other process without the calling process having to know anything about it. I used call vs. cast here, but it's a trivial change if you will never care about the result and don't want the calling process to block.

This is a better pattern for something long-running. For one-off things, Task is a lot simpler and easier to use, but I prefer to use GenServer for global processes like this personally.

like image 93
bitwalker Avatar answered Sep 30 '22 10:09

bitwalker