Skip to content

Conversation

royitaqi
Copy link
Contributor

@royitaqi royitaqi commented Sep 17, 2025

NOTE: This patch is discarded in favor of #159797.

Motiviation

This helps the development of the lldb-dap binary. For example, when testing a locally built lldb-dap binary, one probably wants to restart the server after each build in order to use the latest binary. Not doing so leads to confusion like "why the new lldb-dap isn't doing what I just changed?".

Two different solutions considered

Solution 1: Restart server when lldb-dap binary's modification time changes. This patch implements solution 1.

Solution 2: Restart server when lldb-dap binary has changed (as detected by a file system watcher). #159797 implements solution 2.

This patch (solution 1)

If the lldb-dap binary's modification time has changed, the next time the user start a debug session, the existing dialog box will show up and prompt the user to restart the server.

In that dialog box, the new modification time will be appended to the internal "spawn info" variable. It will then be compared to the existing "spawn info", and prompt for restart if they are different. The modification time is also displayed in the user prompt (see screenshot below).

screenshot

@llvmbot
Copy link
Member

llvmbot commented Sep 17, 2025

@llvm/pr-subscribers-lldb

Author: Roy Shi (royitaqi)

Changes

This helps lldb-dap (the binary) development. For example, after rebuilding the lldb-dap binary, one probably wants to restart the server so that it will use the newly built binary instead of a stale one.

With this patch, the modification time will be appended to the internal "spawn info" variable, which is displayed when prompting the user about restarting the server.

See screenshot below.
<img width="520" height="372" alt="screenshot" src="https://github.com/%3Ca%20href="https://github.com/user-attachments/assets/79da55c8-28d3-4f17-b228-3ee22733a3a0">https://github.com/user-attachments/assets/79da55c8-28d3-4f17-b228-3ee22733a3a0" />


Full diff: https://github.com/llvm/llvm-project/pull/159481.diff

4 Files Affected:

  • (modified) lldb/tools/lldb-dap/.vscode/launch.json (+5-5)
  • (modified) lldb/tools/lldb-dap/package-lock.json (+66)
  • (modified) lldb/tools/lldb-dap/package.json (+4)
  • (modified) lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts (+22-9)
diff --git a/lldb/tools/lldb-dap/.vscode/launch.json b/lldb/tools/lldb-dap/.vscode/launch.json
index 8241a5aca0354..524fc2dd5ce7b 100644
--- a/lldb/tools/lldb-dap/.vscode/launch.json
+++ b/lldb/tools/lldb-dap/.vscode/launch.json
@@ -14,11 +14,11 @@
 			],
 			"outFiles": [
 				"${workspaceFolder}/out/**/*.js"
-			],
-			"preLaunchTask": {
-				"type": "npm",
-				"script": "watch"
-			}
+			]
+			// "preLaunchTask": {
+			// 	"type": "npm",
+			// 	"script": "watch"
+			// }
 		}
 	]
 }
diff --git a/lldb/tools/lldb-dap/package-lock.json b/lldb/tools/lldb-dap/package-lock.json
index 26db1ce6df2fd..92143f7f37268 100644
--- a/lldb/tools/lldb-dap/package-lock.json
+++ b/lldb/tools/lldb-dap/package-lock.json
@@ -8,7 +8,11 @@
       "name": "lldb-dap",
       "version": "0.2.16",
       "license": "Apache 2.0 License with LLVM exceptions",
+      "dependencies": {
+        "fs-extra": "^11.3.2"
+      },
       "devDependencies": {
+        "@types/fs-extra": "^11.0.4",
         "@types/node": "^18.19.41",
         "@types/tabulator-tables": "^6.2.10",
         "@types/vscode": "1.75.0",
@@ -835,6 +839,27 @@
         "@jridgewell/sourcemap-codec": "^1.4.14"
       }
     },
+    "node_modules/@types/fs-extra": {
+      "version": "11.0.4",
+      "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz",
+      "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/jsonfile": "*",
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/jsonfile": {
+      "version": "6.1.4",
+      "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz",
+      "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
     "node_modules/@types/node": {
       "version": "18.19.75",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.75.tgz",
@@ -1748,6 +1773,20 @@
       "dev": true,
       "optional": true
     },
+    "node_modules/fs-extra": {
+      "version": "11.3.2",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz",
+      "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==",
+      "license": "MIT",
+      "dependencies": {
+        "graceful-fs": "^4.2.0",
+        "jsonfile": "^6.0.1",
+        "universalify": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=14.14"
+      }
+    },
     "node_modules/function-bind": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -1877,6 +1916,12 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/graceful-fs": {
+      "version": "4.2.11",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+      "license": "ISC"
+    },
     "node_modules/has-flag": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -2094,6 +2139,18 @@
       "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==",
       "dev": true
     },
