Skip to content

Commit 7c8eb41

Browse files
committed
More docs and code comments for ensure_compiled!
1 parent 3dab262 commit 7c8eb41

File tree

2 files changed

+110
-84
lines changed

2 files changed

+110
-84
lines changed

lib/elixir/lib/code.ex

Lines changed: 80 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,56 @@ defmodule Code do
3030
file, without tracking. `eval_file/2` should be used when you are interested in
3131
the result of evaluating the file rather than the modules it defines.
3232
33+
## Code loading on the Erlang VM
34+
35+
Erlang has two modes to load code: interactive and embedded.
36+
37+
By default, the Erlang VM runs in interactive mode, where modules
38+
are loaded as needed. In embedded mode the opposite happens, as all
39+
modules need to be loaded upfront or explicitly.
40+
41+
You can use `ensure_loaded/1` (as well as `ensure_lodead?/1` and
42+
`ensure_lodead!/1`) to check if a module is loaded before using it and
43+
act.
44+
45+
## `ensure_compiled/1` and `ensure_compiled!/1`
46+
47+
Elixir also includes `ensure_compiled/1` and `ensure_compiled!/1`
48+
functions that are a superset of `ensure_loaded/1`.
49+
50+
Since Elixir's compilation happens in parallel, in some situations
51+
you may need to use a module that was not yet compiled, therefore
52+
it can't even be loaded.
53+
54+
When invoked, `ensure_compiled/1` and `ensure_compiled!/1` halt the
55+
compilation of the caller until the module becomes available. Note
56+
the distinction between `ensure_compiled/1` and `ensure_compiled!/1`
57+
is important: if you are using `ensure_compiled!/1`, you are
58+
indicating to the compiler that you can only continue if said module
59+
is available.
60+
61+
If you are using `Code.ensure_compiled/1`, you are implying you may
62+
continue without the module and therefore Elixir may return
63+
`{:error, :unavailable}` for cases where the module is not yet available
64+
(but may be available later on).
65+
66+
For those reasons, developers must typically use `Code.ensure_compiled!/1`.
67+
In particular, do not do this:
68+
69+
case Code.ensure_compiled(module) do
70+
{:module, _} -> module
71+
{:error, _} -> raise ...
72+
end
73+
74+
Finally, note you only need `ensure_compiled!/1` to check for modules
75+
being defined within the same project. It does not apply to modules from
76+
dependencies as dependencies are always compiled upfront.
77+
78+
In most cases, `ensure_loaded/1` is enough. `ensure_compiled!/1`
79+
must be used in rare cases, usually involving macros that need to
80+
invoke a module for callback information. The use of `ensure_compiled/1`
81+
is even less likely.
82+
3383
## Compilation tracers
3484
3585
Elixir supports compilation tracers, which allows modules to observe constructs
@@ -1209,39 +1259,7 @@ defmodule Code do
12091259
If it succeeds in loading the module, it returns `{:module, module}`.
12101260
If not, returns `{:error, reason}` with the error reason.
12111261
1212-
## Code loading on the Erlang VM
1213-
1214-
Erlang has two modes to load code: interactive and embedded.
1215-
1216-
By default, the Erlang VM runs in interactive mode, where modules
1217-
are loaded as needed. In embedded mode the opposite happens, as all
1218-
modules need to be loaded upfront or explicitly.
1219-
1220-
Therefore, this function is used to check if a module is loaded
1221-
before using it and allows one to react accordingly. For example, the `URI`
1222-
module uses this function to check if a specific parser exists for a given
1223-
URI scheme.
1224-
1225-
## `ensure_compiled/1`
1226-
1227-
Elixir also contains an `ensure_compiled/1` function that is a
1228-
superset of `ensure_loaded/1`.
1229-
1230-
Since Elixir's compilation happens in parallel, in some situations
1231-
you may need to use a module that was not yet compiled, therefore
1232-
it can't even be loaded.
1233-
1234-
When invoked, `ensure_compiled/1` halts the compilation of the caller
1235-
until the module given to `ensure_compiled/1` becomes available or
1236-
all files for the current project have been compiled. If compilation
1237-
finishes and the module is not available, an error tuple is returned.
1238-
1239-
`ensure_compiled/1` does not apply to dependencies, as dependencies
1240-
must be compiled upfront.
1241-
1242-
In most cases, `ensure_loaded/1` is enough. `ensure_compiled/1`
1243-
must be used in rare cases, usually involving macros that need to
1244-
invoke a module for callback information.
1262+
See the module documentation for more information on code loading.
12451263
12461264
## Examples
12471265
@@ -1292,30 +1310,28 @@ defmodule Code do
12921310
end
12931311

