vshesh

vshesh

I have a situation where I have a pubsub, and a process receiving events from that pubsub.
I want the receiving process to update some state. That’s pretty easy with a genserver or an agent.
Then, I want another process to “sample” the process with state every second and do something.

What is the best way to accomplish this? Agent + Task?

Does that change if I want to modify the schedule at which the sampling is happening over time? (so the sampler can receive an event that changes the frequency of sampling?)

In Rx I would use a behavior and sample it with rx.sample - anything like that here welcome.

And what is the best way to expose this to the rest of my app? Should I make a mini-supervisor tree with both of these processes since they’re linked (if the receiving process goes down, no sense having the sampling process). How do I make it possible for the supervision tree to treat both of these processess together as a “single process” that can be shut down together?

Showing Posts 13 to 4

thoughtarray

thoughtarray

Hey all and future search engine visitors (like me). I wanted to mention something I feel was missed here. In case it matters to your program, you can ensure that your initial :tick message is sent after GenServer’s event loop is started by using the handle_continue/2 callback.

@impl true
def init(state) do
  {:ok, state, {:continue, :init}}
end

@impl true
def handle_continue(:init, state) do
  send(self(), :tick)
  {:noreply, state}
end
kokolegorille

kokolegorille

A state machine is also a good solution.

derek-zhou

derek-zhou

If you need to go fancy with timers, updating timers, postpone events, etc, you can checkout :gen_statem in erlang. It is basically a GenServer with lots of bells and whistles.

kokolegorille

kokolegorille

I would even go further…

  def init(_) do
    # compute init state
    period = 1_000
    ref = Process.send_after(self(), :tick, period)
    {:ok, %{period: period, tref: ref}}
  end

You can pause, resume, update the frequency rate…

derek-zhou

derek-zhou

Just send yourself a message using send_after then from the handler broadcast your state:

 def init(_) do
    # compute init state
    Process.send_after(self(), :tick, 1000)
    {:ok, state}
  end

def handle_info(:tick, state) do
    Phoenix.PubSub.broadcast(My.PubSub, "sample", {:sample, state})
    Process.send_after(self(), :tick, 1000)
end
kokolegorille

kokolegorille

It’s more common to use a map than a tuple for state…

Then it is easier to make pattern match…

%{chord: chord, tref: tref}
#
# You can pattern match nil tref with...
%{tref: nil}
# instead of 
{_, nil}
# and potentially, it's harder to remember where tref is.
{_, _, _, nil, _, _}

It’s also possible to do it with tuples, but it will be easier to pass from map to struct, than from tuple to struct. Unless You need to interface some Erlang record type, the Elixir way is to use a map.

The idea is to write a functional core, separate from any server logic.

BTW You can also replace 60000 with 60_000, it might be more readable.

kokolegorille

kokolegorille

It is also the case for Process.send_after, it returns a ref. And it is possible to read the elapsed time with Process.read_timer(ref), or cancel timer. I also store the ref in the server state.

Both are doing the same, but not the same way. I don’t use :timer module because it can get overloaded.

From Common Caveats — Erlang System Documentation v29.0.2

Creating timers using erlang:send_after/3 and erlang:start_timer/3, is much more efficient than using the timers provided by the timer module in STDLIB.

vshesh

vshesh OP

Not sure how to do that - the intention to do this resulted in timer, genserver, message etc.
I receive a message, then update some internal state. the “poller” or “sampler” is acting on that updated state. I’m just using one module now and I spawn the timer from there directly. Could you show how you would make it simpler?

derek-zhou

derek-zhou

Since you are already using a PubSub, why don’t you just broadcast from the first GenServer every second using a different topic and let whatever interested party subscribe to it.

If you have to do polling, I’d skip GenServer, timer, sending yourself a message, etc, in the poller altogether, and just spawn off a plain process that sleeps periodically.

vshesh

vshesh OP

This is what I went with:

  @doc """
  :param tempo: integer, beats per minute (# of times chord plays per minute)
  """
  def init(tempo) when is_integer(tempo) do
    Phoenix.PubSub.subscribe(:inputs, "dots")
    {:ok, {nil, generate_tick(tempo}}
  end
  def handle_cast({:tempo, tempo}, {chord, tref}) do
    :timer.cancel(tref)
    {:noreply, {chord, generate_tick(tempo)}}
  end
  defp generate_tick(tempo) do
    {:ok, ref} = :timer.apply_interval(trunc(60000/tempo), __MODULE__, :play, [self])
    ref
  end

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
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
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
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
ryanwinchester
apply_graft/2 doesn’t rewrite an add_many sub-workflow’s deps on an add step. Grafted jobs cancel with “upstream job was deleted” Version...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
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
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
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
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews