Skip to content

Commit c29f7f9

Browse files
authored
Merge pull request #7 from jbsf2/remote-pid-fix
Fix crash when encountering remote pids
2 parents a333939 + 0e22035 commit c29f7f9

9 files changed

+177
-77
lines changed

.gitignore

+4-1
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,7 @@ process_tree-*.tar
2727

2828
.vscode
2929
.tool-versions
30-
scratch.*
30+
scratch.*
31+
32+
.idea
33+
*.iml

bin/dialyzer

+5-23
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,6 @@
22

33
set -euxo pipefail
44

5-
asdf local erlang 24.3.4.14
6-
asdf local elixir 1.14.5-otp-24
7-
rm -rf _build
8-
mix local.hex --force
9-
mix dialyzer
10-
115
asdf local erlang 25.3.2.7
126
asdf local elixir 1.14.5-otp-25
137
rm -rf _build
@@ -20,12 +14,6 @@ rm -rf _build
2014
mix local.hex --force
2115
mix dialyzer
2216

23-
asdf local erlang 24.3.4.14
24-
asdf local elixir 1.15.8-otp-24
25-
rm -rf _build
26-
mix local.hex --force
27-
mix dialyzer
28-
2917
asdf local erlang 25.3.2.7
3018
asdf local elixir 1.15.8-otp-25
3119
rm -rf _build
@@ -38,12 +26,6 @@ rm -rf _build
3826
mix local.hex --force
3927
mix dialyzer
4028

41-
asdf local erlang 24.3.4.14
42-
asdf local elixir 1.16.3-otp-24
43-
rm -rf _build
44-
mix local.hex --force
45-
mix dialyzer
46-
4729
asdf local erlang 25.3.2.7
4830
asdf local elixir 1.16.3-otp-25
4931
rm -rf _build
@@ -68,26 +50,26 @@ rm -rf _build
6850
mix local.hex --force
6951
mix dialyzer
7052

71-
asdf local erlang 27.2
53+
asdf local erlang 27.2.1
7254
asdf local elixir 1.17.3-otp-27
7355
rm -rf _build
7456
mix local.hex --force
7557
mix dialyzer
7658

7759
asdf local erlang 25.3.2.7
78-
asdf local elixir 1.18.0-otp-25
60+
asdf local elixir 1.18.2-otp-25
7961
rm -rf _build
8062
mix local.hex --force
8163
mix dialyzer
8264

8365
asdf local erlang 26.2.5.6
84-
asdf local elixir 1.18.0-otp-26
66+
asdf local elixir 1.18.2-otp-26
8567
rm -rf _build
8668
mix local.hex --force
8769
mix dialyzer
8870

89-
asdf local erlang 27.2
90-
asdf local elixir 1.18.0-otp-27
71+
asdf local erlang 27.2.1
72+
asdf local elixir 1.18.2-otp-27
9173
rm -rf _build
9274
mix local.hex --force
9375
mix dialyzer

bin/script_builder.exs

+10-10
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,25 @@ defmodule ScriptBuilder do
22

33
def elixir_builds() do
44
[
5-
{erlang_build("24"), "1.14.5-otp-24"},
5+
# {erlang_build("24"), "1.14.5-otp-24"},
66
{erlang_build("25"), "1.14.5-otp-25"},
77
{erlang_build("26"), "1.14.5-otp-26"},
88

9-
{erlang_build("24"), "1.15.8-otp-24"},
9+
# {erlang_build("24"), "1.15.8-otp-24"},
1010
{erlang_build("25"), "1.15.8-otp-25"},
1111
{erlang_build("26"), "1.15.8-otp-26"},
1212

13-
{erlang_build("24"), "1.16.3-otp-24"},
13+
# {erlang_build("24"), "1.16.3-otp-24"},
1414
{erlang_build("25"), "1.16.3-otp-25"},
1515
{erlang_build("26"), "1.16.3-otp-26"},
1616

1717
{erlang_build("25"), "1.17.3-otp-25"},
1818
{erlang_build("26"), "1.17.3-otp-26"},
1919
{erlang_build("27"), "1.17.3-otp-27"},
2020

21-
{erlang_build("25"), "1.18.0-otp-25"},
22-
{erlang_build("26"), "1.18.0-otp-26"},
23-
{erlang_build("27"), "1.18.0-otp-27"},
21+
{erlang_build("25"), "1.18.2-otp-25"},
22+
{erlang_build("26"), "1.18.2-otp-26"},
23+
{erlang_build("27"), "1.18.2-otp-27"},
2424
]
2525
end
2626

