venomnert

venomnert

Context:

I am pretty new to OTP. In trying to learn it I’m creating an application that does the following (high level overview):

  1. users submit X number tasks to a GenServer.
  2. The GenServer iterates over the queue of tasks and creates a process for each task.
  3. Once the process is completed it will notify the GenServer it’s done and return its value.
  4. Once the GenServer get’s notified it will update the state with the process’s return value, and notify the client with the updated state.

The diagram demonstrates what I’m trying to achieve.

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:

  1. 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?
  2. How do I call GenServer from a process and passing the state to server?

Showing Posts 1 to 6

al2o3cr

al2o3cr

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).

venomnert

venomnert OP

I believe this is what I needed, thank you @al2o3cr! I’m going to give @joaquinalcerro implementation a shot.

joaquinalcerro

joaquinalcerro

@venomnert,

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:

  1. Limit the amount of tasks that can be spawned to avoid exhausting the resources

  2. 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?

    Check this link for details of each one: Supervisor — Elixir v1.20.2

  3. 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.

  4. All processes should be supervised

  5. Name your supervisors to access them by name which is easier.

  6. Test you supervision tree

I will be monitoring when the talks are published to link it to this thread.

Best regards,

joaquinalcerro

joaquinalcerro

You can also check this thread which has a complete example and explanation:

Best regards,

venomnert

venomnert OP

Note

I just wanted to say thank you for taking the time to providing me with extra detailed information.


My understanding

I notice the main difference between the handle_info in the other thread and the one you have mention on this thread is the following:

  1. Here we are accepting any reasons for :EXIT and :DOWN.
  2. In the other post we are accepting a specific case of :EXIT and :DOWN.

Questions

  1. However, I’m still unclear as to the reason why we are not pattern matching :normal?

  2. In my case is it better to have a single task that handles all of the user submitted tasks or is it better to have a task per user submitted tasks?

Limit the amount of tasks that can be spawned to avoid exhausting the resources

  1. Also can you point me towards a resource on testing tasks and supervisor tree?

Test you supervision tree

joaquinalcerro

joaquinalcerro

@venomnert,

  1. 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.

  2. 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.

  3. Well Andrea recommended this site: GitHub - ferd/sups: PropEr model helper library to validate implementations of supervisor trees · GitHub … 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.

Hope this helps.

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
psy-q
I’m trying to set up Emacs with elixir-ls via lsp-mode and credo via Flycheck. This should mostly be preconfigured as Flycheck picks up c...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews