1

I am having an issue with Handle Cast. After the module loads it calls periodic_comm which after every 5 seconds calls the save_data method. In my save_data method the logs are being printed but the statement does nothing.

GenServer.cast(__MODULE__, {:save_load_data, data})

The handle cast method is defined here

 def handle_cast({:save_load_data, data}, state) do
  Logger.error("uin saving data====================================")
  # state = Map.put_new(state, :load_data, data)
  # Logger.debug "updated state : #{inspect state}"
  {:noreply, state}
end

The entire file looks like this

      defmodule Core.Inverter.Manager do
    # core inverter process that handles the inverter communication
    use GenServer
    require Logger

    def start_link(_) do
      Logger.info("putting some string")
      GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
    end

    @impl true
    def init(_) do
      Logger.info("inverter Manager init'ed")
      {:ok, %{configs: nil, inverter_connection: nil, inverter_impl: nil}}
    end

    # public api
    # initialize the inverter and start periodic comm
    def init_inverter(configs) do
      GenServer.cast(__MODULE__, {:init_inverter, configs})
      GenServer.cast(__MODULE__, {:start_periodic_comm, configs})
    end

    def inverter_off() do
      GenServer.call(__MODULE__, :inverter_off)
    end

    @impl true
    def handle_cast({:init_inverter, configs}, state) do
      Logger.debug("initializing the inverter")
      # fetching inverter type/configurations
      inverter_impl = get_inverter_impl(configs)
      # initializing inverter comm channel
      inverter_connection = inverter_impl.init(configs)

      state = %{
        configs: configs,
        inverter_connection: inverter_connection,
        inverter_impl: inverter_impl
      }

      Logger.warn("inverter type #{state.inverter_impl}")
      {:noreply, state}
    end

    def handle_cast({:start_periodic_comm, configs}, state) do
      Logger.warn(
        "Actually Starting inverter periodic COMM with inverter type #{state.inverter_impl}"
      )

      start_periodic_comm(state)
      {:noreply, state}
    end

    @impl true
    def handle_call(
          :inverter_off,
          _from,
          %{inverter_impl: inverter_impl, inverter_connection: inverter_connection} = state
        ) do
      {:ok, inverter_connection} = inverter_impl.inverter_off(inverter_connection)
      {:reply, :ok, %{state | inverter_connection: inverter_connection}}
    end

    def handle_cast({:save_load_data, data}, state) do
      Logger.error("uin saving data====================================")
      # state = Map.put_new(state, :load_data, data)
      # Logger.debug "updated state : #{inspect state}"
      {:noreply, state}
    end

    defp start_periodic_comm(state) do
      periodic_comm(state)
    end

    defp periodic_comm(state) do
      # call functions from the inverter_type module, e.g infit.ex
      receive do
      after
        5000 ->
          {:ok, data} = state.inverter_impl.get_load_data(state)
          save_data(data)
      end

      periodic_comm(state)
    end

    # defp save_data() do end
    def save_data(data) do
      Logger.debug("saving data")
      Logger.debug("in savinf data data is : #{inspect(data)}")
      GenServer.cast(__MODULE__, {:save_load_data, data})
    end

    defp get_inverter_impl(configs) do
      # add case statements here
      Core.Inverter.Infini
    end
  end
Umer Kiani
  • 3,783
  • 5
  • 36
  • 63

1 Answers1

4

I'm not so familiar with elixir's syntax, but it seems to me that periodic_comm is looping:

    defp periodic_comm(state) do
      # call functions from the inverter_type module, e.g infit.ex
      receive do
      after
        5000 ->
          {:ok, data} = state.inverter_impl.get_load_data(state)
          save_data(data)
      end

      periodic_comm(state)
    end

regardless of the result of save_data, the thread is endlessly looping over calls to periodic_comm, so it gets no chance to receive and execute the save_load_data message.

In order to fix it, you should refactor the server to have the logic in handle_X without looping, which receive all kind of messages. You can use erlang:start_timer (I don't know the elixir's counterpart) or rely on the Timeout in the gen_server return values to receive timeout messages.

José M
  • 3,294
  • 1
  • 14
  • 16
  • 1
    I would use `:timer.send_interval` (https://erlang.org/doc/man/timer.html#send_interval-2) to send a repeating message like this. – Paweł Obrok Nov 20 '20 at 10:46
  • 1
    @PawełObrok the timer module scales poorly if the amount of timers is huge (http://erlang.org/doc/efficiency_guide/commoncaveats.html#timer-module), sometimes this may be an issue. `erlang:start_timer` handles timers internally in a way more scalable fashion. – José M Nov 20 '20 at 10:54
  • I will check and get back to you on this – Umer Kiani Nov 20 '20 at 10:56
  • 1
    @JoséM That's a good point, but of course depends on your usage. The docs say "if many processes create and cancel timers frequently" - if you're creating a constant number of timers at system startup, that fire every couple minutes or something, that might not be a problem. – Paweł Obrok Nov 22 '20 at 17:39
  • @PawełObrok this is our use case the timers will be created at startup and fire on a fixed frequency on different modules. – Umer Kiani Nov 23 '20 at 04:39
  • @JoséM any thoughts on this https://stackoverflow.com/questions/65436480/nerves-project-circuits-spi-clock-not-initialised – Umer Kiani Dec 24 '20 at 10:39