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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With