OTP as a programming platform/framework, empowers not only Erlang, but also Elixir. This post looks into a pitfall when programming GenServer in Elixir, and provides a potential solution.
All callbacks in the GenServer behavior have a limited and pre-defined possible return values. Presumbly, one should pick up the desired return value from valid ones. One example for handle_call/3 looks like below:
{:reply, reply, new_state}
But what if we screw the return value, e.g., by mistakes like below,
{:reply, reply, :ok, new_state}
where atom :ok is added as an extra parameter of the return value. One would hope the Elixir compiler would catch this simple mistake. Nevertheless, neither mix compile or mix escript.build would report this bug. And yes, this bug will be triggered when handle_call/3 is called with a “Bad Argument” exception thrown out.
While “let it crash” and “unit testing” may be good reasons for Elixir compiler to treat this bug as a runtime one rather than a compiling one, this also means this simple bug won’t be discovered util deployment if unit testing is not covered properly. And once it happens, it keeps crashing “thanks” to the fault-tolerant feature using Supervisor…
Without changes in the Elixir compiler, it is still possible to detect this bug, using dialyxir (yes, Elixir version of dialyzer for sure). “mix dialyxir” shows the detection of this bug:
done in 0m1.52s
lib/dig.ex:9: The inferred return type of handle_call/3 ({‘reply’,{‘ok’,{_,_,_,_,_}},’ok’,#{‘__struct__’:=’Elixir.Digdb’, ‘node’:=_, ‘prefix’:=_, ‘remote’:=_, ‘unit_base’:=_, ‘unit_range’:=_, ‘worker_idx’:=_, ‘zero_num’:=_}}) has nothing in common with {‘noreply’,_} | {‘noreply’,_,’hibernate’ | ‘infinity’ | non_neg_integer()} | {‘reply’,_,_} | {‘stop’,_,_} | {‘reply’,_,_,’hibernate’ | ‘infinity’ | non_neg_integer()} | {‘stop’,_,_,_}, which is the expected return type for the callback of the ‘Elixir.GenServer’ behaviour
done (warnings were emitted)