Вызов GenServer из другого GenServer - PullRequest
0 голосов
/ 06 декабря 2018

У меня есть проект, в котором я использую 2 GenServers. Первый GenServer с именем «State» поддерживает состояние, а второй GenServer с именем «Updates» поддерживает список возможных обновлений состояния.Чего я хочу добиться:

При каждом вызове «State» «State» должен вызывать «Updates» и обновлять себя перед возвратом фактического состояния.

Оба GenServer запускаютсясупервизор и я можем вызывать оба GenServers по имени извне, но если я пытаюсь вызвать API «Updates» внутри «State», «State» завершается с ошибкой «no process».Любые предложения?

  def start_link(opts) do
    Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
  end

  def init(_arg) do
    children = [
      {Updates, name: Updates},
      {State, name: State}
    ]

    Supervisor.init(children, strategy: :rest_for_one)
  end

Оба GenServer начинаются с

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

В «состоянии» у меня есть обратный вызов

  @impl true
  def handle_call({:get}, _from, state) do
    updates = Updates.get_updates(Updates)
    {:reply, updates, state}
  end

Опять же, если я позвоню Updates.get_updates(Обновления), например, из iex напрямую все работает, как и ожидалось, поэтому я думаю, что все в порядке с моим руководителем.Похоже, что «State» не знает имя «Updates».

Updates.get_updates / 1 реализация:

  def get_updates(pid) do
    GenServer.call(pid, :get)
  end

, где обратный вызов - это просто состояние в качестве ответа

  @impl true
  def handle_call(:get, _from, state) do
    {:reply, state, state}
  end

1 Ответ

0 голосов
/ 06 декабря 2018

Состояние "завершается с ошибкой" нет процесса ". Любые предложения?

Согласно Документам супервизора , список children:

   children = [
      {Updates, name: Updates},
      {State, name: State}
    ]

должен представлять собой список из child specification кортежей, где дочерняя спецификация имеет следующие допустимые ключи:

Дочерняя спецификация содержит 6 ключей. Первые два необходимы, иостальные необязательны:

: id - любой термин, используемый для внутренней идентификации дочерней спецификации супервизором; по умолчанию используется данный модуль. В случае конфликтующих значений: id супервизорОткажется от инициализации и потребует явных идентификаторов. Этот ключ необходим.

: start - кортеж с аргументами module-function-args для запуска дочернего процесса.требуется.

: restart - атом, который определяет, когда завершить дочерний процесс должен быть перезапущен (см. раздел «Значения перезапуска» ниже). Этот ключ является необязательным и по умолчанию: постоянный.

: shutdown - атом, который определяет, как дочерний процесс должен быть завершен (см. Раздел «Значения выключения» ниже).Этот ключ является необязательным и по умолчанию равен 5000, если типом является: worker или: infinity, если типом является: supervisor.

: type - указывает, что дочерний процесс является: worker или a:руководитель.Этот ключ является необязательным и по умолчанию: worker.

Существует шестой ключ, : modules , который редко изменяется.Он устанавливается автоматически в зависимости от значения в: start.

Обратите внимание, что нет клавиши name:, которую вы указываете в своих дочерних спецификациях.

Однако GenServer.start_link() имеет опцию name::

И start_link / 3, и start / 3 поддерживают GenServer для регистрации имени при запуске с помощью опции: name.Зарегистрированные имена также автоматически очищаются при расторжении.Поддерживаемые значения:

атом - GenServer зарегистрирован локально с заданным именем, используя Process.register / 2.

{: global, term} - GenServer зарегистрирован глобально с данным термином, используя функции в: global module.

{: via, module, term} - GenServer зарегистрирован вданный механизм и имя.Опция: via ожидает модуль, который экспортирует имя_регистра / 2, имя_регистра / 1, имя_сайта / 1 и отправить / 2.Одним из таких примеров является: global модуль, который использует эти функции для хранения списка имен процессов и связанных с ними PID, которые доступны глобально для сети узлов Elixir.Elixir также поставляется с локальным, децентрализованным и масштабируемым реестром, который называется Registry для локального хранения имен, которые генерируются динамически.

Например, мы можем запустить и зарегистрировать наш сервер Stack локально следующим образом:

# Start the server and register it locally with name:

MyStack {:ok, _} = GenServer.start_link(Stack, [:hello], name: MyStack)

Итак, я думаю, что вы должны делать что-то вроде этого:

  def init(_arg) do
    children = [
      Updates,
      State
    ]

Затем в ваших функциях start_link () GenServer:

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

======

Вот полный пример.В application.ex вы можете указать имена, которые хотите зарегистрировать:

