This used to be a question with a somewhat unsatisfying answer. No longer. As of OTP 25.0, via this pull request, the parent of every child process is stored in the process info table of the child, and retrievable via process_info/2
(Erlang) and Process.info/2
(Elixir).
For example:
iex(1)> self()
#PID<0.105.0>
iex(2)> pid = spawn(fn -> receive do :stop -> :ok end end)
#PID<0.106.0>
iex(3)> Process.info(pid, :parent)
{:parent, #PID<0.105.0>}
iex(4)> :erlang.process_info(pid, :parent)
{:parent, #PID<0.105.0>}
The ultimate ancestor of all processes is the :init
process. Its parent is :undefined
, as seen here:
iex(1)> init = Process.whereis(:init)
#PID<0.0.0>
iex(2)> Process.info(init, :parent)
{:parent, :undefined}
The way things used to be
Prior to OTP 25.0, ancestry information for Erlang processes was tracked only for processes spawned by the OTP standard behaviours: gen_server
, gen_statem
, gen_event
and supervisor
. Rather than directly using Erlang’s native spawn()
functions, the OTP behaviours spawn processes via proc_lib
, a module “used to start processes adhering to the OTP design principles.”
proc_lib
uses the process dictionary to track ancestry information. When it spawns a process, proc_lib
stores the full list of available ancestor pids in the new process’ dictionary, under the $ancestors
key.
Elixir’s Agent
, GenServer
and Supervisor
are built on top of Erlang’s OTP modules. When we start any of these processes, proc_lib
spawns the process and tracks ancestry information in the process dictionary. Here’s an example:
iex(2)> {:ok, pid} = Supervisor.start_link(MySupervisor, [])
{:ok, #PID<0.111.0>}
iex(3)> Process.info(pid, :dictionary)
{:dictionary,
[
"$ancestors": [#PID<0.105.0>, #PID<0.79.0>],
"$initial_call": {:supervisor, MySupervisor, 1}
]}
We see that two ancestors are listed for the supervisor: #PID<0.105.0>
and #PID<0.79.0>
. Notably, the :init
process (#PID<0.0.0>
) is not listed as an ancestor. What’s going on?
The IEx shell used proc_lib
to spawn #PID<0.105.0>
, therefore we have its available ancestry information. However, #PID<0.79.0>
was not started by proc_lib
, therefore we have no ancestry information. This highlights a key limitation of obtaining ancestor info via $ancestors
: it only works under certain circumstances, typically involving OTP.
This is almost certainly fine for most use cases. For any processes we truly care about, Elixir and Erlang encourage us to put them in a supervision tree. We will know the parent of any process in the tree.
Although, as of Elixir 1.15, it is not started via proc_lib
, Elixir’s Task
also tracks ancestry info using the $ancestors
key in the process dictionary.
$ancestors
is still useful
If we’re running a pre-25.0 version of OTP, $ancestors
is the only sometimes-available mechanism to find the parent of a process. If we don’t know what version of OTP our code might run under, $ancestors
can be a fallback in case Process.info(pid, :parent)
is not supported.
Additionally, Process.info(pid, :parent)
returns nil for any process that is no longer alive. If, for example, we want to know the grandparent of a process, but the parent process has died, using $ancestors
of the grandchild process is our only hope.
$ancestors
is longer shrouded in uncertainty
$ancestors
has historically existed in a gray area. The proc_lib
module documentation states that proc_lib
tracks ancestor info, but it doesn’t say how, and the $
in $ancestors
indicates it’s for internal use. Discussions about using it have featured gentle cautions and direct warnings, sometimes accompanied by skeptical queries asking “why would you want to know the parent of a process?”.
Now that parent info is available for all Erlang processes, the “why would you want to know the parent of a process?” pushback should hopefully die. By including the ability to find the parent of a process, the language itself has recognized that we might have legitimate reasons for doing so. Like any language feature, it can be abused. We can all judge for ourselves based on our own circumstances.
Additionally, OTP’s inclusion of process_info(pid, :parent)
has lifted the cloud around $ancestors
. Warnings about $ancestors
have centered on the risk that it’s an undocumented feature subject to change. Now it doesn’t matter.
$ancestors
is not disappearing from older versions of OTP. process_info(pid, :parent)
is not disappearing from future versions of OTP. No matter what version of OTP we might be using, there is a reliable way to get the parent of a process in most/all situations we care about. Even if we don’t know what version of OTP our code might run under, we can develop our code to gracefully handle any circumstance.
There’s a hex package for that
The process_tree package includes a single module called ProcessTree
that handles all the complexities related to finding the parent of a process. It works for older and newer versions of OTP, using Process.info(pid, :parent)
when available, and falling back to $ancestors
when necessary.