chgeuer

chgeuer

Supervision strategy for a stateful web client?

I wrote a small GenServer which performs a long-running web task (specifically device code authentication against an identity provider). When the server is launched, it fetches an initial value from the identity provider, and then regularly polls via HTTP for updates. So I have state (the values fetched initially), and a potentially crashing sequence of HTTP calls.

If both, state and the polling loop, are in the same gen_server, a failing HTTP call wipes away my state. So I understand I need to keep that state in an Agent, have a process for polling (which uses the PID of the Agent to store/update/fetch state), and an overall process which supervises the state Agent, and the polling worker.

               SUP
              /   \
             /     \
            /       \
        State <---- Worker
        Agent       polling

My question is: Where should the API be implemented for interacting with the whole thing? For example, I want to check if the authentication was successful, so I need to read values from the state. The client only has the PID of the overall supervisor, which in turn has the PID of the state agent. So when my client wants to see parts of the state, I need to ask the overall supervisor, which then forwards the call to the state agent.

Is that how it’s supposed to be?

Most Liked

keathley

keathley

I really think that you want to use names here instead of always going through the supervisor. This removes a bottleneck from your system and makes it easier to reason about crashes. I also want to add some nuance to what @tomekowal is saying about state.

Processes absolutely can crash for no reason. This is typically do to a supervisor being restarted due to an unrelated crash. For instance your worker and agent may be working fine but their supervisor is managed by a supervisor with an all_for_one strategy. If one of your supervisors siblings crashes then your worker and agent will be restarted as well. This is pretty rare but is worth keeping in mind.

But @tomekowal’s main point is correct. Most of the time a process will crash because it ends up in a bad state. When that happens it’s better to just allow the process to crash and come back in a good state.

The main issue with the way you’ve built your system currently is that the worker is responsible for pushing state into the agent. What that means is that if your worker state is bad than you’ll push bad state into the agent, and the agent will crash. This will continue happening repeatedly because the bad state is never being cleaned up. We aren’t allowing the agent to come back up in a known good state. These kinds of bugs crop up all the time especially when people intermingle persistence with their process state. The bad state is persisted somewhere, process crashes, process restarts and loads data into memory, next message it crashes again, etc.

What I try to do is to isolate my state to a single process as much as possible. I then send commands to that process and allow that process to update its own internal state. If something gets into a bad state the crash will be isolated and I can restart in a good state.

Here’s how I would re-write what you have so far: stateful_server example · GitHub

axelson

axelson

Scenic Core Team

I’d just like to mention a good blog post on state isolation. It introduces the idea of an “error kernel”:

Erlang programs have a concept called the error kernel . The kernel is the part of the program which must be correct for its correct operation. Good Erlang design begins with identifying the error kernel of the system: What part must not fail or it will bring down the whole system? Once you have the kernel identified, you seek to make it minimal. Whenever the kernel is about to do an operation which is dangerous and might crash, you “outsource” that computation to another process, a dumb slave worker. If he crashes and is killed, nothing really bad has happened - since the kernel keeps going.

keathley

keathley

I think @jola is correct here. You want to name these processes somehow in order to look them up. Otherwise when your polling process crashes it won’t have access to the existing state. So you can either use a registry or give them well defined names. Without knowing more about your problem I’d build something similar to this:

defmodule Authenticator do
  def child_spec(args) do
    children = [
      Worker,
      {State, name: args[:name]},
    ]

    %{
      id: __MODULE__,
      type: :supervisor,
      start: {Supervisor, :start_link, [children, [strategy: :one_for_one]]}
    }
  end

  def lookup(name) do
    Agent.get(name, & &1)
  end
end

Now you can add Authenticator to your supervision tree. When you need to look things up it’ll go through the authenticator module and the details of how things are stored and accessed can be hidden away. For instance if you decide to go the ETS route because you need concurrent reads then this will be encapsulated in Authenticator and your callers won’t have to change.

Where Next?

Popular in Questions Top

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
Kurisu
For example for a current url like http://localhost:4000/cosmetic/products?_utf8=✓&amp;query=perfume&amp;page=2, I would like to get: ...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lists...
New
tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New

Other popular topics Top

Nvim
Anybody knows a comprehensive comparison of Django and Phoenix, thanks for the help. Where are they similar? Where do they differ the m...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
Lily
In templates/appointment/index.html.eex: &lt;%= for appointment &lt;- @appointments do %&gt; &lt;tr&gt; &lt;td&gt;&lt;%= appoi...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

We're in Beta

About us Mission Statement