-
Notifications
You must be signed in to change notification settings - Fork 37.2k
Fix deadlock caused by import 'vscode' from modules loaded via require(esm)
#285417
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Fix deadlock caused by import 'vscode' from modules loaded via require(esm)
#285417
Conversation
| // Module loading tricks based on `module.registerHooks`. | ||
| // `module.registerHooks` is a generic interceptor that intercepts `require(...)`, `import ...`, and `import(...)`. | ||
| // However, at this time, `NodeModuleInterceptor` only intercepts `import 'vscode'` and `import('vscode')`. | ||
| // This can also intercept `require('vscode')`, but interception by `NodeModuleRequireInterceptor` takes precedence. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit confusing that only require('vscode') gets intercepted by NodeModuleRequireInterceptor. So I tried modifying it as follows to have require('vscode') intercepted by NodeModuleInterceptor.
--- a/src/vs/workbench/api/node/extHostExtensionService.ts
+++ b/src/vs/workbench/api/node/extHostExtensionService.ts
@@ -35,7 +35,7 @@ class NodeModuleRequireInterceptor extends RequireInterceptor {
const originalLoad = node_module._load;
node_module._load = function load(request: string, parent: { filename: string }, isMain: boolean) {
request = applyAlternatives(request);
- if (!that._factories.has(request)) {
+ if (!that._factories.has(request) || request === 'vscode') {
return originalLoad.apply(this, arguments);
}However, this causes an ENAMETOOLONG error.
$ cd repro-vscode-test-hang
$ # Edit files
$ vim
$ git diff
diff --git a/test/index.test.js b/test/index.test.js
index 209123b..3b8b882 100644
--- a/test/index.test.js
+++ b/test/index.test.js
@@ -1,6 +1,10 @@
import assert from 'node:assert/strict';
// The following will hang the test.
-import * as vscode from 'vscode';
+// import * as vscode from 'vscode';
+
+import { createRequire } from 'node:module';
+const require = createRequire(import.meta.url);
+const vscode = require('vscode');
assert.equal(1, 2);
diff --git a/test/runTest.js b/test/runTest.js
index 01ac63b..99c3376 100644
--- a/test/runTest.js
+++ b/test/runTest.js
@@ -12,7 +12,7 @@ async function main() {
extensionDevelopmentPath,
extensionTestsPath,
launchArgs: ['--disable-extensions'],
- // vscodeExecutablePath: '/Users/mizdra/src/github.com/microsoft/vscode/scripts/code.sh',
+ vscodeExecutablePath: '/Users/mizdra/src/github.com/microsoft/vscode/scripts/code.sh',
});
} catch (err) {
console.error(err);
$ npm start
Error: ENAMETOOLONG: name too long, open 'data:text/javascript;base64,Y29uc3QgX3ZzY29kZUluc3RhbmNlID0gZ2xvYmFsVGhpcy5fVlNDT0RFX0lNUE9SVF9WU0NPREVfQVBJKCc5MGMzNTUwMC0yNjY0LTQ2M2...(long text)...107'
at Object.readFileSync (node:fs:441:20)
at t.readFileSync (node:electron/js2c/node_init:2:11013)
at defaultLoadImpl (node:internal/modules/cjs/loader:1112:17)
at loadSource (node:internal/modules/cjs/loader:1742:20)
at Module._extensions..js (node:internal/modules/cjs/loader:1841:44)
at Module.load (node:internal/modules/cjs/loader:1448:32)
at Module._load (node:internal/modules/cjs/loader:1270:12)
at c._load (node:electron/js2c/node_init:2:17993)
at Module._load (file:///Users/mizdra/src/github.com/microsoft/vscode/out/vs/workbench/api/node/extensionHostProcess.js:68:29)
at Module.load (file:///Users/mizdra/src/github.com/microsoft/vscode/out/vs/workbench/api/node/extHostExtensionService.js:32:37)
at Module.load [as _load] (file:///Users/mizdra/src/github.com/microsoft/vscode/out/vs/workbench/api/node/proxyResolver.js:290:33)
at TracingChannel.traceSync (node:diagnostics_channel:328:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:244:24)
at Module.require (node:internal/modules/cjs/loader:1470:12)
at require (node:internal/modules/helpers:147:16)
at file:///Users/mizdra/src/github.com/mizdra/repro-vscode-test-hang/test/index.test.js:8:16
at ModuleJobSync.runSync (node:internal/modules/esm/module_job:454:37)
at ModuleLoader.importSyncForRequire (node:internal/modules/esm/loader:435:47)
at loadESMFromCJS (node:internal/modules/cjs/loader:1544:24)
at Module._compile (node:internal/modules/cjs/loader:1695:5)
at Module._extensions..js (node:internal/modules/cjs/loader:1848:10)
at Module.load (node:internal/modules/cjs/loader:1448:32)
at Module._load (node:internal/modules/cjs/loader:1270:12)
at c._load (node:electron/js2c/node_init:2:17993)
at Module._load (file:///Users/mizdra/src/github.com/microsoft/vscode/out/vs/workbench/api/node/extensionHostProcess.js:68:29)
at Module.load (file:///Users/mizdra/src/github.com/microsoft/vscode/out/vs/workbench/api/node/extHostExtensionService.js:32:37)
at Module.load [as _load] (file:///Users/mizdra/src/github.com/microsoft/vscode/out/vs/workbench/api/node/proxyResolver.js:290:33)
at TracingChannel.traceSync (node:diagnostics_channel:328:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:244:24)
at Module.require (node:internal/modules/cjs/loader:1470:12)
at require (node:internal/modules/helpers:147:16)
at Object.run (/Users/mizdra/src/github.com/mizdra/repro-vscode-test-hang/test/runner.cjs:3:3)
at file:///Users/mizdra/src/github.com/microsoft/vscode/out/vs/workbench/api/common/extHostExtensionService.js:601:42
at new Promise (<anonymous>)
at ExtHostExtensionService._doHandleExtensionTests (file:///Users/mizdra/src/github.com/microsoft/vscode/out/vs/workbench/api/common/extHostExtensionService.js:580:16)
at async ExtHostExtensionService.$extensionTestsExecute (file:///Users/mizdra/src/github.com/microsoft/vscode/out/vs/workbench/api/common/extHostExtensionService.js:558:20)For some reason, originalLoad.apply() appears to return a base64-encoded path generated by NodeModuleInterceptor instead of the module instance. This seems to be a Node.js bug.
Since I couldn't find a workaround, I decided to abandon intercepting require('vscode') with NodeModuleInterceptor.
| // `module.registerHooks` is a generic interceptor that intercepts `require(...)`, `import ...`, and `import(...)`. | ||
| // However, at this time, `NodeModuleInterceptor` only intercepts `import 'vscode'` and `import('vscode')`. | ||
| // This can also intercept `require('vscode')`, but interception by `NodeModuleRequireInterceptor` takes precedence. | ||
| // In the future, we can consider migrating all interception logic to `NodeModuleInterceptor` and removing `NodeModuleRequireInterceptor`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
module._load was able to return an object from a hook. However, hooks registered via module.registerHooks currently cannot do this. Therefore, NodeModuleInterceptor circumvents this limitation by using base64-encoded source text and NodeModuleInterceptor._vscodeImportFnName. This is a complex workaround.
Incidentally, it appears Node.js plans to implement hooks that can return objects. Once implemented, this complex workaround could be removed. Porting the logic from NodeModuleRequireInterceptor to NodeModuleInterceptor should also become easier.
|
The review is ready. Could you please review it? @mjbvz |
close: #285297
Background
An extension with code like the following will cause a deadlock.
The detailed reasons are explained at #285297 (comment). This PR attempts to fix that issue using
module.registerHooks.How to test
The test for https://github.com/mizdra/repro-vscode-test-hang can run to completion without hanging.