Skip to content

Commit

Permalink
Bugfix: Document Store needs to be globally unique (lexical-lsp#438)
Browse files Browse the repository at this point in the history
* Bugfix: Document Store needs to be globally unique

Due to all nodes being connected via distribution after the ETS store
is enabled, we can no longer register a document store globally with a
non-unique name because Document.Store needs to be associated with an
editor.
This change creates some entropy when the server starts to make the
document store's name unique, and passes that entropy to the project
node.

Fixes lexical-lsp#425

* Stop the backend if we're not the leader
  • Loading branch information
scohen authored Oct 25, 2023
1 parent d88e53d commit 0bda057
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 15 deletions.
3 changes: 2 additions & 1 deletion apps/remote_control/lib/lexical/remote_control/bootstrap.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ defmodule Lexical.RemoteControl.Bootstrap do
alias Lexical.RemoteControl
require Logger

def init(%Project{} = project, remote_control_config) do
def init(%Project{} = project, document_store_entropy, remote_control_config) do
Lexical.Document.Store.set_entropy(document_store_entropy)
maybe_append_hex_path()
Application.put_all_env(remote_control: remote_control_config)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,20 +97,18 @@ defmodule Lexical.RemoteControl.ProjectNode do
end
end

alias Lexical.Document
alias Lexical.RemoteControl.ProjectNodeSupervisor
use GenServer

def start(project, paths) do
node_name = Project.node_name(project)
remote_control_config = Application.get_all_env(:remote_control)
bootstrap_args = [project, Document.Store.entropy(), remote_control_config]

with {:ok, node_pid} <- ProjectNodeSupervisor.start_project_node(project),
:ok <- start_node(project, paths),
:ok <-
:rpc.call(node_name, RemoteControl.Bootstrap, :init, [
project,
remote_control_config
]) do
:ok <- :rpc.call(node_name, RemoteControl.Bootstrap, :init, bootstrap_args) do
{:ok, node_pid}
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,13 @@ defmodule Lexical.RemoteControl.Search.Store.Backends.Ets do

@impl GenServer
def handle_call(:prepare, _from, %State{} = state) do
{reply, new_state} = State.prepare(state)
case State.prepare(state) do
{:error, :not_leader} = error ->
{:stop, :normal, error, state}

{:reply, reply, new_state}
{reply, new_state} ->
{:reply, reply, new_state}
end
end

def handle_call(:sync, _from, %State{} = state) do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ defmodule Lexical.RemoteControl.Search.Store.State do

{:update_index, result} ->
update_index_complete(new_state, result)

:initialize_fuzzy ->
initialize_fuzzy(new_state)
end
end

Expand Down Expand Up @@ -152,6 +155,9 @@ defmodule Lexical.RemoteControl.Search.Store.State do
Logger.info("backend reports stale")
{:update_index, state.update_index.(state.project, all(state))}

{:error, :not_leader} ->
:initialize_fuzzy

error ->
Logger.error("Could not initialize index due to #{inspect(error)}")
error
Expand All @@ -178,12 +184,7 @@ defmodule Lexical.RemoteControl.Search.Store.State do
end

defp update_index_complete(%__MODULE__{} = state, {:ok, updated_entries, deleted_paths}) do
fuzzy =
state
|> all()
|> Fuzzy.from_entries()

starting_state = %__MODULE__{state | fuzzy: fuzzy, loaded?: true}
starting_state = initialize_fuzzy(%__MODULE__{state | loaded?: true})

new_state =
updated_entries
Expand Down Expand Up @@ -211,4 +212,13 @@ defmodule Lexical.RemoteControl.Search.Store.State do
:ok
end
end

defp initialize_fuzzy(%__MODULE__{} = state) do
fuzzy =
state
|> all()
|> Fuzzy.from_entries()

%__MODULE__{state | fuzzy: fuzzy}
end
end
23 changes: 22 additions & 1 deletion projects/lexical_shared/lib/lexical/document/store.ex
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,28 @@ defmodule Lexical.Document.Store do
{:noreply, State.unload(state, uri)}
end

def set_entropy(entropy) do
:persistent_term.put(entropy_key(), entropy)
entropy
end

def entropy do
case :persistent_term.get(entropy_key(), :undefined) do
:undefined ->
[:positive]
|> System.unique_integer()
|> set_entropy()

entropy ->
entropy
end
end

def name do
{:via, :global, __MODULE__}
{:via, :global, {__MODULE__, entropy()}}
end

defp entropy_key do
{__MODULE__, :entropy}
end
end

0 comments on commit 0bda057

Please sign in to comment.