defmodule WpOdata.Server.Sap do
use GenServer
alias WpOdata.Protocols.Query
def start_link(_) do
GenServer.start_link(__MODULE__, nil, [])
end
def init(_) do
{:ok, nil}
end
def handle_call({:get, data}, _from, state) do
IO.puts "Hello"
{:reply, "Hello", state}
end
def handle_info(_msg, state) do
{:noreply, state}
end
end
When I send a request does not match to the handle_call like:
GenServer.call(sap, {:post, "hello"})
Then the genserver will be terminate:
iex(1)> [error] GenServer #PID<0.257.0> terminating
** (FunctionClauseError) no function clause matching in WpOdata.Server.Sap.handle_call/3
(wp_odata) lib/wp_odata/server/sap.ex:15: WpOdata.Server.Sap.handle_call({:post, "hello"}, {#PID<0.184.0>, #Reference<0.0.1.1340>}, nil)
(stdlib) gen_server.erl:615: :gen_server.try_handle_call/4
(stdlib) gen_server.erl:647: :gen_server.handle_msg/5
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Last message: {:post, "hello"}
State: nil
My question is, how to handle this kind of error?
Is there a way to catch all incoming request, that not match handle_call clause?
As you would do it everywhere else… Use a “catch all” clause… eg. def handle_call(_msg, _from, state), do: # handle it
Especially in a handle_call you should be aware of the fact that returning :noreply-tuple will halt the calling process until you send an answer/reply to (_)from!
Also, usually you should design your public API in a way that there is no need for your caller to ever touch GenServer.call, in fact, your calling site should be unaware of the detail if you implemented on top of a GenServer, Agent or implemented the receive-loop on your own. You should always provide wrapper functions:
defmodule WpOdata.Server.Sap do
use GenServer
alias WpOdata.Protocols.Query
# Public API
@doc "Starts the SAP-subsystem stuff"
def start_link(_), do: GenServer.start_link(__MODULE__, nil, [])
@doc "Retrieves data from SAP"
def get(query), do: GenSerner.call(__MODULE__, {:get, query})
# GenServer callbacks
@doc false
def init(_) do
{:ok, nil}
end
@doc false
def handle_call({:get, data}, _from, state) do
IO.puts "Hello"
{:reply, "Hello", state}
end
@doc false
def handle_info(_msg, state) do
{:noreply, state}
end
end
PS: Maybe you got a mail before with unfinished post, this was because my password storage rampaged and injected my email, then hitting tab, injecting password into nowhere and hitting enter again…
def handle_call(_msg, _from, state) do
{:reply, :unknown_call, state}
end
The real question is, though, do you WANT to? It is usually preferred/good to fail when an unexpected message is sent. That way you don’t end up in undefined states due to a typo or a missed fixup during a refactor (for a couple common examples).
By failing, your program avoids entering an unhandled/unknown state. Ask yourself: if something randomly calls in with some arbitrary message, what is a valid response? If there is no good, answer, then fail.
This is the “let it crash” philosophy. It isn’t always the Right™ thing, but it usually is. This is the beauty of separated processes: one job / action / task / response handler can die because it was going to enter a bad/unknown/unexpected state, and the rest of the program continues on.
def handle_call({:get, data}, _from, state} do
try do
{:reply, Query.get(data), state}
rescue
Protocol.UndefinedError -> {:reply, :badarg, state}
end
end
If Query.get/1 is a (or at least potentially) longrunning function, it might be better to use a :noreply tuple and apawn a worker to not block your GenServer, roughly like this:
def handle_call({:get, data}, from, state} do
spawn(fn ->
try do
send from, Query.get(data)
rescue
Protocol.UndefinedError ->
send from, badarg
end
end
{:noreply, state}
end