david234
Task Supervisor with max_restart and max_seconds
I am working on a code that will ping an external API every 15 minutes, to retrieve the response and store it in the database.
def get_forecast(city) do
app_id = My.api_key()
query_params = URI.encode_query(%{"q" => city, "APPID" => app_id})
url =
"https://api.weather.org/data/2.5/forecast?" <> query_params
case HTTPoison.get(url) do
{:ok, %HTTPoison.Response{status_code: 200} = response} ->
{:ok, Jason.decode!(response.body)}
{:ok, %HTTPoison.Response{status_code: status_code}} ->
{:error, {:status, status_code}}
{:error, reason} ->
{:error, reason}
end
end
I am running this inside a Task, and that Task is running inside a Genserver.
In application.ex
use Application
def start(_type, _args) do
children = [
# Starts a worker by calling: FsiIntegration.Worker.start_link(arg)
# {FsiIntegration.Worker, arg}
{Task.Supervisor, name: Integration.TaskSupervisor, restart: :transient, max_restarts: 3, max_seconds: 4000}
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Integration.Supervisor]
Supervisor.start_link(children, opts)
end
In genserver file
defmodule Integration.FsiServer do
use GenServer
@timeout 15000
#Public API
def start_task do
GenServer.start_link(__MODULE__, %{ref: nil}, name: __MODULE__)
end
def execute_task(pid) do
GenServer.call(pid, {:execute, @timeout})
end
#Callbacks API
def init(state) do
{:ok, state}
end
# In this case the task is already running, so we just return :ok.
def handle_call({:execute, _task_timeout}, _from, %{ref: ref} = state) when is_reference(ref) do
{:reply, :ok, state}
end
# The task is not running yet, so let's start it.
def handle_call({:execute, task_timeout}, _from, %{ref: nil} = state) do
IO.inspect state
task =
Task.Supervisor.start_child(Integration.TaskSupervisor, fn ->**
{:ok, _} = IntegrationGet.get_forecast("city")**
end)
{:reply, :ok, %{state | ref: task.ref}}
end
# The task completed successfully
def handle_info({ref, answer}, %{ref: ref} = state) do
Process.demonitor(ref, [:flush])
{:noreply, %{state | ref: nil}}
end
# The task failed
def handle_info({:DOWN, ref, :process, _pid, _reason}, %{ref: ref} = state) do
{:noreply, %{state | ref: nil}}
end
end
My questions are :
I know I have to improve on the below code. Can you guys me insight on how to make this better
use Application
def start(_type, _args) do
children = [
# Starts a worker by calling: FsiIntegration.Worker.start_link(arg)
# {FsiIntegration.Worker, arg}
{Task.Supervisor, name: Integration.TaskSupervisor, restart: :transient, max_restarts: 3, max_seconds: 4000}
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Integration.Supervisor]
Supervisor.start_link(children, opts)
end
def handle_call({:execute, task_timeout}, _from, %{ref: nil} = state) do
IO.inspect state
task =
Task.Supervisor.start_child(Integration.TaskSupervisor, fn ->
{:ok, _} = IntegrationGet.get_forecast("city")
end)
{:reply, :ok, %{state | ref: task.ref}}
end
-
The time interval between restarts I have set max_seconds to 5 seconds. But the restart happens immediately.
-
Is Task.Supervisor.start_child is good for making an external API request. Just it will make a single API request in this case. Or I have to go for async_nolink or async under Task module
My use case is request an external API and store the response in the DB.
Most Liked
kwando
The max_restarts and max_seconds options only states how many (max_restarts) restarts of a child in a time period of time (max_seconds) the supervisor should tolerate before crashing.
You can read about max_seconds here: Supervisor — Elixir v1.20.2.
- I would start with something simpler like this:
defmodule WeatherPoller do
use GenServer
def start_link([]) do
GenServer.start_link(__MODULE__, [])
end
def init([]) do
send(self(), :execute)
{:ok, []}
end
def handle_info(:execute, state) do
case IntegrationGet.get_forecast("city") do
{:ok, response} ->
store_response(response)
{:error, error} ->
handle_error(response)
end
{:noreply, schedule_next(state)}
end
defp schedule_next(state) do
Process.send_after(self(), :execute, :timer.seconds(15))
state
end
end
LostKobrakai
sleep will block the process from handling anything else in the meantime. It’s usually better to use sent_interval to receive another message at a later time to trigger the restart, but staying responsive to other messages.
Popular in Discussions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








