Supervision design for static but interdependent processes, startup order

Hey all, I’m pulling my hair out over how to properly design my supervision structure. It works right now but only because processes are restarted often enough to eventually fall in the right order.

I’m sure there’s an obvious right solution here that I just don’t see (pretty new to elixir). Anyone got an idea? :pray:

I’m building a single-user interactive app. There are four GenServers:

  • The ModelManager serves as a central point to store the underlying data. (makes it easier to implement sync later) Processes can subscribe to the ModelManager and be notified in the event of changes.
  • The Frontend (a TUI using the Ratatouille library) presents the UI and pushes changes to the ModelManager. It also listens to change events emitted from the ModelManager (e.g., because of sync).
  • The frontend actually needs a little MsgTransformer process between itself and the ModelManager to transform the messages into a shape that Ratatouille can deal with.
  • The FileSaver implements autosave by listening to change events from the ModelManager and writing them to disk.

I even made a little diagram:

The problem I’m facing is startup: in principle it’s clear what should happen:

  • The ModelManager should start first.
  • Then we can start FileServer and MsgTransformer (the MsgTransformer actually belonging to the Frontend; nobody else has to know about this)
  • Finally we can start the Frontend.

This order is necessary so everyone can subscribe / reference the other processes when they need to. But I have no idea how to make this happen.

Right now I’m just putting all four processes as directly supervised children under my main app tree. Some process (the frontend specifically) crash upon init because the ModelManager isn’t live yet, so it can’t subscribe. But the supervisor restarts it, so it’ll eventually be fine. — But this is definitely not a clean design right?

1 Like

I typically group processes by strategy and dependencies. If you need to start a collection of processes together then I would place them under a dedicated supervisor. You may want the :one_for_all so that a restart in one restarts all of them. You can use module based supervisors and either split the responsibility or just tack it on. I would personally only split when the friction required it.

You’ve described your supervision tree perfectly here. The app starts only ModelManager, that supervises FileServer and Frontend where frontend is also a supervisor for MsgTransformer.

1 Like

Hey, thanks for this! Can I ask a few follow-up questions?

  • I’m kinda hesitant to have ModelManager supervise the frontend (for instance) because these are not really logically connected: It’s thinkable that a variant of the app would run without the frontend (and instead with a web frontend or GUI, for instance, or as a sync server). Any alternatives come up here?
  • How do I make sure the ModelManager is started before the frontend etc.? As in, what code do I use? I feel a bit dumb here, honestly, as if there’s some command I just don’t know?
  • Oh, and can something be both a GenServer and a Supervisor at the same time? (possible I still have my OOP goggles on a bit too tightly here, maybe it’s just fine?)
1 Like

If Frontend depends on it then it should start first. Your second point kind of illustrates that. You could try to detect if the named process is started but you might as well use supervisors to guarantee it.

You are basically pulling the childspec calls from your app into each supervisor with a bit more boilerplate. A supervisor is a behavior of a GenServer, which I map to something like traits in PHP. Since you want to manage dependent processes, it makes sense to group them as a supervisor tree and when your dependency crashes you likely want to re-initialize some state for it.

I think you understand quite a bit but it helps to put theory to practice. You could spike on a throwaway branch and try different strategies or groupings until it “feels right”. You could also look at all the other children your app handles. The docs for supervisors Supervisor — Elixir v1.17.2 has examples and is pretty comprehensive. The DockYard Academy section on supervisors and the drills are a great as well curriculum/reading/supervisors.livemd at main · DockYard-Academy/curriculum · GitHub.

2 Likes

Children are started in the order you list them. Once one returns from init, the next one starts.

5 Likes

Wait really?? Omg that was the missing piece!! I thought they were started in parallel or in some unspecified order!

Thanks a lot for this!

1 Like

It sounds like you’re pretty well sorted on the questions you had, but reading your post I think that I’d recommend not having a separate process for the MsgTransformer, and instead convert that into a plain module that is used by the frontend process. My reasoning is that it sounds like the MsgTransformer process is being used for code organization rather than isolation/resilience (plus adding a process always adds performance overhead). If you haven’t already, I highly recommend reading Saša Jurić excellent blog post on this topic: The Erlangelist - To spawn, or not to spawn?

6 Likes