Skip to content

Commit

Permalink
Run edoc chunk generation in spawned process (#1484)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
plux committed Jan 18, 2024
1 parent b29ef4a commit 273c857
Showing 1 changed file with 72 additions and 14 deletions.
86 changes: 72 additions & 14 deletions apps/els_lsp/src/els_docs.erl
Original file line number Diff line number Diff line change
Expand Up @@ -317,24 +317,82 @@ get_edoc_chunk(M, Uri) ->
%% edoc in Erlang/OTP 24 and later can create doc chunks for edoc
case {code:ensure_loaded(edoc_doclet_chunks), code:ensure_loaded(edoc_layout_chunks)} of
{{module, _}, {module, _}} ->
Path = els_uri:path(Uri),
Dir = erlang_ls:cache_root(),
ok = edoc:run(
[els_utils:to_list(Path)],
[
{doclet, edoc_doclet_chunks},
{layout, edoc_layout_chunks},
{dir, Dir}
| edoc_options()
]
),
Chunk = filename:join([Dir, "chunks", atom_to_list(M) ++ ".chunk"]),
{ok, Bin} = file:read_file(Chunk),
{ok, binary_to_term(Bin)};
case edoc_run(Uri) of
ok ->
{ok, Bin} = file:read_file(chunk_file_path(M)),
{ok, binary_to_term(Bin)};
error ->
error
end;
E ->
?LOG_DEBUG("[edoc_chunk] load error", [E]),
error
end.

-spec chunk_file_path(module()) -> file:filename_all().
chunk_file_path(M) ->
Dir = erlang_ls:cache_root(),
filename:join([Dir, "chunks", atom_to_list(M) ++ ".chunk"]).

-spec is_chunk_file_up_to_date(binary(), module()) -> boolean().
is_chunk_file_up_to_date(Path, Module) ->
ChunkPath = chunk_file_path(Module),
filelib:is_file(ChunkPath) andalso
filelib:last_modified(ChunkPath) > filelib:last_modified(Path).

-spec edoc_run(uri()) -> ok | error.
edoc_run(Uri) ->
Ref = make_ref(),
Module = els_uri:module(Uri),
Path = els_uri:path(Uri),
Opts = [
{doclet, edoc_doclet_chunks},
{layout, edoc_layout_chunks},
{dir, erlang_ls:cache_root()}
| edoc_options()
],
Parent = self(),
case is_chunk_file_up_to_date(Path, Module) of
true ->
?LOG_DEBUG("Chunk file is up to date!"),
ok;
false ->
%% Run job to generate chunk file
%% This can be slow, run it in a spawned process so
%% we can timeout
spawn_link(
fun() ->
Name = list_to_atom(lists:concat(['docs_', Module])),
try
%% Use register to ensure we only run one of these
%% processes at the same time.
true = register(Name, self()),
?LOG_DEBUG("Generating doc chunks for ~s.", [Module]),
Res = edoc:run([els_utils:to_list(Path)], Opts),
?LOG_DEBUG("Done generating doc chunks for ~s.", [Module]),
Parent ! {Ref, Res}
catch
_:Err:St ->
?LOG_INFO(
"Generating do chunks for ~s failed: ~p\n~p",
[Module, Err, St]
),
%% Respond to parent with error
Parent ! {Ref, error}
end
end
),
receive
{Ref, Res} ->
Res
after 1000 ->
%% This took too long, return and let job continue
%% running in background in order to let it generate
%% a chunk file
error
end
end.

-else.
-dialyzer({no_match, function_docs/5}).
-dialyzer({no_match, type_docs/5}).
Expand Down

0 comments on commit 273c857

Please sign in to comment.