GenServer (Generic Server)

  • Great for scheduled/cron jobs

  • Start seperate process that holds some state, changes its state based on calls(sync expecting response) or casts(async, no resp)

  • Can recieve other/more messages than agent with handle_info

  • GenServer is a single process, so best to minimize server work and only handle scheduling, use Task delegation, or offload most work to client caller

react_phoenix_web/simplegen.ex

defmodule ReactPhoenixWeb.SimpleGen do
  use GenServer
  @interval 1_000

    # Runs in Caller Context 🐌Alice
  def start_link(arg1) do
    GenServer.start_link(__MODULE__, arg1)
  end

  def increment(count) do
    GenServer.cast(__MODULE__, {:increment, count})
  end

  # Runs in Single Server Context 🐨Bob
  def init(arg1) do 
    # start_link calls this
    Process.send_after(self(), :work, @interval)
    {:ok, arg1}
  end

  def handle_info(:work, state) do
      # send/send_after within the server calls this
    Process.send_after(self(), :increment, @interval)
    {:noreply, state}
  end

  def handle_call(:get_data, _from, state) do
      resp = state
    {:reply, resp, state}
  end

  def handle_cast({:increment, count}, state) do
    {:noreply, state+count}
  end
end

Add to application.ex to supervise and init on startup

    def start(_type, _args) do
    # List all child processes to be supervised
    children = [
        #....
      {ReactPhoenixWeb.SimpleGen, 1}
    ]
        #....

Then just call with ReactPhoenixWeb.SimpleGen.increment(5)

Raw Examples

start_link(module, init_arg, options \\ []) calls init of the module with the init_args

{:ok, pid} = GenServer.start_link() #starts new linked process(one dies both die)

counter = GenServer.call(pid, :get_data) #sync/blocking, default timeout 5 sec
IO.puts "Counter: #{counter}"
GenServer.cast(pid, :increment)
counter = GenServer.call(pid, :get_data)
IO.puts "Counter: #{counter}
  • Don't use Process.sleep, instead will have to store data in state, use :timer.after to send message after time and process/complex work in handler

Raw Example without GenServer

defmodule SimpleGenServerMock do
   def start_link() do
       # runs in the caller context 🐌Alice
       spawn_link(__MODULE__, :init, [])
   end
   def call(pid, arguments) do
       # runs in the caller context 🐌Alice
       send pid, {:call, self(), arguments}
       receive
         {:response, data} -> data
       end
   end
   def cast(pid, arguments) do
       # runs in the caller context 🐌Alice
       send pid, {:cast, arguments}
   end
   def init() do
      # runs in the server context 🐨Bob
      initial_state = 1
      loop(initial_state)
   end
   def loop(state) do
      # runs in the server context 🐨Bob
      receive command do
         {:call, pid, :get_data} -> 
           # do some work on data here and update state         
           {new_state, response} = {state, state} 
           send pid, {:response, data}
           loop(new_state)
         {:cast, :increment} -> 
           # do some work on data here and update state         
           new_state = state + 1
           loop(new_state)      
      end
   end
end

Usage

pid = SimpleGenServerMock.start_link()
counter = SimpleGenServerMock.call(pid, :get_data)
IO.puts "Counter: #{counter}
SimpleGenServerMock.cast(pid, :increment)
counter = SimpleGenServerMock.call(pid, :get_data)
IO.puts "Counter: #{counter}

Last updated