12941312
@doc """
1295-
Ensures the given module is compiled and loaded.
1296-
1297-
If the module is already loaded, it works as no-op. If the module was
1298-
not compiled yet, `ensure_compiled/1` halts the compilation of the caller
1299-
until the module given to `ensure_compiled/1` becomes available or
1300-
all files for the current project have been compiled. If compilation
1301-
finishes and the module is not available, an error tuple is returned.
1313+
Similar to `ensure_compiled!/1` but indicates you can continue without said module.
13021314
1303-
Given this function halts compilation, use it carefully. In particular,
1304-
avoid using it to guess which modules are in the system. Overuse of this
1305-
function can also lead to deadlocks, where two modules check at the same time
1306-
if the other is compiled. This returns a specific unavailable error code,
1307-
where we cannot successfully verify a module is available or not.
1315+
While `ensure_compiled!/1` indicates to the Elixir compiler you can
1316+
only continue when said module is available, this function indicates
1317+
you may continue compilation without said module.
13081318
13091319
If it succeeds in loading the module, it returns `{:module, module}`.
13101320
If not, returns `{:error, reason}` with the error reason.
1311-
13121321
If the module being checked is currently in a compiler deadlock,
13131322
this function returns `{:error, :unavailable}`. Unavailable doesn't
13141323
necessarily mean the module doesn't exist, just that it is not currently
13151324
available, but it (or may not) become available in the future.
13161325
1317-
Check `ensure_loaded/1` for more information on module loading
1318-
and when to use `ensure_loaded/1` or `ensure_compiled/1`.
1326+
Therefore, if you can only continue if the module is available, use
1327+
`ensure_compiled!/1` instead. In particular, do not do this:
1328+
1329+
case Code.ensure_compiled(module) do
1330+
{:module, _} -> module
1331+
{:error, _} -> raise ...
1332+
end
1333+
1334+
See the module documentation for more information on code loading.
13191335
"""
13201336
@spec ensure_compiled(module) ::
13211337
{:module, module}
@@ -1325,11 +1341,24 @@ defmodule Code do
13251341
end
13261342

13271343
@doc """
1328-
Same as `ensure_compiled/1` but raises if the module cannot be compiled.
1344+
Ensures the given module is compiled and loaded.
1345+
1346+
If the module is already loaded, it works as no-op. If the module was
1347+
not compiled yet, `ensure_compiled!/1` halts the compilation of the caller
1348+
until the module given to `ensure_compiled!/1` becomes available or
1349+
all files for the current project have been compiled. If compilation
1350+
finishes and the module is not available or is in a deadlock, an error
1351+
is raised.
1352+
1353+
Given this function halts compilation, use it carefully. In particular,
1354+
avoid using it to guess which modules are in the system. Overuse of this
1355+
function can also lead to deadlocks, where two modules check at the same time
1356+
if the other is compiled. This returns a specific unavailable error code,
1357+
where we cannot successfully verify a module is available or not.
13291358
1330-
This is the preferred function to use if you want to ensure a module
1331-
is compiled and you want to raise in case it isn't.
1359+
See the module documentation for more information on code loading.
13321360
"""
1361+
@doc since: "1.12.0"
13331362
@spec ensure_compiled!(module) :: module
13341363
def ensure_compiled!(module) do
13351364
case ensure_compiled(module, :hard) do

