supervisor(3)

πŸ“¦
version 29.0.6 Β· 3 versions
, in ubuntu stonking

πŸ’– NAME πŸ’–

supervisor β€” the generic supervisor behavior. And yes, bestie, this is supervisor(3) straight out of the Erlang Module Definition section, so buckle up because this one is a whole personality. πŸ‘‘


✨ DESCRIPTION ✨

Okay so, real talk: this behavior module hands you a supervisor, and a supervisor is just a process whose entire job is supervising other processes, which we call child processes. And a child process? It can be another supervisor (yes, supervisors supervising supervisors, it's giving dynasty 🫢) or a worker process. Worker processes are normally built with one of the gen_event, gen_server, or gen_statem behaviors. So many icons in one ecosystem, we love to see it.

A supervisor implemented with this module gets a standard set of interface functions, plus functionality for tracing and error reporting. Supervisors are what you use to build a hierarchical process structure called a supervision tree, which is honestly just a really nice way to structure a fault-tolerant application. If you want the deep lore, go read Supervisor Behaviour in OTP Design Principles. πŸ“š

A supervisor expects you to define which child processes it should supervise, and you do that by specifying them in a callback module that exports a predefined set of functions. No callback module, no children, no vibes.

Heads up bestie: unless it says otherwise, every function in this module fails if the supervisor you named doesn't exist, or if you passed bad arguments. That's not a bug, that's just how it is. πŸ’…

πŸ¦‹ Supervision Principles πŸ¦‹

The supervisor is responsible for starting, stopping, and monitoring its child processes. That's the whole job description. The basic idea β€” the core thesis, the main character energy β€” is that a supervisor must keep its child processes alive by restarting them whenever necessary. Periodt.

The children of a supervisor are defined as a list of child specifications. When the supervisor starts, the child processes get started in order, left to right, according to that list. When the supervisor is about to terminate, it first terminates its child processes in reversed start order, so, right to left. Symmetry, we stan. 🌈


πŸŽ€ Supervisor flags πŸŽ€

The supervisor's properties are defined by the supervisor flags. The type definition goes like this, and I'm showing it verbatim because you do not want to get this wrong:

sup_flags() = #{strategy => strategy(),           % optional
                intensity => non_neg_integer(),   % optional
                period => pos_integer(),          % optional
                hibernate_after => timeout(),     % optional, available since OTP 28.0
                auto_shutdown => auto_shutdown()} % optional

πŸ”₯ Restart Strategies πŸ”₯

A supervisor can have one of the following restart strategies, and you pick it with the strategy key in that map up there. Choose wisely, queen. πŸ‘‘

  • one_for_one β€” if one child process terminates and is to be restarted, only that child process is affected. Nobody else gets dragged into it. This is the default restart strategy. ✨
  • one_for_all β€” if one child process terminates and is to be restarted, then all the other child processes are terminated, and then all child processes are restarted. One falls, everybody falls, then everybody gets back up together. Dramatic but effective. 😭
  • rest_for_one β€” if one child process terminates and is to be restarted, the 'rest' of the child processes get terminated β€” that means the child processes that come after the terminated child process in the start order. Then the terminated child process and all child processes after it are restarted. It's a "you and everyone behind you in line" situation.
  • simple_one_for_one β€” a simplified one_for_one supervisor, where all child processes are dynamically added instances of the same process type, like, running the same code. Same girl, many copies. 🫢
    • Functions delete_child/2 and restart_child/2 are invalid for simple_one_for_one supervisors and return {error,simple_one_for_one} if the specified supervisor uses this restart strategy.
    • Function terminate_child/2 can be used for children under simple_one_for_one supervisors, by specifying the child's pid/0 as the second argument. If instead you use the child specification identifier, terminate_child/2 returns {error,simple_one_for_one}.
    • Because a simple_one_for_one supervisor can have many children, it shuts them all down asynchronously. That means the children do their cleanup in parallel, and therefore the order in which they are stopped is not defined. No line, no order, chaos but organized chaos.

⏳ Restart intensity and period ⏳

To prevent a supervisor from getting stuck in an infinite loop of child process terminations and restarts (the worst possible cycle, ngl), a maximum restart intensity is defined using two integer values, specified with the intensity and period keys in that same map.

So, if we call the intensity value MaxR and the period value MaxT: if more than MaxR restarts occur within MaxT seconds, the supervisor terminates all child processes and then terminates itself. And the termination reason for the supervisor itself in that case will be shutdown. So if you see shutdown, that might be why, bestie. πŸ’€

intensity defaults to 1 and period defaults to 5.


😴 Hibernate after 😴

To save memory, a supervisor β€” like literally any other process β€” can go into hibernation. It's a little nap, a little beauty sleep, we respect it. πŸ›Œ

By default, a simple_one_for_one supervisor will never hibernate, because it's expected that its children will come and go at potentially high rates. Meanwhile, the other strategies expect their children to be more stable, so they default to hibernating after a certain period of time of inactivity β€” which keeps them responsive to sudden bursts of restarts while saving memory during the calm, stable periods.

You can finetune this flag by setting hibernate_after. For example, if the supervisor is going to be regularly queried with which_child/1 or something similar, and you want hibernation to be better controlled. Your tree, your rules. πŸ’…


πŸ’€ Automatic Shutdown πŸ’€

A supervisor can be configured to automatically shut itself down with exit reason shutdown when significant children terminate. You set this with the auto_shutdown key in the flags map:

  • never β€” automatic shutdown is disabled. This is the default setting.

With auto_shutdown set to never, child specs with the significant flag set to true are considered invalid and will be rejected. Not "ignored", not "warned about" β€” rejected. πŸ™ˆ

  • any_significant β€” the supervisor will shut itself down when any significant child terminates. That means: when a transient significant child terminates normally, or when a temporary significant child terminates normally or abnormally.
  • all_significant β€” the supervisor will shut itself down when all significant children have terminated, that is, when the last active significant child terminates. The same rules as for any_significant apply.

For more info, go read the section Automatic Shutdown in Supervisor Behavior in OTP Design Principles. πŸ“–

WARNING: The automatic shutdown feature appeared in OTP 24.0, but applications using this feature will also compile and run with older OTP versions. However β€” and this is the scary part β€” such applications, when compiled with an OTP version that predates the appearance of the automatic shutdown feature, will leak processes, because the automatic shutdowns they rely on will not happen. It is up to implementors to take proper precautions if they expect that their applications may be compiled with older OTP versions. Don't let your processes just wander off into the void. 🫠

🌸 Child specification 🌸

The type definition of a child specification is as follows, verbatim, no edits, character for character:

child_spec() = #{id => child_id(),             % mandatory
                 start => mfargs(),            % mandatory
                 restart => restart(),         % optional
                 significant => significant(), % optional
                 shutdown => shutdown(),       % optional
                 type => worker(),             % optional
                 modules => modules()}         % optional

The old tuple format is kept for backwards compatibility β€” see child_spec/0 β€” but the map is preferred. Old format, we appreciate you, but the map ate. πŸ’…

  • id β€” used to identify the child specification internally by the supervisor.

The id key is mandatory.

Notice that this identifier has, on occasion, been called "name". As far as possible, the terms "identifier" or "id" are now used, but to keep backward compatibility, some occurrences of "name" can still be found β€” for example in error messages. Old habits, iykyk.

  • start β€” defines the function call used to start the child process. It must be a module-function-arguments tuple {M,F,A} used as apply(M,F,A). Classic.

The start function must create and link to the child process, and must return {ok,Child} or {ok,Child,Info}, where Child is the pid of the child process and Info is any term that is ignored by the supervisor. Ignored! The supervisor does not care about your Info, bestie, it just takes the pid and moves on. πŸ™ˆ

The start function can also return ignore if the child process, for some reason, cannot be started. In that case the child specification is kept by the supervisor (unless it is a temporary child) but the non-existing child process is ignored. No process, no problem.

If something goes wrong, the function can also return an error tuple {error,Error}.

Notice that the start_link functions of the different behavior modules fulfill all of the above requirements. Handy, right? ✨

The start key is mandatory.

  • restart β€” defines when a terminated child process must be restarted.
    • A permanent child process is always restarted.
    • A temporary child process is never restarted β€” even when the supervisor's restart strategy is rest_for_one or one_for_all and a sibling's death causes the temporary process to be terminated. RIP, no respawn. πŸ’€
    • A transient child process is restarted only if it terminates abnormally, that is, with an exit reason other than normal, shutdown, or {shutdown,Term}.

