How to properly reschedule a job in GenServer if there's an error?

A simple GenServer, sends http requests, gets rescheduled by Pocess.send_after(...)

  @impl true
  def handle_info(:fetch_data, state) do
    update_data()   # ok | {:error, err}
    reschedule()     
    {:noreply, state}
  end

How to properly account for an error in an http request – update_data() ? I handle it inside the function, but what about the inverval of rescheduling? Should I call reschedule() from within update_data(...) and with a smaller interval?

Your questions seem related to business requirements and not to implementation details so not really sure why you’re asking.

What’s the problem you are solving for here?

Also you could show us the source of both of the functions.

How much are you sure about what I’m asking then?

This thread will only go somewhere if you clarify your original post further. The question you figured you’ll shoot back at me doesn’t progress anything.

3 Likes

What is the initial state of the GenServer?

Empty, and it’s not used. update_data(...) updates data in the Db.

Since the GenServer state is unused and since the handle_info function does not reply any response to the calling process, the only thing you can do with errors is log them for yourself.

As for rescheduling, it does not matter if you do it in the update_data/0 function or if you do it using Process.send_after/4 in the GenServer. You can see an example of Process.send_after/4 in the GenServer docs GenServer — Elixir v1.12.3

It looks like you are doing periodic updates by having the handle_info calling reschedule (I assume it will call Process.send_after(self(), :fetch_data, @some_interval)). Now if you want to have another reschedule() call on some error condition, you may end up with more than one :fetch_data loop. It is not so bad if the update_data() call is idempotent, however, consider the case when the error condition persists for a while, you may end up with an unbounded number of loops.

You haven’t understood the question. What matters isn’t “where”, but “how” and “properly so”.

Correct. How then would I reschedule it all properly?

Also, after N unsuccessful attempts, with progressive delay, GenServer might have to be stopped.

If you want this sort of job scheduling semantics I would consider a dedicated job library like Oban. This splits the “runtime” execution responsibilities which are handled with internal genservers from the state of the job lifecycle itself, which is managed in a database and ensures you get the retry guarantees you want across node reboots and so forth.

The issue with answering this question is that “proper” is context dependent. Retrying a message push to a genserver that is associated with say a websocket client is significantly different than retrying a background job that represents say pushing a password reset email. You have to define the constraints that constitute “proper” for you, and then we can answer more precisely.

1 Like

In my case it’s more like pushing a password reset email.

In that case I would use Oban if you are already using Postgres.

If you store the next delay in the GenServer state you can easily do progressive delay. If the next delay is larger enough you are still failing you can quit the GenServer. All functionalities you need is included.

1 Like

My question is about GenServer itself.

I’d have to save a delay in a state, right?

I am not sure what do you mean. If you want the delay to be dependent on the pass/fail status of the current result and the last delay (progressive delay), then the last delay would need to be stored somewhere. The GenServer’s state is a natural place.