diff --git a/lib/trinity/scheduler.ex b/lib/trinity/scheduler.ex index 2cbae3c..76c37a6 100644 --- a/lib/trinity/scheduler.ex +++ b/lib/trinity/scheduler.ex @@ -2,6 +2,8 @@ defmodule Trinity.Scheduler do alias Trinity.SimProcess alias Trinity.Scheduler.SimulationSupervisor + import ExUnit.Assertions, only: [assert: 1] + defmodule Simulation do @type t :: %__MODULE__{ queue: :ets.table, @@ -221,30 +223,55 @@ defmodule Trinity.Scheduler do :ok end - def handle_down(%Simulation{} = sim, pid, :normal) do - # TODO: use get_sim() - %{queue: queue, proc_queue_keys: proc_queue_keys} = sim + def handle_next do + %{queue: queue, proc_queue_keys: proc_queue_keys, now: now} = get_sim() + perform_next(queue, proc_queue_keys, now) + end + + def handle_down(pid, :normal) do + sim = get_sim() destroy_process(sim, pid) - perform_next(queue, proc_queue_keys, sim.now) - [] + :ok end - def handle_down(%Simulation{} = sim, pid, reason) do - # TODO: use get_sim() - %{queue: queue, proc_queue_keys: proc_queue_keys, proc_links: proc_links} = sim - linked = gather_linked(pid, proc_links) + def handle_down(pid, reason) do + sim = get_sim() + destroy_or_message_linked(sim, pid, reason, MapSet.new([pid])) + :ok + end - destroy_process(sim, pid) - Enum.each(linked, fn lp -> - destroy_process(sim, lp) - Process.exit(lp, reason) - receive do - {:EXIT, ^lp, _reason} -> :noop - end + defp destroy_or_message_linked(%Simulation{} = sim, for_pid, reason, visited) do + linked = get_linked(sim.proc_links, for_pid) + linked = Enum.reject(linked, &MapSet.member?(visited, &1)) + + visited = Enum.reduce(linked, visited, fn p, visited -> MapSet.put(visited, p) end) + Enum.reduce(linked, visited, fn p, visited -> + visited = destroy_or_message_linked(sim, p, reason, visited) + destroy_or_message(sim, p, reason) + visited end) + end + + defp destroy_or_message(%Simulation{} = sim, pid, reason) do + # TODO: trap_exit + trap_exit = false + case trap_exit do + false -> + destroy_process(sim, pid) - perform_next(queue, proc_queue_keys, sim.now) - linked + assert Process.alive?(pid) + Process.exit(pid, reason) + receive do + {:EXIT, ^pid, _reason} -> :noop + end + end + end + + defp get_linked(proc_links, pid) do + case :ets.lookup(proc_links, pid) do + [{_pid, links}] -> links + [] -> [] + end end def handle_sent(dest_pid) when is_pid(dest_pid) do @@ -383,32 +410,6 @@ defmodule Trinity.Scheduler do end end - defp gather_linked(pid, proc_links) do - # TODO: there must be a nicer way to structure this - # (especially the uniq) - gather_linked(pid, proc_links, [pid]) - |> Enum.uniq() - |> List.delete(pid) - end - - defp gather_linked(pid, proc_links, acc) do - case :ets.lookup(proc_links, pid) do - [{^pid, links}] -> - links = Enum.filter(links, &(&1 not in acc)) - acc = links ++ acc - - links_of_links = - Enum.reduce(links, acc, fn p, acc -> - gather_linked(p, proc_links, acc) - |> Enum.uniq() - end) - - links_of_links ++ acc - - _ -> acc - end - end - defp get_proc_node, do: Process.get(sim_node_key()) defp put_proc_node(proc_nodes, node_procs, node) do @@ -440,6 +441,7 @@ defmodule Trinity.Scheduler do sim = get_sim() %{ queue: :ets.tab2list(sim.queue), + proc_queue_keys: :ets.tab2list(sim.proc_queue_keys), proc_links: :ets.tab2list(sim.proc_links), proc_nodes: :ets.tab2list(sim.proc_nodes), diff --git a/lib/trinity/scheduler/simulation_supervisor.ex b/lib/trinity/scheduler/simulation_supervisor.ex index b0aab7d..36d7253 100644 --- a/lib/trinity/scheduler/simulation_supervisor.ex +++ b/lib/trinity/scheduler/simulation_supervisor.ex @@ -89,7 +89,6 @@ defmodule Trinity.Scheduler.SimulationSupervisor do def handle_info({:EXIT, from, reason}, %State{} = state) do %State{ - sim: sim, root_pid: root_pid, parent_pid: parent_pid, } = state @@ -99,21 +98,19 @@ defmodule Trinity.Scheduler.SimulationSupervisor do # The parent process died, kill the simulation exit(reason) - ^root_pid -> - # The root died, end the simulation - end_simulation(state, reason) - {:noreply, state} - _ -> - killed = Trinity.Scheduler.handle_down(sim, from, reason) + Trinity.Scheduler.handle_down(from, reason) - case root_pid in killed do - # The root was linked and therefore killed, end the simulation - true -> end_simulation(state, reason) - _ -> :noop - end + case Process.alive?(root_pid) do + true -> + Trinity.Scheduler.handle_next() + {:noreply, state} - {:noreply, state} + false -> + # The root is dead, end the simulation + end_simulation(state, reason) + {:noreply, state} + end end end