Skip to content

File System Watcher Emitting git: URIs Causes Incorrect SHA Hashing for Restart Logic #3375

Open
@alexcrocha

Description

@alexcrocha

The Ruby LSP experiences unnecessary restarts when monitoring files for changes. This occurs because the file system watcher sometimes emits URIs with a git: scheme instead of the expected file: scheme, causing the extension to incorrectly calculate file content hashes and trigger restarts even when actual file contents haven't changed.

Problem

In extension/src/workspace.ts, we monitor files like Gemfile.lock for changes that would require restarting the language server. When these files change, debouncedRestartWithHashCheck calculates a hash of the file contents using the fileContentsSha function to determine if an actual content change occurred.

The current implementation:

const debouncedRestartWithHashCheck = async (uri: vscode.Uri) => {
  const fsPath = uri.fsPath;
  const currentSha = await this.fileContentsSha(uri);
  const storedSha = this.restartDocumentShas.get(fsPath);

  if (currentSha && storedSha !== currentSha) {
    this.restartDocumentShas.set(fsPath, currentSha);
    await this.debouncedRestart(`${fsPath} changed, matching ${pattern}`);
  }
};

The issue occurs when the file system watcher emits URIs with a git: scheme instead of the expected file: scheme:

git:/workspace/project/Gemfile.lock?%7B%22path%22%3A%22%2Fworkspace%2Fproject%2FGemfile.lock%22%2C%22ref%22%3A%22~%22%7D

When a git: URI is passed to fileContentsSha, vscode.workspace.fs.readFile(uri) resolves content based on a Git reference (ref: "~") rather than the file on disk. This leads to calculating a SHA hash for a Git-managed version of the file, not the current version on disk.

Consequences

This leads to unintended restarts when Git-referenced content differs from disk content.

Potential Solution

We are able to prevent the restart loop behaviour by converting git: URIs to file: URIs before hashing:

const debouncedRestartWithHashCheck = async (uri: vscode.Uri) => {
  const fsPath = uri.fsPath;
  // Convert git: URI scheme to file: before calculating the SHA
  const fileUri = uri.scheme === "git" ? vscode.Uri.file(fsPath) : uri;
  const currentSha = await this.fileContentsSha(fileUri);
  const storedSha = this.restartDocumentShas.get(fsPath);

  if (currentSha && storedSha !== currentSha) {
    this.restartDocumentShas.set(fsPath, currentSha);
    await this.debouncedRestart(`${fsPath} changed, matching ${pattern}`);
  }
};

This ensures we hash the actual file content regardless of the URI scheme provided.

Further Investigation Needed

While this approach addresses the immediate symptom, I don't fully understand when or why the file system watcher emits URIs with a git: scheme. I suspect the answer lies in how we're eagerly computing SHA's for watched files.

Further investigation is needed.

Reproduction

I have not been able to reproduce this issue consistently. I identified the problem when encountering a restart loop that produced this message in the logs:

Restarting the Ruby LSP because /workspace/project/Gemfile.lock changed, matching {Gemfile.lock,gems.locked}

These findings are based on debugging the restart loop. Notably, while no changes were being made to Gemfile.lock at the time the restart loop began, Gemfile.lock did have uncommitted changes that had been made earlier.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions