Skip to content
Merged
10 changes: 9 additions & 1 deletion lib/elixir/lib/kernel/error_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,15 @@ defmodule Kernel.ErrorHandler do
:erlang.garbage_collect(self())

receive do
{^ref, value} -> value
{^ref, {:loading, pid}} ->
ref = :erlang.monitor(:process, pid)

receive do
{:DOWN, ^ref, _, _, _} -> :found
end

{^ref, value} ->
value
end
end
end
102 changes: 87 additions & 15 deletions lib/elixir/lib/kernel/parallel_compiler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ defmodule Kernel.ParallelCompiler do
{:ok, [atom], [warning] | info()}
| {:error, [error] | [Code.diagnostic(:error)], [warning] | info()}
def compile_to_path(files, path, options \\ []) when is_binary(path) and is_list(options) do
spawn_workers(files, {:compile, path}, options)
spawn_workers(files, {:compile, path}, Keyword.put(options, :dest, path))
end

@doc """
Expand Down Expand Up @@ -338,8 +338,11 @@ defmodule Kernel.ParallelCompiler do
end

defp write_module_binaries(result, {:compile, path}, timestamp) do
File.mkdir_p!(path)
Code.prepend_path(path)

Enum.flat_map(result, fn
{{:module, module}, binary} when is_binary(binary) ->
{{:module, module}, {binary, _}} when is_binary(binary) ->
full_path = Path.join(path, Atom.to_string(module) <> ".beam")
File.write!(full_path, binary)
if timestamp, do: File.touch!(full_path, timestamp)
Expand All @@ -351,7 +354,7 @@ defmodule Kernel.ParallelCompiler do
end

defp write_module_binaries(result, _output, _timestamp) do
for {{:module, module}, binary} when is_binary(binary) <- result, do: module
for {{:module, module}, {binary, _}} when is_binary(binary) <- result, do: module
end

## Verification
Expand All @@ -366,7 +369,7 @@ defmodule Kernel.ParallelCompiler do

defp maybe_check_modules(result, runtime_modules, state) do
compiled_modules =
for {{:module, module}, binary} when is_binary(binary) <- result,
for {{:module, module}, {binary, _}} when is_binary(binary) <- result,
do: module

profile(
Expand Down Expand Up @@ -439,8 +442,8 @@ defmodule Kernel.ParallelCompiler do

try do
case output do
{:compile, path} -> compile_file(file, path, parent)
:compile -> compile_file(file, dest, parent)
{:compile, _} -> compile_file(file, dest, false, parent)
:compile -> compile_file(file, dest, true, parent)
:require -> require_file(file, parent)
end
catch
Expand Down Expand Up @@ -546,9 +549,9 @@ defmodule Kernel.ParallelCompiler do
wait_for_messages([], spawned, waiting, files, result, warnings, errors, state)
end

defp compile_file(file, path, parent) do
defp compile_file(file, path, force_load?, parent) do
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
:erlang.put(:elixir_compiler_dest, path)
:erlang.put(:elixir_compiler_dest, {path, force_load?})
:elixir_compiler.file(file, &each_file(&1, &2, parent))
end

Expand Down Expand Up @@ -582,7 +585,7 @@ defmodule Kernel.ParallelCompiler do
end

defp count_modules(result) do
Enum.count(result, &match?({{:module, _}, binary} when is_binary(binary), &1))
Enum.count(result, &match?({{:module, _}, {binary, _}} when is_binary(binary), &1))
end

defp each_cycle_return({kind, modules, warnings}), do: {kind, modules, warnings}
Expand Down Expand Up @@ -649,19 +652,37 @@ defmodule Kernel.ParallelCompiler do
state
)

{:module_available, child, ref, file, module, binary} ->
state.each_module.(file, module, binary)
{{:module_loaded, module}, _ref, _type, _pid, _reason} ->
result =
Map.update!(result, {:module, module}, fn {binary, _loader} -> {binary, true} end)

spawn_workers(queue, spawned, waiting, files, result, warnings, errors, state)

# Release the module loader which is waiting for an ack
{:module_available, child, ref, file, module, binary, loaded?} ->
state.each_module.(file, module, binary)
send(child, {ref, :ack})
{available, result} = update_result(result, :module, module, binary)

{available, load_status} =
case Map.get(result, {:module, module}) do
[_ | _] = pids when loaded? ->
{Enum.map(pids, &{&1, :found}), loaded?}

# When compiling files to disk, we only load the module
# if other modules are waiting for it.
[_ | _] = pids ->
pid = load_module(module, binary, state.dest)
{Enum.map(pids, &{&1, {:loading, pid}}), pid}

_ ->
{[], loaded?}
end

spawn_workers(
available ++ queue,
spawned,
waiting,
files,
result,
Map.put(result, {:module, module}, {binary, load_status}),
warnings,
errors,
state
Expand All @@ -680,7 +701,9 @@ defmodule Kernel.ParallelCompiler do

{waiting, files, result} =
if not is_list(available_or_pending) or on in defining do
send(child_pid, {ref, :found})
# If what we are waiting on was defined but not loaded, we do it now.
{reply, result} = load_pending(kind, on, result, state)
send(child_pid, {ref, reply})
{waiting, files, result}
else
waiting = Map.put(waiting, child_pid, {kind, ref, file_pid, on, defining, deadlock})
Expand Down Expand Up @@ -774,6 +797,55 @@ defmodule Kernel.ParallelCompiler do
{{:error, Enum.reverse(errors, fun.()), info}, state}
end

defp load_pending(kind, module, result, state) do
case result do
%{{:module, ^module} => {binary, load_status}}
when kind in [:module, :struct] and is_binary(binary) ->
case load_status do
true ->
{:found, result}

false ->
pid = load_module(module, binary, state.dest)
result = Map.put(result, {:module, module}, {binary, pid})
{{:loading, pid}, result}

pid when is_pid(pid) ->
{{:loading, pid}, result}
end

_ ->
{:found, result}
end
end

# We load modules in a separate process to avoid blocking
# the parallel compiler. We store the PID of this process and
# all entries monitor it to know once the module is loaded.
defp load_module(module, binary, dest) do
{pid, _ref} =
:erlang.spawn_opt(
fn ->
beam_location =
case dest do
nil ->
[]

dest ->
:filename.join(
:elixir_utils.characters_to_list(dest),
Atom.to_charlist(module) ++ ~c".beam"
)
end

:code.load_binary(module, beam_location, binary)
end,
monitor: [tag: {:module_loaded, module}]
)

pid
end

defp update_result(result, kind, module, value) do
available =
case Map.get(result, {kind, module}) do
Expand Down
Loading
Loading