Idempotent Supervision Tree

Aleksei Matiushkin on December 29, 2018

One of the most exciting features of OTP, the heart of Erlang VM, would be the Supervision Tree. The long-lived processes are maintained as a tre... [Read Full]
markdown guide

Wouldn't it be even better to use Genserver's init callback?


Yes, in this particular case we have a callback and it could be used.

I wanted to show the most generic approach applicable to all use-cases.


This approach does have a couple downsides though.

It's susceptible to race conditions. Your process could receive a message before it's actually been initialized (if it's named, which it is in your examples).

Also, what happens if the process dies before you call do_initialization/0? Then do_initialization/0 could raise an exception and crash your supervisor.

The init/1 callback, on the other hand, is guaranteed to be called before any messages are processed by the genserver. It also runs within the context of the genserver, so if there is an error, only the genserver crashes.

Finally, there is also handle_continue/2 for work that you would like to do after the init/2 but you don't want to block start_link/3 from returning.

init/1 and handle_continue/2 are the most generic approaches applicable to all use cases.

GenServer.handle_continue/2 is OTP 21+ and there are still many users on 20-. One might simply spawn another process/task in do_init, if there is a need to initialize asynchronously.

Also, I am not sure there is the best strategy in what process should crash. Sometimes it should be GenServer and sometimes it should be its supervisor. I like the explicitness and callbacks are kinda magic; maybe I should reconsider my attitude, though.

You should definitely reconsider. Using the GenServer callbacks is the OTP way.

If you want the supervisor to crash when the GenServer crashes, that's what the "maximum restart intensity" is for (default 3 restarts in 5 seconds, configurable via max_restarts and max_intensity).

It's true that GenServer.handle_continue/2 is OTP 21+. The old practice was to use send(self(), :post_init) in the init callback to allow start_link to return while triggering some additional initialization (not to spawn a process). This was unfortunately also susceptible to the race condition that the process could receive another message before :post_init, if the process was named. So in OTP < 21, there is no good solution to this problem.

code of conduct - report abuse