Skip to content

Commit 041d59b

Browse files
committed
Add each_long_verification_threshold, closes #14336
1 parent f7f82b2 commit 041d59b

File tree

4 files changed

+67
-41
lines changed

4 files changed

+67
-41
lines changed

lib/elixir/lib/kernel/parallel_compiler.ex

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ defmodule Kernel.ParallelCompiler do
270270
max(:erlang.system_info(:schedulers_online), 2)
271271
end)
272272

273-
{:ok, cache} = Module.ParallelChecker.start_link(schedulers)
273+
{:ok, cache} = Module.ParallelChecker.start_link(options)
274274

275275
{status, modules_or_errors, info} =
276276
try do
@@ -341,16 +341,12 @@ defmodule Kernel.ParallelCompiler do
341341
File.mkdir_p!(path)
342342
Code.prepend_path(path)
343343

344-
Enum.flat_map(result, fn
345-
{{:module, module}, {binary, _}} when is_binary(binary) ->
346-
full_path = Path.join(path, Atom.to_string(module) <> ".beam")
347-
File.write!(full_path, binary)
348-
if timestamp, do: File.touch!(full_path, timestamp)
349-
[module]
350-
351-
_ ->
352-
[]
353-
end)
344+
for {{:module, module}, {binary, _}} when is_binary(binary) <- result do
345+
full_path = Path.join(path, Atom.to_string(module) <> ".beam")
346+
File.write!(full_path, binary)
347+
if timestamp, do: File.touch!(full_path, timestamp)
348+
module
349+
end
354350
end
355351

356352
defp write_module_binaries(result, _output, _timestamp) do
@@ -362,16 +358,12 @@ defmodule Kernel.ParallelCompiler do
362358
defp verify_modules(result, compile_warnings, dependent_modules, state) do
363359
modules = write_module_binaries(result, state.output, state.beam_timestamp)
364360
_ = state.after_compile.()
365-
runtime_warnings = maybe_check_modules(result, dependent_modules, state)
361+
runtime_warnings = maybe_check_modules(modules, dependent_modules, state)
366362
info = %{compile_warnings: Enum.reverse(compile_warnings), runtime_warnings: runtime_warnings}
367363
{{:ok, modules, info}, state}
368364
end
369365

370-
defp maybe_check_modules(result, runtime_modules, state) do
371-
compiled_modules =
372-
for {{:module, module}, {binary, _}} when is_binary(binary) <- result,
373-
do: module
374-
366+
defp maybe_check_modules(compiled_modules, runtime_modules, state) do
375367
profile(
376368
state,
377369
fn ->

lib/elixir/lib/module/parallel_checker.ex

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ defmodule Module.ParallelChecker do
1414
@doc """
1515
Initializes the parallel checker process.
1616
"""
17-
def start_link(schedulers \\ nil) do
18-
:proc_lib.start_link(__MODULE__, :init, [schedulers])
17+
def start_link(opts \\ []) do
18+
:proc_lib.start_link(__MODULE__, :init, [opts])
1919
end
2020

2121
@doc """
@@ -114,15 +114,15 @@ defmodule Module.ParallelChecker do
114114
end
115115

116116
send(pid, {__MODULE__, module, warnings})
117-
send(checker, {__MODULE__, :done})
117+
send(checker, {__MODULE__, :done, module})
118118
end
119119

120120
{:DOWN, ^mon_ref, _, _, _} ->
121121
:ok
122122
end
123123
end)
124124

125-
register(checker, spawned, ref)
125+
register(checker, module, spawned, ref)
126126
:ok
127127
end
128128

@@ -515,13 +515,13 @@ defmodule Module.ParallelChecker do
515515
:gen_server.call(server, {:unlock, module, mode}, :infinity)
516516
end
517517

518-
defp register(server, pid, ref) do
519-
:gen_server.cast(server, {:register, pid, ref})
518+
defp register(server, module, pid, ref) do
519+
:gen_server.cast(server, {:register, module, pid, ref})
520520
end
521521

522522
## Server callbacks
523523

524-
def init(schedulers) do
524+
def init(options) do
525525
table = :ets.new(__MODULE__, [:set, :public, {:read_concurrency, true}])
526526
:proc_lib.init_ack({:ok, {self(), table}})
527527

@@ -544,11 +544,26 @@ defmodule Module.ParallelChecker do
544544
end
545545
end
546546

547+
schedulers =
548+
Keyword.get_lazy(options, :max_concurrency, fn ->
549+
max(:erlang.system_info(:schedulers_online), 2)
550+
end)
551+
552+
threshold = Keyword.get(options, :long_verification_threshold, 10) * 1000
553+
554+
callback =
555+
case Keyword.get(options, :each_long_verification, fn _module, _pid -> :ok end) do
556+
fun when is_function(fun, 1) -> fn module, _pid -> fun.(module) end
557+
fun when is_function(fun, 2) -> fun
558+
end
559+
547560
state = %{
548561
waiting: %{},
549562
modules: [],
550-
spawned: 0,
551-
schedulers: schedulers || max(:erlang.system_info(:schedulers_online), 2),
563+
spawned: %{},
564+
schedulers: schedulers,
565+
threshold: threshold,
566+
callback: callback,
552567
protocols: [],
553568
table: table
554569
}
@@ -559,11 +574,11 @@ defmodule Module.ParallelChecker do
559574
def handle_call(:start, _from, %{modules: modules, protocols: protocols, table: table} = state) do
560575
:ets.insert(table, Enum.map(protocols, &{&1, :uncached}))
561576

562-
for {pid, ref} <- modules do
577+
for {_module, pid, ref} <- modules do
563578
send(pid, {ref, :cache})
564579
end
565580

566-
for {_pid, ref} <- modules do
581+
for {_module, _pid, ref} <- modules do
567582
receive do
568583
{^ref, :cached} -> :ok
569584
end
@@ -594,30 +609,38 @@ defmodule Module.ParallelChecker do
594609
{:reply, :ok, %{state | waiting: waiting, protocols: protocols}}
595610
end
596611

597-
def handle_info({__MODULE__, :done}, state) do
598-
state = %{state | spawned: state.spawned - 1}
599-
{:noreply, run_checkers(state)}
612+
def handle_info({__MODULE__, :timeout, module, pid}, state) do
613+
state.callback.(module, pid)
614+
{:noreply, state}
615+
end
616+
617+
def handle_info({__MODULE__, :done, module}, state) do
618+
{timer, spawned} = Map.pop!(state.spawned, module)
619+
Process.cancel_timer(timer)
620+
{:noreply, run_checkers(%{state | spawned: spawned})}
600621
end
601622

602623
def handle_info({__MODULE__, :stop}, state) do
603624
{:stop, :normal, state}
604625
end
605626

606-
def handle_cast({:register, pid, ref}, %{modules: modules} = state) do
607-
{:noreply, %{state | modules: [{pid, ref} | modules]}}
627+
def handle_cast({:register, module, pid, ref}, %{modules: modules} = state) do
628+
{:noreply, %{state | modules: [{module, pid, ref} | modules]}}
608629
end
609630

610631
defp run_checkers(%{modules: []} = state) do
611632
state
612633
end
613634

614635
defp run_checkers(%{spawned: spawned, schedulers: schedulers} = state)
615-
when spawned >= schedulers do
636+
when map_size(spawned) >= schedulers do
616637
state
617638
end
618639

619-
defp run_checkers(%{modules: [{pid, ref} | modules]} = state) do
640+
defp run_checkers(%{modules: [{module, pid, ref} | modules]} = state) do
620641
send(pid, {ref, :check})
621-
run_checkers(%{state | modules: modules, spawned: state.spawned + 1})
642+
timer = Process.send_after(self(), {__MODULE__, :timeout, module, pid}, state.threshold)
643+
spawned = Map.put(state.spawned, module, timer)
644+
run_checkers(%{state | modules: modules, spawned: spawned})
622645
end
623646
end

lib/mix/lib/mix/compilers/elixir.ex

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,7 +1001,8 @@ defmodule Mix.Compilers.Elixir do
10011001
defp compiler_loop(stale, stale_modules, dest, timestamp, opts, state) do
10021002
ref = make_ref()
10031003
parent = self()
1004-
threshold = opts[:long_compilation_threshold] || 10
1004+
compilation_threshold = opts[:long_compilation_threshold] || 10
1005+
verification_threshold = opts[:long_verification_threshold] || 10
10051006
profile = opts[:profile]
10061007
verbose = opts[:verbose] || false
10071008

@@ -1022,11 +1023,18 @@ defmodule Mix.Compilers.Elixir do
10221023
end,
10231024
each_long_compilation: fn file, pid ->
10241025
Mix.shell().info(
1025-
"Compiling #{Path.relative_to(file, File.cwd!())} (it's taking more than #{threshold}s)" <>
1026-
"\n#{debug_stacktrace(pid)}"
1026+
"Compiling #{Path.relative_to(file, File.cwd!())} " <>
1027+
"(it's taking more than #{compilation_threshold}s)#{debug_stacktrace(pid)}"
10271028
)
10281029
end,
1029-
long_compilation_threshold: threshold,
1030+
each_long_verification: fn module, pid ->
1031+
Mix.shell().info(
1032+
"Verifying #{inspect(module)} " <>
1033+
"(it's taking more than #{verification_threshold}s)#{debug_stacktrace(pid)}"
1034+
)
1035+
end,
1036+
long_compilation_threshold: compilation_threshold,
1037+
long_verification_threshold: verification_threshold,
10301038
profile: profile,
10311039
beam_timestamp: timestamp,
10321040
return_diagnostics: true
@@ -1286,7 +1294,7 @@ defmodule Mix.Compilers.Elixir do
12861294
defp debug_stacktrace(pid) do
12871295
with true <- Mix.debug?(),
12881296
{:current_stacktrace, stacktrace} <- Process.info(pid, :current_stacktrace) do
1289-
Exception.format_stacktrace(stacktrace)
1297+
[?\n, Exception.format_stacktrace(stacktrace)]
12901298
else
12911299
_ -> ""
12921300
end

lib/mix/lib/mix/tasks/compile.elixir.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ defmodule Mix.Tasks.Compile.Elixir do
6868
* `--ignore-module-conflict` - does not emit warnings if a module was previously defined
6969
* `--long-compilation-threshold N` - sets the "long compilation" threshold
7070
(in seconds) to `N` (see the docs for `Kernel.ParallelCompiler.compile/2`)
71+
* `--long-verification-threshold N` - sets the "long verification" threshold
72+
(in seconds) to `N` (see the docs for `Kernel.ParallelCompiler.compile/2`)
7173
* `--purge-consolidation-path-if-stale PATH` - deletes and purges modules in the
7274
given protocol consolidation path if compilation is required
7375
* `--profile` - if set to `time`, outputs timing information of compilation steps
@@ -100,6 +102,7 @@ defmodule Mix.Tasks.Compile.Elixir do
100102
debug_info: :boolean,
101103
verbose: :boolean,
102104
long_compilation_threshold: :integer,
105+
long_verification_threshold: :integer,
103106
purge_consolidation_path_if_stale: :string,
104107
profile: :string,
105108
all_warnings: :boolean,

0 commit comments

Comments
 (0)