Elixir Supervisors - Как вы называете контролируемую задачу

Я действительно борюсь с супервизорами Elixir и придумываю, как назвать их, чтобы я мог их использовать. По сути, я просто пытаюсь запустить контролируемый Task, которому я могу отправлять сообщения.

Итак, у меня есть следующее:

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

с точкой входа в проект как:

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

На данный момент я даже не уверен, что использую то, что нужно (конкретно Task). По сути, все, что я хочу, - это создать процесс или задачу, или GenServer, или что-то еще, что нужно, при запуске приложения, чтобы я мог отправлять сообщения, которые, по сути, будут делать System.cmd(cmd, opts). Я хочу, чтобы эта задача или процесс контролировались. Когда я отправляю ему {:run, cmd, opts} сообщение, например {:run, "mv", ["/file/to/move", "/move/to/here"]}, я хочу, чтобы он порождал новую задачу или процесс для выполнения этой команды. Для моего использования мне даже не нужно когда-либо возвращать ответ от задачи, мне просто нужно, чтобы он выполнялся. Любые советы о том, куда идти, были бы полезны. Я прочитал руководство по началу работы, но, честно говоря, это оставило меня в еще большем замешательстве, потому что, когда я пытаюсь сделать то, что сделано, никогда не получается, как в приложении.

Спасибо за терпеливость.


person kkirsche    schedule 14.07.2015    source источник


Ответы (1)


Я бы просто использовал GenServer, настроенный следующим образом:

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

Затем вы можете отправить запущенному процессу команду для выполнения следующим образом:

# 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"])

По сути, мы создаем «одноэлементный» процесс (только один процесс может быть зарегистрирован с заданным именем, и мы регистрируем процесс Run.Command с именем модуля, поэтому любые последовательные вызовы start_link во время выполнения процесса завершится ошибкой. Однако это упрощает настройку API (функция run), который может прозрачно выполнять команду в другом процессе, при этом вызывающий процесс не должен ничего знать об этом. Я использовал здесь call vs. cast, но это тривиальное изменение, если вы никогда не будете заботиться о результате и не хотите, чтобы вызывающий процесс блокировался.

Это лучший образец для чего-то длительного. Для разовых вещей Task намного проще и проще в использовании, но я лично предпочитаю использовать GenServer для подобных глобальных процессов.

person bitwalker    schedule 14.07.2015
comment
Это прекрасное решение. Причина, по которой вы не можете назвать Задачу, заключается в том, что если вы хотите отправлять ей сообщения ... вы больше не хотите использовать Задачу. - person José Valim; 14.07.2015
comment
Спасибо, что объяснили это так подробно. Для меня это очень помогло @bitwalker. Спасибо за ваше время, помощь и терпение. - person kkirsche; 14.07.2015
comment
@bitwalker При попытке использовать это я получаю тайм-аут GenServer.call. Разве начальник не должен предотвращать подобные вещи? (выход) завершился через: GenServer.call (Run.Command, {: run, ls, [.]}, 5000) ** (EXIT) тайм-аут (elixir) lib / gen_server.ex: 356: GenServer.call/3 - person kkirsche; 14.07.2015
comment
Ах, неважно. Приведение лучше подходит для асинхронных ситуаций, когда вызов лучше, когда требуется ответ из-за синхронности. Прости! - person kkirsche; 14.07.2015
comment
@kkirsche Супервизор не предотвращает тайм-ауты, он просто следит за тем, чтобы процесс был перезапущен после сбоя, поэтому вы можете либо установить тайм-аут на большее значение при использовании call, если вы вызываете долго выполняющиеся команды, либо использовать cast: ) - person bitwalker; 14.07.2015