SKILL.md
Erlang OTP Behaviors
Introduction
OTP (Open Telecom Platform) behaviors provide reusable patterns for common process types in Erlang systems. These abstractions handle complex details like message passing, error handling, and state management, allowing developers to focus on business logic while maintaining system reliability.
Behaviors define interfaces that processes must implement, with OTP handling the infrastructure. Gen_server provides client-server processes, gen_statem implements state machines, supervisors manage process lifecycles, and gen_event coordinates event distribution. Understanding these patterns is essential for production Erlang.
This skill covers gen_server for stateful processes, gen_statem for complex state machines, supervisor trees for fault tolerance, gen_event for event handling, application behavior for packaging, and patterns for building robust OTP systems.
Gen_Server Basics
Gen_server implements client-server processes with synchronous and asynchronous communication.
-module(counter_server).
-behaviour(gen_server).
%% API
-export([start_link/0, increment/0, decrement/0, get_value/0, reset/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
%% State record
-record(state, {count = 0}).
%%%===================================================================
%%% API
%%%===================================================================
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
increment() ->
gen_server:cast(?SERVER, increment).
decrement() ->
gen_server:cast(?SERVER, decrement).
get_value() ->
gen_server:call(?SERVER, get_value).
reset() ->
gen_server:call(?SERVER, reset).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
{ok, #state{}}.
%% Synchronous calls (with response)
handle_call(get_value, _From, State) ->
{reply, State#state.count, State};
handle_call(reset, _From, State) ->
{reply, ok, State#state{count = 0}};
handle_call(_Request, _From, State) ->
{reply, ignored, State}.
%% Asynchronous casts (no response)
handle_cast(increment, State) ->
NewCount = State#state.count + 1,
{noreply, State#state{count = NewCount}};
handle_cast(decrement, State) ->
NewCount = State#state.count - 1,
{noreply, State#state{count = NewCount}};
handle_cast(_Msg, State) ->
{noreply, State}.
%% Handle other messages
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Complex gen_server example: Cache
%%%===================================================================
-module(cache_server).
-behaviour(gen_server).
-export([start_link/1, put/2, get/1, delete/1, clear/0, size/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-record(state, {
cache = #{},
max_size = 1000,
hits = 0,
misses = 0
}).
start_link(MaxSize) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [MaxSize], []).
put(Key, Value) ->
gen_server:call(?MODULE, {put, Key, Value}).
get(Key) ->
gen_server:call(?MODULE, {get, Key}).
delete(Key) ->
gen_server:cast(?MODULE, {delete, Key}).
clear() ->
gen_server:cast(?MODULE, clear).
size() ->
gen_server:call(?MODULE, size).
init([MaxSize]) ->
process_flag(trap_exit, true),
{ok, #state{max_size = MaxSize}}.
handle_call({put, Key, Value}, _From, State) ->
Cache = State#state.cache,
case maps:size(Cache) >= State#state.max_size of
true ->
{reply, {error, cache_full}, State};
false ->
NewCache = maps:put(Key, Value, Cache),
{reply, ok, State#state{cache = NewCache}}
end;
handle_call({get, Key}, _From, State) ->
Cache = State#state.cache,
case maps:find(Key, Cache) of
{ok, Value} ->
NewState = State#state{hits = State#state.hits + 1},
{reply, {ok, Value}, NewState};
error ->
NewState = State#state{misses = State#state.misses + 1},
{reply, not_found, NewState}
end;
handle_call(size, _From, State) ->
Size = maps:size(State#state.cache),
{reply, Size, State};
handle_call(_Request, _From, State) ->
{reply, {error, unknown_request}, State}.
handle_cast({delete, Key}, State) ->
NewCache = maps:remove(Key, State#state.cache),
{noreply, State#state{cache = NewCache}};
handle_cast(clear, State) ->
{noreply, State#state{cache = #{}}};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(Reason, State) ->
io:format("Cache terminating: ~p~n", [Reason]),
io:format("Stats - Hits: ~p, Misses: ~p~n", [State#state.hits, State#state.misses]),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% gen_server with timeouts
%%%===================================================================
-module(session_server).
-behaviour(gen_server).
-export([start_link/0, touch/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-define(TIMEOUT, 30000). % 30 seconds
-record(state, {
last_activity,
data = #{}
}).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
touch() ->
gen_server:cast(?MODULE, touch).
init([]) ->
{ok, #state{last_activity = erlang:system_time(millisecond)}, ?TIMEOUT}.
handle_call(_Request, _From, State) ->
{reply, ok, State, ?TIMEOUT}.
handle_cast(touch, State) ->
NewState = State#state{last_activity = erlang:system_time(millisecond)},
{noreply, NewState, ?TIMEOUT};
handle_cast(_Msg, State) ->
{noreply, State, ?TIMEOUT}.
handle_info(timeout, State) ->
io:format("Session timed out~n"),
{stop, normal, State};
handle_info(_Info, State) ->
{noreply, State, ?TIMEOUT}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
