Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

GenServer Drills

exercises/genserver_drills.livemd

GenServer Drills

Mix.install([
  {:jason, "~> 1.4"},
  {:kino, "~> 0.9", override: true},
  {:youtube, github: "brooklinjazz/youtube"},
  {:hidden_cell, github: "brooklinjazz/hidden_cell"}
])

Navigation

Home Report An Issue GenServersScore Tracker

Drills

Drills help you develop familiarity and muscle memory with syntax through repeated exercises. Unlike usual problems, Drills are not intended to develop problem solving skills, they are purely for developing comfort and speed.

This set of drills is for GenServers.

> A GenServer is a process like any other Elixir process and it can be used to keep state, execute code asynchronously and so on. The advantage of using a generic server process (GenServer) implemented using this module is that it will have a standard set of interface functions and include functionality for tracing and error reporting. It will also fit into a supervision tree. > > * HexDocs: GenServer

GenServer

Create a Zero Genserver that does nothing other than store the integer 0 in its state. Use GenServer.start_link/3 to start the Zero process. Use :sys.get_state/1 to view the state of your Zero process.

Example Solution

defmodule Zero do
  use GenServer

  @impl true
  def init(_opts) do
    {:ok, 0}
  end
end

{:ok, pid} = GenServer.start_link(Zero, "init_arg")
:sys.get_state(pid)
defmodule Zero do
  use GenServer

  @impl true
  def init(_opts) do
    {:ok, 0}
  end
end

{:ok, pid} = GenServer.start_link(Zero, 0)
:sys.get_state(pid)

Use GenServer.start_link/3 to start your Zero GenServer as a named process.

Example Solution

GenServer.start_link(Zero, "init_arg", [name: :my_name])
GenServer.start_link(Zero, 0, name: :my_name)

Create a SimpleCounter GenServer whose state starts as 0. Implement a GenServer.handle_call/3 callback function which accepts the :increment message and increments the state by 1 and returns :ok.

Use GenServer.start_link/3 and GenServer.call/3 to spawn a SimpleCounter process and send it an :increment message.

Use :sys.get_state/1 to see that the state of the counter has incremented.

Example Solution

defmodule SimpleCounter do
  use GenServer do
  
  def init(_opts) do
    {:ok, 0}
  end
  
  def handle_call(:increment, _from, state) do
    {:reply, :ok, state + 1}
  end
end

{:ok, pid} = GenServer.start_link(SimpleCounter, [])
GenServer.call(pid, :increment)
:sys.get_state(pid)
defmodule SimpleCounter do
  use GenServer

  @impl true
  def init(_opts) do
    {:ok, 0}
  end

  @impl true
  def handle_call(:increment, _from, state) do
    {:reply, :ok, state + 1}
  end
end

{:ok, pid} = GenServer.start_link(SimpleCounter, 0)
GenServer.call(pid, :increment)
:sys.get_state(pid)

Create an InitialState GenServer whose initial state can be configured. Call GenServer.start_link/3 to spawn a State GenServer with an initial state. Use :sys.get_state/1 to confirm the state matches your configured state.

Example Solution

defmodule InitialState do
  use GenServer

  @impl true
  def init(state) do
    {:ok, state}
  end
end

{:ok, pid} = GenServer.start_link(InitialState, "my initial state")

:sys.get_state(pid)
defmodule InitialState do
  use GenServer

  @impl true
  def init(state) do
    {:ok, state}
  end
end

{:ok, pid} = GenServer.start_link(InitialState, 0)

:sys.get_state(pid)

Create a State module.

It should:

Manually test each function (State.set/2, State.get/1 and State.start_link/3) to confirm they work as expected.

Example Solution


