Almost drop-in replacement for GenServer to preserve state between crashes.

Benefits

Peeper.GenServer is an almost drop-in replacement for GenServer that preserves the state between crashes. All the callbacks from GenServer are supported.

Internally it creates a sub-supervision tree with an actual worker that delegates to the implementation and a state keeper. That said, it creates three processes instead of one, and this should be considered when building a very concurrent applications.

The main use-case would be a long-lived GenServer process with no error path handling at all (yeah, that famous Let It Crash.) Using this abstraction, one is free to send unexpected messages to the process, raise from its handle_××× clauses and whatnot.

There are two differencies compared to bare GenServer. init/1 callback cannot return anything but {:ok, state} tuple (this might have changed in the future,) and Peeper.{call/3,cast/2,send/2} is to be used instead of GenServer.{call/3,cast/2} and Kernel.send/2 (this is not gonna change.)

Please note, that whatever is set in init/1 callback as a state, will be overriden by the latest state available upon restarts. That being said, init/1 would play its role in setting the state during the first run only.

Instead of using Peeper’s counterparts, one might either name the process and use Name.GenServer as a name of the underlying GenServer or get the GenServer’s pid via Peeper.gen_server/1 and use GenServer.{call/3,cast/2} with it.

Example

    iex> {:ok, pid} = Peeper.Impls.Full.start_link(state: 0, name: Counter)
    ...> Peeper.call(pid, :state)
    0
    iex> Peeper.cast(pid, :inc)
    :ok
    iex> GenServer.call(Counter.GenServer, :state)
    1
    iex> Peeper.call(pid, :state)
    1
    iex> # emulate crash
    ...> Process.exit(Peeper.Supervisor.worker(pid), :raise)
    ...> %{} = Peeper.Supervisor.which_children(pid)
    iex> Peeper.call(Counter, :state)
    1
    iex> Peeper.send(pid, :inc)
    :inc
    iex> Peeper.call(Counter, :state)
    2

The function receives either an initial state, or a keyword having keys state and (optionally) name. Also this keyword might have a configuration for the top supervisor (keys: [:strategy, :max_restarts, :max_seconds, :auto_shutdown].)

All other values passed would be re-passed to the underlying GenServer’s options.

Listener

One might pass listener: MyListener key to PeeperImpl.start_link/1 where MyListener should be an implementation of Peeper.Listener behaviour. The callbacks will be called when the state of the underlying behaviour is changed and on termination respectively.

That might be a good place to attach telemetry events, or logs, or whatnot.

Usage

Installation

def deps do
  [
    {:peeper, "~> 0.1"}
  ]
end

Documentation