3
votes

Let's say I have GenServer instance:

defmodule MyModule do
  use GenServer

  def init(_) do
   {:ok, %{}}
  end

#...
end

I want MyModule be supervised, but when it crashes, do something before it restarts with state before crash:

defmodule MyModule do
  use GenServer

  def init(_) do
   {:ok, %{}}
  end

 def init(:restart, previous_state) do
   some_func(previous_state)
   {:ok, previous_state}
 end

#...
end

But I'm not sure how to implement this

1
You could use terminate/2 callback and store the state somewhere else (Agent, Database, ETS) and load it in init/1 but I don't think you can make the supervisor pass the old state on restart automatically. - Dogbert
What do you want to do with that state? Apart from logging, I cannot imagine a use case where the same genserver would reuse it's old state which just led to it's crash. Wouldn't it immediately crash again if you receive a similar message? The crash sends an error message to the supervisor. You could probably catch that and do something with it. Otherwise use the method suggested by Dogbert to store the state somewhere and make the genserver load it from there when it is restarting. - Joe Eifert
@Johannes I would say that in my case not state can cause crash itself, but exceptions raised by applying not valid calls to this state - Joe Half Face
If these are valid exceptions, why not just recover from the exception in the call? - Justin Wood

1 Answers

1
votes

I remember that this was described in Dave Thomas' book. The idea is to have another process which keeps the replica of the state. This process has one job: to keep track of the changes to to state. This should prevent it from crashing (if the original process notify the watcher about the state change, it means that it didn't crash while applying the changes).

Then, when the original process crashes and is restarted, it can just fetch the previous state from the backup process (it should get the PID of that process from the supervisor).

def start_link(backup_pid) do
   GenServer.start_link(__MODULE__, backup_pid)
end  

def init(backup_pid) do
  state = Backup.get_state(backup_pid)
  {:ok, state}
end

def terminate(_reason, state) do
  Backup.save_state(state)
end