chgeuer
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?
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #blog-post
- #elixir-ls
- #ai
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming











Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
hauleth
Maybe store state in shared ETS table?
jola
Communicating with genservers (and agents) can also be done by looking them up in a Registry or naming them.
The Agent documentation has a named agent as its first example Agent — Elixir v1.20.2
chgeuer
Thanks for the pointers to ETS and named processes.
Hi @hauleth, given that the state is for a specific sign-in operation, I don’t want it to live “globally” in ETS, but co-located to the sign-in process.
Hi @jola, given that there are multiple concurrent sign-in operations, it doesn’t feel right to give the agent a “name”, because it’s only dedicated to a specific process.
idi527
You might also be able to use a “single” process with supervised / retried http requests. Thus when the polling fails, it wouldn’t crash the genserver. You can keep the pid of that genserver in a registry under the device code.
jola
Right, but a Registry might still work, take a look at using via tuples (
{:via, module, name}) to both register and call/cast. If an operation has any kind of ID, a request ID or whatever, you would be able to use that to dynamically register processes and look them up. You can even pass the via tuple as an argument toGenServer.calletc.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:
Now you can add
Authenticatorto 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.chasers
+1 for naming them.
peerreynders
a
:publictable can only be accessed “globally” if it is named - otherwise any process accessing it has to somehow have to get ahold of the table ID. So it isn’t uncommon for a supervisor to create a public table for one of its child processes and hand the child process the table id. Then each time the process is restarted it takes over the existing table.with protected tables you can play the
heir-give_awaygame. A simple owner process transfers ownership to a requesting process but gets it back when that process dies.Demo script:
Demo session:
Public table:
Protected “heir” table:
The above is for simple demonstration only as not all edge cases are covered.
chgeuer
Yesterday I played a bit with the idea. I’m currently not using ETS, just on Supervisor, GenServer (as worker) and Agent. Essentially, the job of the worker is to increment a counter. The public API is implemented in the SuperVisor.
WorkerSupervisorstarts the Agent and theWorker(and passes it’s own SuperVisor PID to the Worker).Workerdiscovers (through the Supervisor) which is it’s associated State Agent process.Workercontinuously overwrites the state in the Agent (upon state changes). Only single-directional writes fromWorkerto Agent.Workerfetches the current state from the Agent.It would be interesting to hear your opinions on that approach.
tomekowal
In my opinion, you are not far from a perfect solution, but there is room for improvement.
a) agent can exist when the worker dies, but worker can’t exist without the agent: this begs for
:rest_for_onestrategyb) extracting the agent PID every time seems a little bit inefficient, it would be better to get the Agent PID once and store in a worker as its state.
A way to perform b) is this:
I hope you get what I mean
I would leave Supervisor alone and put the logic inside
WorkerandState. The public API would be in the Worker module.There are of course other options. You’ve assumed that HTTP calls are potentially crashing, but most HTTP libraries don’t throw exceptions but return
{:error, reason}. It might be OK to keep the state and work in one process as @idi527 suggested. It is also very common to keep state in ets tables as @peerreynders suggested and usually those are created in the supervisor.