@@ -31,10 +31,10 @@ defmodule ScriptBuilder do
3131

3232
def erlang_builds() do
3333
%{
34-
"24" => "24.3.4.14",
34+
# "24" => "24.3.4.14",
3535
"25" => "25.3.2.7",
3636
"26" => "26.2.5.6",
37-
"27" => "27.2"
37+
"27" => "27.2.1"
3838
}
3939
end
4040

@@ -54,7 +54,7 @@ defmodule ScriptBuilder do
5454
IO.puts("asdf local erlang #{erlang_build}")
5555
IO.puts("asdf local elixir #{elixir_build}")
5656
IO.puts("mix local.hex --force")
57-
IO.puts("mix test\n")
57+
IO.puts("mix test --warnings-as-errors=false\n")
5858
end)
5959
end
6060

@@ -72,4 +72,4 @@ defmodule ScriptBuilder do
7272
end
7373
end
7474

75-
ScriptBuilder.test_commands()
75+
ScriptBuilder.dialyzer_commands()

bin/test

+17-32
Original file line numberDiff line numberDiff line change
@@ -2,78 +2,63 @@
22

33
set -euo pipefail
44

5-
asdf local erlang 24.3.4.14
6-
asdf local elixir 1.14.5-otp-24
7-
mix local.hex --force
8-
mix test
9-
105
asdf local erlang 25.3.2.7
116
asdf local elixir 1.14.5-otp-25
127
mix local.hex --force
13-
mix test
8+
mix test --warnings-as-errors=false
149

1510
asdf local erlang 26.2.5.6
1611
asdf local elixir 1.14.5-otp-26
1712
mix local.hex --force
18-
mix test
19-
20-
asdf local erlang 24.3.4.14
21-
asdf local elixir 1.15.8-otp-24
22-
mix local.hex --force
23-
mix test
13+
mix test --warnings-as-errors=false
2414

2515
asdf local erlang 25.3.2.7
2616
asdf local elixir 1.15.8-otp-25
2717
mix local.hex --force
28-
mix test
18+
mix test --warnings-as-errors=false
2919

3020
asdf local erlang 26.2.5.6
3121
asdf local elixir 1.15.8-otp-26
3222
mix local.hex --force
33-
mix test
34-
35-
asdf local erlang 24.3.4.14
36-
asdf local elixir 1.16.3-otp-24
37-
mix local.hex --force
38-
mix test
23+
mix test --warnings-as-errors=false
3924

4025
asdf local erlang 25.3.2.7
4126
asdf local elixir 1.16.3-otp-25
4227
mix local.hex --force
43-
mix test
28+
mix test --warnings-as-errors=false
4429

4530
asdf local erlang 26.2.5.6
4631
asdf local elixir 1.16.3-otp-26
4732
mix local.hex --force
48-
mix test
33+
mix test --warnings-as-errors=false
4934

5035
asdf local erlang 25.3.2.7
5136
asdf local elixir 1.17.3-otp-25
5237
mix local.hex --force
53-
mix test
38+
mix test --warnings-as-errors=false
5439

5540
asdf local erlang 26.2.5.6
5641
asdf local elixir 1.17.3-otp-26
5742
mix local.hex --force
58-
mix test
43+
mix test --warnings-as-errors=false
5944

