Skip to content

Commit 51d6cfd

Browse files
committed
Batch cachers in parallel checker (#15545)
1 parent 83ae474 commit 51d6cfd

1 file changed

Lines changed: 111 additions & 76 deletions

File tree

lib/elixir/lib/module/parallel_checker.ex

Lines changed: 111 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,37 @@ defmodule Module.ParallelChecker do
6868
def spawn({pid, {checker, table}}, module, module_map, signatures, beam_location, log?) do
6969
# Protocols may have been consolidated. So if we know their beam location,
7070
# we discard their module map on purpose and start from file.
71-
info =
72-
if beam_location != [] and Keyword.has_key?(module_map.attributes, :__protocol__) do
73-
List.to_string(beam_location)
74-
else
75-
cache_from_module_map(table, module_map, signatures)
76-
end
71+
if beam_location != [] and Keyword.has_key?(module_map.attributes, :__protocol__) do
72+
spawn_and_register_cache(pid, checker, table, module, List.to_string(beam_location), log?)
73+
else
74+
{mode, module_tuple} = cache_from_module_map(table, module_map, signatures)
75+
ref = make_ref()
76+
spawned = spawn_checker(ref, pid, checker, table, module, module_tuple, log?)
77+
register_cache_and_checker(checker, mode, module, spawned, ref)
78+
:ok
79+
end
80+
end
7781

78-
inner_spawn(pid, checker, table, module, info, log?)
82+
defp spawn_checker(ref, pid, checker, table, module, module_tuple, log?) do
83+
spawn(fn ->
84+
mon_ref = Process.monitor(pid)
85+
86+
receive do
87+
{^ref, :check, profile} ->
88+
# Set the compiler info so we can collect warnings
89+
Process.link(pid)
90+
:erlang.put(:elixir_compiler_info, {pid, self()})
91+
{warnings, errors} = check_module(module_tuple, {checker, table}, log?, profile)
92+
send(pid, {__MODULE__, module, warnings, errors})
93+
send(checker, {__MODULE__, :checked, ref})
94+
95+
{:DOWN, ^mon_ref, _, _, _} ->
96+
:ok
97+
end
98+
end)
7999
end
80100

81-
defp inner_spawn(pid, checker, table, module, info, log?) do
101+
defp spawn_and_register_cache(pid, checker, table, module, info, log?) do
82102
ref = make_ref()
83103

84104
spawned =
@@ -89,56 +109,36 @@ defmodule Module.ParallelChecker do
89109
{^ref, :cache} ->
90110
Process.link(pid)
91111

92-
{mode, module_tuple} =
93-
cond do
94-
is_binary(info) ->
95-
location =
96-
case :code.which(module) do
97-
[_ | _] = path -> path
98-
_ -> info
99-
end
100-
101-
with {:ok, binary} <- File.read(location),
102-
{:ok,
103-
{_, [{:debug_info, {:debug_info_v1, backend, data}}, {~c"ExCk", checker}]}} <-
104-
:beam_lib.chunks(binary, [:debug_info, ~c"ExCk"]),
105-
{:ok, module_map} <- backend.debug_info(:elixir_v1, module, data, []),
106-
{@elixir_checker_version, contents} <- :erlang.binary_to_term(checker) do
107-
{cache_chunk(table, module, contents), module_map_to_module_tuple(module_map)}
108-
else
109-
_ -> {:uncached, nil}
110-
end
111-
112-
is_tuple(info) ->
113-
info
112+
location =
113+
case :code.which(module) do
114+
[_ | _] = path -> path
115+
_ -> info
114116
end
115117

116-
# We only make the module available now, so they are not visible during inference
117-
:ets.insert(table, {module, mode})
118-
send(checker, {ref, :cached})
119-
120-
receive do
121-
{^ref, :check, profile} ->
122-
# Set the compiler info so we can collect warnings
123-
:erlang.put(:elixir_compiler_info, {pid, self()})
124-
125-
{warnings, errors} =
126-
if module_tuple do
127-
check_module(module_tuple, {checker, table}, log?, profile)
128-
else
129-
{[], []}
130-
end
131-
132-
send(pid, {__MODULE__, module, warnings, errors})
133-
send(checker, {__MODULE__, :done, module})
118+
with {:ok, binary} <- File.read(location),
119+
{:ok, {_, [{:debug_info, {:debug_info_v1, backend, data}}, {~c"ExCk", exck}]}} <-
120+
:beam_lib.chunks(binary, [:debug_info, ~c"ExCk"]),
121+
{:ok, module_map} <- backend.debug_info(:elixir_v1, module, data, []),
122+
{@elixir_checker_version, contents} <- :erlang.binary_to_term(exck) do
123+
mode = cache_chunk(table, module, contents)
124+
module_tuple = module_map_to_module_tuple(module_map)
125+
spawned = spawn_checker(ref, pid, checker, table, module, module_tuple, log?)
126+
:ets.insert(table, {module, mode})
127+
send(checker, {__MODULE__, :cached, module, spawned, ref})
128+
else
129+
_ ->
130+
# Nothing to check, so we notify everyone we are done
131+
:ets.insert(table, {module, :uncached})
132+
send(checker, {__MODULE__, :cached, module, nil, ref})
133+
send(pid, {__MODULE__, module, [], []})
134134
end
135135

136136
{:DOWN, ^mon_ref, _, _, _} ->
137137
:ok
138138
end
139139
end)
140140

141-
register(checker, module, spawned, ref)
141+
register_cache(checker, module, spawned, ref)
142142
:ok
143143
end
144144

@@ -192,7 +192,7 @@ defmodule Module.ParallelChecker do
192192
log? = not match?({_, false}, value)
193193

194194
for {module, file} <- runtime_files do
195-
inner_spawn(self(), checker, table, module, file, log?)
195+
spawn_and_register_cache(self(), checker, table, module, file, log?)
196196
end
197197

198198
count = :gen_server.call(checker, :start, :infinity)
@@ -533,8 +533,12 @@ defmodule Module.ParallelChecker do
533533
:gen_server.call(server, {:unlock, module, mode}, :infinity)
534534
end
535535

536-
defp register(server, module, pid, ref) do
537-
:gen_server.cast(server, {:register, module, pid, ref})
536+
defp register_cache_and_checker(server, mode, module, pid, ref) do
537+
:gen_server.cast(server, {:register_cache_and_checker, mode, module, pid, ref})
538+
end
539+
540+
defp register_cache(server, module, pid, ref) do
541+
:gen_server.cast(server, {:register_cache, module, pid, ref})
538542
end
539543

540544
## Server callbacks
@@ -577,7 +581,8 @@ defmodule Module.ParallelChecker do
577581

578582
state = %{
579583
waiting: %{},
580-
modules: [],
584+
caches: [],
585+
checkers: [],
581586
spawned: %{},
582587
schedulers: schedulers,
583588
threshold: threshold,
@@ -590,20 +595,9 @@ defmodule Module.ParallelChecker do
590595
:gen_server.enter_loop(__MODULE__, [], state)
591596
end
592597

593-
def handle_call(:start, _from, %{modules: modules, protocols: protocols, table: table} = state) do
598+
def handle_call(:start, _from, %{caches: caches, protocols: protocols, table: table} = state) do
594599
:ets.insert(table, Enum.map(protocols, &{&1, :uncached}))
595-
596-
for {_module, pid, ref} <- modules do
597-
send(pid, {ref, :cache})
598-
end
599-
600-
for {_module, _pid, ref} <- modules do
601-
receive do
602-
{^ref, :cached} -> :ok
603-
end
604-
end
605-
606-
{:reply, length(modules), run_checkers(%{state | protocols: []})}
600+
{:reply, length(caches), run_caches(%{state | protocols: []})}
607601
end
608602

609603
def handle_call({:lock, module}, from, %{waiting: waiting} = state) do
@@ -633,10 +627,21 @@ defmodule Module.ParallelChecker do
633627
{:noreply, state}
634628
end
635629

636-
def handle_info({__MODULE__, :done, module}, state) do
637-
# Unfortunately we cannot assume uniqueness because the same module
638-
# may be defined by mistake several times
639-
{timer, spawned} = Map.pop(state.spawned, module)
630+
def handle_info({__MODULE__, :cached, module, pid, ref}, state) do
631+
{_nil, spawned} = Map.pop(state.spawned, ref)
632+
633+
state =
634+
if pid do
635+
%{state | spawned: spawned, checkers: [{module, pid, ref} | state.checkers]}
636+
else
637+
%{state | spawned: spawned}
638+
end
639+
640+
{:noreply, run_caches(state)}
641+
end
642+
643+
def handle_info({__MODULE__, :checked, ref}, state) do
644+
{timer, spawned} = Map.pop(state.spawned, ref)
640645
timer && Process.cancel_timer(timer)
641646
{:noreply, run_checkers(%{state | spawned: spawned})}
642647
end
@@ -645,11 +650,41 @@ defmodule Module.ParallelChecker do
645650
{:stop, :normal, state}
646651
end
647652

648-
def handle_cast({:register, module, pid, ref}, %{modules: modules} = state) do
649-
{:noreply, %{state | modules: [{module, pid, ref} | modules]}}
653+
def handle_cast({:register_cache, module, pid, ref}, %{caches: caches} = state) do
654+
{:noreply, %{state | caches: [{module, pid, ref} | caches]}}
655+
end
656+
657+
def handle_cast(
658+
{:register_cache_and_checker, mode, module, pid, ref},
659+
%{caches: caches, checkers: checkers} = state
660+
) do
661+
{:noreply,
662+
%{state | caches: [{module, mode} | caches], checkers: [{module, pid, ref} | checkers]}}
663+
end
664+
665+
defp run_caches(%{caches: [], spawned: spawned} = state) do
666+
if spawned == %{}, do: run_checkers(state), else: state
667+
end
668+
669+
defp run_caches(%{spawned: spawned, schedulers: schedulers} = state)
670+
when map_size(spawned) >= schedulers do
671+
state
672+
end
673+
674+
defp run_caches(%{caches: [cache | caches]} = state) do
675+
case cache do
676+
{_module, pid, ref} ->
677+
send(pid, {ref, :cache})
678+
spawned = Map.put(state.spawned, ref, nil)
679+
run_caches(%{state | caches: caches, spawned: spawned})
680+
681+
{module, mode} ->
682+
:ets.insert(state.table, {module, mode})
683+
run_caches(%{state | caches: caches})
684+
end
650685
end
651686

652-
defp run_checkers(%{modules: []} = state) do
687+
defp run_checkers(%{checkers: []} = state) do
653688
state
654689
end
655690

@@ -658,11 +693,11 @@ defmodule Module.ParallelChecker do
658693
state
659694
end
660695

661-
defp run_checkers(%{modules: [{module, pid, ref} | modules]} = state) do
696+
defp run_checkers(%{checkers: [{module, pid, ref} | checkers]} = state) do
662697
send(pid, {ref, :check, state.profile})
663698
timer = :erlang.send_after(state.threshold, self(), {__MODULE__, :timeout, module, pid})
664-
spawned = Map.put(state.spawned, module, timer)
665-
run_checkers(%{state | modules: modules, spawned: spawned})
699+
spawned = Map.put(state.spawned, ref, timer)
700+
run_checkers(%{state | checkers: checkers, spawned: spawned})
666701
end
667702

668703
defp profile(module, :time, fun) do

0 commit comments

Comments
 (0)