Skip to content

Commit d9125b0

Browse files
author
José Valim
committed
Ensure compilation does not leave random spurious data on failure
Prior to this patch, the parallel compiler could leave spurious processes and the module definition could leave stale data in the elixir_modules table. The issue has been fixed by emitting the proper exit signals and by using monitors. Signed-off-by: José Valim <[email protected]>
1 parent d1d46db commit d9125b0

File tree

4 files changed

+52
-16
lines changed

4 files changed

+52
-16
lines changed

lib/elixir/lib/kernel/parallel_compiler.ex

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ defmodule Kernel.ParallelCompiler do
9696
else
9797
:elixir_compiler.file(h, Keyword.get(options, :dest))
9898
end
99-
{:compiled, h}
99+
{:shutdown, h}
100100
catch
101101
kind, reason ->
102102
{:failure, kind, reason, System.stacktrace}
@@ -166,7 +166,7 @@ defmodule Kernel.ParallelCompiler do
166166

167167
spawn_compilers(entries, original, output, options, waiting, queued, schedulers, result)
168168

169-
{:DOWN, _down_ref, :process, down_pid, {:compiled, file}} ->
169+
{:DOWN, _down_ref, :process, down_pid, {:shutdown, file}} ->
170170
if callback = Keyword.get(options, :each_file) do
171171
callback.(file)
172172
end
@@ -187,9 +187,15 @@ defmodule Kernel.ParallelCompiler do
187187
defp handle_failure(ref, reason, entries, waiting, queued) do
188188
if file = find_failure(ref, queued) do
189189
print_failure(file, reason)
190+
190191
if all_missing?(entries, waiting, queued) do
191192
collect_failures(queued, length(queued) - 1)
192193
end
194+
195+
Enum.each queued, fn {child, _, _} ->
196+
Process.exit(child, :kill)
197+
end
198+
193199
exit({:shutdown, 1})
194200
end
195201
end
@@ -201,7 +207,7 @@ defmodule Kernel.ParallelCompiler do
201207
end
202208
end
203209

204-
defp print_failure(_file, {:compiled, _}) do
210+
defp print_failure(_file, {:shutdown, _}) do
205211
:ok
206212
end
207213

lib/elixir/src/elixir_code_server.erl

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66

