%%% @doc %%% Process utilities module inspired by Elixir's Process module. %%% %%% This module provides convenient functions for working with processes, %%% building on top of Erlang's built-in process functionality. %%% @end -module(process). -compile({no_auto_import,[whereis/1,demonitor/2]}). %% API -export([alive/1, monitor/1, demonitor/1, demonitor/2]). -export([flag/2, info/0, info/1, info/2]). -export([sleep/1, exit/2, cancel_timer/1]). -export([send_after/3, send_interval/3]). -export([group_leader/0, group_leader/2]). -export([get/0, get/1, put/2, get_keys/0, get_keys/1, erase/0, erase/1]). -export([whereis/1, register/2, unregister/1, registered/0]). %% Types -type process_ref() :: pid() | atom() | {atom(), node()}. -type process_flag() :: trap_exit | error_handler | min_heap_size | min_bin_vheap_size | priority | save_calls | sensitive | max_heap_size. -type process_info_item() :: registered_name | status | links | monitors | monitored_by | trap_exit | error_handler | priority | group_leader | total_heap_size | heap_size | stack_size | reductions | garbage_collection | suspending | current_function | current_location | current_stacktrace | initial_call | dictionary | message_queue_len | messages | binary | memory. -type monitor_option() :: {alias, reply_demonitor | explicit_unalias}. -export_type([process_ref/0, process_flag/0, process_info_item/0, monitor_option/0]). %%% @doc %%% Returns true if the process is alive, false otherwise. %%% %%% This is equivalent to is_process_alive/1 but with a more convenient name. %%% @end -spec alive(process_ref()) -> boolean(). alive(Pid) when is_pid(Pid) -> is_process_alive(Pid); alive(Name) when is_atom(Name) -> case whereis(Name) of undefined -> false; Pid -> is_process_alive(Pid) end; alive({Name, Node}) when is_atom(Name), is_atom(Node) -> case rpc:call(Node, erlang, whereis, [Name]) of {badrpc, _} -> false; undefined -> false; Pid -> rpc:call(Node, erlang, is_process_alive, [Pid]) end. %%% @doc %%% Monitors the given process and returns the monitor reference. %%% %%% This is a convenience wrapper around erlang:monitor/2. %%% @end -spec monitor(process_ref()) -> reference(). monitor(Process) -> erlang:monitor(process, Process). %%% @doc %%% Removes the monitor identified by the given reference. %%% %%% This is equivalent to demonitor(MonitorRef, [flush]). %%% @end -spec demonitor(reference()) -> true. demonitor(MonitorRef) -> demonitor(MonitorRef, [flush]). %%% @doc %%% Removes the monitor identified by the given reference with options. %%% %%% This is a convenience wrapper around erlang:demonitor/2. %%% @end -spec demonitor(reference(), [flush | info]) -> true. demonitor(MonitorRef, Options) -> erlang:demonitor(MonitorRef, Options). %%% @doc %%% Sets a process flag for the current process. %%% %%% This is a convenience wrapper around erlang:process_flag/2. %%% @end -spec flag(process_flag(), term()) -> term(). flag(Flag, Value) -> erlang:process_flag(Flag, Value). %%% @doc %%% Returns information about the current process. %%% %%% This is equivalent to info(self()). %%% @end -spec info() -> [{process_info_item(), term()}]. info() -> info(self()). %%% @doc %%% Returns information about the given process. %%% %%% Returns undefined if the process is not alive. %%% @end -spec info(process_ref()) -> [{process_info_item(), term()}] | undefined. info(Process) -> case resolve_process(Process) of undefined -> undefined; Pid -> erlang:process_info(Pid) end. %%% @doc %%% Returns specific information about the given process. %%% %%% Returns undefined if the process is not alive or the info item is not available. %%% @end -spec info(process_ref(), process_info_item()) -> {process_info_item(), term()} | undefined. info(Process, Item) -> case resolve_process(Process) of undefined -> undefined; Pid -> erlang:process_info(Pid, Item) end. %%% @doc %%% Sleeps the current process for the given number of milliseconds. %%% %%% This is a convenience wrapper around timer:sleep/1. %%% @end -spec sleep(non_neg_integer()) -> ok. sleep(Timeout) -> timer:sleep(Timeout). %%% @doc %%% Terminates the given process with the given reason. %%% %%% This is a convenience wrapper around erlang:exit/2. %%% @end -spec exit(process_ref(), term()) -> true. exit(Process, Reason) -> case resolve_process(Process) of undefined -> true; Pid -> erlang:exit(Pid, Reason) end. %%% @doc %%% Cancels a timer created by send_after/3 or send_interval/3. %%% %%% This is a convenience wrapper around erlang:cancel_timer/1. %%% @end -spec cancel_timer(reference()) -> non_neg_integer() | false. cancel_timer(TimerRef) -> erlang:cancel_timer(TimerRef). %%% @doc %%% Sends a message to a process after the given time. %%% %%% This is a convenience wrapper around erlang:send_after/3. %%% @end -spec send_after(non_neg_integer(), process_ref(), term()) -> reference(). send_after(Time, Dest, Msg) -> case resolve_process(Dest) of undefined -> error({badarg, Dest}); Pid -> erlang:send_after(Time, Pid, Msg) end. %%% @doc %%% Sends a message to a process repeatedly at the given interval. %%% %%% This is a convenience wrapper around timer:send_interval/3. %%% @end -spec send_interval(non_neg_integer(), process_ref(), term()) -> {ok, reference()} | {error, term()}. send_interval(Interval, Dest, Msg) -> case resolve_process(Dest) of undefined -> error({badarg, Dest}); Pid -> timer:send_interval(Interval, Pid, Msg) end. %%% @doc %%% Returns the group leader of the current process. %%% %%% This is a convenience wrapper around erlang:group_leader/0. %%% @end -spec group_leader() -> pid(). group_leader() -> erlang:group_leader(). %%% @doc %%% Sets the group leader of the given process. %%% %%% This is a convenience wrapper around erlang:group_leader/2. %%% @end -spec group_leader(pid(), process_ref()) -> true. group_leader(GroupLeader, Process) -> case resolve_process(Process) of undefined -> error({badarg, Process}); Pid -> erlang:group_leader(GroupLeader, Pid) end. %%% @doc %%% Returns the process dictionary of the current process. %%% %%% This is a convenience wrapper around erlang:get/0. %%% @end -spec get() -> [{term(), term()}]. get() -> erlang:get(). %%% @doc %%% Returns the value associated with the given key in the process dictionary. %%% %%% This is a convenience wrapper around erlang:get/1. %%% @end -spec get(term()) -> term() | undefined. get(Key) -> erlang:get(Key). %%% @doc %%% Stores a key-value pair in the process dictionary. %%% %%% This is a convenience wrapper around erlang:put/2. %%% @end -spec put(term(), term()) -> term() | undefined. put(Key, Value) -> erlang:put(Key, Value). %%% @doc %%% Returns all keys in the process dictionary. %%% %%% This is a convenience wrapper around erlang:get_keys/0. %%% @end -spec get_keys() -> [term()]. get_keys() -> erlang:get_keys(). %%% @doc %%% Returns all keys associated with the given value in the process dictionary. %%% %%% This is a convenience wrapper around erlang:get_keys/1. %%% @end -spec get_keys(term()) -> [term()]. get_keys(Value) -> erlang:get_keys(Value). %%% @doc %%% Erases the entire process dictionary. %%% %%% This is a convenience wrapper around erlang:erase/0. %%% @end -spec erase() -> [{term(), term()}]. erase() -> erlang:erase(). %%% @doc %%% Erases the given key from the process dictionary. %%% %%% This is a convenience wrapper around erlang:erase/1. %%% @end -spec erase(term()) -> term() | undefined. erase(Key) -> erlang:erase(Key). %%% @doc %%% Returns the PID of the process registered under the given name. %%% %%% This is a convenience wrapper around erlang:whereis/1. %%% @end -spec whereis(atom()) -> pid() | undefined. whereis(Name) -> erlang:whereis(Name). %%% @doc %%% Registers the current process under the given name. %%% %%% This is a convenience wrapper around erlang:register/2. %%% @end -spec register(atom(), process_ref()) -> true. register(Name, Process) -> case resolve_process(Process) of undefined -> error({badarg, Process}); Pid -> erlang:register(Name, Pid) end. %%% @doc %%% Unregisters the given name. %%% %%% This is a convenience wrapper around erlang:unregister/1. %%% @end -spec unregister(atom()) -> true. unregister(Name) -> erlang:unregister(Name). %%% @doc %%% Returns a list of all registered process names. %%% %%% This is a convenience wrapper around erlang:registered/0. %%% @end -spec registered() -> [atom()]. registered() -> erlang:registered(). %%%============================================================================= %%% Internal functions %%%============================================================================= %% @private %% Resolves a process reference to a PID -spec resolve_process(process_ref()) -> pid() | undefined. resolve_process(Pid) when is_pid(Pid) -> case is_process_alive(Pid) of true -> Pid; false -> undefined end; resolve_process(Name) when is_atom(Name) -> whereis(Name); resolve_process({Name, Node}) when is_atom(Name), is_atom(Node) -> case rpc:call(Node, erlang, whereis, [Name]) of {badrpc, _} -> undefined; Pid -> Pid end.