I am able to spawn a process from the GenServer; however, I’m struggling to connect between the GenServer and process, so the process can call the GenServer; in return, the GenServer can handle_call/3.
Questions:
Am I taking the right approach of calling a process from a GenServer? Is there better way to create process from GenServer and notify the process about the state?
How do I call GenServer from a process and passing the state to server?
This sounds a lot like the Task functionality in the Elixir standard library. Here’s some relevant discussion from 2018, complete with a code example:
The tricky part with spawning additional processes is making sure everything works sensibly when things go wrong - what happens when those processes shut down, when the parent shuts down, etc. The Task library wraps the relevant parts (linking, supervising and so on).
Even though this code might work for your current use case you have to consider what happens if one of the tasks crashes.
With this code, if one task crashes, your GenServer will also crash with your state because the Task is still sending the GenServer a message that is currently not handled. You can handle them as follows:
def handle_info({:EXIT, _task, _reason}, state) do
IO.puts("Crashed!!!!!")
{:noreply, state}
end
def handle_info({:DOWN, _ref, :process, _pid, reason}, state) do
IO.inspect(reason)
{:noreply, state}
end
The first message will have the tuple {:EXIT, task, reason}. The other one is {:DOWN, _ref, :process, _pid, reason}. You already have a tuple similar to this one but handles the happy path when the task exits normally like this {:DOWN, _ref, :process, _pid, :normal}… note the :normal atom at the end will be pattern matched.
With this in place, your GenServer will not crash and will be available to handle other tasks.
So this might work but: I just wanted to share with you some recommendations @whatyouhide gave us during this weeks ElixirConfLA in Medellin.
He recommended us:
Limit the amount of tasks that can be spawned to avoid exhausting the resources
Use the proper strategy for your use case:
a. one_to_one
b. one_for_all
c. rest_for_one
So what happens if one of the task fails? is it worth continue processing the other tasks? how will your state be affected if one task fails?
Nest you supervision tree with other supervisors with the proper strategy. For example, under your main application supervisor, create a worker supervisor with a one_for_one strategy.
All processes should be supervised
Name your supervisors to access them by name which is easier.
Test you supervision tree
I will be monitoring when the talks are published to link it to this thread.
So we are pattern matching the tuples with :EXIT and :DOWN one with the :normal atom and the other one with any reason (you need to pattern match all the cases). The normal case will match when the task finishes successfully without crashing and the other one when it crashes for any reason.
As always, depends. If you are having lots of users request to a single GenServer, it might become a bottleneck and you might be better of spawning a GenServer per user request. If that is not the case, you might be already set. Another question: what happens to the state if two users send tasks to the GenServer at the same time? should the state be affected by both user’s tasks? or do you need to have different state for each user request… if this is the case, you might need to spawn a GenServer per user request which will also spawn tasks async.
Well Andrea recommended this site: https://github.com/ferd/sups … but he also said it was a bit hard to implement. Having said that, you could use the Observer (in iex session, call it by " :observer.start() ") to see your supervision tree and kill the process to see how is it working.