77
-define(timeout, 30000).
88
-record(elixir_code_server, {
9-
compilation_status=[],
109
argv=[],
1110
loaded=[],
1211
at_exit=[],
13-
pool={[],0},
1412
paths={[],[]},
13+
mod_pool={[],0},
14+
mod_ets=dict:new(),
15+
compilation_status=[],
1516
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
1617
erl_compiler_options=nil
1718
}).
@@ -35,8 +36,19 @@ init(ok) ->
3536
_ = code:ensure_loaded('Elixir.Macro.Env'),
3637
_ = code:ensure_loaded('Elixir.Module.LocalsTracker'),
3738
_ = code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
39+
40+
%% The table where we store module definitions
41+
_ = ets:new(elixir_modules, [set, protected, named_table, {read_concurrency, true}]),
42+
3843
{ok, #elixir_code_server{}}.
3944

45+
handle_call({defmodule, Pid, Tuple}, _From, Config) ->
46+
{Ref, New} = defmodule(Pid, Tuple, Config),
47+
{reply, Ref, New};
48+
49+
handle_call({undefmodule, Ref}, _From, Config) ->
50+
{reply, ok, undefmodule(Ref, Config)};
51+
4052
handle_call({acquire, Path}, From, Config) ->
4153
Current = Config#elixir_code_server.loaded,
4254
case orddict:find(Path, Current) of
@@ -73,11 +85,11 @@ handle_call({compilation_status, CompilerPid}, _From, Config) ->
7385
Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
7486

7587
handle_call(retrieve_module_name, _From, Config) ->
76-
case Config#elixir_code_server.pool of
88+
case Config#elixir_code_server.mod_pool of
7789
{[H|T], Counter} ->
78-
{reply, module_tuple(H), Config#elixir_code_server{pool={T,Counter}}};
90+
{reply, module_tuple(H), Config#elixir_code_server{mod_pool={T,Counter}}};
7991
{[], Counter} ->
80-
{reply, module_tuple(Counter), Config#elixir_code_server{pool={[],Counter+1}}}
92+
{reply, module_tuple(Counter), Config#elixir_code_server{mod_pool={[],Counter+1}}}
8193
end;
8294

8395
handle_call(erl_compiler_options, _From, Config) ->
@@ -137,16 +149,19 @@ handle_cast({unload_files, Files}, Config) ->
137149
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
138150
{noreply, Config#elixir_code_server{loaded=Unloaded}};
139151

140-
handle_cast({return_module_name, H}, #elixir_code_server{pool={T,Counter}} = Config) ->
141-
{noreply, Config#elixir_code_server{pool={[H|T],Counter}}};
152+
handle_cast({return_module_name, H}, #elixir_code_server{mod_pool={T,Counter}} = Config) ->
153+
{noreply, Config#elixir_code_server{mod_pool={[H|T],Counter}}};
142154

143155
handle_cast({paths, PA, PZ}, #elixir_code_server{} = Config) ->
144156
{noreply, Config#elixir_code_server{paths={PA,PZ}}};
145157

146158
handle_cast(Request, Config) ->
147159
{stop, {badcast, Request}, Config}.
148160

149-
handle_info(_Request, Config) ->
161+
handle_info({'DOWN', Ref, process, _Pid, _Reason}, Config) ->
162+
{noreply, undefmodule(Ref, Config)};
163+
164+
handle_info(_Msg, Config) ->
150165
{noreply, Config}.
151166

152167
terminate(_Reason, _Config) ->
@@ -158,6 +173,21 @@ code_change(_Old, Config, _Extra) ->
158173
module_tuple(I) ->
159174
{list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I}.
160175

176+
defmodule(Pid, Tuple, #elixir_code_server{mod_ets=ModEts} = Config) ->
177+
ets:insert(elixir_modules, Tuple),
178+
Ref = erlang:monitor(process, Pid),
179+
Mod = erlang:element(1, Tuple),
180+
{Ref, Config#elixir_code_server{mod_ets=dict:store(Ref, Mod, ModEts)}}.
181+
182+
undefmodule(Ref, #elixir_code_server{mod_ets=ModEts} = Config) ->
183+
case dict:find(Ref, ModEts) of
184+
{ok, Mod} ->
185+
ets:delete(elixir_modules, Mod),
186+
Config#elixir_code_server{mod_ets=dict:erase(Ref, ModEts)};
187+
error ->
188+
Config
189+
end.
190+
161191
erl_compiler_options() ->
162192
Key = "ERL_COMPILER_OPTIONS",
163193
case os:getenv(Key) of

lib/elixir/src/elixir_module.erl

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ do_compile(Line, Module, Block, Vars, E) ->
5151
check_module_availability(Line, File, Module),
5252

5353
Docs = elixir_compiler:get_opt(docs),
54-
{Data, Defs, Clas} = build(Line, File, Module, Docs, ?m(E, lexical_tracker)),
54+
{Data, Defs, Clas, Ref} = build(Line, File, Module, Docs, ?m(E, lexical_tracker)),
5555

5656
try
5757
{Result, NE} = eval_form(Line, Module, Data, Block, Vars, E),
@@ -92,7 +92,7 @@ do_compile(Line, Module, Block, Vars, E) ->
9292
ets:delete(Data),
9393
ets:delete(Defs),
9494
ets:delete(Clas),
95-
ets:delete(elixir_modules, Module)
95+
elixir_code_server:call({undefmodule, Ref})
9696
end.
9797

9898
%% Hook that builds both attribute and functions and set up common hooks.
@@ -110,7 +110,8 @@ build(Line, File, Module, Docs, Lexical) ->
110110
Defs = ets:new(Module, [set, public]),
111111
Clas = ets:new(Module, [bag, public]),
112112

113-
ets:insert(elixir_modules, {Module, Data, Defs, Clas, Line, File}),
113+
Ref = elixir_code_server:call({defmodule, self(),
114+
{Module, Data, Defs, Clas, Line, File}}),
114115

115116
ets:insert(Data, {before_compile, []}),
116117
ets:insert(Data, {after_compile, []}),
@@ -132,7 +133,7 @@ build(Line, File, Module, Docs, Lexical) ->
132133
elixir_locals:setup(Module),
133134
elixir_def_overridable:setup(Module),
134135

135-
{Data, Defs, Clas}.
136+
{Data, Defs, Clas, Ref}.
136137

137138
%% Receives the module representation and evaluates it.
138139

lib/elixir/src/elixir_sup.erl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,4 @@ init(ok) ->
2828
}
2929
],
3030

31-
_ = ets:new(elixir_modules, [set, public, named_table, {read_concurrency, true}]),
3231
{ok, {{one_for_one, 3, 10}, Workers}}.

0 commit comments

Comments
 (0)