Welcome to your first hands-on GenServer lesson in this course on OTP Fundamentals. We will build a simple Stack server that holds state, accepts updates, and responds to requests using standard GenServer patterns. You will see how to start a GenServer, initialize state, expose a small public API, and handle synchronous vs. asynchronous messages. GenServer is part of Elixir/OTP, so there is nothing extra to install.
Here is how we define the module, start the process, and set the initial state:
Explanation:
use GenServerbrings in the GenServer behavior.start_link/1boots the server and registers it under the module name (name: __MODULE__), so you do not need to pass a PID later.init/1sets the initial state to the provided list.
We provide a clean API that hides GenServer details from callers:
Explanation:
push/1usesGenServer.cast/2, which is asynchronous (fire-and-forget). It returns immediately.pop/0usesGenServer.call/2, which is synchronous. It waits for a reply from the server.- Both target the registered name (
__MODULE__), not a PID.
Note: GenServer.call/2 has a default timeout of 5000 milliseconds (5 seconds). If the server does not reply within this time, a GenServer.CallError (timeout) is raised. You can override the timeout by calling GenServer.call(__MODULE__, :pop, 10_000) for a 10-second timeout. In production code, you should consider handling possible timeout errors using try/rescue or by setting an appropriate timeout for your use case.
These callbacks update and read the server’s state:
Explanation:
handle_cast/2processes the async push by prepending the item to the list. It returns{:noreply, new_state}.handle_call/3for:popmatches two cases:- Non-empty stack
[head | tail]: replies withheadand keepstailas the new state. - Empty stack
[]: replies withniland keeps the state unchanged.
- Non-empty stack
Explanation:
- We start with an empty stack, push
1,2, and3, then pause briefly withProcess.sleep/50to allow the asynchronouscastoperations to complete before popping. - This ensures that the
popcalls reflect the expected stack state and you see3and then2printed. - The final
Process.sleep/100gives time for any remaining async casts to be handled before the script exits.
You now have a working mental model for GenServer: booting and initialization for setup, asynchronous updates for fire-and-forget tasks, synchronous requests for retrieving data, and callbacks for managing state. When you are ready, head to the practice section and apply what you have learned to reinforce these patterns.
