Genserver
mix new App --sup
app/lib/app.ex
defmodule App.Service do
use GenServer
def start_link(state) do
GenServer.start_link(__MODULE__, state, name: __MODULE__)
end
def init(state) do
{:ok, state}
end
def get_state(pid) do
GenServer.call(pid, :get_state)
end
def set_state(pid,state) do
GenServer.call(pid, {:set_state, state})
end
def handle_call(:get_state, _from, state) do
{:reply, state, state}
end
def handle_call({:set_state, new_state}, _from, state)do
{:reply,state,[new_state | state]}
end
end
defmodule App.Supervisor do
use Supervisor
def start do
Supervisor.start_link(__MODULE__, [])
end
def init(_) do
children = [
{App.Service,[]}
]
Supervisor.init(children, strategy: :one_for_one)
end
end
# App.Supervisor.start() # # IO.inspect x # pid = Process.whereis(App.Service) # # The follwing is nil # IO.inspect pid # Process.exit(pid, :kill) # IO.inspect "______________" # pid = Process.whereis(App.Service) # # IO.inspect "_____" # IO.inspect Process.whereis(App.Service) # App.Service.get_state(pid) # App.Service.set_state(pid, "we are the world") # App.Service.set_state(pid, "hurrrray") # IO.inspect App.Service.get_state(pid)
mix.exs
defmodule App.MixProject do
use Mix.Project
def project do
[
app: :app,
version: "0.1.0",
elixir: "~> 1.14",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger],
mod: {App.Application, []}
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
]
end
end
application.exs
defmodule App.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
@impl true
def start(_type, _args) do
children = [
# Starts a worker by calling: App.Worker.start_link(arg)
{App.Service, []}
]
opts = [strategy: :one_for_one, name: App.Supervisor]
Supervisor.start_link(children, opts)
end
end
__________________________________________________________
Understanding Elixir OTP: GenServer and Supervisors
This tutorial explains the provided code, which implements a classic Elixir pattern: a stateful worker (GenServer) monitored by a Supervisor.
1. The Big Picture
The goal of this application is to maintain a list of items (state) in memory.
- The Service: Holds the state.
- The Supervisor: Watches the Service. If the Service crashes (or is killed), the Supervisor restarts it immediately.
- The Application: The entry point that tells the VM what to start when you run
iex -S mix.
2. The Worker: App.Service
Located in app/lib/app.ex, this module is a GenServer (Generic Server). It splits logic into two parts: the Client API (helper functions you call) and the Server Callbacks (logic running inside the process).
The Code Breakdown
defmodule App.Service do
use GenServer
# 1. Start the process
def start_link(state) do
# __MODULE__ is "App.Service". We register the process under this name
# so we don't have to track the PID manually.
GenServer.start_link(__MODULE__, state, name: __MODULE__)
end
# 2. Initialize State
def init(state) do
{:ok, state}
end
# --- Client API ---
def get_state(pid) do
GenServer.call(pid, :get_state)
end
def set_state(pid, state) do
GenServer.call(pid, {:set_state, state})
end
# --- Server Callbacks ---
# Handling get_state
def handle_call(:get_state, _from, state) do
# Return: {:reply, response_to_client, internal_state}
{:reply, state, state}
end
# Handling set_state
def handle_call({:set_state, new_item}, _from, current_list) do
# Logic: Prepend the new_item to the current_list
new_state = [new_item | current_list]
# Note: You are returning 'current_list' (the old state) to the caller,
# but saving 'new_state' internally.
{:reply, current_list, new_state}
end
endKey Takeaway
The handle_call for :set_state has a specific behavior:
- It receives a new item.
- It replies with the old list.
- It updates its internal state to the new list (with the item prepended).
3. The Entry Point: App.Application
In application.exs, you defined the application startup logic. This is triggered because mix.exs has mod: {App.Application, []}.
defmodule App.Application do
use Application
def start(_type, _args) do
children = [
# This tells the Supervisor to start App.Service with an empty list [] as initial state
{App.Service, []}
]
opts = [strategy: :one_for_one, name: App.Supervisor]
Supervisor.start_link(children, opts)
end
endNote regarding App.Supervisor:
In your app.ex file, you manually defined a module named App.Supervisor. However, your App.Application is ignored that module and starting a supervisor directly using Supervisor.start_link/2. The App.Service is currently being supervised directly by the Application root supervisor.
4. Interactive Tutorial: Testing Fault Tolerance
Now, let's look at the commented-out script at the bottom of your file. This demonstrates the power of OTP.
Open your terminal in the project folder and run:
iex -S mix
Step 1: Verify the Service is running
Because of App.Application, the service starts automatically.
# Check if the process exists by looking up its name pid = Process.whereis(App.Service) # Output: #PID<0.152.0> (The ID will vary)
Step 2: Manipulate State
Let's use the API functions.
# Get initial state (defined as [] in App.Application) App.Service.get_state(pid) # Output: [] # Add an item App.Service.set_state(pid, "we are the world") # Output: [] <-- Rembember, handle_call returns the OLD state # Add another App.Service.set_state(pid, "hurray") # Output: ["we are the world"] # Check current state App.Service.get_state(pid) # Output: ["hurray", "we are the world"]
Step 3: The "Let it Crash" Test
This is the most important part of the tutorial. We will kill the process and watch the Supervisor bring it back to life.
# 1. Get the current PID pid = Process.whereis(App.Service) # 2. Kill the process forcefully Process.exit(pid, :kill) # 3. Check if it's alive new_pid = Process.whereis(App.Service) # 4. Compare pid == new_pid # Output: false
What happened?
- You killed the process.
- The Supervisor noticed the child died.
- The Supervisor restarted
App.Service.start_link([])immediately. - The
new_pidis different because it is a brand new process. - Important: The state reset to
[]because the process memory was wiped when it died. (To keep state across restarts, you would need a database or ETS table).
Summary
mix.exsdefines the entry point (App.Application).App.Applicationstarts a Supervisor.- The Supervisor starts
App.Service. App.Serviceholds state in a loop.- If
App.Servicedies, the Supervisor restarts it fresh.