Packages

An example open source Elixir application.

Current section

Files

Jump to
elevator lib orderWatchdog.ex
Raw

lib/orderWatchdog.ex

defmodule OrderWatchdog do
@moduledoc """
A module for keeping track of the hall orders which are underway to being handled, and triggering a resend of orders which stay unhandled for too long.
"""
use GenServer, restart: :permanent
require Logger
@baseWaitingTime Application.compile_env(:elevator, :orderWatchdogWaitingTime)
@randomInterval Application.compile_env(:elevator, :orderWatchdogRandomInterval)
# Public functions
# --------------------------------------------
@doc "Initializes the `OrderWatchdog` by calling `multiTriggerImpatientOrderPush/0`."
def init(orders) do
multiTriggerImpatientOrderPush()
{:ok, orders}
end
@doc "Starts the `OrderWatchdog` in a supervision tree, see `Supervisor`."
def start_link([]), do: GenServer.start_link(__MODULE__, [], name: __MODULE__)
# API
# --------------------------------------------
@doc "Requests the `OrderWatchdog`s to add an order, and begin a watchdog timer on it for resending."
def addOrder(order), do: GenServer.multi_call(__MODULE__, {:addOrder, order})
@doc "Signals to the `OrderWatchdog`s that a floor has been cleared, which triggers a deletion of all orders in the `OrderWatchdog`s handled by the signalling node."
def floorFinished(floor),
do: Enum.map([:up, :down, :cab], fn type -> finishedOrder({floor, type, node()}) end)
@doc "Triggers all `OrderWatchdog`s to push their list of orders to the other n-1 `OrderWatchdog`s, effectively synchronizing the `OrderWatchdog`s."
def multiTriggerImpatientOrderPush(),
do: GenServer.abcast(__MODULE__, :triggerImpatientOrderPush)
# Calls/Casts
# --------------------------------------------
# Handles the OrderWatchdog being asked to watch a new order. Sends a delayed message to itself to check on the order.
def handle_call({:addOrder, newOrder}, _from, orders) do
unless Enum.any?(orders, fn {_timestamp, order} -> order == newOrder end) do
newEntry = {:os.system_time(:milli_seconds), newOrder}
Process.send_after(
__MODULE__,
{:orderExpired, newEntry},
@baseWaitingTime + Enum.random(0..@randomInterval)
)
{:reply, :ok, [newEntry | orders]}
else
{:reply, :ok, orders}
end
end
# Handles the OrderWatchdog being informed that a given order is finished, triggering it to remove it from its list.
def handle_cast({:finishedOrder, finishedOrder}, orders) do
remainingOrders = Enum.reject(orders, fn {_timestamp, order} -> order == finishedOrder end)
{:noreply, remainingOrders}
end
# Handles someone pushing their impatientOrderList to the OrderWatchdog, triggering it to add the discrepancy to its list and watch them.
def handle_cast({:impatientOrderPush, remoteList}, localList) do
fullList = mergeOrderLists([remoteList, localList])
fullList
|> Enum.reject(fn entry -> entry in localList end)
|> Enum.map(fn entry ->
Process.send_after(
__MODULE__,
{:orderExpired, entry},
@baseWaitingTime + Enum.random(0..@randomInterval)
)
end)
{:noreply, fullList}
end
# Handles someone triggering an impatient order push from the OrderWatchdog, pushing its list of impatient orders to all other reachable OrderWatchdogs.
def handle_cast(:triggerImpatientOrderPush, orders) do
GenServer.abcast(Node.list(), __MODULE__, {:impatientOrderPush, orders})
{:noreply, orders}
end
# Gets a nudge to check whether an expired order has been dealt with in the meantime. If not, it re-issues it as a button press to the OrderDistributor.
def handle_info({:orderExpired, {timestamp, impatientOrder}}, orderList) do
if Enum.any?(orderList, fn entry -> entry == {timestamp, impatientOrder} end) do
finishedOrder(impatientOrder)
with {floor, type, _handledBy} <- impatientOrder do
OrderDistributor.buttonPressed({floor, type})
end
end
{:noreply, orderList}
end
# Private functions
# --------------------------------------------
# Signals to all OrderWatchdogs that a specific impatient order is finished, and should be removed.
defp finishedOrder(order), do: GenServer.abcast(__MODULE__, {:finishedOrder, order})
defp mergeOrderLists(orderLists) do
orderLists
|> Enum.concat()
|> Enum.sort(fn el1, el2 -> el1 >= el2 end)
|> Enum.uniq()
end
end