Reputation: 2069
Consider such simple GenServer module:
defmodule Crap do
use GenServer
... #All the needed crap
def handle_info(:kill_me_pls, state) do
GenServer.stop(self)
{:noreply, state}
end
def terminate(_, state) do
IO.inspect "Look! I'm dead."
end
end
And consider putting these expressions into the repl:
{:ok, pid} = Crap.start_link
send_message_to_that_pid
And this is where my conceirns start because Process.alive? pid
returns true, but process is not responsive and terminate
isn't being called, although if I call GenServer.stop(pid)
called in repl on 'clean' process (that had not receive kill message) kills it properly. If stop called on process that received :kill_me_pls message hungs up the repl.
Upvotes: 10
Views: 4547
Reputation: 222040
The correct way to stop a GenServer "from inside" is to return a {:stop, reason, new_state}
value from handle_*
or {:stop, reason, reply, new_state}
from handle_call
. The reason your code fails is that calling GenServer.stop
sends a special message to the GenServer, and since a GenServer is a single process and currently in handle_info
, it cannot respond to the stop
call, so your process hangs / creates a deadlock.
Here's the correct way:
def handle_info(:kill_me_pls, state) do
{:stop, :normal, state}
end
You can read more about the possible return values, check out the text under the headings Return values
in the documentation of GenServer.
Demo:
iex(1)> defmodule Crap do
...(1)> use GenServer
...(1)>
...(1)> def handle_info(:kill_me_pls, state) do
...(1)> {:stop, :normal, state}
...(1)> end
...(1)>
...(1)> def terminate(_, state) do
...(1)> IO.inspect "Look! I'm dead."
...(1)> end
...(1)> end
iex(2)> {:ok, pid} = GenServer.start Crap, []
{:ok, #PID<0.98.0>}
iex(3)> send(pid, :kill_me_pls)
"Look! I'm dead."
:kill_me_pls
iex(4)> Process.alive?(pid)
false
Upvotes: 21