Skip to content

Instantly share code, notes, and snippets.

@kalpak92
Created September 3, 2019 13:30
Show Gist options
  • Save kalpak92/e009322c74048ad332e890791e38ee92 to your computer and use it in GitHub Desktop.
Save kalpak92/e009322c74048ad332e890791e38ee92 to your computer and use it in GitHub Desktop.
A simple Server tutorial to understand the GenServer
defmodule HelloServer do
use GenServer
## Server API
def init(initial_value) do # initailizing the state with the value passed
{:ok, initial_value}
end
# add the value to the state and return ok
def handle_call({:add, value}, _from, state) do
{:reply, "#{value} add", state + value}
end
# returns the state to the caller
def handle_call(:get,_from,state) do
:timer.sleep 2000
{:reply,state,state}
end
# just reset the state to value 1
def handle_cast(:reset,state) do
:timer.sleep 2000
IO.puts "value has been reset "
{:noreply,1}
end
# This executes periodically
def handle_info(:work,state) do
IO.puts " This message prints every after 2 seconds"
schedule_work()
{:noreply,state}
end
#catch-all clause for the handle_info for handling unkown messages
def handle_info(_msg, state) do
IO.puts "unknown message"
{:noreply, state}
end
defp schedule_work() do
Process.send_after(self(), :work, 2*1000) # In 2 seconds
end
end
@kalpak92
Copy link
Author

kalpak92 commented Sep 3, 2019

— cast

handle_cast( message, state ) :: { :noreply, state }

Handle cast is used in the situation where you don’ t care or expect reply from the server. Suppose, if you want to write a log server which will log details of the request whenever you make an unauthorised request, in that situation you cannot break your flow of execution waiting for reply whether the server has written the log or not. If you do so that will be the weirdest part of your code. This is where the cast come into play. All the log mechanism should run in backside. This is why you should use the cast to make asynchronous requests which do not block execution flow.

— call

handle_call( message, _from, state ) :: ( :reply, return_value, state )

This is just opposite to the cast which I mentioned above. It blocks the flow of execution and waits for the value to return from the caller. The best example to explain this situation is sign in and sign up mechanisms. You cannot simply move to next without getting the proper response from the server based on your sign in request with out validation. Here you supposed to wait for the authentication from the server. This is where the call come into handy. Based on the server response you can redirect the flow.

— info

handle_info(message,state) :: { :noreply, state }

handle_info/2 must be used for all other messages a server may receive that are not sent via GenServer.call/2 or GenServer.cast/2, including regular messages sent with Kernel.send/2. This is highly useful in the situations like monitoring the process using Process.monitor/1 or if you want to schedule the work to repeat after certain interval of time using Process.send_after/4

So, whenever you make a request like Kernel.send(pid,message) , the handle_info/2 callback is triggered in the registered process of pid . This handle_info does the asynchronous requests.

Example

    defmodule HelloServer do
      use GenServer
    
      ## Server API
      def init(initial_value) do   # initiating the state with the value 1 passed 
        {:ok,initial_value}
      end
    
      # add the value to the state and returns :ok
      def handle_call({:add,value},_from,state) do
        {:reply, "#{value} add",state + value}
      end
    
      # returns the state to the caller
      def handle_call(:get,_from,state) do
        :timer.sleep 2000
        {:reply,state,state}
      end
      
      # just reset the state to value 1
      def handle_cast(:reset,state) do
        :timer.sleep 2000
        IO.puts "value has been reset "
        {:noreply,1}
      end
    
      # This executes periodically
      def handle_info(:work,state) do
        IO.puts " This message prints every after 2 seconds"
        schedule_work()
        {:noreply,state}
      end
    
      #catch-all clause for the handle_info for handling unkown messages
      def handle_info(_msg, state) do
        IO.puts "unknown message"
        {:noreply, state}
      end
    
      defp schedule_work() do
        Process.send_after(self(), :work, 2 * 1000) # In 2 seconds
      end
    
    end

Sending Messages to GenServer

init

The GenServer.start_link will call initdefinition . Whatever you send the input to the start_link, it sends back to the init . Here we are sending initial value 1 to initiate the server state.

    iex> {:ok,pid} = GenServer.start_link HelloServer,1,[]
    {:ok, #PID<0.88.0>}

call

Acall is synchronous. It’ll return the value upon execution.The result can be assigned to a variable since the call returns the value. It blocks the calling process until the value is returned.

    iex> GenServer.call pid,{:add,200}
    "200 add"
    iex> GenServer.call pid,:get
    201

The two call functions to understand the code flow . At the initial call with message {:add,200}, it returns the string immediately, where we cannot observe whether the flow is blocked or not.While coming to the second call with message :get I put timer.sleep 2000 purposely to understand that flow is blocked.

cast

The cast is an asynchronous. It won’t block the Execution flow. The virtual-machine says :ok I received a message and will process that later.

    iex> GenServe.cast pid,:reset
    :ok
    iex>
    "value has been reset" # it is printed after 2 seconds.

Once you make a call with message :reset immediately the virtual machine will say :ok . Here I purposely added :timer.sleep 2000 which sleeps for 2 seconds, but it won’t block the flow unlike the previous handle_call one did. It will wait in background.

Mean while you will get the iex shell back so you can fire other commands too. So after 2 seconds again it will print the string “value has been reset”.

info

All messages that are sent to a process directly (instead of via call or cast) will end up here. It is asynchronous like cast and does not block the calling process.

    iex> send pid,:work
    This message prints every after 2 seconds
    :work
    This message prints every after 2 seconds
    This message prints every after 2 seconds         ...

Unlike the cast and call you have to call this with Kernel.send definition, which will trigger the respective  handle_info  matching with the message passed. Here our message is :work .

Inside the handle_info we are calling a private function called schedule_work which executes  Process.send_after(self(),:work,2*1000) which again fires the handle_info after every 2 seconds. 2*1000 .

So the handle_info callback is triggered when the external requests are made unlike calling with  GenServer.call  or  GenServer.cast . Process.monitoralso triggers the handle_info definition.

Execute Process.exit(pid, :kill) to get out of the print loop.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment