Skip to content

Commit 273c857

Browse files
authored
Run edoc chunk generation in spawned process (#1484)
Doc chunk generation can be very slow, this would cause completion to hang until finished. Instead return after 1 second. Add check to only generate doc chunks if the chunkfile is missing or not up to date.
1 parent b29ef4a commit 273c857

File tree

1 file changed

+72
-14
lines changed

1 file changed

+72
-14
lines changed

apps/els_lsp/src/els_docs.erl

+72-14
Original file line numberDiff line numberDiff line change
@@ -317,24 +317,82 @@ get_edoc_chunk(M, Uri) ->
317317
%% edoc in Erlang/OTP 24 and later can create doc chunks for edoc
318318
case {code:ensure_loaded(edoc_doclet_chunks), code:ensure_loaded(edoc_layout_chunks)} of
319319
{{module, _}, {module, _}} ->
320-
Path = els_uri:path(Uri),
321-
Dir = erlang_ls:cache_root(),
322-
ok = edoc:run(
323-
[els_utils:to_list(Path)],
324-
[
325-
{doclet, edoc_doclet_chunks},
326-
{layout, edoc_layout_chunks},
327-
{dir, Dir}
328-
| edoc_options()
329-
]
330-
),
331-
Chunk = filename:join([Dir, "chunks", atom_to_list(M) ++ ".chunk"]),
332-
{ok, Bin} = file:read_file(Chunk),
333-
{ok, binary_to_term(Bin)};
320+
case edoc_run(Uri) of
321+
ok ->
322+
{ok, Bin} = file:read_file(chunk_file_path(M)),
323+
{ok, binary_to_term(Bin)};
324+
error ->
325+
error
326+
end;
334327
E ->
335328
?LOG_DEBUG("[edoc_chunk] load error", [E]),
336329
error
337330
end.
331+
332+
-spec chunk_file_path(module()) -> file:filename_all().
333+
chunk_file_path(M) ->
334+
Dir = erlang_ls:cache_root(),
335+
filename:join([Dir, "chunks", atom_to_list(M) ++ ".chunk"]).
336+
337+
-spec is_chunk_file_up_to_date(binary(), module()) -> boolean().
338+
is_chunk_file_up_to_date(Path, Module) ->
339+
ChunkPath = chunk_file_path(Module),
340+
filelib:is_file(ChunkPath) andalso
341+
filelib:last_modified(ChunkPath) > filelib:last_modified(Path).
342+
343+
-spec edoc_run(uri()) -> ok | error.
344+
edoc_run(Uri) ->
345+
Ref = make_ref(),
346+
Module = els_uri:module(Uri),
347+
Path = els_uri:path(Uri),
348+
Opts = [
349+
{doclet, edoc_doclet_chunks},
350+
{layout, edoc_layout_chunks},
351+
{dir, erlang_ls:cache_root()}
352+
| edoc_options()
353+
],
354+
Parent = self(),
355+
case is_chunk_file_up_to_date(Path, Module) of
356+
true ->
357+
?LOG_DEBUG("Chunk file is up to date!"),
358+
ok;
359+
false ->
360+
%% Run job to generate chunk file
361+
%% This can be slow, run it in a spawned process so
362+
%% we can timeout
363+
spawn_link(
364+
fun() ->
365+
Name = list_to_atom(lists:concat(['docs_', Module])),
366+
try
367+
%% Use register to ensure we only run one of these
368+
%% processes at the same time.
369+
true = register(Name, self()),
370+
?LOG_DEBUG("Generating doc chunks for ~s.", [Module]),
371+
Res = edoc:run([els_utils:to_list(Path)], Opts),
372+
?LOG_DEBUG("Done generating doc chunks for ~s.", [Module]),
373+
Parent ! {Ref, Res}
374+
catch
375+
_:Err:St ->
376+
?LOG_INFO(
377+
"Generating do chunks for ~s failed: ~p\n~p",
378+
[Module, Err, St]
379+
),
380+
%% Respond to parent with error
381+
Parent ! {Ref, error}
382+
end
383+
end
384+
),
385+
receive
386+
{Ref, Res} ->
387+
Res
388+
after 1000 ->
389+
%% This took too long, return and let job continue
390+
%% running in background in order to let it generate
391+
%% a chunk file
392+
error
393+
end
394+
end.
395+
338396
-else.
339397
-dialyzer({no_match, function_docs/5}).
340398
-dialyzer({no_match, type_docs/5}).

0 commit comments

Comments
 (0)