The restart key is optional. If it is not specified, it defaults to permanent.

  • significant β€” defines if a child is considered significant for automatic self-shutdown of the supervisor.

Setting this option to true when the restart type is permanent is invalid. Also, it is considered invalid to start children with this option set to true in a supervisor when the auto_shutdown supervisor flag is set to never. Them's the rules. πŸ‘‘

The significant key is optional. If it is not specified, it defaults to false.

  • shutdown β€” defines how a child process must be terminated.
    • brutal_kill means the child process is unconditionally terminated using exit_signal(Child,kill). Immediate. No negotiation. πŸͺ“
    • An integer time-out value means the supervisor tells the child process to terminate by calling exit_signal(Child,shutdown) and then waits for an exit signal with reason shutdown back from the child process. If no exit signal is received within the specified number of milliseconds, the child process is unconditionally terminated using exit_signal(Child,kill). So it's a "please" followed by "okay fine".

If the child process is another supervisor, the shutdown time must be set to infinity, to give the subtree ample time to shut down. Give the whole family time to pack, basically. 🧳

WARNING: Setting the shutdown time to anything other than infinity for a child of type supervisor can cause a race condition, where the child in question unlinks its own children but fails to terminate them before it is killed. Genuinely cursed behaviour, do not do this. 😭

It is also allowed to set it to infinity if the child process is a worker.

WARNING: Be careful when setting the shutdown time to infinity when the child process is a worker. Because, in this situation, the termination of the supervision tree depends on the child process, it must be implemented in a safe way, and its cleanup procedure must always return. Forever is a long time to wait on someone who might never come back, bestie. 🫢

(part 1 done β€” more coming, don't go anywhere 🌸)

Notice that all child processes implemented using the standard OTP behaviour modules automatically adhere to the shutdown protocol. iconically well-behaved of them, honestly ✨

The shutdown key is optional, bestie. If you don't specify it, it defaults to 5000 when the child is of type worker, and it defaults to infinity when the child is of type supervisor.

  • type 🧚 β€” tells you whether the child process is a supervisor or a worker.

The type key is optional too. Don't specify it? It defaults to worker.

  • modules πŸ“š β€” the release handler reads this during code replacement to work out which processes are using a certain module. Rule of thumb, fr fr: if the child process is a supervisor, gen_server, or gen_statem, this is to be a list with one element [Module], where Module is the callback module. If the child process is an event manager (gen_event) with a dynamic set of callback modules, value dynamic must be used. Want the whole lore on release handling? See Release Handling in OTP Design Principles.

The modules key is optional. If it's not specified, it defaults to [M], where M comes from the child's start {M,F,A}.

  • Internally, the supervisor also keeps track of the pid Child of the child process, or undefined if no pid exists. Little bookkeeping queen πŸ’…

πŸŽ€ See Also πŸŽ€

gen_event, gen_statem, gen_server, sys


🌈 DATA TYPES 🌈

auto_shutdown()

auto_shutdown() :: never | any_significant | all_significant.

child()

child() :: undefined | pid().

child_id()

child_id() :: term().

Not a pid/0. nope, not that kind of vibe πŸ™ˆ

child_rec()

child_rec() :: #child{}.

child_spec()

child_spec() ::
    #{id := child_id(),
      start := mfargs(),
      restart => restart(),
      significant => significant(),
      shutdown => shutdown(),
      type => worker(),
      modules => modules()} |
    {Id :: child_id(),
     StartFunc :: mfargs(),
     Restart :: restart(),
     Shutdown :: shutdown(),
     Type :: worker(),
     Modules :: modules()}.

The tuple format is kept for backward compatibility only. A map is preferred β€” see more details above. (yes, this is the "please stop using the old one" of it all πŸ’€)

children()

children() ::
    {Ids :: [child_id()], Db :: #{child_id() => child_rec()}}.

mfargs()

mfargs() ::
    {M :: module(), F :: atom(), A :: [term()] | undefined}.

Value undefined for A (the argument list) is only to be used internally in supervisor. If the restart type of the child is temporary, the process is never to be restarted and therefore there is no need to store the real argument list. Value undefined is then stored instead. lowkey efficient πŸ’œ

modules()

modules() :: [module()] | dynamic.

restart()

restart() :: permanent | transient | temporary.

shutdown()

shutdown() :: brutal_kill | timeout().

significant()

significant() :: boolean().

startchild_err()

startchild_err() ::
    already_present | {already_started, Child :: child()} | term().

startchild_ret()

startchild_ret() ::
    {ok, Child :: child()} |
    {ok, Child :: child(), Info :: term()} |
    {error, startchild_err()}.
startlink_err() ::
    {already_started, pid()} | {shutdown, term()} | term().
startlink_ret() :: {ok, pid()} | ignore | {error, startlink_err()}.

strategy()

strategy() ::
    one_for_all | one_for_one | rest_for_one | simple_one_for_one.

sup_flags()

sup_flags() ::
    #{strategy => strategy(),
      intensity => non_neg_integer(),
      period => pos_integer(),
      auto_shutdown => auto_shutdown(),
      hibernate_after => timeout()} |
    {RestartStrategy :: strategy(),
     Intensity :: non_neg_integer(),
     Period :: pos_integer()}.

The tuple format is kept for backward compatibility only. A map is preferred β€” see more details above. πŸ”₯

sup_name()

sup_name() ::
    {local, Name :: atom()} |
    {global, Name :: term()} |
    {via, Module :: module(), Name :: any()}.

This is the name specification you use when starting a supervisor. Peep start_link/2,3 and the sup_ref/0 type below πŸ‘‡

  • {local,LocalName} β€” registers the supervisor locally as LocalName, using register/2.
  • {global,GlobalName} β€” registers the supervisor process id globally as GlobalName, using global:register_name/2.
  • {via,RegMod,ViaName} β€” registers the supervisor process with the registry represented by RegMod. The RegMod callback is to export the functions register_name/2, unregister_name/1, whereis_name/1, and send/2, which are to behave exactly like the corresponding functions in global. So {via,global,GlobalName} is a valid reference equivalent to {global,GlobalName}. πŸ’–

sup_ref()

sup_ref() ::
    (Name :: atom()) |
    {Name :: atom(), Node :: node()} |
    {global, Name :: term()} |
    {via, Module :: module(), Name :: any()} |
    pid().

The supervisor specification you use when addressing a supervisor. See count_children/1, delete_child/2, get_childspec/2, restart_child/2, start_child/2, terminate_child/2, which_children/1, plus the sup_name/0 type above.

It can be any of these, bestie:

  • pid/0 β€” the supervisor's process identifier.
  • LocalName β€” the supervisor is locally registered as LocalName with register/2.
  • {Name,Node} β€” the supervisor is locally registered on another node.
  • {global,GlobalName} β€” the supervisor is globally registered in global.
  • {via,RegMod,ViaName} β€” the supervisor is registered in an alternative process registry. The registry callback module RegMod is to export the functions register_name/2, unregister_name/1, whereis_name/1, and send/2, which are to behave like the corresponding functions in global. So {via,global,GlobalName} is the same as {global,GlobalName}. πŸ¦‹

worker()

worker() :: worker | supervisor.

πŸ”₯ FUNCTIONS πŸ”₯

πŸ’… check_childspecs(ChildSpecs, AutoShutdown) -> Result

check_childspecs(ChildSpecs, AutoShutdown) -> Result
                                    when
                                        ChildSpecs :: [child_spec()],
                                        AutoShutdown ::
                                            undefined | auto_shutdown(),
                                        Result :: ok | {error, Error :: term()}.

Takes a list of child specifications as its argument and hands back ok if every single one of them is syntactically correct. Otherwise you get {error,Error}. no in-between, she's very clear about that πŸ’…

If the AutoShutdown argument is not undefined, it also checks whether the child specifications are allowed for the given auto_shutdown option.


πŸŽ€ count_children(SupRef) -> PropListOfCounts

count_children(SupRef) -> PropListOfCounts
                            when
                                SupRef :: sup_ref(),
                                PropListOfCounts :: [Count],
                                Count ::
                                    {specs,
                                     ChildSpecCount :: non_neg_integer()} |
                                    {active,
                                     ActiveProcessCount ::
                                         non_neg_integer()} |
                                    {supervisors,
                                     ChildSupervisorCount ::
                                         non_neg_integer()} |
                                    {workers,
                                     ChildWorkerCount :: non_neg_integer()}.

