Skip to content

Commit d9e84b0

Browse files
committed
Load modules lazily
1 parent 7e33cee commit d9e84b0

File tree

5 files changed

+65
-22
lines changed

5 files changed

+65
-22
lines changed

lib/elixir/lib/kernel/parallel_compiler.ex

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ defmodule Kernel.ParallelCompiler do
198198
{:ok, [atom], [warning] | info()}
199199
| {:error, [error] | [Code.diagnostic(:error)], [warning] | info()}
200200
def compile_to_path(files, path, options \\ []) when is_binary(path) and is_list(options) do
201-
spawn_workers(files, {:compile, path}, options)
201+
spawn_workers(files, {:compile, path}, Keyword.put(options, :dest, path))
202202
end
203203

204204
@doc """
@@ -338,6 +338,9 @@ defmodule Kernel.ParallelCompiler do
338338
end
339339

340340
defp write_module_binaries(result, {:compile, path}, timestamp) do
341+
File.mkdir_p!(path)
342+
Code.prepend_path(path)
343+
341344
Enum.flat_map(result, fn
342345
{{:module, module}, binary} when is_binary(binary) ->
343346
full_path = Path.join(path, Atom.to_string(module) <> ".beam")
@@ -439,8 +442,8 @@ defmodule Kernel.ParallelCompiler do
439442

440443
try do
441444
case output do
442-
{:compile, path} -> compile_file(file, path, parent)
443-
:compile -> compile_file(file, dest, parent)
445+
{:compile, _} -> compile_file(file, dest, false, parent)
446+
:compile -> compile_file(file, dest, true, parent)
444447
:require -> require_file(file, parent)
445448
end
446449
catch
@@ -546,9 +549,9 @@ defmodule Kernel.ParallelCompiler do
546549
wait_for_messages([], spawned, waiting, files, result, warnings, errors, state)
547550
end
548551

549-
defp compile_file(file, path, parent) do
552+
defp compile_file(file, path, force_load?, parent) do
550553
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
551-
:erlang.put(:elixir_compiler_dest, path)
554+
:erlang.put(:elixir_compiler_dest, {path, force_load?})
552555
:elixir_compiler.file(file, &each_file(&1, &2, parent))
553556
end
554557

@@ -649,19 +652,30 @@ defmodule Kernel.ParallelCompiler do
649652
state
650653
)
651654

652-
{:module_available, child, ref, file, module, binary} ->
655+
{:module_available, child, ref, file, module, binary, loaded?} ->
653656
state.each_module.(file, module, binary)
654657

658+
available =
659+
case Map.get(result, {:module, module}) do
660+
[_ | _] = pids ->
661+
# We prefer to load in the client, if possible,
662+
# to avoid locking the compilation server.
663+
loaded? or load_module(module, binary, state)
664+
Enum.map(pids, &{&1, :found})
665+
666+
_ ->
667+
[]
668+
end
669+
655670
# Release the module loader which is waiting for an ack
656671
send(child, {ref, :ack})
657-
{available, result} = update_result(result, :module, module, binary)
658672

659673
spawn_workers(
660674
available ++ queue,
661675
spawned,
662676
waiting,
663677
files,
664-
result,
678+
Map.put(result, {:module, module}, binary),
665679
warnings,
666680
errors,
667681
state
@@ -680,6 +694,8 @@ defmodule Kernel.ParallelCompiler do
680694

681695
{waiting, files, result} =
682696
if not is_list(available_or_pending) or on in defining do
697+
# If what we are waiting on was defined but not loaded, we do it now.
698+
load_pending(kind, on, result, state)
683699
send(child_pid, {ref, :found})
684700
{waiting, files, result}
685701
else
@@ -774,6 +790,30 @@ defmodule Kernel.ParallelCompiler do
774790
{{:error, Enum.reverse(errors, fun.()), info}, state}
775791
end
776792

793+
defp load_pending(kind, module, result, state) do
794+
with true <- kind in [:module, :struct],
795+
%{{:module, ^module} => binary} when is_binary(binary) <- result,
796+
false <- :erlang.module_loaded(module) do
797+
load_module(module, binary, state)
798+
end
799+
end
800+
801+
defp load_module(module, binary, state) do
802+
beam_location =
803+
case state.dest do
804+
nil ->
805+
[]
806+
807+
dest ->
808+
:filename.join(
809+
:elixir_utils.characters_to_list(dest),
810+
Atom.to_charlist(module) ++ ~c".beam"
811+
)
812+
end
813+
814+
:code.load_binary(module, beam_location, binary)
815+
end
816+
777817
defp update_result(result, kind, module, value) do
778818
available =
779819
case Map.get(result, {kind, module}) do

lib/elixir/lib/module.ex

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -590,9 +590,11 @@ defmodule Module do
590590
name/arity pairs. Inlining is applied locally, calls from another
591591
module are not affected by this option
592592
593-
* `@compile {:autoload, false}` - disables automatic loading of
594-
modules after compilation. Instead, the module will be loaded after
595-
it is dispatched to
593+
* `@compile {:autoload, true}` - configures if modules are automatically
594+
loaded after definition. It defaults to `false` when compiling modules
595+
to `.beam` files in disk (as the modules are then lazily loaded from
596+
disk). If modules are not compiled to disk, then they are always loaded,
597+
regardless of this flag
596598
597599
* `@compile {:no_warn_undefined, Mod}` or
598600
`@compile {:no_warn_undefined, {Mod, fun, arity}}` - does not warn if

lib/elixir/src/elixir_erl_compiler.erl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
spawn(Fun) ->
1010
CompilerInfo = get(elixir_compiler_info),
11+
{error_handler, ErrorHandler} = erlang:process_info(self(), error_handler),
1112

1213
CodeDiagnostics =
1314
case get(elixir_code_diagnostics) of
@@ -17,6 +18,7 @@ spawn(Fun) ->
1718

1819
{_, Ref} =
1920
spawn_monitor(fun() ->
21+
erlang:process_flag(error_handler, ErrorHandler),
2022
put(elixir_compiler_info, CompilerInfo),
2123
put(elixir_code_diagnostics, CodeDiagnostics),
2224

lib/elixir/src/elixir_module.erl

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
159159
put_compiler_modules([Module | CompilerModules]),
160160
{Result, ModuleE, CallbackE} = eval_form(Line, Module, DataBag, Block, Vars, Prune, E),
161161
CheckerInfo = checker_info(),
162-
BeamLocation = beam_location(ModuleAsCharlist),
162+
{BeamLocation, Forceload} = beam_location(ModuleAsCharlist),
163163

164164
{Binary, PersistedAttributes, Autoload} =
165165
elixir_erl_compiler:spawn(fun() ->
@@ -219,17 +219,17 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
219219

220220
compile_error_if_tainted(DataSet, E),
221221
Binary = elixir_erl:compile(ModuleMap),
222-
Autoload = proplists:get_value(autoload, CompileOpts, true),
222+
Autoload = Forceload or proplists:get_value(autoload, CompileOpts, false),
223223
spawn_parallel_checker(CheckerInfo, Module, ModuleMap, BeamLocation),
224224
{Binary, PersistedAttributes, Autoload}
225225
end),
226226

227227
Autoload andalso code:load_binary(Module, BeamLocation, Binary),
228+
make_module_available(Module, Binary, Autoload),
228229
put_compiler_modules(CompilerModules),
229230
eval_callbacks(Line, DataBag, after_compile, [CallbackE, Binary], CallbackE),
230231
elixir_env:trace({on_module, Binary, none}, ModuleE),
231232
warn_unused_attributes(DataSet, DataBag, PersistedAttributes, E),
232-
make_module_available(Module, Binary),
233233
(element(2, CheckerInfo) == nil) andalso
234234
[VerifyMod:VerifyFun(Module) ||
235235
{VerifyMod, VerifyFun} <- bag_lookup_element(DataBag, {accumulate, after_verify}, 2)],
@@ -550,10 +550,12 @@ bag_lookup_element(Table, Name, Pos) ->
550550

551551
beam_location(ModuleAsCharlist) ->
552552
case get(elixir_compiler_dest) of
553-
Dest when is_binary(Dest) ->
554-
filename:join(elixir_utils:characters_to_list(Dest), ModuleAsCharlist ++ ".beam");
553+
{Dest, ForceLoad} when is_binary(Dest) ->
554+
BeamLocation =
555+
filename:join(elixir_utils:characters_to_list(Dest), ModuleAsCharlist ++ ".beam"),
556+
{BeamLocation, ForceLoad};
555557
_ ->
556-
""
558+
{"", true}
557559
end.
558560

559561
%% Integration with elixir_compiler that makes the module available
@@ -574,7 +576,7 @@ spawn_parallel_checker(CheckerInfo, Module, ModuleMap, BeamLocation) ->
574576
end,
575577
'Elixir.Module.ParallelChecker':spawn(CheckerInfo, Module, ModuleMap, BeamLocation, Log).
576578

577-
make_module_available(Module, Binary) ->
579+
make_module_available(Module, Binary, Loaded) ->
578580
case get(elixir_module_binaries) of
579581
Current when is_list(Current) ->
580582
put(elixir_module_binaries, [{Module, Binary} | Current]);
@@ -587,7 +589,7 @@ make_module_available(Module, Binary) ->
587589
ok;
588590
{PID, _} ->
589591
Ref = make_ref(),
590-
PID ! {module_available, self(), Ref, get(elixir_compiler_file), Module, Binary},
592+
PID ! {module_available, self(), Ref, get(elixir_compiler_file), Module, Binary, Loaded},
591593
receive {Ref, ack} -> ok end
592594
end.
593595

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,6 @@ defmodule Mix.Compilers.Elixir do
180180
end
181181

182182
Mix.Project.ensure_structure()
183-
184-
# We don't want to cache this path as we will write to it
185-
true = Code.prepend_path(dest)
186183
previous_opts = set_compiler_opts(opts)
187184

188185
try do

0 commit comments

Comments
 (0)