+    "node_modules/jsonfile": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
+      "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
+      "license": "MIT",
+      "dependencies": {
+        "universalify": "^2.0.0"
+      },
+      "optionalDependencies": {
+        "graceful-fs": "^4.1.6"
+      }
+    },
     "node_modules/jsonwebtoken": {
       "version": "9.0.2",
       "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
@@ -3182,6 +3239,15 @@
       "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
       "dev": true
     },
+    "node_modules/universalify": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+      "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 10.0.0"
+      }
+    },
     "node_modules/url-join": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index 6566ba3bdee13..3633e292a6264 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -27,7 +27,11 @@
   "categories": [
     "Debuggers"
   ],
+  "dependencies": {
+    "fs-extra": "^11.0.4"
+  },
   "devDependencies": {
+    "@types/fs-extra": "^11.0.4",
     "@types/node": "^18.19.41",
     "@types/tabulator-tables": "^6.2.10",
     "@types/vscode": "1.75.0",
diff --git a/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts b/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts
index 774be50053a17..59dd8a7a6b250 100644
--- a/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts
+++ b/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts
@@ -1,3 +1,4 @@
+import * as fs from 'fs-extra';
 import * as child_process from "node:child_process";
 import { isDeepStrictEqual } from "util";
 import * as vscode from "vscode";
@@ -54,7 +55,7 @@ export class LLDBDapServer implements vscode.Disposable {
       return this.serverInfo;
     }
 
-    this.serverInfo = new Promise((resolve, reject) => {
+    this.serverInfo = new Promise(async (resolve, reject) => {
       const process = child_process.spawn(dapPath, dapArgs, options);
       process.on("error", (error) => {
         reject(error);
@@ -82,7 +83,7 @@ export class LLDBDapServer implements vscode.Disposable {
         }
       });
       this.serverProcess = process;
-      this.serverSpawnInfo = this.getSpawnInfo(dapPath, dapArgs, options?.env);
+      this.serverSpawnInfo = await this.getSpawnInfo(dapPath, dapArgs, options?.env);
     });
     return this.serverInfo;
   }
@@ -104,13 +105,13 @@ export class LLDBDapServer implements vscode.Disposable {
       return true;
     }
 
-    const newSpawnInfo = this.getSpawnInfo(dapPath, args, env);
+    const newSpawnInfo = await this.getSpawnInfo(dapPath, args, env);
     if (isDeepStrictEqual(this.serverSpawnInfo, newSpawnInfo)) {
       return true;
     }
 
     const userInput = await vscode.window.showInformationMessage(
-      "The arguments to lldb-dap have changed. Would you like to restart the server?",
+      "The lldb-dap binary and/or the arguments to it have changed. Would you like to restart the server?",
       {
         modal: true,
         detail: `An existing lldb-dap server (${this.serverProcess.pid}) is running with different arguments.
@@ -131,8 +132,7 @@ Restarting the server will interrupt any existing debug sessions and start a new
     switch (userInput) {
       case "Restart":
         this.serverProcess.kill();
-        this.serverProcess = undefined;
-        this.serverInfo = undefined;
+        this.cleanUp(this.serverProcess);
         return true;
       case "Use Existing":
         return true;
@@ -149,27 +149,40 @@ Restarting the server will interrupt any existing debug sessions and start a new
     this.cleanUp(this.serverProcess);
   }
 
-  cleanUp(process: child_process.ChildProcessWithoutNullStreams) {
+  private cleanUp(process: child_process.ChildProcessWithoutNullStreams) {
     // If the following don't equal, then the fields have already been updated
     // (either a new process has started, or the fields were already cleaned
     // up), and so the cleanup should be skipped.
     if (this.serverProcess === process) {
       this.serverProcess = undefined;
       this.serverInfo = undefined;
+      this.serverSpawnInfo = undefined;
     }
   }
 
-  getSpawnInfo(
+  private async getSpawnInfo(
     path: string,
     args: string[],
     env: NodeJS.ProcessEnv | { [key: string]: string } | undefined,
-  ): string[] {
+  ): Promise<string[]> {
     return [
       path,
       ...args,
       ...Object.entries(env ?? {}).map(
         (entry) => String(entry[0]) + "=" + String(entry[1]),
       ),
+      `(${await this.getFileModifiedTimestamp(path)})`,
     ];
   }
+
+  private async getFileModifiedTimestamp(file: string): Promise<string | null> {
+    try {
+      if (!(await fs.pathExists(file))) {
+        return null;
+      }
+      return (await fs.promises.stat(file)).mtime.toLocaleString();
+    } catch (error) {
+      return null;
+    }
+  }
 }

"type": "npm",
"script": "watch"
}
]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove these changes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the good catch. I appreciate it.
These are unintentionally done during debug.

Comment on lines +177 to +187

private async getFileModifiedTimestamp(file: string): Promise<string | null> {
try {
if (!(await fs.pathExists(file))) {
return null;
}
return (await fs.promises.stat(file)).mtime.toLocaleString();
} catch (error) {
return null;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use the builtin method for this: vscode.workspace.createFileSystemWatcher

that will be more reliable

Copy link
Contributor Author

@royitaqi royitaqi Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(The following is probably more for my learning.)

I did a Google search and it said this:

While vscode.createFileSystemWatcher is reliable, file system watching is not always perfect and can be affected by the operating system. According to the VS Code team, the OS may decide to drop file events at any time, so there is no 100% guarantee that an event will be detected.

It seems it's because the underlying fs.watch is "not always reliable".

--

FWIW, I have a local implementation using the createFileSystemWatcher but somehow it's not triggering when I touch the lldb-dap binary. Still trying to figure out why.

The code is very simple:

      this.serverFileWatcher = vscode.workspace.createFileSystemWatcher(absoluteDapPath);
      this.serverFileWatcher.onDidChange(() => {
        this.serverFileChanged = true;
      });

The callback is never executed when I do touch <absoluteDapPath>.

It seems there is a bunch of reasons where createFileSystemWatcher might not work.

--

What reliability issue did you have in mind about the "modification time" approach?

I might have been naive and missed something important. Currently, it appears to me that checking the file modification time is better in terms of simplicity (only depend on the FS supporting modification time), debuggability (user can verify the modification time), and consistency (as long as the modification time changed, it should be detected).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. I used createFileSystemWatcher for a few extensions that are being shipped in the marketplace already and never got a complaint.
Can you share with me the correct link for a bunch of reasons and "not always reliable".? The links seem to take me to google and not actual pages where you got the info you mention
Also, could you share the snippet of your own local copy of createFileSystemWatcher?

What I believed so far is that createFileSystemWatcher is used internally by vscode to identify when files change, and because it's a crucial feature of the IDE, I assumed it was thoroughly tested in all platforms.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wrong. What my extensions were using is https://www.npmjs.com/package/chokidar
Try using that one

@royitaqi
Copy link
Contributor Author

Reverted the unintended changes in the "modification time" approach by 4b989d8. cc @walter-erquinigo, in case you think the "modification time" approach is okay to go after reading my last comment.

--

Still trying to figure out why my local createFileSystemWatcher implementation doesn't work...

royitaqi added a commit that referenced this pull request Sep 23, 2025
# Motiviation

This helps the development of the lldb-dap binary. For example, when
testing a locally built lldb-dap binary, one probably wants to restart
the server after each build in order to use the latest binary. Not doing
so leads to confusion like "why the new lldb-dap isn't doing what I just
changed?".

This patch adds dependency to the `chokidar` package. The reason is that
we need something to detect changes to the `lldb-dap` binary file and
`chokidar` appears to be the most reliable package to do so. An
alternative solution which doesn't require adding dependencies is
discussed below (solution 1).

# Two different solutions considered

Solution 1: Restart server when lldb-dap binary's modification time
changes. #159481 implements
solution 1.

Solution 2: Restart server when lldb-dap binary has changed (as detected
by a file system watcher). This patch implements solution 2 (using
`chokidar`).

# This patch (solution 2)

If the lldb-dap binary has changed, the next time the user start a debug
session, a dialog box will show up and prompt the user to restart the
server. Depend on what has changed, the dialog box will show different
content (see below)

* When both the lldb-dap binary and the arguments have changed:
<img width="520" height="357" alt="diff_args_and_binary"
src="https://github.com/%3Ca%20href="https://github.com/user-attachments/assets/6580e05f-05c3-4d80-8c1a-9c3abf279218">https://github.com/user-attachments/assets/6580e05f-05c3-4d80-8c1a-9c3abf279218"
/>

* When only the lldb-dap binary has changed:
<img width="260" height="384" alt="diff_binary"
src="https://github.com/%3Ca%20href="https://github.com/user-attachments/assets/933b8987-9c21-44f3-ad68-57b8eeb58525">https://github.com/user-attachments/assets/933b8987-9c21-44f3-ad68-57b8eeb58525"
/>

* When only the arguments have changed (existing):
<img width="520" height="343" alt="diff_args"
src="https://github.com/%3Ca%20href="https://github.com/user-attachments/assets/c0e6771c-ad32-4fb8-b5df-b34cce48ed1a">https://github.com/user-attachments/assets/c0e6771c-ad32-4fb8-b5df-b34cce48ed1a"
/>
@royitaqi royitaqi closed this Sep 23, 2025
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request Sep 23, 2025
…ged (#159797)

# Motiviation

This helps the development of the lldb-dap binary. For example, when
testing a locally built lldb-dap binary, one probably wants to restart
the server after each build in order to use the latest binary. Not doing
so leads to confusion like "why the new lldb-dap isn't doing what I just
changed?".

This patch adds dependency to the `chokidar` package. The reason is that
we need something to detect changes to the `lldb-dap` binary file and
`chokidar` appears to be the most reliable package to do so. An
alternative solution which doesn't require adding dependencies is
discussed below (solution 1).

# Two different solutions considered

Solution 1: Restart server when lldb-dap binary's modification time
changes. llvm/llvm-project#159481 implements
solution 1.

Solution 2: Restart server when lldb-dap binary has changed (as detected
by a file system watcher). This patch implements solution 2 (using
`chokidar`).

# This patch (solution 2)

If the lldb-dap binary has changed, the next time the user start a debug
session, a dialog box will show up and prompt the user to restart the
server. Depend on what has changed, the dialog box will show different
content (see below)

* When both the lldb-dap binary and the arguments have changed:
<img width="520" height="357" alt="diff_args_and_binary"
src="https://github.com/%3Ca%20href="https://github.com/user-attachments/assets/6580e05f-05c3-4d80-8c1a-9c3abf279218">https://github.com/user-attachments/assets/6580e05f-05c3-4d80-8c1a-9c3abf279218"
/>

* When only the lldb-dap binary has changed:
<img width="260" height="384" alt="diff_binary"
src="https://github.com/%3Ca%20href="https://github.com/user-attachments/assets/933b8987-9c21-44f3-ad68-57b8eeb58525">https://github.com/user-attachments/assets/933b8987-9c21-44f3-ad68-57b8eeb58525"
/>

* When only the arguments have changed (existing):
<img width="520" height="343" alt="diff_args"
src="https://github.com/%3Ca%20href="https://github.com/user-attachments/assets/c0e6771c-ad32-4fb8-b5df-b34cce48ed1a">https://github.com/user-attachments/assets/c0e6771c-ad32-4fb8-b5df-b34cce48ed1a"
/>
JDevlieghere pushed a commit to swiftlang/llvm-project that referenced this pull request Oct 14, 2025
…59797)

# Motiviation

This helps the development of the lldb-dap binary. For example, when
testing a locally built lldb-dap binary, one probably wants to restart
the server after each build in order to use the latest binary. Not doing
so leads to confusion like "why the new lldb-dap isn't doing what I just
changed?".

This patch adds dependency to the `chokidar` package. The reason is that
we need something to detect changes to the `lldb-dap` binary file and
`chokidar` appears to be the most reliable package to do so. An
alternative solution which doesn't require adding dependencies is
discussed below (solution 1).

# Two different solutions considered

Solution 1: Restart server when lldb-dap binary's modification time
changes. llvm#159481 implements
solution 1.

Solution 2: Restart server when lldb-dap binary has changed (as detected
by a file system watcher). This patch implements solution 2 (using
`chokidar`).

# This patch (solution 2)

If the lldb-dap binary has changed, the next time the user start a debug
session, a dialog box will show up and prompt the user to restart the
server. Depend on what has changed, the dialog box will show different
content (see below)

* When both the lldb-dap binary and the arguments have changed:
<img width="520" height="357" alt="diff_args_and_binary"
src="https://github.com/%3Ca%20href="https://github.com/user-attachments/assets/6580e05f-05c3-4d80-8c1a-9c3abf279218">https://github.com/user-attachments/assets/6580e05f-05c3-4d80-8c1a-9c3abf279218"
/>

* When only the lldb-dap binary has changed:
<img width="260" height="384" alt="diff_binary"
src="https://github.com/%3Ca%20href="https://github.com/user-attachments/assets/933b8987-9c21-44f3-ad68-57b8eeb58525">https://github.com/user-attachments/assets/933b8987-9c21-44f3-ad68-57b8eeb58525"
/>

* When only the arguments have changed (existing):
<img width="520" height="343" alt="diff_args"
src="https://github.com/%3Ca%20href="https://github.com/user-attachments/assets/c0e6771c-ad32-4fb8-b5df-b34cce48ed1a">https://github.com/user-attachments/assets/c0e6771c-ad32-4fb8-b5df-b34cce48ed1a"
/>

(cherry picked from commit 6007a4d)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants