Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion apps/language_server/lib/language_server/build.ex
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ defmodule ElixirLS.LanguageServer.Build do
end)

if Keyword.get(opts, :compile?) do
Tracer.save()
Logger.info("Compile took #{div(us, 1000)} milliseconds")
else
Logger.info("Mix project load took #{div(us, 1000)} milliseconds")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ defmodule ElixirLS.LanguageServer.Providers.WorkspaceSymbols do
GenServer.start_link(__MODULE__, :ok, opts |> Keyword.put_new(:name, __MODULE__))
end

def notify_settings_stored() do
GenServer.cast(__MODULE__, :notify_settings_stored)
def notify_settings_stored(project_dir) do
GenServer.cast(__MODULE__, {:notify_settings_stored, project_dir})
end

def notify_build_complete(server \\ __MODULE__) do
Expand Down Expand Up @@ -150,9 +150,7 @@ defmodule ElixirLS.LanguageServer.Providers.WorkspaceSymbols do
end

@impl GenServer
def handle_cast(:notify_settings_stored, state) do
project_dir = :persistent_term.get(:language_server_project_dir)

def handle_cast({:notify_settings_stored, project_dir}, state) do
# as of LSP 3.17 only one tag is defined and clients are required to silently ignore unknown tags
# so there's no need to pass the list
tag_support =
Expand Down
5 changes: 2 additions & 3 deletions apps/language_server/lib/language_server/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1865,9 +1865,8 @@ defmodule ElixirLS.LanguageServer.Server do
state = create_gitignore(state)

if state.mix_project? do
:persistent_term.put(:language_server_project_dir, state.project_dir)
WorkspaceSymbols.notify_settings_stored()
Tracer.notify_settings_stored()
WorkspaceSymbols.notify_settings_stored(state.project_dir)
Tracer.notify_settings_stored(state.project_dir)
end

JsonRpc.telemetry(
Expand Down
196 changes: 4 additions & 192 deletions apps/language_server/lib/language_server/tracer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,8 @@ defmodule ElixirLS.LanguageServer.Tracer do
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end

def notify_settings_stored() do
GenServer.cast(__MODULE__, :notify_settings_stored)
end

def save() do
GenServer.cast(__MODULE__, :save)
def notify_settings_stored(project_dir) do
GenServer.cast(__MODULE__, {:notify_settings_stored, project_dir})
end

defp get_project_dir() do
Expand Down Expand Up @@ -57,19 +53,7 @@ defmodule ElixirLS.LanguageServer.Tracer do
])
end

project_dir = :persistent_term.get(:language_server_project_dir, nil)
state = %{project_dir: project_dir}

if project_dir != nil do
{us, _} =
:timer.tc(fn ->
for table <- @tables do
init_table(table, project_dir)
end
end)

Logger.info("Loaded DETS databases in #{div(us, 1000)}ms")
end
state = %{project_dir: nil}

{:ok, state}
end
Expand All @@ -80,26 +64,12 @@ defmodule ElixirLS.LanguageServer.Tracer do
end

@impl true
def handle_cast(:notify_settings_stored, state) do
project_dir = :persistent_term.get(:language_server_project_dir)
maybe_close_tables(state)

def handle_cast({:notify_settings_stored, project_dir}, state) do
for table <- @tables do
table_name = table_name(table)
:ets.delete_all_objects(table_name)
end

if project_dir != nil do
{us, _} =
:timer.tc(fn ->
for table <- @tables do
init_table(table, project_dir)
end
end)

Logger.info("Loaded DETS databases in #{div(us, 1000)}ms")
end

{:noreply, %{state | project_dir: project_dir}}
end

Expand All @@ -113,22 +83,8 @@ defmodule ElixirLS.LanguageServer.Tracer do
{:noreply, state}
end

def handle_cast(:save, %{project_dir: project_dir} = state) do
for table <- @tables do
table_name = table_name(table)

sync(table_name)
end

write_manifest(project_dir)

{:noreply, state}
end

@impl true
def terminate(reason, state) do
maybe_close_tables(state)

case reason do
:normal ->
:ok
Expand Down Expand Up @@ -168,112 +124,6 @@ defmodule ElixirLS.LanguageServer.Tracer do
end
end

defp maybe_close_tables(%{project_dir: nil}), do: :ok

defp maybe_close_tables(_state) do
for table <- @tables do
close_table(table)
end

:ok
end

defp dets_path(project_dir, table) do
Path.join([project_dir, ".elixir_ls", "#{table}.dets"])
end

def init_table(table, project_dir) do
table_name = table_name(table)
path = dets_path(project_dir, table)

opts = [file: path |> String.to_charlist(), auto_save: 60_000, repair: true]

:ok = path |> Path.dirname() |> File.mkdir_p()

case :dets.open_file(table_name, opts) do
{:ok, _} ->
:ok

{:error, {:needs_repair, _} = reason} ->
Logger.warning("Unable to open DETS #{path}: #{inspect(reason)}")
File.rm_rf!(path)

{:ok, _} = :dets.open_file(table_name, opts)

{:error, {:repair_failed, _} = reason} ->
Logger.warning("Unable to open DETS #{path}: #{inspect(reason)}")
File.rm_rf!(path)

{:ok, _} = :dets.open_file(table_name, opts)

{:error, {:cannot_repair, _} = reason} ->
Logger.warning("Unable to open DETS #{path}: #{inspect(reason)}")
File.rm_rf!(path)

{:ok, _} = :dets.open_file(table_name, opts)

{:error, {:not_a_dets_file, _} = reason} ->
Logger.warning("Unable to open DETS #{path}: #{inspect(reason)}")
File.rm_rf!(path)

{:ok, _} = :dets.open_file(table_name, opts)

{:error, {:format_8_no_longer_supported, _} = reason} ->
Logger.warning("Unable to open DETS #{path}: #{inspect(reason)}")
File.rm_rf!(path)

{:ok, _} = :dets.open_file(table_name, opts)
end

case :dets.to_ets(table_name, table_name) do
^table_name ->
:ok

{:error, reason} ->
Logger.warning("Unable to read DETS #{path}: #{inspect(reason)}")
File.rm_rf!(path)

{:ok, _} = :dets.open_file(table_name, opts)
^table_name = :dets.to_ets(table_name, table_name)
end
catch
kind, payload ->
{payload, stacktrace} = Exception.blame(kind, payload, __STACKTRACE__)
error_msg = Exception.format(kind, payload, stacktrace)

Logger.error(
"Unable to init tracer table #{table} in directory #{project_dir}: #{error_msg}"
)

JsonRpc.show_message(
:error,
"Unable to init tracer tables in #{project_dir}"
)

JsonRpc.telemetry(
"lsp_server_error",
%{
"elixir_ls.lsp_process" => inspect(__MODULE__),
"elixir_ls.lsp_server_error" => error_msg
},
%{}
)

unless :persistent_term.get(:language_server_test_mode, false) do
Process.sleep(2000)
System.halt(1)
else
IO.warn("Unable to init tracer table #{table} in directory #{project_dir}: #{error_msg}")
end
end

def close_table(table) do
table_name = table_name(table)
sync(table_name)

:ok = :dets.close(table_name)
end

