Does Phoenix have a timeout on Genserver calls?

Background

I have a Phoenix LiveView project that calls a dependency. Sometimes this call is fast, other times it takes up to a couple of minutes.

When the call is fast (lets say, 10 seconds) Phoenix recieves the answer and all my variables in assigns are updated correctly.

However, for the case where the call to the external service takes a couple of minutes, the app freezes (which is expected since I am performing a call, which is blocking) and then when it resumes, nothing happens.

No error is shown, and none of my variables in assigns are updated. This is very confusing. It will still happen, even when the call to the external service was successful.

Code

This is the code I have in my Phoenix project application.ex:

def start(_type, _args) do
    children = [
      Telemetry,
      {Phoenix.PubSub, name: WebInterface.PubSub},
      Endpoint,
      Manager #3rd party dependency
    ]

    opts = [strategy: :one_for_one, name: WebInterface.Supervisor]
    Supervisor.start_link(children, opts)
  end

This is the default boilerplate auto-generated code with my 3rd party dependency Manager added as a child.

Now, my Phoenix app calls Manager, and inside of manager magic happens. This magic calls other GenServers which then reply when the are done working.

Basically, when a user clicks a button, I call Manager.do_something() which may or may not take quite a while.

Current Theory

My current theory here, knowing that Manager does a Genserver.call inside, is that some code in Phoenix, for some reason, decided that 2 minutes is too long and then times out.

This is supported by the fact that if I make the call to Manager quick (by changing the code and returning a dummy value) then everything works as expected.

Questions

I am not familiar with Phoenix, so this means I am probably missing something:

  • Is there a default timeout for genservers that I am not aware of?
  • How can I change this behaviour, so my Phoenix app waits for the result and updates the assigns variable as expected?

Yes, the default is 5 seconds:
https://hexdocs.pm/elixir/GenServer.html#call/3

While you can tweak the default, I highly recommend to rethink your architecture. You are in for a lot of pain for unresponsive GenServers.

3 Likes

There’s also timeouts within LV specifically. Generally you want to take any long running request in a LV to be async to the LV process.

2 Likes

So, as a rule of thumb, what would be the time limit you recommend a synchronous blocking operation takes? No more than 10 seconds? (a couple of retries for LV?)

I’d say everything > 1 sec is already slow (maybe besides form submits). LV is backing UI, so responsiveness is on a whole different timescale.

3 Likes

I’ll echo @LostKobrakai Anything over 1 second except in really pathetic cases is too slow for synchronous call in a GenServer, including LiveViews. You can use a task to update the LV in the end. If the process is taking more than a few seconds, you would want to update the LV with progress as well.

So, if I have an HTTP call that takes a couple of seconds, I should also make that async?
Seems like a lot of work for a single HTTP call. Am I missing something? Does LV have some feature that makes this easy?

The naive version is extremely easy, check out the Task module.

def handle_event("make_req", ..., socket) do
  Task.async(fn ->
    HTTP.request(...)
  end)
  # ...
end

def handle_info({_taskref, %HTTP.Response{} = res}, socket) do
  ...
end
2 Likes

For a more complete example: Task — Elixir v1.13.4

1 Like

So, in a typical Phoenix LV app, you will have GenServers talk to your 3rd party services, and do the communication asynchronously that way. This is very interesting, I didn’t see this coming for sure, not in any of the resources I read.

Thanks !

Not for every genserver, but if a process needs to stay responsive – like a LV process – then any heavy work should be done elsewhere.

1 Like

I’d say that your application should not depends on 3rd party HTTP API for interactive functionalities (one way web hooks are ok). But if you must, you can use a Task.