60-
asdf local erlang 27.2
45+
asdf local erlang 27.2.1
6146
asdf local elixir 1.17.3-otp-27
6247
mix local.hex --force
63-
mix test
48+
mix test --warnings-as-errors=false
6449

6550
asdf local erlang 25.3.2.7
66-
asdf local elixir 1.18.0-otp-25
51+
asdf local elixir 1.18.2-otp-25
6752
mix local.hex --force
68-
mix test
53+
mix test --warnings-as-errors=false
6954

7055
asdf local erlang 26.2.5.6
71-
asdf local elixir 1.18.0-otp-26
56+
asdf local elixir 1.18.2-otp-26
7257
mix local.hex --force
73-
mix test
58+
mix test --warnings-as-errors=false
7459

75-
asdf local erlang 27.2
76-
asdf local elixir 1.18.0-otp-27
60+
asdf local erlang 27.2.1
61+
asdf local elixir 1.18.2-otp-27
7762
mix local.hex --force
78-
mix test
63+
mix test --warnings-as-errors=false
7964

lib/process_tree.ex

+23-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ defmodule ProcessTree do
1818
@dialyzer {:no_match, {:known_ancestors, 3}}
1919
@dialyzer {:no_match, {:try_parent, 4}}
2020
@dialyzer {:no_unused, {:older_dictionary_ancestors, 2}}
21+
@dialyzer {:no_unused, {:ensure_pid_is_local, 1}}
2122
end
2223

