0Pricing
Erlang OTP: Distributed & Fault-Tolerant Systems Programming · Lesson

Complex Supervision Trees

Design and implement intricate nested supervision hierarchies to manage dependencies and failure domains effectively.

Complex Supervision Trees is a free Erlang OTP: Distributed & Fault-Tolerant Systems Programming lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Erlang OTP: Distributed & Fault-Tolerant Systems Programming learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Supervision Trees: The Basics

In Erlang, supervisors are special processes that oversee other processes, called children. If a child process crashes, the supervisor can restart it, ensuring fault tolerance.

A supervision tree is formed when a supervisor itself becomes a child of another supervisor. This creates a hierarchy, much like an organizational chart.

Why Complex Trees?

As applications grow, a single supervisor isn't enough. Complex supervision trees allow us to:

  • Manage dependencies: Group related processes so they start and stop together.
  • Isolate failures: A crash in one part of the tree won't necessarily bring down unrelated parts.
  • Improve modularity: Each supervisor can be responsible for a specific subsystem, making the application easier to understand and maintain.

Child Spec: Worker vs. Supervisor

Every process a supervisor manages is defined by a child specification. A child spec tells the supervisor how to start, restart, and shut down the child process.

Crucially, a child can be of two types:

  • worker: A regular process (like a gen_server) that performs application logic.
  • supervisor: Another supervisor process, forming a nested level in the tree.

The Worker: my_worker_module

Let's start with a simple worker process. This gen_server will be the leaf node in our supervision tree. It just prints messages when it starts or receives calls.

-module(my_worker_module).
-behaviour(gen_server).

-export([start_link/1, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

start_link(Id) ->
    gen_server:start_link(?MODULE, Id, []).

init(Id) ->
    io:format("Worker ~p starting...~n", [Id]),
    {ok, Id}.

handle_call(Req, _From, State) ->
    io:format("Worker ~p received call: ~p~n", [State, Req]),
    {reply, ok, State}.

handle_cast(Msg, State) ->
    io:format("Worker ~p received cast: ~p~n", [State, Msg]),
    {noreply, State}.

handle_info(Msg, State) ->
    io:format("Worker ~p received info: ~p~n", [State, Msg]),
    {noreply, State}.

terminate(_Reason, State) ->
    io:format("Worker ~p terminating...~n", [State]).

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

The Nested Supervisor: my_nested_sup

This supervisor will manage our my_worker_module processes. It defines two workers, 'WorkerA' and 'WorkerB', each with slightly different restart strategies.

Notice how its child_specs define type => worker.

-module(my_nested_sup).
-behaviour(supervisor).

-export([start_link/0, init/1]).

start_link() ->
    supervisor:start_link({local, ?MODULE}, ?MODULE, []).

init([]) ->
    SupFlags = #{
        strategy => one_for_one,
        intensity => 10,
        period => 1
    },
    ChildSpecs = [
        #{
            id => worker_A,
            start => {my_worker_module, start_link, ["WorkerA"]},
            type => worker,
            restart => permanent,
            shutdown => 5000,
            modules => [my_worker_module]
        },
        #{
            id => worker_B,
            start => {my_worker_module, start_link, ["WorkerB"]},
            type => worker,
            restart => transient,
            shutdown => 2000,
            modules => [my_worker_module]
        }
    ],
    {ok, {SupFlags, ChildSpecs}}.

The Top-Level Supervisor: my_app_sup

This is the root of our complex tree. It supervises my_nested_sup. Notice that its child_spec for my_nested_sup has type => supervisor. This is how you build nested trees!

-module(my_app_sup).
-behaviour(supervisor).

-export([start_link/0, init/1]).

start_link() ->
    supervisor:start_link({local, ?MODULE}, ?MODULE, []).

init([]) ->
    SupFlags = #{
        strategy => one_for_one,
        intensity => 10,
        period => 1
    },
    ChildSpecs = [
        #{
            id => my_nested_sup_child,
            start => {my_nested_sup, start_link, []},
            type => supervisor,
            restart => permanent,
            shutdown => infinity,
            modules => [my_nested_sup]
        }
    ],
    {ok, {SupFlags, ChildSpecs}}.

Running the Complex Tree

To see our tree in action, compile all three modules (my_worker_module.erl, my_nested_sup.erl, my_app_sup.erl) and then start the top-level supervisor. You'll see the workers start up!

You can use supervisor:which_children(PidOrName) to inspect the tree.

% In the Erlang shell:

c(my_worker_module).
c(my_nested_sup).
c(my_app_sup).

{ok, MySupPid} = my_app_sup:start_link().

% Check the children of the top-level supervisor:
supervisor:which_children(MySupPid).

% Find the nested supervisor's PID:
{_, NestedSupPid, _, _} = lists:keyfind(my_nested_sup_child, 1, supervisor:which_children(MySupPid)).

% Check the children of the nested supervisor:
supervisor:which_children(NestedSupPid).

Visualizing the Hierarchy

Our complex supervision tree looks like this:

  • my_app_sup (top-level supervisor)
    • supervises my_nested_sup (a child supervisor)
      • supervises worker_A (a worker process)
      • supervises worker_B (a worker process)

This structure ensures that if worker_A crashes, only my_nested_sup handles it. If my_nested_sup itself crashes, my_app_sup will restart it, bringing worker_A and worker_B back to life.

Benefits of Complex Trees

Complex supervision trees are a cornerstone of building robust Erlang applications. They provide:

  • Fault Isolation: Failures are contained to specific branches.
  • Logical Grouping: Components with related functions are supervised together.
  • Clear Responsibilities: Each supervisor has a well-defined set of processes it's responsible for.
  • Scalability: Easier to add or remove subsystems without disrupting the entire application.

Quick Check: Supervision Trees

Which of the following are key benefits of using complex (nested) supervision trees in Erlang?

Recap: Complex Supervision

Today, we explored complex supervision trees in Erlang. We learned that supervisors can manage other supervisors, creating nested hierarchies. This powerful pattern enables robust fault tolerance by isolating failures, managing dependencies, and improving the modularity of your applications. By defining child specs with type => supervisor, you can build intricate and resilient Erlang systems.

Frequently asked questions

Is the “Complex Supervision Trees” lesson free?

Yes — the full text of “Complex Supervision Trees” is free to read here on the web, and the Erlang OTP: Distributed & Fault-Tolerant Systems Programming course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Erlang OTP: Distributed & Fault-Tolerant Systems Programming course, upgrade to CoddyKit PRO.

What will I learn in “Complex Supervision Trees”?

Design and implement intricate nested supervision hierarchies to manage dependencies and failure domains effectively. You practise Erlang OTP: Distributed & Fault-Tolerant Systems Programming with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Erlang OTP: Distributed & Fault-Tolerant Systems Programming?

No prior experience is required. Erlang OTP: Distributed & Fault-Tolerant Systems Programming on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Complex Supervision Trees” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Erlang OTP: Distributed & Fault-Tolerant Systems Programming lesson?

Yes. Every Erlang OTP: Distributed & Fault-Tolerant Systems Programming lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Complex Supervision Trees
  2. Dynamic Process Management
  3. Advanced Restart Strategies
  4. Supervisor Bridges & Mixed Process Hierarchies
← Back to Erlang OTP: Distributed & Fault-Tolerant Systems Programming