lib/elixir/lib/kernel/parallel_compiler.ex

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -380,24 +380,31 @@ defmodule Kernel.ParallelCompiler do
380380
# There is potentially a deadlock. We will release modules with
381381
# the following order:
382382
#
383-
# 1. Code.ensure_compiled/1 checks (deadlock = soft)
384-
# 2. Struct/import/require checks (deadlock = hard)
385-
# 3. Modules without a known definition
386-
# 4. Code invocation (deadlock = raise)
383+
# 1. Code.ensure_compiled/1 checks without a known definition (deadlock = soft)
384+
# 2. Code.ensure_compiled/1 checks with a known definition (deadlock = soft)
385+
# 3. Struct/import/require/ensure_compiled! checks without a known definition (deadlock = hard)
386+
# 4. Modules without a known definition
387+
# 5. Code invocation (deadlock = raise)
387388
#
388-
# In theory there is no difference between hard and raise, the
389+
# The reason for step 3 and 4 is to not treat typos as deadlocks and
390+
# help developers handle those sooner. However, this can have false
391+
# positives in case multiple modules are defined in the same file
392+
# and the module we are waiting for is defined later on.
393+
#
394+
# Finally, note there is no difference between hard and raise, the
389395
# difference is where the raise is happening, inside the compiler
390396
# or in the caller.
391-
cond do
392-
deadlocked = deadlocked(waiting, :soft) || deadlocked(waiting, :hard) ->
393-
spawn_workers(deadlocked, spawned, waiting, files, result, warnings, state)
394397

395-
without_definition = without_definition(waiting, files) ->
396-
spawn_workers(without_definition, spawned, waiting, files, result, warnings, state)
398+
deadlocked =
399+
deadlocked(waiting, :soft, false) ||
400+
deadlocked(waiting, :soft, true) || deadlocked(waiting, :hard, false) ||
401+
without_definition(waiting)
397402

398-
true ->
399-
errors = handle_deadlock(waiting, files)
400-
{{:error, errors, warnings}, state}
403+
if deadlocked do
404+
spawn_workers(deadlocked, spawned, waiting, files, result, warnings, state)
405+
else
406+
errors = handle_deadlock(waiting, files)
407+
{{:error, errors, warnings}, state}
401408
end
402409
end
403410

@@ -450,33 +457,23 @@ defmodule Kernel.ParallelCompiler do
450457
defp each_cycle_return({kind, modules}), do: {kind, modules, []}
451458
defp each_cycle_return(modules) when is_list(modules), do: {:compile, modules, []}
452459

453-
# The goal of this function is to find leaves in the dependency graph,
454-
# i.e. to find code that depends on code that we know is not being defined.
455-
# Note that not all files have been compile yet, so they may not be in waiting.
456-
defp without_definition(waiting, files) do
460+
defp without_definition(waiting) do
457461
nillify_empty(
458-
for %{pid: pid} <- files,
459-
{_, ^pid, ref, on, _, _} <- List.wrap(List.keyfind(waiting, pid, 1)),
460-
not depended?(on, waiting),
462+
for {_, _, ref, on, _, _} <- waiting,
463+
not defining?(on, waiting),
461464
do: {ref, :not_found}
462465
)
463466
end
464467

465-
defp deadlocked(waiting, type) do
466-
{depended, not_depended} =
467-
for {_, _, ref, on, _, ^type} <- waiting, reduce: {[], []} do
468-
{depended, not_depended} ->
469-
if depended?(on, waiting) do
470-
{[{ref, :deadlock} | depended], not_depended}
471-
else
472-
{depended, [{ref, :deadlock} | not_depended]}
473-
end
474-
end
475-
476-
nillify_empty(depended) || nillify_empty(not_depended)
468+
defp deadlocked(waiting, type, defining?) do
469+
nillify_empty(
470+
for {_, _, ref, on, _, ^type} <- waiting,
471+
defining?(on, waiting) == defining?,
472+
do: {ref, :deadlock}
473+
)
477474
end
478475

479-
defp depended?(on, waiting) do
476+
defp defining?(on, waiting) do
480477
Enum.any?(waiting, fn {_, _, _, _, defining, _} -> on in defining end)
481478
end
482479

0 commit comments

Comments
 (0)