Adopting Elixir by Ben Marx

Adopting Elixir by Ben Marx

Author:Ben Marx
Language: eng
Format: epub
Tags: Pragmatic Bookshelf
Publisher: Pragmatic Bookshelf


Supervisors

Most new Elixir developers tend to think of supervisors in terms of fault tolerance because they provide the restart strategies that are the essential part of building reliable systems. Supervisors are so much more. They form the backbone of Elixir applications.

Ultimately, supervisors are responsible for how our processes start and shut down, whether an application is crashing and restarting or simply starting. Restarting of processes is optional, while starting and stopping them is essential. Let’s explore startup flow by addressing a bug in our PathAllocator implementation defined in ​Use GenServer as a Coordinator​.

Starting a supervisor is a matter of defining all of its child specifications and then calling start_link. A child specification specifies exactly how the supervisor starts a child process, when and how many times to restart it, and how to shut it down. For a complete reference on child specifications, consult the Elixir documentation for Supervisors.[46]

We can start the PathAllocator example in ​Use GenServer as a Coordinator​ under a supervisor by defining a list of children, where each element is a tuple with the module name as first element and the argument given to start_link as second argument, like this:

​ children = [

​ {PathAllocator, System.tmp_dir}

​ ]

​ Supervisor.start_link(children, ​strategy:​ ​:one_for_one​)

Upon startup, a supervisor starts all of its children in the order they’re defined. Similarly, upon shutdown, the supervisor terminates all of its children in the reverse order. The initialization logic is in the init/1 function, but we haven’t defined the termination logic.

Whenever a supervisor restarts its children or the node shuts down, PathAllocator’s supervisor is going to send it an exit signal. By default, that exit signal will terminate the PathAllocator, regardless if it has processed all messages in its inbox or not, leaving spurious paths behind.

Let’s address this bug by cleaning up all files in the refs map on terminate. The first step is to trap exits with Process.flag(:trap_exit, true) on init, like this:

making_fun_transition/path_allocator_2.ex

​ ​def​ init(tmp_dir) ​do​

​ Process.flag(​:trap_exit​, true)

​ {​:ok​, {tmp_dir, %{}}}

​ ​end​

By trapping exits, if any external process causes the allocator to exit, the allocator won’t shut down immediately. Instead, it will run the terminate/2 callback. Next, we can write the termination logic, like this:

making_fun_transition/path_allocator_2.ex

​ ​def​ terminate(_reason, {_tmp_dir, refs}) ​do​

​ for {_, path} <- refs ​do​

​ File.rm(path)

​ ​end​

​ ​:ok​

​ ​end​

The logic is dead simple. We simply remove every file in refs. This change guarantees PathAllocator will clean up all entries on shutdown or even when a bug causes part of your application to restart.

This example highlights the importance of proper termination of supervision trees in our applications. Once the supervisor sends the exit shutdown signal to the worker, the worker has 5 seconds to terminate, by default. If a given process requires more time to shutdown, you can specify the shutdown time when defining the process child specification.

Be careful, though. terminate/2 won’t happen in extreme scenarios, such as a spilled beer or a machine shutdown, so be defensive. For example, our PathAllocator’s init function could remove all files from the given directory to ensure a fresh start.

When



Download



Copyright Disclaimer:
This site does not store any files on its server. We only index and link to content provided by other sites. Please contact the content providers to delete copyright contents if any and email us, we'll remove relevant links or contents immediately.