Is there a way to keep GenServer state persistent?

582 views Asked by At

I am trying to implement a counter using GenServer. The idea is to increment a counter whenever a client visits the server, and print it in the console. However, after every visit GenServer state reset or PID terminated. Is there a way to keep GenServer state persistent?

Here's the code:

counter file in lib/myapp_web/livecount.ex

defmodule Counter do
    use GenServer
    # Client
  
    def start_link(integer \\ 0, opts \\ []) when is_integer(integer) do
      GenServer.start_link(__MODULE__, integer, opts)
    end

    def add(pid) do
        GenServer.call(pid, :add)    
    end

    # Server
    @impl true
    def handle_call(:add, _from, state) do
        value = state + 1
        {:reply, "Dashboard hit: #{value}", value}
    end

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

I added the following code in lib/myapp_web/endpoint.ex


  def message(conn, _opts) do
    Code.require_file("lib/myapp_web/livecount.ex")
    {:ok, pid} = Counter.start_link()
    message = Counter.add(pid)
    IO.puts """
    Message: #{inspect(message)}
    """

    conn
  end 

Here is the message in the console:

[info] Sent 200 in 30ms
Message: "Dashboard hit: 1"

[info] CONNECTED TO Phoenix.LiveView.Socket in 143µs
  Transport: :websocket
  Serializer: Phoenix.Socket.V2.JSONSerializer
  Parameters: %{"_csrf_token" => "YyUeC3lgIQ83AlU3VBdoSg9PAwgYGl4QVQW_1PMHOjfg1s72wvoYHW2T", "_mounts" => "0", "vsn" => "2.0.0"}
[info] CONNECTED TO Phoenix.LiveView.Socket in 105µs
  Transport: :websocket
  Serializer: Phoenix.Socket.V2.JSONSerializer
  Parameters: %{"_csrf_token" => "TToLLhxmCAVIXXsJLVIJOjNYC2QIJy0bxNBzTVdB05HYH6VBKag5XjA_", "_mounts" => "0", "vsn" => "2.0.0"}

I want to print

Message: "Dashboard hit: 1"
Message: "Dashboard hit: 2"
Message: "Dashboard hit: 3"

whenever a client visits or reloads the page, and increase counter per all users visit.

1

There are 1 answers

1
Aleksei Matiushkin On

There are many things to fix/improve here.

① one should not call Code.require_file/1 from within runtime, unless absolutely sure it’s the only way; the regular file located in lib is available by default
② one should not start processes unmonitored (unsupervised) from within the regular code; start it within the supervision tree in application.ex
③ give the process a name to avoid the necessity to track a pid
④ you probably want to track each user’s visits separately; the current attempt would have a single counter per all users

That said, somewhat as the below would work (assuming the process has been started in application.ex within its supervision tree.)

  use GenServer
 
  def start_link(integer \\ 0) when is_integer(integer) do
    GenServer.start_link(__MODULE__, integer, name: __MODULE__)
  end

  def add, do: GenServer.call(__MODULE__, :add)

and

  def message(conn, _opts) do
    message = Counter.add()
    IO.inspect(message, label: "Message:")
    conn
  end 

To track counters per user, one might hold a map conn => counter in the state, or like.