markmark206

markmark206

I would like to perform an action periodically, and I am looking for the simplest reasonable way to do this.

As an example, let’s say I want to delete old entries from a db table, every few hours. The action is idempotent, relatively inexpensive, precision “doesn’t matter,” and concurrent executions of multiple runs (e.g. from multiple replicas of the service) is not a problem (db transactions will handle them safely).

A commonly recommended approach for doing this seems to be a variation of using a GenServer with send_after (or, I suppose, spinning up an oban job;).

This makes a lot of sense, but I coded up just running an infinite supervised “do”+“sleep” recursion, and it seems to work, and it seems ridiculously concise and simple, and I can’t explain to myself why that wouldn’t be enough.

Is there any reason why I shouldn’t do this? ; )

If you have any thoughts / guidance on this, I would much appreciate them!

Thank you!

PS An example of what this might look like in code:

application.ex (starts the task, restart: :permanent):

defmodule MyApp.Application do
    def start(_type, _args) do
        children = [
           ...,
           Supervisor.child_spec(
               {Task, fn -> MyApp.Sweeper.delete_old_data(sleep_ms) end},
               restart: :permanent
           ), 
           ...
        ]
        Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
    end
end

where delete_old_data() just keeps doing the thing and sleeping, forever:

defmodule MyApp.Sweeper do
    def delete_old_data(wait_ms) do
        ... delete old things ...
        Process.sleep(wait_ms)
        delete_old_data(wait_ms)
    end
end

Showing Posts 1 to 10

kokolegorille

kokolegorille

You are not supervised if You do so… what happens if delete old things fails?

UPDATE: Yes You are… sorry didn’t read your code properly :slight_smile:

al2o3cr

al2o3cr

What happens when this needs to shut down? I assume it will snooze right through the first round of “polite” notifications from its supervisor…

markmark206

markmark206 OP

Yes, this is exactly what I am curious about – can my solution be more lightweight than requiring introducing a dependency (especially as rich as oban, with its own database dependency and schemas, etc.).

I don’t need persistence for my “job” (which is part of what Oban provides) I just need to call a function every once in a while, for as long as my application is running.

Can a simple BEAM process (with receive after) and a supervisor be sufficient?

markmark206

markmark206 OP

What happens when this needs to shut down? I assume it will snooze right through the first round of “polite” notifications from its supervisor…

This is a good point, but does it matter?

It seems like the process will be killed within 5 seconds (I am just using the default :shutdown value according to Supervisor — Elixir v1.14.3 ), which seems fine – I am obv only using delete_old_data() for its side effects, and I do want the process to disappear (whether it is sleeping or asking the db to do the pruning) when the application shuts down.

Is there any reason I should care?

cmo

cmo

I would use a GenServer at least so you have it in a file somewhere and not in application.ex. Not sure what you’d gain from going the task or process method? Are you trying to reinvent a wheel or save some LOC? It certainly makes it more work to extend and hides the code away.

If you had oban as a dep already that would probably be the right choice.

tfwright

tfwright

Not sure what you’d gain

Well, he did say he was aiming at simplest, and I’d actually tend to agree that a module not importing a separately defined behavior is a bit simpler…at least in principle? I’m also interested in anything more substantial he might lose though by starting here.

For myself I usually start with Quantum unless I know I’m going to need persistence. I do always know I am going to need scheduling.

kwando

kwando

It certainly works like you did but I think it is but I is more idiomatic to keep the details out of the application file. The very least you could do is to put the child_spec/1 inside the MyApp.Sweeper module…

defmodule MyApp.Application do
    def start(_type, _args) do
        children = [
           ...,
            MyApp.Sweeper, 
           ...
        ]
        Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
    end
end

defmodule MyApp.Sweeper do
  def child_spec(_) do
     Supervisor.child_spec(
       {Task, fn -> MyApp.Sweeper.delete_old_data(sleep_ms) end},
         restart: :permanent
       )
   end
end

but really I think a standard GenServer + send_after / :timer.send_interval is the way to go for this… easier to read and understand :slight_smile:

defmodule MyApp.Sweeper do
   use GenServer
   def init([]) do
     :timer.send_interval(self(), :timer.hours(5), :delete_old_data)
     {:ok, []}
   end

  def handle_info(:delete_old_data, state) do
     # delete old data here, or spawn a Task doing it to keep the Sweeper responsive
   {:noreply, state}
  end

  def start_link([]) do
    GenServer.start_link(__MODULE__, [])
  end
end
Sebb

Sebb

https://github.com/quantum-elixir/quantum-core

seems a great fit.

KristerV

KristerV

came here to say this. Quantum is both reliable and readable. custom solutions may be reliable (thanks to supervisors), but they are very annoying to read a few months down the line.

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
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
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
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
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

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
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
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
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