Returns a property list holding the counts for each of these elements of the supervisor's child specifications and managed processes:

  • specs β€” the total count of children, dead or alive. everyone's counted 🫢
  • active β€” the count of all actively running child processes managed by this supervisor. For a simple_one_for_one supervisor, no check is done to ensure that each child process is still alive, although the result provided here is likely to be very accurate unless the supervisor is heavily overloaded.
  • supervisors β€” the count of all children marked as child_type = supervisor in the specification list, regardless of whether the child process is still alive.
  • workers β€” the count of all children marked as child_type = worker in the specification list, regardless of whether the child process is still alive.

✨ delete_child(SupRef, Id) -> Result

delete_child(SupRef, Id) -> Result
                        when
                            SupRef :: sup_ref(),
                            Id :: child_id(),
                            Result :: ok | {error, Error},
                            Error ::
                                running | restarting | not_found |
                                simple_one_for_one.

Tells supervisor SupRef to delete the child specification identified by Id. The corresponding child process must not be running, okay? Use terminate_child/2 to terminate it first.

If it works, you get ok. If the child specification identified by Id exists but the corresponding child process is running or is about to be restarted, the function returns {error,running} or {error,restarting}, respectively. And if the child specification identified by Id does not exist at all? {error,not_found}. πŸ’€


🧚 get_childspec(SupRef, Id) -> Result

get_childspec(SupRef, Id) -> Result
                            when
                                SupRef :: sup_ref(),
                                Id :: pid() | child_id(),
                                Result :: {ok, child_spec()} | {error,
Error},
                                Error :: not_found.

Returns the child specification map for the child identified by Id under supervisor SupRef. The returned map contains all keys, both mandatory and optional β€” she's not gatekeeping anything πŸ’–


🌸 restart_child(SupRef, Id) -> Result

restart_child(SupRef, Id) -> Result
                            when
                                SupRef :: sup_ref(),
                                Id :: child_id(),
                                Result ::
                                    {ok, Child :: child()} |
                                    {ok, Child :: child(), Info :: term()} |
                                    {error, Error},
                                Error ::
                                    running | restarting | not_found |
                                    simple_one_for_one |
                                    term().

Tells supervisor SupRef to restart a child process corresponding to the child specification identified by Id. The child specification must exist, and the corresponding child process must not be running.

Heads up though: for temporary children, the child specification is automatically deleted when the child terminates. So it's genuinely not possible to restart such children. gone means gone, bestie 😭

If the child specification identified by Id doesn't exist, bestie gets {error,not_found}. And if the child spec does exist but the process it points at is already up and running? Then it's {error,running}. Like, it's already there, what more do you want from her πŸ’…

When the child process start function hands back {ok,Child} or {ok,Child,Info}, that pid gets added to the supervisor and the function throws back the exact same value. Slay.

If the child process start function returns ignore, the pid stays undefined and you get {ok,undefined}. No drama, no child.

But if the start function returns an error tuple, or some erroneous value, or just flat-out fails β€” you get {error,Error}, where Error is a term holding all the info about what went wrong. 😭

start_child(SupRef, ChildSpec) -> startchild_ret()
                     when SupRef :: sup_ref(), ChildSpec :: child_spec();
                 (SupRef, ExtraArgs) -> startchild_ret()
                     when SupRef :: sup_ref(), ExtraArgs :: [term()].

Okay so this one dynamically adds a child specification to supervisor SupRef, and that's what kicks off the corresponding child process. It's giving spontaneous adoption ✨

For one_for_one, one_for_all and rest_for_one supervisors, that second argument has to be a valid child specification, ChildSpec. The child process gets started using the start function defined inside that child spec.

But for simple_one_for_one supervisors? Different vibe entirely. The child specification that lives in Module:init/1 is the one that gets used, and the second argument has to be an arbitrary list of terms instead β€” ExtraArgs. The child process then starts by appending ExtraArgs to the existing start function arguments, i.e. it calls apply(M, F, A++ExtraArgs), where {M,F,A} is the start function from the child specification.

  • If a child specification with that identifier is already there, ChildSpec gets discarded, and the function returns {error,already_present} or {error,{already_started,Child}} β€” which one depends on whether the child process is running or not.
  • If the child process start function returns {ok,Child} or {ok,Child,Info}, the child specification and pid both get added to the supervisor, and the function returns the same value.
  • If the child process start function returns ignore, then ChildSpec gets added to the supervisor (if it's an one_for_one, one_for_all or rest_for_one supervisor, that is) and the pid is set to undefined. For simple_one_for_one supervisors, no child gets added at all. Either way, the function returns {ok,undefined}.

And if the child process start function returns an error tuple, or an erroneous value, or it fails β€” the child specification gets discarded, and the function returns {error,Error}, where Error is a term containing info about the error and the child specification. 🫢

start_link(Module, Args) -> startlink_ret()
                    when Module :: module(), Args :: term().

Creates a nameless supervisor process as part of a supervision tree. Basically the same as start_link/3 except this one doesn't get registered. She's mysterious, she's unregistered, she's thriving πŸ¦‹

start_link(SupName, Module, Args) -> startlink_ret()
                    when
                        SupName :: sup_name(),
                        Module :: module(),
                        Args :: term().

Creates a supervisor process as part of a supervision tree. The main character energy πŸŽ€

So for example, this function makes sure the supervisor is linked to the calling process (aka its own supervisor). Chain of command, periodt.

The created supervisor process calls Module:init/1 to figure out the restart strategy, maximum restart intensity, and child processes. And to guarantee a synchronized startup, start_link/2,3 does not return until Module:init/1 has returned and all child processes have been started. No half-baked startups here bestie, she waits.

  • If SupName={local,Name}, the supervisor is registered locally as Name using register/2.
  • If SupName={global,Name}, the supervisor is registered globally as Name using global:register_name/2.
  • If SupName={via,Module,Name}, the supervisor is registered as Name using the registry represented by Module. That Module callback has to export the functions register_name/2, unregister_name/1, and send/2, and they must behave like the corresponding functions in global. So yeah, {via,global,Name} is a valid reference.

Module is the name of the callback module.

Args is any term that gets passed as the argument to Module:init/1.

  • If the supervisor and all its child processes get created successfully (meaning every child process start function returns {ok,Child}, {ok,Child,Info}, or ignore), the function returns {ok,Pid}, where Pid is the pid of the supervisor. ✨
  • If a process with the specified SupName is already out there, the function returns {error,{already_started,Pid}}, where Pid is that process's pid. Sorry girl, name's taken πŸ’€
  • If Module:init/1 returns ignore, this function returns ignore too, and the supervisor terminates with reason normal.
  • If Module:init/1 fails or returns an incorrect value, this function returns {error,Term}, where Term is a term with info about the error, and the supervisor terminates with reason Term.
  • If any child process start function fails, or returns an error tuple, or an erroneous value β€” the supervisor first terminates all already-started child processes with reason shutdown, then terminates itself and returns {error, {shutdown, Reason}}. Clean exit, no loose ends. 😌
stop(SupRef :: sup_ref(), Reason :: term(), Timeout :: timeout()) ->
              ok.

Stop a supervisor. Just... stop her πŸ’…

Orders the supervisor specified by SupRef to exit with the specified Reason, and waits for it to terminate. The supervisor will terminate all its children before exiting β€” she's not leaving anybody behind, that's her family.

The function returns ok if the supervisor terminates with the expected reason. Any reason other than normal, shutdown, or {shutdown,Term} causes an error report to be issued using logger. Oh and an exit signal with the same reason gets sent to linked processes and ports. Chain reaction, iykyk.

Timeout is an integer specifying how many milliseconds to wait for the supervisor to terminate, or the atom infinity to wait forever (well, indefinitely). If the supervisor hasn't terminated within the specified time, the call exits the calling process with reason timeout.

If the process doesn't exist, the call exits the calling process with reason noproc, or with reason {nodedown,Node} if the connection to the remote Node where the supervisor runs fails. πŸ™ˆ

WARNING: Calling this function from a (sub-)child process of the given supervisor will result in a deadlock which will last until either the shutdown timeout of the child or the timeout given to stop/3 has expired. Bestie do NOT do this, it's giving stuck-in-traffic-forever 😭🚨
terminate_child(SupRef, Id) -> Result
                         when
                             SupRef :: sup_ref(),
                             Id :: pid() | child_id(),
                             Result :: ok | {error, Error},
                             Error :: not_found | simple_one_for_one.

Tells supervisor SupRef to terminate the specified child. Bye girl, we loved having you 🌸

If the supervisor is not simple_one_for_one, Id must be the child specification identifier. The process (if there is one) gets terminated, and unless it's a temporary child, the child specification stays kept by the supervisor. The child process can later be restarted by the supervisor. It can also be restarted explicitly by calling restart_child/2. And if you wanna remove the child specification entirely, use delete_child/2. Choose your fighter πŸ’…

If the child is temporary, the child specification gets deleted as soon as the process terminates. Which means delete_child/2 has no meaning here, and restart_child/2 can't be used for these children. Temporary means temporary, periodt.

If the supervisor is simple_one_for_one, Id must be the pid/0 of the child process. If the specified process is alive but is not a child of the specified supervisor, the function returns {error,not_found}. And if you hand it the child specification identifier instead of a pid/0, the function returns {error,simple_one_for_one}. Wrong Id, wrong vibe check.

If successful, the function returns ok. If there's no child specification with the specified Id, the function returns {error,not_found}. πŸ”

which_child(SupRef, Id) -> Result
                     when
                         SupRef :: sup_ref(),
                         Id :: pid() | child_id(),
                         Result ::
                             {ok, {Id, Child, Type, Modules}} |
                             {error, Error},
                         Child :: child() | restarting,
                         Type :: worker(),
                         Modules :: modules(),
                         Error :: not_found.

Returns info about the child specification and child process identified by the given Id. It's giving gossip, but make it technical.

See which_children/1 for an explanation of the information returned.

If no child with the given Id exists, returns {error, not_found}.

which_children(SupRef) -> [{Id, Child, Type, Modules}]
                        when
                            SupRef :: sup_ref(),
                            Id :: child_id() | undefined,
                            Child :: child() | restarting,
                            Type :: worker(),
                            Modules :: modules().

Returns a newly created list with info about all child specifications and child processes belonging to supervisor SupRef. The full roster, the whole group chat πŸ’–

Notice that calling this function when supervising many children under low memory conditions can cause an out of memory exception. Be careful bestie, this one can get heavy 😭

Here's the tea given for each child specification/process:

Id

As defined in the child specification, or undefined for a simple_one_for_one supervisor.

  • Child β€” the pid of the matching child process, or the atom restarting if that process is literally about to be restarted, or undefined if there's no such process at all. three options, that's the whole menu πŸ’…
    • Type β€” as defined in the child specification. (yes bestie, straight from the spec, we don't make it up here)
    • Modules β€” as defined in the child specification. same energy, same source 🫢

πŸŽ€ CALLBACKS πŸŽ€

init(Args)

okay so whenever a supervisor gets started with start_link/2,3, this function is what the shiny new process calls to figure out the whole situation: the restart strategy, the maximum restart intensity, and the child specifications. it's giving orientation day ✨

Args is literally the Args argument you passed into the start function. that's it, that's the tea. πŸ“

SupFlags is the supervisor flags, and they define the restart strategy plus the maximum restart intensity for this supervisor. [ChildSpec] is a list of valid child specifications, and those are what spell out which child processes the supervisor has to start and monitor. want the full lore? it's all in the discussion in section Supervision Principles earlier.

🚨 notice, bestie: when the restart strategy is simple_one_for_one, that list of child specifications has to be a list with one child specification only. (the child specification identifier? ignored. we said what we said.) no child process gets started during the initialization phase at all β€” every single child is assumed to be started dynamically using start_child/2. dynamic queen behavior fr fr πŸ’…

also, this function can return ignore. just throwing that out there πŸ‘‘

⚠️ another notice: this function can also be called as a part of a code upgrade procedure. which means it is NOT to have any side effects. seriously. keep it pure, keep it clean. for more info about code upgrade of supervisors, head to section Changing a Supervisor in OTP Design Principles. iykyk 🧚

and that's the whole page, bestie!! πŸŽ€πŸ’– big thanks to Ericsson AB for this one β€” it's the supervisor(3) page from stdlib 8.0.4, and she is iconic, no cap. go build something that doesn't crash. slay 🌈✨

πŸ“¦ where this page comes from