Skip to content

Fix deadlock caused by import 'vscode' from modules loaded via require(esm)#285417

Closed
mizdra wants to merge 2 commits intomicrosoft:mainfrom
mizdra:fix-failed-to-load-vscode-module-from-cjs
Closed

Fix deadlock caused by import 'vscode' from modules loaded via require(esm)#285417
mizdra wants to merge 2 commits intomicrosoft:mainfrom
mizdra:fix-failed-to-load-vscode-module-from-cjs

Conversation

@mizdra
Copy link
Copy Markdown
Contributor

@mizdra mizdra commented Dec 30, 2025

close: #285297

Background

An extension with code like the following will cause a deadlock.

// src/extension.cjs
const vscode = require('vscode');
require('./sub.js');
exports.activate = function (context) {
  // ...
};
// src/sub.js
// The following will hang the test.
import * as vscode from 'vscode';

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.

$ gh pr checkout 285417

$ # Build vscode
$ npm install
$ git clone git@github.com:mizdra/repro-vscode-test-hang.git
$ cd repro-vscode-test-hang

$ # Edit files...
$ vim
$ git diff -U1                 
diff --git a/test/runTest.js b/test/runTest.js
index 01ac63b..99c3376 100644
--- a/test/runTest.js
+++ b/test/runTest.js
@@ -14,3 +14,3 @@ async function main() {
       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',
     });

$ npm start

Protected by Socket Firewall

> repro-vscode-test-hang@1.0.0 start
> node test/runTest.js

npm warn Unknown project config "disturl". This will stop working in the next major version of npm.
npm warn Unknown project config "target". This will stop working in the next major version of npm.
npm warn Unknown project config "ms_build_id". This will stop working in the next major version of npm.
npm warn Unknown project config "runtime". This will stop working in the next major version of npm.
npm warn Unknown project config "build_from_source". This will stop working in the next major version of npm.
npm warn Unknown project config "timeout". This will stop working in the next major version of npm.

> code-oss-dev@1.108.0 electron
> node build/lib/electron.ts

[21:57:55] Synchronizing built-in extensions...
[21:57:55] You can manage built-in extensions with the --builtin flag
[21:57:55] [github] ms-vscode.js-debug-companion@1.1.3 ✔︎
[21:57:55] [github] ms-vscode.js-debug@1.105.0 ✔︎
[21:57:55] [github] ms-vscode.vscode-js-profile-table@1.0.10 ✔︎
[main 2025-12-30T12:57:55.974Z] [CSS_DEV] DONE, 283 css modules (52ms)
[main 2025-12-30T12:57:56.178Z] update#setState disabled
Started local extension host with pid 81065.
No default agent registered
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:

1 !== 2

	at file:///Users/mizdra/src/github.com/mizdra/repro-vscode-test-hang/test/index.test.js:6:8
...

// 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.
Copy link
Copy Markdown
Contributor Author

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`.
Copy link
Copy Markdown
Contributor Author

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.

@mizdra mizdra marked this pull request as ready for review December 30, 2025 13:29
@mizdra
Copy link
Copy Markdown
Contributor Author

mizdra commented Dec 30, 2025

The review is ready. Could you please review it? @mjbvz

@mizdra
Copy link
Copy Markdown
Contributor Author

mizdra commented Feb 8, 2026

Is there anything I should do to get this Pull Request accepted? Since @vscode/test-cli internally uses require(esm), I believe many users are potentially affected by this bug. I'm happy to help since it should benefit a lot of people.

To aid understanding of the issue, here's a bit more detail on how to reproduce it. While I mentioned that the deadlock can be reproduced with https://github.com/mizdra/repro-vscode-test-hang, that is not the minimal reproduction code. This issue can be reproduced without using @vscode/test-electron. The truly minimal reproduction code is found at https://github.com/mizdra/repro-vscode-extension-hang. I recommend reviewing that first. It's also a good idea to use https://github.com/mizdra/repro-vscode-extension-hang when verifying whether a fix resolves the issue.

@ejfasting
Copy link
Copy Markdown

Any way to get this reviewed @jrieken? Would be a nice improvement :)

Copilot AI review requested due to automatic review settings April 30, 2026 08:24
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Fixes a deadlock when an extension is loaded via require(esm) and then performs an ESM import 'vscode' by switching from a custom loader-thread approach to module.registerHooks.

Changes:

  • Replaces the ESM loader-thread interceptor with a module.registerHooks-based interceptor for resolving import 'vscode'.
  • Removes MessageChannel-based cross-thread resolution and inlines resolution logic in-process.
  • Updates ExtHost initialization comments to reflect the new interception approach.
Show a summary per file
File Description
src/vs/workbench/api/node/extHostExtensionService.ts Reworks ESM interception to use module.registerHooks to avoid loader-thread deadlocks when importing vscode.

Copilot's findings

  • Files reviewed: 1/1 changed files
  • Comments generated: 4

Comment thread src/vs/workbench/api/node/extHostExtensionService.ts
Comment thread src/vs/workbench/api/node/extHostExtensionService.ts
Comment thread src/vs/workbench/api/node/extHostExtensionService.ts
Comment thread src/vs/workbench/api/node/extHostExtensionService.ts
@jrieken jrieken added this to the 1.119.0 milestone Apr 30, 2026
@jrieken
Copy link
Copy Markdown
Member

jrieken commented Apr 30, 2026

continues in #313500. Not ideal that the agent created a new PR, but your OG commit is in there and this PR will get "thank you'd" by our tools. And hereby also by me: Thanks for this contribution, this wasn't a trivial find nor fix

@jrieken jrieken closed this Apr 30, 2026
@mizdra mizdra deleted the fix-failed-to-load-vscode-module-from-cjs branch April 30, 2026 14:52
@mizdra
Copy link
Copy Markdown
Contributor Author

mizdra commented Apr 30, 2026

Thank you for the review as well. It must have been very challenging to understand the background and the fix for this issue. I am glad you accepted my fix and refined it further.

@mizdra
Copy link
Copy Markdown
Contributor Author

mizdra commented May 6, 2026

It looks like this fix was released in version 1.119. Thank you for releasing it.

By the way, will this change be included in the release notes? Since it’s a bug fix, I thought it might be listed at https://code.visualstudio.com/updates/v1_119#_thank-you, but it doesn’t seem to be there.

If the omission is intentional, that’s fine. If it’s not intentional, should I ask someone to add it?

@ntrogh
Copy link
Copy Markdown
Contributor

ntrogh commented May 7, 2026

@mizdra Thanks for the contribution! Adding it to the 1.119 release notes - should be live later today.
Ref: microsoft/vscode-docs#9747

@mizdra
Copy link
Copy Markdown
Contributor Author

mizdra commented May 7, 2026

@ntrogh Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Using require(esm) breaks the mechanism for intercepting the vscode module

6 participants