0

I am testing the following pattern in a GenServer:

  def handle_info({:tcp, _, data}, s) do
    # IO.puts "\nrx: \n#{Base.encode16(data)}\n"

    extra = _proc_data(<<s.extra::binary, data::binary>>)

    :inet.setopts(s.socket, active: :once)

    {:noreply, %{s | extra: extra}}
  end

There is a problem when data comes in fast, and i'm unable to update state before :inet.setopts(s.socket, active: :once) releases new data

Must {:noreply, %{s | extra: extra}} be the last line for handle_info, or can I perform the :inet.setopts(s.socket, active: :once) last?

Is there a better way to do this?

Peer Stritzinger
  • 8,232
  • 2
  • 30
  • 43
Charles Okwuagwu
  • 10,538
  • 16
  • 87
  • 157
  • The `:noreply` does need to be the last thing since it is the return value of the function. Regarding updating the state, would it solve your problem to have another `GenServer` call from this function? I'm having a little bit of trouble understanding the exact problem but you might start down that path as a refactor. – Christian Di Lorenzo Jan 27 '16 at 17:05
  • @ChristianDiLorenzo The problem is from previous call to `_proc_data` I may have extra bytes which i need to hold in `GenServer state`, and pre-pend to any new data before processing. The requirement to call `{:noreply, new_state}` last in `handle_info` becomes an issue, since calling `:inet.setopts(s.socket, active: :once)` will release new data before i have saved the extra from previous step – Charles Okwuagwu Jan 27 '16 at 17:14
  • 1
    As long as `_proc_data` doesn't `receive` any messages, your code is correct as written. Any additional data released by the `setopts` call will simply be placed in the message queue, and `gen_server` will call your `handle_info` function again. The effect is the same as if the data had arrived just after `handle_info` returned. – legoscia Jan 27 '16 at 18:02
  • Thanks. I have just figured that out now. @sasajuric pointed out that `handle_*` are not re-entrant. That helped me tack down the cause to something else. – Charles Okwuagwu Jan 27 '16 at 18:08
  • @legoscia what if `_proc_data` does a `gen_tcp:send`? I guess any received data still queues up sequentially correct? – Charles Okwuagwu Jan 27 '16 at 18:10
  • 1
    Yes, sending data wouldn't have any effect on the message queue. – legoscia Jan 27 '16 at 18:17

1 Answers1

1

One technique I've used in the past is to send the data to another GenServer for processing. This would allow the current process to call :inet.setopts(s.socket, active: :once) faster.

def handle_info({:tcp, _, data}, s) do
    # IO.puts "\nrx: \n#{Base.encode16(data)}\n"

    GenServer.cast(s.processor, {:data, data})

    :inet.setopts(s.socket, active: :once)

    {:noreply, s}
end

Processor:

defmodule DataProcessor do
  use GenServer

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

  def init(_), do: {:ok, %{}}

  def handle_cast({:data, data}, state) do
    extra = _proc_data(s.extra <> data)    
    {:noreply, %{state| extra: extra}}
  end

  defp _proc_data(data) do
    ...
  end
end
TattdCodeMonkey
  • 155
  • 1
  • 7