defmodule State do
  use GenServer

  @impl true
  def init(state) do
    {:ok, state}
  end

  @impl true
  def handle_cast({:set, new_state}, state) do
    {:noreply, new_state}
  end

  @impl true
  def handle_call(:get, _from, state) do
    # response is not specified
    {:reply, state, state}
  end

  def start_link(state) do
    GenServer.start_link(pid, state)
  end

  def set(pid, new_state) do
    GenServer.cast(pid, {:set, new_state})
  end

  def get(pid) do
    GenServer.call(pid, :get)
  end
end

{:ok, pid} = State.start_link("initial state")
"initial state" = State.get(pid)
State.set(pid, "updated state")
"updated state" = State.get(pid)
defmodule State do
  use GenServer

  @impl true
  def init(state) do
    {:ok, state}
  end

  @impl true
  def handle_cast({:set, new_state}, _state) do
    {:noreply, new_state}
  end

  @impl true
  def handle_call(:get, _from, state) do
    {:reply, state, state}
  end

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

  def set(pid, new_state) do
    GenServer.cast(pid, {:set, new_state})
  end

  def get(pid) do
    GenServer.call(pid, :get)
  end
end

{:ok, pid} = State.start_link("test")
IO.puts("test" == State.get(pid))
State.set(pid, "test1")
IO.puts("test1" == State.get(pid))

Create a minimal Named GenServer that can be started and configured as a named process using Named.start_link/1. Start a Named process and use GenServer.whereis/1 and/or Process.whereis/1 to find the pid of the named process.

Example Solution

defmodule Named do
  use GenServer

  def start_link(opts) do
    name = Keyword.get(opts, :name, __MODULE__)
    GenServer.start_link(__MODULE__, [], name: name)
  end

  def init(_opts) do
    {:ok, "any state!"}
  end
end

Named.start_link([name: :my_configured_name])
Process.whereis(:my_configured_name)
defmodule Named do
  use GenServer

  @impl true
  def init(_opts) do
    {:ok, 0}
  end

  def start_link(name) do
    GenServer.start_link(__MODULE__, [], name: name)
  end
end

Named.start_link(:test)
GenServer.whereis(:test)
Process.whereis(:test)

Create a minimal NamedState GenServer that can be started and configured as a named process with a configurable state using NamedState.start_link. Use :sys.get_state/1 to confirm the initial state is as expected and GenServer.whereis/1 and/or Process.whereis/1 to find the pid of the named process.

Example Solution

There are many ways to configure NamedState.start_link/1

Using multiple parameters:

defmodule NamedState do
  use GenServer

  def start_link(state, opts \\ []) do
    name = Keyword.get(opts, :name, __MODULE__)
    GenServer.start_link(__MODULE__, state, name: name)
  end

  def init(state) do
    {:ok, state}
  end
end

NamedState.start_link("initial state", name: :multi_arg_example)

Process.whereis(:multi_arg_example)

Using a keyword list with different keys:

defmodule NamedState do
  use GenServer

  def start_link(opts) do
    name = Keyword.get(opts, :name, __MODULE__)
    state = Keyword.get(opts, :state)
    GenServer.start_link(__MODULE__, state, name: name)
  end

  def init(state) do
    {:ok, state}
  end
end

NamedState.start_link(state: "initial state", name: :keyword_list_example)
Process.whereis(:keyword_list_example)
defmodule NamedState do
  use GenServer

  def start_link(name, state) do
    GenServer.start_link(__MODULE__, state, name: name)
  end

  def init(state) do
    {:ok, state}
  end
end

NamedState.start_link(:abc, "test")
:sys.get_state(:abc)

Commit Your Progress

DockYard Academy now recommends you use the latest Release rather than forking or cloning our repository.

Run git status to ensure there are no undesirable changes. Then run the following in your command line from the curriculum folder to commit your progress.

$ git add .
$ git commit -m "finish GenServer Drills exercise"
$ git push

We’re proud to offer our open-source curriculum free of charge for anyone to learn from at their own pace.

We also offer a paid course where you can learn from an instructor alongside a cohort of your peers. We will accept applications for the June-August 2023 cohort soon.

Navigation

Home Report An Issue GenServersScore Tracker