Supervision Conventions/Questions

Hello there,

Been lurking for awhile, first time posting. I’ve checked for guidelines briefly, but please let me know if I should move this elsewhere, or format differently, etc.

Context

The last few days I’ve been toying around with Elixir and OTP. As a little project to learn with, I’ve been porting a turn based card game I wrote in Ruby to support multiplayer gameplay.

I have a bare bones version of it up and running but I’m looking for some feedback on the design.

Here’s a rough outline of the current iteration of my OTP application:

/application
  /Registry
  /GamesSupervisor (DynamicSupervisor)
    /GameSupervisor (Supervisor)
      /GameServer (GenServer) 
  /PlayersSupervisor (DynamicSupervisor
    /PlayerServer (GenServer)

At a super high level, as you can probably guess, the application starts up the Registry, GamesSupervisor and PlayersSupervisor.

The GamesSupervisor is responsible for creating new games via GamesSupervisor.create_game(<game_id>). This is done by starting a GameSupervisor, which starts a GameServer that gets registered using the Registry. The GameServer holds the state of the game (turn, board state, current choice, etc).

The GameServer exposes a join_game(<pid>, <player_name>) “call” function that tells the PlayersSupervisor to start a PlayerServer. Many players can join a game, and the PlayerServer is used for bidirectional communication between the game server and the individual players (e.g presenting choices and making decisions).

My high level goal is to isolate failure (in this case bugs in the game) to an individual game. For instance, if the game erroneously allows the player to choose an invalid choice (according to the game logic) it would not impact other games.


Question(s)

DynamicSupervisor Placement

The first thing I’ve gotten hung up on is where the PlayersSupervisor should go in the hierarchy. My intuition tells me it should be a child of GameSupervisor because the PlayerServer processes that it ends up starting are conceptually part of the game?

However, it’s not clear to me if there is a benefit to nesting the dynamic PlayersSupervisor under GameSupervisor. I’ve not come across any examples of folks having multiple instances (processes) of a dynamic supervisor. Seems possible, just requiring a more dynamic process name to avoid the “already started” error.

Registry Usage

Is it common to use the registry to registry multiple types (for lack of better word) of processes or would you create multiple named registries? For example, registering processes that represent my GameServer alongside PlayerServer within the same registry.

External Interface

From the reading I’ve been doing, it seems common to expose a function on your supervisor as the entry point (e.g GamesSupervisor.create_game(<game_id>).

While this makes sense to me, I’m currently planning on keeping the GamesSupervisor interface small. Instead, I’m planning to interact with the GameServer and PlayerServer processes directly, relying on their processes being locatable via the registry.

So I guess my question is around convention; do folks tend to use the supervisor as the interface to dispatching to the processes it supervises? Maybe this is just personal preference?

1 Like

This is the wrong way to go about it. When designing your supervision tree, you need to think about error isolation, runtime guarantees, etc. So the better question would be: if a player process keeps crashing, should it bring down the game process? Maybe that’s what you want to happen, or maybe you just want to display a “waiting for player to reconnect” message to other players. This was extremely helpful for me to better understand this approach: https://ferd.ca/the-zen-of-erlang.html

Is it common to use the registry to registry multiple types (for lack of better word) of processes or would you create multiple named registries?

Both are fine, pick what fits your situation best.

I’m planning to interact with the GameServer and PlayerServer processes directly, relying on their processes being locatable via the registry

This is also fine. You just want to make sure you’re not relying on static info (e.g. a pid), as if the process crashes and restarts the pid will be different and you won’t be able to reconnect. Using (e.g.) a combination of Registry and a :via tuple should be fine, though.

1 Like