Give Codeship a try

Want to learn more?

One of the benefits of microservices is that part of the system can go down without bringing the entire system down.

With Elixir, each process is in essence a microservice. It’s a small, isolated process that communicates with other processes via message passing, all orchestrated by the Erlang BEAM VM.

No memory is shared between processes, so the failure of one process is guaranteed to not effect other processes. But the key ability of Elixir isn’t just how processes work; it’s how they can be linked together, monitor one another, and use supervising functionality to determine what to do if a process fails.

In this article, we will touch on linking, monitoring, and supervisors, with an example of how to implement a simple caching GenServer that’s supervised by a supervisor.

The reason our process that spawned this code wasn’t affected at all is because they aren’t linked. You’ll notice that it still printed the "I'm done." message. Linking ties two or more processes together…if one process fails, so does the process that is linked to it. To begin a new linked process, you change the above code only slightly to call the spawn_link function instead.

Now that we have linked the process to our current (self()) process, it won’t print the "I'm done." message. When the linked process went down, so did our current one. Links are bidirectional. It doesn’t matter which process fails; by linking them, they are both effected.

So what if we actually did want to recover from a failure in a linked process? To do this, we will have to do something called trapping exits. When a linked process fails, we are given an opportunity to recover from it. We can listen for exits using a receive block, the typical way that messages are passed from one process to another.

You’ll notice that in the receive block above, I am actually pattern matching for two different :EXIT messages. The first one is what happens when a process exits normally upon finishing its task. The second one will catch errors and in our case will output:

Monitoring Processes

Links are bidirectional, but monitoring on the other hand is unidirectional. It allows you to monitor (hence the name) the status of another process without linking yourself to it. You’re observing it at a safe distance. Unlike linking, an error in a monitored process won’t bring down your current one; you’ll just be notified of it.

Supervising

Linking and monitoring are available when you need them, but Elixir comes with Supervisor functionality. This allows us to easily define what behavior should occur when the code that is being supervised fails. We’ll use an example of a cache store, which fetches the cached value as long as it hasn’t expired.

In the code below, we first fetch the total value, providing a function to call if it doesn’t exist or if it has already expired. We then identify the pid of this named process, which is 0.109.0. After sending a :kill message to the process, we then identify the pid again and can see that it is now 0.115.0. It has automatically been restarted by its supervisor and is now able to fetch the total value again (which would need to be recalculated because all state was lost when the process was killed).

Because this example runs as an application, we’ll implement the start function which is called automatically. Its job in this case is to start the Supervisor module for this application by calling the start_link function. Supervisors, like any other concurrent code in Elixir, are simply a specialized process.

defmodule CashMan do
use Application
def start(_type, _args) do
CashMan.Supervisor.start_link
end
end

The implementation for the Supervisor module includes the use Supervisor statement. This gives us all of the functionality which comes built in to Elixir for this behavior.

We’ll call the start_link function that comes with Supervisor, passing it the __MODULE__ (our current module, to use as the supervising module), an initial value, which in our case is simply :ok, and the name of this process.

The init function is then called automatically, which is where we can define the exact behavior for this specific supervisor: which children will it supervise, and which strategies should be used in case they fail.

Supervisors can supervise children (GenServers), but they can also supervise other supervisors, creating a supervision hierarchy or tree. Benjamin Tan Wei Hao produced an excellent cheatsheet detailing all of the different functions and options for supervisors.

I chose to use :one_for_one in the example above because the supervisor is only supervising one child. You would also use this strategy when it is an isolated process that shouldn’t effect any other children that the supervisor is supervising.

By calling :observer.start in any iex console, you will be able to see examples of supervisors. Logging contains one which you can explore! Ours from the example above looks like the following:

Conclusion

For a deeper dive into the world of Elixir and OTP, I recommend The Little Elixir and OTP Guidebook. It does a great job diving much deeper into each of the subjects we touched on above. The topic of supervisors in Elixir is much deeper and more nuanced than I could have hoped to cover in a single article. As usual, the Elixir website has an excellent guide on supervisors also.

The cool thing about Elixir is that all of the more advanced/abstracted functionality is built on top of the building blocks of processes, which can link themselves to other processes and send and receive messages from one process to another. Everything else is an abstraction, including a supervisor which is just a specialized GenServer that comes with the language.

Subscribe via Email

Over 60,000 people from companies like Netflix, Apple, Spotify and O'Reilly are reading our articles. Subscribe to receive a weekly newsletter with articles around Continuous Integration, Docker, and software development best practices.

We promise that we won't spam you. You can unsubscribe any time.

Join the Discussion

Leave us some comments on what you think about this topic or if you like to add something.