children = [
  # Starts a worker by calling: Servers.Worker.start_link(arg)
  # {Servers.Worker, arg},
  {
    Servers.CurrentState, [ 
      init_state_with: [:hello, 10], 
      name_to_register: Servers.CurrentState
    ] 
  },
  {
    Servers.Updates, [
      init_state_with: [:goodbye], 
      name_to_register: Servers.Updates
    ]
  }
]

Тогда вы сможете определить два ваших GenServer следующим образом:

lib / servers / updates.ex:

defmodule Servers.Updates do
  use GenServer

  def start_link(arg) do  

    GenServer.start_link(
      __MODULE__, 
      arg[:init_state_with], 
      name: arg[:name_to_register])                                       

  end

  ## Callbacks

  @impl true
  def init(state) do
    {:ok, state}
  end

  @impl true
  def handle_call(:get_updates, _from, state) do
    {:reply, state, state}
  end

  @impl true
  def handle_cast({:push, item}, state) do
    {:noreply, [item | state]}
  end


  ##User interface:

  def get() do
    GenServer.call(__MODULE__, :get_updates)
  end

  def add(item) do
    GenServer.cast(__MODULE__, {:push, item})
  end

end

lib / servers / current_state.ex:

defmodule Servers.CurrentState do
  use GenServer

  def start_link(args) do  

    GenServer.start_link(
      __MODULE__, 
      args[:init_state_with], 
      name: args[:name_to_register])

  end

  ## Callbacks

  @impl true
  def init(state) do
    IO.inspect(state, label: "The CurrentState server is starting with state")
    {:ok, state}
  end

  @impl true
  def handle_call(:get_state, _from, state) do
    state_to_add = Servers.Updates.get()
    new_state = state_to_add ++ state

    {:reply, new_state, new_state}
  end


  ##User interface:

  def get() do
    GenServer.call(__MODULE__, :get_state)
  end

end

Тогда, вы можете проверить вещи с помощью:

defmodule Servers.Go do
  def test() do
    IO.inspect("Updates has state: #{inspect Servers.Updates.get()}" )
    IO.inspect("CurrentState has state: #{inspect Servers.CurrentState.get()}" )
    :ok
  end
end

В iex:

~/elixir_programs/servers$ iex -S mix
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]

Compiling 1 file (.ex)

The CurrentState server is starting with state: [:hello, 10]

Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)

iex(1)> Servers.Go.test()
"Updates has state: [:goodbye]"
"CurrentState has state: [:goodbye, :hello, 10]"
:ok

iex(2)> 

(обратите внимание, что первая строка вывода смешана с сообщениями запуска сервера.)

Однако вы можете использовать __MODULE__ для упрощения вещей:

application.ex :

children = [
  # Starts a worker by calling: Servers.Worker.start_link(arg)
  # {Servers.Worker, arg},

  { Servers.CurrentState,  [:hello, 10] }
  { Servers.Updates, [:goodbye] }

]

lib / servers / updates.ex :

defmodule Servers.Updates do
  use GenServer

  def start_link(arg) do  
                  #arg comes from child specification tuple
                  #inside the `children` list in application.ex

    #                        | module where the GenServer is defined
    #                        | 
    #                        |        | send arg to the GenServer's init() function       
    #                        V        V
    GenServer.start_link(__MODULE__, arg, name: __MODULE__)
    #                                      ^
    #                                      |
    #                     register the specified name for this GenServer

  end

  ## Callbacks

  @impl true
  def init(state) do
    {:ok, state}
  end

  @impl true
  def handle_call(:get_updates, _from, state) do
    {:reply, state, state}
  end

  @impl true
  def handle_cast({:push, item}, state) do
    {:noreply, [item | state]}
  end

  ## User interface:

  def get() do
    GenServer.call(__MODULE__, :get_updates)
  end

  def add(item) do
    GenServer.cast(__MODULE__, {:push, item})
  end

end

lib / servers / current_state.ex:

defmodule Servers.CurrentState do
  use GenServer

  def start_link(arg) do  
                  #arg comes from child specification tuple
                  #inside the `children` list in application.ex

    #                        | module where the GenServer is defined
    #                        | 
    #                        |        | send arg to the GenServer's init() function       
    #                        V        V
    GenServer.start_link(__MODULE__, arg, name: __MODULE__)
    #                                       ^
    #                                       |
    #                     register the specified name for this GenServer 
  end

  ## Callbacks

  @impl true
  def init(state) do
    IO.inspect(state, label: "The CurrentState server is starting with state")
    {:ok, state}
  end

  @impl true
  def handle_call(:get_state, _from, state) do
    state_to_add = Servers.Updates.get()
    new_state = state_to_add ++ state

    {:reply, new_state, new_state}
  end

  ## User interface:

  def get() do
    GenServer.call(__MODULE__, :get_state)
  end

end
...