fixed some race conditions. (why are there so many?) and hopefully optomized logging a bit

pull/200/head
connor rigby 2016-11-15 16:09:57 -08:00
parent b8949840fd
commit b62fe312cc
9 changed files with 158 additions and 39 deletions

View File

@ -21,6 +21,7 @@ defmodule Farmbot.BotState do
pins: %{},
configuration: %{},
informational_settings: %{},
farm_scheduler: %Farmbot.Scheduler.State.Serializer{},
authorization: %{
token: nil,
email: nil,
@ -36,6 +37,7 @@ defmodule Farmbot.BotState do
pins: %{},
configuration: %{},
informational_settings: %{},
farm_scheduler: Farmbot.Scheduler.State.Serializer.t,
authorization: %{
token: map | nil,
email: String.t | nil,
@ -115,13 +117,14 @@ defmodule Farmbot.BotState do
spawn fn -> apply_status(rcontents) end
old_config = rcontents.configuration
old_auth = rcontents.authorization
old_sch = rcontents.farm_scheduler
Map.put(default_state, :configuration, old_config)
|> Map.put(:authorization, old_auth)
end
_ ->
spawn fn -> apply_auth(default_state.authorization) end
spawn fn -> apply_status(default_state) end
default_state
spawn fn -> apply_auth(default_state.authorization) end
spawn fn -> apply_status(default_state) end
default_state
end
end
@ -202,6 +205,13 @@ defmodule Farmbot.BotState do
state}
end
# I HAVE NO CLUE WHAT IM DOING
def handle_cast({:scheduler,
%Farmbot.Scheduler.State.Serializer{} = sch_state}, state)
do
{:noreply, %State{state | farm_scheduler: sch_state}}
end
# Lock the frontend from doing stuff
def handle_cast({:add_lock, string}, state) do
maybe_index = Enum.find_index(state.locks, fn(%{reason: str}) -> str == string end)
@ -262,7 +272,6 @@ defmodule Farmbot.BotState do
end
def handle_info({:connected, network, ip_addr}, state) do
Process.sleep(2000) # UGH
# GenServer.cast(Farmbot.BotState, {:update_info, :private_ip, address})
new_info = Map.put(state.informational_settings, :private_ip, ip_addr)
email = state.authorization.email
@ -276,6 +285,7 @@ defmodule Farmbot.BotState do
%{email: email, pass: pass, server: server, token: token,
network: network})
set_time
Farmbot.node_reset(ip_addr)
{:noreply,
Map.put(state, :authorization, auth)
|> Map.put(:informational_settings, new_info)
@ -284,7 +294,7 @@ defmodule Farmbot.BotState do
{:error, "enetunreach"} ->
Logger.warn("Something super weird happened.. Probably a race condition.")
# Just crash ourselves and try again.
{:crash, state}
handle_info({:connected, network, ip_addr}, state)
error ->
Logger.error("Something bad happened when logging in!: #{inspect error}")
Farmbot.factory_reset

View File

@ -7,6 +7,13 @@ defmodule Farmbot do
use Supervisor
@state_path Application.get_env(:farmbot, :state_path)
def node_reset(address) do
# ip = Farmbot.BotState.get_status |> Map.get(:informational_settings) |> Map.get(:private_ip)
Node.stop
full_node_name = "farmbot@#{address}" |> String.to_atom
{:ok, _pid} = Node.start(full_node_name)
end
@doc """
Shortcut to Nerves.Firmware.reboot
"""

View File

@ -1,9 +1,72 @@
defmodule Farmbot.Logger do
use GenServer
@moduledoc """
Right now this doesn't do anything but eventually it will save log messages
and push them to teh frontend
"""
def log(message, channels, tags) do
RPC.MessageHandler.log(message, channels, tags)
GenServer.cast(__MODULE__, {:log, message, tags, Timex.now})
end
def get_all do
GenServer.call(__MODULE__, :get_all)
end
def init(_args) do
{:ok, []}
end
def start_link(args) do
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end
def handle_cast({:log, message, tags, time}, messages) do
{:noreply, [{message, tags, time} | messages]}
end
def handle_call(:get_all, _from, messages) do
{:reply, Enum.reverse(messages), messages}
end
def handle_call({:get, ammount}, _from, messages) do
{:reply,
Enum.reverse(messages)
|> Enum.take(ammount),
messages}
end
def handle_call({:get_tag, list_of_tags}, _from, messages) do
filtered = filter(messages, list_of_tags)
|> Enum.map(fn({m, _, time}) -> {m, time} end)
{:reply, filtered, messages}
end
def filter(list_of_tags) when is_list(list_of_tags) do
GenServer.call(__MODULE__, {:get_tag, list_of_tags})
end
def filter(messages, []) do
Enum.reverse messages
end
def filter(messages, list_of_tags)
when is_list(list_of_tags) do
filter(messages, List.first(list_of_tags), list_of_tags)
end
def filter(messages, tag, list_of_tags)
when is_bitstring(tag) and is_list(list_of_tags) do
{bleep, bloop} = Enum.partition(messages, fn({_m, tags, _}) ->
contain_tag?(tags, tag)
end)
filter(messages -- bloop, list_of_tags -- [tag])
end
def contain_tag?(tags, tag) do
Enum.any?(tags, fn(t) ->
tag == t
end)
end
end
# Filtr.filter logs, ["who cares about regimens"]

View File

@ -163,6 +163,7 @@ defmodule Regimen.VM do
msg = "Regimen: #{state.regimen.name} completed without errors!"
Logger.debug(msg)
Farmbot.Logger.log(msg, [:ticker, :success_toast], ["RegimenManager"])
RPC.MessageHandler.send_status
end
# this gets called if the scheduler crashes.

View File

@ -9,6 +9,49 @@ defmodule Farmbot.Scheduler do
"""
defmodule State do
defmodule Serializer do
@moduledoc """
When all you want is relevant information :tm:
"""
@type regimen_info ::
%{regimen: Regimen.t,
info: %{ start_time: DateTime.t,
status: State.regimen_flag }
}
@type t :: %__MODULE__{
process_info: [regimen_info],
current_sequence: Sequence.t | nil,
sequence_log: [Sequence.t]
}
defstruct [
process_info: [],
current_sequence: nil,
sequence_log: []
]
@doc """
Turns a state into something more readable (by the web app)
"""
@spec serialize(State.t) :: State.Serializer.t
def serialize(state) do
regimen_info_list = Enum.map(state.regimens, fn({_pid, regimen, time, _items, flag}) ->
%{regimen: regimen,
info: %{
start_time: time,
status: flag}}
end)
cs = case state.current_sequence do
{_, sequence} -> sequence
uh -> uh
end
%__MODULE__{ process_info: regimen_info_list,
current_sequence: cs,
sequence_log: state.sequence_log}
end
end
@moduledoc false
@typedoc """
@ -58,12 +101,14 @@ defmodule Farmbot.Scheduler do
case SafeStorage.read(__MODULE__) do
{:ok, %State{} = last_state} ->
Logger.debug("loading previous #{__MODULE__} state: #{inspect last_state}")
Map.update!(last_state, :regimens, fn(old_regimens) ->
new_state = Map.update!(last_state, :regimens, fn(old_regimens) ->
Enum.map(old_regimens, fn({_,regimen, finished_items, time, _}) ->
{:ok, pid} = Regimen.VM.start_link(regimen, finished_items, time)
{pid,regimen, finished_items, time, :normal}
end)
end)
save_and_update(new_state)
new_state
_ ->
Logger.debug("starting new #{__MODULE__} state.")
default_state
@ -107,29 +152,15 @@ defmodule Farmbot.Scheduler do
{:noreply, %State{state | current_sequence: nil}}
end
def handle_call(:state, _from, state) do
{:reply, state, state}
end
# This strips out pids, tuples and whatnot for json status updates
def handle_call(:jsonable, _from, state) do
regimen_info_list = Enum.map(state.regimens, fn({_pid, regimen, time, _items, flag}) ->
%{regimen: regimen,
info: %{
start_time: time,
status: flag}}
end)
cs = case state.current_sequence do
{_, sequence} -> sequence
uh -> uh
end
jsonable =
%{process_info: regimen_info_list,
current_sequence: cs,
sequence_log: state.sequence_log}
def handle_call(:jsonable, state) do
jsonable = State.Serializer.serialize(state)
{:reply, jsonable, state}
end
def handle_call(:state, _from, state) do
{:reply, state, state}
end
def handle_call({:add, {:sequence, sequence}}, _from, state) do
{:reply, :ok, %State{state | sequence_log: state.sequence_log ++ [sequence]}}
@ -150,7 +181,7 @@ defmodule Farmbot.Scheduler do
{:ok, pid} = Regimen.VM.start_link(regimen, [], start_time)
reg_tup = {pid, regimen, [], start_time, :normal}
new_state = %State{state | regimens: current ++ [reg_tup]}
SafeStorage.write(__MODULE__, :erlang.term_to_binary(new_state))
save_and_update(new_state)
{:reply, :starting, new_state}
# If the regimen is in paused state.
@ -169,16 +200,18 @@ defmodule Farmbot.Scheduler do
# The Sequence finished. Cleanup if its still alive..
def handle_info({:done, {:sequence, pid, _sequence}}, state) do
GenServer.stop(pid, :normal)
{:noreply, %State{state | current_sequence: nil}}
new_state = %State{state | current_sequence: nil}
save_and_update(new_state)
{:noreply, new_state }
end
# A regimen is ready to be stopped.
def handle_info({:done, {:regimen, pid, regimen}}, state) do
Logger.debug("Regimen: #{regimen.name} has finished.")
GenServer.stop(pid, :normal)
reg_tup = find_regimen(regimen, state.regimens)
new_state = %State{state | regimens: state.regimens -- [reg_tup]}
SafeStorage.write(__MODULE__, :erlang.term_to_binary(new_state))
save_and_update(new_state)
GenServer.stop(pid, :normal)
{:noreply, new_state}
end
@ -196,7 +229,7 @@ defmodule Farmbot.Scheduler do
fn({^pid, ^regimen, _old_items, ^start_time, _flag}) ->
{pid, regimen, finished_items, start_time, flag}
end)}
SafeStorage.write(__MODULE__, :erlang.term_to_binary(new_state))
save_and_update(new_state)
{:noreply, new_state}
is_nil(found) ->
# Something is not good. try to clean up.
@ -247,6 +280,13 @@ defmodule Farmbot.Scheduler do
regimens: regimens}}
end
@spec save_and_update(State.t) :: :ok
def save_and_update(%State{} = state) do
GenServer.cast(Farmbot.BotState,
{:scheduler, State.Serializer.serialize(state)})
SafeStorage.write(__MODULE__, :erlang.term_to_binary(state))
end
@doc """
Finds a regimen in the list.
"""

View File

@ -5,6 +5,7 @@ defmodule Farmbot.Supervisor do
def init(%{target: target, compat_version: compat_version,
version: version, env: env}) do
children = [
# worker(Farmbot.Logger, [[]], restart: :permanent),
# Storage that needs to persist across reboots.
worker(SafeStorage, [env], restart: :permanent),
worker(SSH, [env], restart: :permanent),

View File

@ -56,6 +56,7 @@ defmodule RPC.MessageHandler do
@doc """
Shortcut for logging a message to the frontend.
= Channel can be =
| :ticker |
| :error_ticker |
| :error_toast |
| :success_toast |
@ -72,13 +73,9 @@ defmodule RPC.MessageHandler do
# This is what actually updates the rest of the world about farmbots status.
def send_status do
status =
Map.merge(Farmbot.BotState.get_status,
%{farm_scheduler: GenServer.call(Farmbot.Scheduler, :jsonable)})
m = %{id: nil,
method: "status_update",
params: [status] }
@transport.emit(Poison.encode!(m))
params: [Farmbot.BotState.get_status] }
@transport.emit(Poison.encode!(m))
end
end

View File

@ -50,7 +50,7 @@ defmodule JsonRpc.Parser do
Poison.encode!(
%{ id: nil,
method: "log_message",
params: [%{ status: Farmbot.BotState.get_status,
params: [%{ status: %{location: Farmbot.BotState.get_current_pos}, # Shhhh
time: :os.system_time(:seconds),
message: message,
channels: channels,

View File

@ -98,8 +98,8 @@ defmodule Farmbot.Mixfile do
[
{:nerves, "~> 0.3.0"},
{:nerves_firmware_http, github: "nerves-project/nerves_firmware_http"},
{:farmbot_configurator, github: "Farmbot/farmbot_configurator"}
# {:farmbot_configurator, path: "../farmbot_configurator"}
# {:farmbot_configurator, github: "Farmbot/farmbot_configurator"}
{:farmbot_configurator, path: "../farmbot_configurator"}
]
end