defp modules_by_file_matchspec(file, return) do
[
{{:"$1", :"$2"},
Expand Down Expand Up @@ -434,11 +284,6 @@ defmodule ElixirLS.LanguageServer.Tracer do
end
end

defp sync(table_name) do
:ok = :dets.from_ets(table_name, table_name)
:ok = :dets.sync(table_name)
end

defp in_project_sources?(path) do
project_dir = get_project_dir()

Expand Down Expand Up @@ -486,37 +331,4 @@ defmodule ElixirLS.LanguageServer.Tracer do
:ets.safe_fixtable(table, false)
end
end

defp manifest_path(project_dir) do
Path.join([project_dir, ".elixir_ls", "tracer_db.manifest"])
end

def write_manifest(project_dir) do
path = manifest_path(project_dir)
File.rm_rf!(path)

File.write!(path, "#{@version}", [:write])
end

def read_manifest(project_dir) do
with {:ok, text} <- File.read(manifest_path(project_dir)),
{version, ""} <- Integer.parse(text) do
version
else
other ->
IO.warn("Manifest: #{inspect(other)}")
nil
end
end

def manifest_version_current?(project_dir) do
read_manifest(project_dir) == @version
end

def clean_dets(project_dir) do
for path <-
Path.join([SourceFile.Path.escape_for_wildcard(project_dir), ".elixir_ls/*.dets"])
|> Path.wildcard(),
do: File.rm_rf!(path)
end
end
5 changes: 2 additions & 3 deletions apps/language_server/test/providers/references_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ defmodule ElixirLS.LanguageServer.Providers.ReferencesTest do
require ElixirLS.Test.TextLoc

setup_all context do
File.rm_rf!(FixtureHelpers.get_path(".elixir_ls/calls.dets"))
{:ok, pid} = Tracer.start_link([])
project_path = FixtureHelpers.get_path("")
:persistent_term.put(:language_server_project_dir, project_path)
Tracer.notify_settings_stored()

Tracer.notify_settings_stored(project_path)

compiler_options = Code.compiler_options()
Build.set_compiler_options(ignore_module_conflict: true)
Expand Down
40 changes: 2 additions & 38 deletions apps/language_server/test/tracer_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,23 @@ defmodule ElixirLS.LanguageServer.TracerTest do
alias ElixirLS.LanguageServer.Test.FixtureHelpers

setup context do
File.rm_rf!(FixtureHelpers.get_path(".elixir_ls/tracer_db.manifest"))
File.rm_rf!(FixtureHelpers.get_path(".elixir_ls/calls.dets"))
File.rm_rf!(FixtureHelpers.get_path(".elixir_ls/modules.dets"))
{:ok, _pid} = start_supervised(Tracer)

{:ok, context}
end

test "set project dir" do
project_path = FixtureHelpers.get_path("")
:persistent_term.put(:language_server_project_dir, project_path)

Tracer.notify_settings_stored()
Tracer.notify_settings_stored(project_path)

assert GenServer.call(Tracer, :get_project_dir) == project_path
end

test "saves DETS" do
project_path = FixtureHelpers.get_path("")
:persistent_term.put(:language_server_project_dir, project_path)
Tracer.notify_settings_stored()

Tracer.save()
GenServer.call(Tracer, :get_project_dir)

assert File.exists?(FixtureHelpers.get_path(".elixir_ls/calls.dets"))
assert File.exists?(FixtureHelpers.get_path(".elixir_ls/modules.dets"))
end

test "skips save if project dir not set" do
Tracer.save()
GenServer.call(Tracer, :get_project_dir)
end

describe "call trace" do
setup context do
project_path = FixtureHelpers.get_path("")
:persistent_term.put(:language_server_project_dir, project_path)
Tracer.notify_settings_stored()
Tracer.notify_settings_stored(project_path)
GenServer.call(Tracer, :get_project_dir)

{:ok, context}
Expand Down Expand Up @@ -153,18 +131,4 @@ defmodule ElixirLS.LanguageServer.TracerTest do
assert [] == sorted_calls()
end
end

describe "manifest" do
test "return nil when not found" do
project_path = FixtureHelpers.get_path("")
assert nil == Tracer.read_manifest(project_path)
end

test "reads manifest" do
project_path = FixtureHelpers.get_path("")
Tracer.write_manifest(project_path)

assert 3 == Tracer.read_manifest(project_path)
end
end
end
Loading