farmbot_os/farmbot_core/lib/regimen/supervisor.ex

186 lines
6.0 KiB
Elixir

defmodule Farmbot.Regimen.Supervisor do
@moduledoc false
use Supervisor
alias Farmbot.Asset
alias Asset.PersistentRegimen
alias Farmbot.Regimen.NameProvider
require Farmbot.Logger
@doc "Debug function to see what regimens are running."
def whats_going_on do
IO.warn("THIS SHOULD NOT BE USED IN PRODUCTION")
prs = Asset.all_persistent_regimens()
Enum.map(prs, fn %PersistentRegimen{regimen_id: rid, farm_event_id: fid, time: start_time} =
pr ->
r = Farmbot.Asset.get_regimen_by_id!(rid, fid)
server_name = NameProvider.via(r)
pid = GenServer.whereis(server_name)
alive = if pid, do: "is alive", else: "is not alive"
state = if pid, do: :sys.get_state(pid)
info = %{
_status:
"[#{r.id}] scheduled by FarmEvent: [#{fid}] #{Timex.from_now(start_time)}, #{alive}",
_id: r.id,
_farm_event_id: r.farm_event_id,
pid: pid,
persistent_regimen: pr
}
if state do
timezone = Farmbot.Asset.device().timezone
next = state.next_execution
timer_ms = state.timer |> Process.read_timer() || 0
from_now = Timex.from_now(next, timezone)
next_tick = Timex.from_now(Timex.shift(Timex.now(), milliseconds: timer_ms), timezone)
state = %{
state
| regimen: %{
state.regimen
| regimen_items: "#{Enum.count(state.regimen.regimen_items)} items."
}
}
Map.put(info, :state, state)
|> Map.put(:_next_execution, from_now)
|> Map.put(:_next_tick, next_tick)
else
info
end
end)
end
@doc "Stops all running instances of a regimen."
def stop_all_managers(regimen) do
Farmbot.Logger.info(3, "Stopping all running regimens by id: #{inspect(regimen.id)}")
prs = Asset.persistent_regimens(regimen)
for %PersistentRegimen{farm_event_id: feid} <- prs do
reg_with_fe_id = %{regimen | farm_event_id: feid}
name = NameProvider.via(reg_with_fe_id)
case GenServer.whereis(name) do
nil ->
Farmbot.Logger.info(3, "Could not find regimen by id: #{reg_with_fe_id.id} and tag: #{feid}")
regimen_server ->
GenServer.stop(regimen_server)
end
Asset.delete_persistent_regimen(reg_with_fe_id)
end
end
@doc "Looks up all regimen instances that are running, and reindexes them."
def reindex_all_managers(regimen, time \\ nil) do
prs = Asset.persistent_regimens(regimen)
Farmbot.Logger.debug(3, "Reindexing #{Enum.count(prs)} running regimens by id: #{regimen.id}")
for %{farm_event_id: feid} <- prs do
reg_with_fe_id = %{regimen | farm_event_id: feid}
name = NameProvider.via(reg_with_fe_id)
case GenServer.whereis(name) do
nil ->
Farmbot.Logger.info(3, "Could not find regimen by id: #{reg_with_fe_id.id} and tag: #{feid}")
regimen_server ->
if time do
Asset.update_persistent_regimen_time(regimen, time)
end
GenServer.call(regimen_server, {:reindex, reg_with_fe_id, time})
end
end
end
@doc false
def start_link(args) do
Supervisor.start_link(__MODULE__, args, name: __MODULE__)
end
def init([]) do
prs = Asset.all_persistent_regimens()
children = build_children(prs)
opts = [strategy: :one_for_one]
supervise(children, opts)
end
def add_child(regimen, time) do
regimen.farm_event_id || raise "Starting a regimen process requires a farm event id tag."
# Farmbot.Logger.debug 3, "Starting regimen: #{regimen.name} #{regimen.farm_event_id} at #{inspect time}"
Asset.add_persistent_regimen(regimen, time)
args = [regimen, time]
opts = [restart: :transient, id: regimen.farm_event_id]
spec = worker(Farmbot.Regimen.Manager, args, opts)
Supervisor.start_child(__MODULE__, spec)
end
def stop_child(regimen) do
regimen.farm_event_id || raise "Stopping a regimen process requires a farm event id tag."
name = NameProvider.via(regimen)
case GenServer.whereis(name) do
nil ->
Farmbot.Logger.info(
3,
"Could not find regimen by id: #{regimen.id} and tag: #{regimen.farm_event_id}"
)
_regimen_server ->
Farmbot.Logger.debug(3, "Stopping regimen: #{regimen.name} (#{regimen.farm_event_id})")
Supervisor.terminate_child(Farmbot.Regimen.Supervisor, regimen.farm_event_id)
Supervisor.delete_child(Farmbot.Regimen.Supervisor, regimen.farm_event_id)
end
Asset.delete_persistent_regimen(regimen)
end
@doc "Builds a list of supervisor children. Will also delete and not build a child from stale data."
@spec build_children([%PersistentRegimen{}]) :: [Supervisor.child_spec()]
def build_children(prs) do
Enum.reject(prs, fn %PersistentRegimen{regimen_id: rid, farm_event_id: feid} ->
reg = Asset.get_regimen_by_id(rid, feid)
if Asset.get_farm_event_by_id(feid) && reg do
_rejected = false
else
Farmbot.Logger.debug(
3,
"Deleting stale persistent regimen: regimen_id: #{rid} farm_event_id: #{feid}"
)
# Build a fake regimen to allow the deletion of the persistent regimen
# if reg above is nil.
backup = %Farmbot.Asset.Regimen{
farm_event_id: feid,
id: rid,
name: "Not Real",
regimen_items: []
}
Asset.delete_persistent_regimen(reg || backup)
_rejected = true
end
end)
|> Enum.map(fn %PersistentRegimen{regimen_id: id, time: time, farm_event_id: feid} ->
regimen = Asset.get_regimen_by_id!(id, feid)
farm_event = Asset.get_farm_event_by_id(feid)
fe_time = Timex.parse!(farm_event.start_time, "{ISO:Extended}")
if Timex.compare(fe_time, time) != 0 do
Asset.update_persistent_regimen_time(regimen, fe_time)
Farmbot.Logger.debug(1, "FarmEvent start time and stored regimen start time are different.")
end
args = [regimen, fe_time]
opts = [restart: :transient, id: feid]
worker(Farmbot.Regimen.Manager, args, opts)
end)
end
end