Current section
Files
Jump to
Current section
Files
lib/process/extra.ex
defmodule Process.Extra do
@moduledoc """
Extensions to the standard library's `Process` module.
"""
@doc """
Exits the specified `pid_or_atom` with the provided `:reason`.
This function is similar to `Process.kill/2` with the following differences:
It accepts an atom, in which case it will attempt to resolve the module via `Process.whereis/1`,
and if it is unable to resolve it will function as a no-op.
"""
@spec exit(atom | pid, reason :: term) :: true
def exit(atom, reason) when is_atom(atom) do
case Process.whereis(atom) do
nil -> true
pid when is_pid(pid) -> exit(pid, reason)
end
end
def exit(pid, reason) when is_pid(pid) do
Process.exit(pid, reason)
end
@doc """
Returns a stream of nearest neighbors closest to the `pid` via traversal.
Which process relationships to traverse is defined by the options below.
## Options
* `:links` - `boolean` - whether to include linked processes. Default: `true`.
* `:monitors` - `boolean` - whether to include monitored processes. Default: `true`.
"""
@spec nearest(pid, keyword) :: Enum.t
def nearest(pid, opts \\ []) when is_pid(pid) and is_list(opts) do
traverse_fn =
build_traverse_fn(opts)
Stream.resource(
fn -> do_nearby_init(traverse_fn, pid) end,
&do_nearby_next/1,
fn _ -> :ok end
)
end
defp do_nearby_init(traverse_fn, pid), do: {traverse_fn.(pid), MapSet.new, MapSet.new([pid]), traverse_fn}
defp do_nearby_next({[pid | rest], current_layer, already_checked, traverse_fn}) do
{[pid], {rest, MapSet.put(current_layer, pid), MapSet.put(already_checked, pid), traverse_fn}}
end
defp do_nearby_next({[], current_layer, already_checked, traverse_fn}) do
current_layer
|> Stream.flat_map(traverse_fn)
|> Enum.into(MapSet.new)
|> MapSet.difference(already_checked)
|> Enum.into([])
|> case do
[] -> {:halt, :ok}
pids -> {[], {pids, [], already_checked, traverse_fn}}
end
end
@spec build_traverse_fn(keyword) :: (pid -> [pid])
defp build_traverse_fn(opts) do
nearby_atoms =
[:links, :monitors]
|> Enum.reject(&(Keyword.get(opts, &1) == false))
fn pid ->
nearby_atoms
|> Enum.flat_map(&Process.info(pid, &1) |> extract_processes())
end
end
defp extract_processes({:monitors, items}),
do: items |> Stream.filter(&match?({:process, _}, &1)) |> Stream.map(&elem(&1, 1))
defp extract_processes({:links, items}),
do: items |> Stream.filter(&is_pid/1)
end