GenServer timeout - how do i handle this?

Hi all,

I watched this video here Learn Elixir Quickly: Part 2 - Gas Station API - YouTube

I tried to ask the video author in the comment section but no reply. So wonder anyone can assist me on this.

I noticed polygon api will have timeout more than eth.

example,

03:12:39.046 [error] #PID<0.357.0> running GasStation.Endpoint (connection #PID<0.355.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: GET /gas/polygon
** (exit) exited in: GenServer.call(GasStation.Fetchers.Polygon, :fetch, 5000)
    ** (EXIT) time out

and sometimes i see this

03:15:07.180 [error] #PID<0.388.0> running GasStation.Endpoint (connection #PID<0.387.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: GET /gas/polygon
** (exit) an exception was raised:
    ** (MatchError) no match of right hand side value: {:error, %HTTPoison.Error{reason: :timeout, id: nil}}
        (gas_station 0.1.0) lib/gas_station.ex:16: anonymous fn/2 in GasStation.Endpoint.do_match/4
        (gas_station 0.1.0) deps/plug/lib/plug/router.ex:246: anonymous fn/4 in GasStation.Endpoint.dispatch/2
        (telemetry 1.2.1) /Users/ericchua/Builder/codes/learn/elixir/youtube_zane/gas_station/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
        (gas_station 0.1.0) deps/plug/lib/plug/router.ex:242: GasStation.Endpoint.dispatch/2
        (gas_station 0.1.0) lib/gas_station.ex:1: GasStation.Endpoint.plug_builder_call/2
        (plug_cowboy 2.6.1) lib/plug/cowboy/handler.ex:11: Plug.Cowboy.Handler.init/2
        (cowboy 2.10.0) /Users/ericchua/Builder/codes/learn/elixir/youtube_zane/gas_station/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
        (cowboy 2.10.0) /Users/ericchua/Builder/codes/learn/elixir/youtube_zane/gas_station/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3    

Any idea how to handle this timeout issue better?

Thanks.

For httpoison timeout refer to: HTTPoison.Request — HTTPoison v2.1.0

For GenServer.call third argument defines call timeout in milliseconds

1 Like

One big issue: by default, GenServer.call has a timeout of 5 seconds. HTTPoison.get has a timeout of 8 seconds.

If a request to one of these services takes longer than 5 seconds, the Plug worker that sent the fetch request will time out and crash (your first example). But the fetcher will keep running.

If a request to one of these services takes more than 8 seconds, HTTPoison.get will return {:error, %HTTPoison.Error{reason: :timeout}} and the fetcher will cache that value for 30 seconds.

During that 30 seconds, further calls to that fetcher will return that same :error tuple immediately and crash in gas_station.ex’s handlers since they match on {:ok, gas_info} (your second example).

There’s also a hidden bug in those fetchers - if they ever get a non-200 HTTP status in a reply, they will still try to decode the body (and probably crash).

Some suggestions:

  • adjust the code in GasStation.Fetchers.GasServer.get to give GenServer.call a longer timeout, since a “normal” request could take up to 8 seconds

  • adjust the code in the fetchers to be smarter about handling errors - should timeouts be cached? Would it be better to cache the “last valid data”? What should happen if HTTPoison connects successfully but gets an HTTP error?

  • adjust the code in the router to not assume {:ok, gas_info} responses

1 Like

Thanks for sharing those suggestions.

I have worked on the fixes according to your suggestions.

Am I on the right track? any ways to improve my codes better?

Thanks.