2324
@doc """
@@ -33,7 +34,7 @@ defmodule ProcessTree do
3334
subsequent calls to `get()` with the same key will return the cached value. This behavior
3435
can be disabled by passing `cache: false` as an option.
3536
36-
## parents, `$ancestors` and `$callers`
37+
## Parents, `$ancestors` and `$callers`
3738
3839
Erlang provides [two mechanisms](https://saltycrackers.dev/posts/how-to-get-the-parent-of-an-elixir-process/)
3940
for finding the ancestors of a process: `:erlang.process_info(pid, :parent)`, available
@@ -50,6 +51,12 @@ defmodule ProcessTree do
5051
if necessary examine the caller's parents/`$ancestors` and `$callers`, with priority again given to
5152
the parent/`$ancestors` hierarchy.
5253
54+
## Remote ancestors
55+
56+
In cases where it is called within a process started by a remote node, for example via the
57+
`Node.spawn` functions, `get()` will examine only the processes local to its node. Its
58+
search stops when it encounters a remote pid.
59+
5360
## Options
5461
5562
* `:cache` - If `false`, the value found in the tree will not be cached in the dictionaries
@@ -107,6 +114,11 @@ defmodule ProcessTree do
107114
is spawned by parent/grandparent GenServers that have registered names, and the parent GenServer dies,
108115
then the parent & grandparent may be represented in the list of the child's known ancestors using
109116
the their registered names - atoms, rather than pids. Precise behavior depends on OTP major version.
117+
118+
In cases where it is called within a process started by a remote node, for example via the
119+
`Node.spawn` functions, the list returned by `known_ancestors()` will contain only the pids
120+
belonging to the local node. `known_ancestors()` ends its search when it encounters a remote
121+
pid in the ancestry hierarchy.
110122
"""
111123
@spec known_ancestors(pid()) :: [pid() | atom()]
112124
def known_ancestors(pid) do
@@ -117,7 +129,7 @@ defmodule ProcessTree do
117129
@doc """
118130
Returns the parent of `pid`, if the parent is known.
119131
120-
Returns `:unknown` if the parent is unknown.
132+
Returns `:unknown` if the parent is unknown, or if the parent belongs to a remote node.
121133
122134
Returns `:undefined` if `pid` represents the `:init` process (`PID<0.0.0>`).
123135
@@ -254,7 +266,7 @@ defmodule ProcessTree do
254266
nil
255267

256268
{:parent, parent} ->
257-
parent
269+
ensure_pid_is_local(parent)
258270

259271
nil ->
260272
nil
@@ -268,6 +280,14 @@ defmodule ProcessTree do
268280
defp process_info_parent(_pid), do: nil
269281
end
270282

283+
@spec ensure_pid_is_local(pid()) :: pid() | nil
284+
defp ensure_pid_is_local(pid) do
285+
case node(pid) == node() do
286+
true -> pid
287+
false -> nil
288+
end
289+
end
290+
271291
@spec get_dictionary_value(id(), term()) :: term()
272292
defp get_dictionary_value(nil, _key), do: nil
273293

mix.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule ProcessTree.MixProject do
22
use Mix.Project
33

4-
@version "0.2.0"
4+
@version "0.2.1"
55
@github_page "https://github.com/jbsf2/process-tree"
66

77
def project do

mix.lock

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
%{
2-
"dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
3-
"earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
4-
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
2+
"dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"},
3+
"earmark_parser": {:hex, :earmark_parser, "1.4.43", "34b2f401fe473080e39ff2b90feb8ddfeef7639f8ee0bbf71bb41911831d77c5", [:mix], [], "hexpm", "970a3cd19503f5e8e527a190662be2cee5d98eed1ff72ed9b3d1a3d466692de8"},
4+
"erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
55
"ex_doc": {:hex, :ex_doc, "0.30.9", "d691453495c47434c0f2052b08dd91cc32bc4e1a218f86884563448ee2502dd2", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "d7aaaf21e95dc5cddabf89063327e96867d00013963eadf2c6ad135506a8bc10"},
6-
"makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"},
7-
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
8-
"makeup_erlang": {:hex, :makeup_erlang, "0.1.3", "d684f4bac8690e70b06eb52dad65d26de2eefa44cd19d64a8095e1417df7c8fd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b78dc853d2e670ff6390b605d807263bf606da3c82be37f9d7f68635bd886fc9"},
9-
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
6+
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
7+
"makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
8+
"makeup_erlang": {:hex, :makeup_erlang, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"},
9+
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
1010
}

test/process_tree_test.exs

+51
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,57 @@ defmodule ProcessTreeTest do
442442
end
443443
end
444444

445+
describe "handling remote pids" do
446+
setup do
447+
ensure_epmd_running()
448+
ensure_local_node_started()
449+
:ok
450+
end
451+
452+
test "get() stops walking the tree if it encounters a remote pid" do
453+
Node.spawn_link(remote_node(), RemoteTestFunctions, :test_nested_get, [self()])
454+
455+
assert_receive :done
456+
end
457+
458+
test "known_ancestors() only includes ancestors on the local node" do
459+
Node.spawn_link(remote_node(), RemoteTestFunctions, :test_ancestors, [self()])
460+
461+
assert_receive :done
462+
end
463+
464+
test "parent() doesn't blow up" do
465+
Node.spawn_link(remote_node(), RemoteTestFunctions, :test_get_parent, [self()])
466+
467+
assert_receive :done
468+
end
469+
end
470+
471+
@spec remote_node() :: node()
472+
defp remote_node() do
473+
{:ok, pid, _} =
474+
:peer.start_link(%{connection: :standard_io, args: [~c"-pa" | :code.get_path()]})
475+
476+
:peer.call(pid, :net_kernel, :start, [[:other, :shortnames]])
477+
:peer.call(pid, :erlang, :node, [])
478+
end
479+
480+
defp ensure_local_node_started() do
481+
%{started: started} = :net_kernel.get_state()
482+
483+
if started == :no do
484+
{:ok, _} = :net_kernel.start([:process_tree_test, :shortnames])
485+
end
486+
end
487+
488+
defp ensure_epmd_running() do
489+
{_, exit_code} = System.cmd("epmd", ["-names"], stderr_to_stdout: true)
490+
491+
if exit_code != 0 do
492+
System.cmd("epmd", ["-daemon"])
493+
end
494+
end
495+
445496
@spec full_name(atom()) :: atom()
446497
defp full_name(pid_name) do
447498
prefix = Process.get(:process_name_prefix)

0 commit comments

Comments
 (0)