deerob4

deerob4

What is the right way to communicate between two GenServers?

I’m creating a game system, which includes a lobby and a countdown timer. Once enough players have joined, the countdown timer begins and upon completion tells the lobby to start the game.

Both are implemented as separate GenServers, registered using a via tuple in Registry. I’m a bit unsure of the best way for CountdownTimer to notify Lobby that the countdown has completed. I can think of a few different options:

  • Use Registry.lookup/2 to get the pid of the Lobby process, then send from the countdown timer to be handled in handle_info.

  • Add a countdown_completed/1 function to the lobby that takes the lobby id and calls GenServer.cast/2 to do whatever it needs. CountdownTimer can then call this upon completion. I’m not a fan of this because it seems to tie the two together a bit much.

  • Implement a pubsub mechanism using either Registry or Phoenix.PubSub so that the lobby can subscribe to the countdown completion event. This also has the advantage that my Phoenix channel could subscribe as well. This seems to be the most flexible solution, but I worry that it might be overkill.

I’d appreciate some advice on which method (or an alternative one) to go for. Thank you!

Most Liked

LostKobrakai

LostKobrakai

Imho processes should be separated based on their runtime behaviour (error separation and stuff like that) and not on “concerns” like you’d separate classes by. The lobby and the timer are entangled in their runtime behavior so in my opinion there’s no reason to not run them in the same process.

LostKobrakai

LostKobrakai

I’m wondering why you need two processes here in the first place. Couldn’t the lobby do it’s own countdown.

peerreynders

peerreynders

This is a valid question. The scope of the problem you have described could be solved with the Lobby process using Process.send_after/4 and simply process the “complete” msg value when it is returned via the handle_info/2 callback.

I’m not a fan of this because it seems to tie the two together a bit much.

Now lets say there is a good reason for the CountdownTimer process to exist - maybe because it somehow coordinates a UI display of the countdown timer.

You can take inspiration from send_after. So rather than defining Lobby.countdown_completed/0, you solve the problem in the CountdownTimer API when the countdown is initiated.

CountdownTimer.new_timer(completed_msg, time)

CountdownTimer will implement this as a call so it will get from for free.

  • it can either use send_after itself with a {from, completed_msg} message value
  • or store {from, completed_msg} in the process state for later

So when the time comes CountdownTimer simply uses GenServer.cast(from, completed_msg) to return the requested completed_msg value to the Lobby that requested the timer (which it then has to handle in its own handle_cast/2).

In this particular situation I think it’s fine as the CountdownTimer process really doesn’t care if the Lobby process has died. Furthermore the CountdownTimer could just slap a monitor onto each of its client processes so that it can cleanup their timers as soon as possible.

srowley

srowley

I have a simple auction application that implements a timer for bidding and I simply use Process.send_after/4 and handle_info/2, and store the timer with the rest of the auction state. My front end can make calls to the server as needed to get an updated timer value, and when the timer expires handle_info/2 receives the message, does some things and lets the front end know that the timer expired.

That works fine.

Because the auction server also needs to send a bunch of other messages to the front end I ended up implementing a PubSub type of thing later anyway, to which this approach fit in nicely since my application was already generating a message for timer expiration.

Honestly the most trying part of implementing that was thinking through the various values the timer could have (false, a number, nil) and handling them appropriately, because in my case the timer gets started/paused/reset frequently under different conditions.

peerreynders

peerreynders

Have a read through the following topic and the related article: To spawn, or not to spawn?

So, to summarize, in that post “thought concerns” refer to “I want to somehow organize a larger chunk of code, so it’s easier to work with”, while “runtime concerns” refer to “I want to get some observable runtime benefits, such as fault-tolerance, scalability, or potential for parallelism”.

Roughly (i.e. nothing is black and white):

  • thought concerns → modules
  • runtime concerns → processes

Also possibly:

I like this idea, but would it still work if the Lobby process is restarted for some reason?

The server argument for cast can be a name - so require that as an argument for new_timer.

The bigger issue is, how would a restarted Lobby process re-aquire the previous state (which may have been responsible for the last process crashing in the first place)? If the state is lost then the timer is meaningless.

I’m also not sure how it’s different

Coupling is significantly reduced.

CountdownTimer.new_timer(completed_msg, time) lets the client choose the exact format of the message to be handled by its own handle_cast/2 callback. And CountdownTimer isn’t coupled to the client module - not even at runtime.

Where Next?

Popular in Questions Top

LegitStack
I’m hoping you guys can give me some general advice and perhaps code examples if you’re feeling up to it. I’m very interested in Elixir,...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New

Other popular topics Top

vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 45766 226
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement