Running some job at periodic interval when live view mounts

Hi,

I am working on an application that mainly handles real-time data and user interaction. We have used live_view for it and are currently migrated to the latest live_view 20.0.

With the new live_view, I am delighted to have out-of-the-box solutions like assign_async, start_async, or stream. These new functions made our code base clean and elegant.

However, I have one very frequent case where we have to check our database periodically at intervals for some data updates and change our view accordingly after live_view mounts. So, it doesn’t make sense to make a dedicated Genserver for this case and publish messages when data changes.

For example, some other application is inserting some rules in the database. And based on that rule, my live_view needs to be updated. As insertion is done from another app, I cannot receive that update with Phoenix.PubSub and I also do not want to use any external PubSub for this case.

So, currently, we do something like the following.

def mount(_, session, socket) do

timer_ref = Process.send_after(self(), :check_changes, 2000)

socket =
  socket
  |> assign(:timer, timer_ref)

{:ok, socket}
end

def handle_info(:check_changes, socket) do
# check timer_ref, and clearout from assigns, if there, 
Process.cancel_timer(socket.assigns.timer_ref)

# code related to checking
Process.send_after(self(), :check_changes, 2000)
{:noreply, socket}
end

It works kinda well if the check_changes handle doesn’t take too much time to process…otherwise, UI becomes laggy as the live_view process handles large operations.

So, I am thinking of some functions that can run parallelly with the live_view process at an interval and send updates to live_view, and live_view can handle that with handle_async as it does it for the start_async function.

Do you have any thoughts or better ideas, or does this kind of solution already exist? if not then I will implement this myself with the same logical pattern as start_async.

I’d keep the timer logic in the LV and on tick call start_async. That will eventually call handle_async with the results, which you can then assign to update the LV. There’s no real use to put the timer into an external process and fetching updates can be done quite well with start_async.

1 Like

Well, if we call start_async multiple times with a process timer, how can we ensure that the previously started job is completed and that we are not respawning when the previous process is still running?

I do not like to keep a process timer inside LV; I think LV should be free from such tasks and only deal with UI interactions. With our current implementation, we have multiple process timers in an LV doing different jobs, and this thing does not scale up well, as developers sometimes ignore the consequences and add some heavy tasks inside the LV process.

I will be happy to have more opinions.

You could start the timer again only after the previous async task completed.

But the UI is what requires the timers to run. The timers itself are already in handled in an external process. I don’t think adding more processes to the setup would improve things.

Trying to solve people problems with technical solutions rarely goes well.

1 Like

Great explanation and solution. Thanks a lot.