Skip to content

Extension host frozen ~14s by synchronous require() of undici llhttp on Copilot's first HTTPS request (starves other extensions / language clients at startup) #322584

Description

@rchiodo

VS Code version: Version: 1.125.1 (user setup)
Commit: fcf6047
Date: 2026-06-19T08:39:34Z
Electron: 42.2.0
ElectronBuildId: 14159160
Chromium: 148.0.7778.97
Node.js: 24.15.0
V8: 14.8.178.14-electron.0
OS: Windows_NT x64 10.0.26100
Extension: built-in copilot extension (resources/app/extensions/copilot/dist/extension.js)

Summary

During workspace startup, the extension-host main thread is frozen for ~14 seconds inside a single synchronous require() triggered by the built-in Copilot extension's first HTTPS request. Because the entire extension host shares one JS thread, this blocks all other extensions' timers, microtasks, and I/O callbacks for the duration. In my case it delayed the Pylance language client from sending its initialize request to the (already-spawned) language server by ~14–60s, making the language server appear to "hang" at startup.

Root cause

When Copilot opens its first HTTPS connection, the TLS handshake completes and undici lazily require()s its HTTP/1 parser (llhttp) from inside the connection callback. That require is a synchronous CommonJS module load (readFileSyncreadFileUtf8). On Windows the read appears to be heavily amplified by Defender real-time scanning and disk contention from other extensions spawning child processes at startup, stretching this one synchronous load to ~14s.

A synchronous require cannot yield, so for those ~14s the event loop runs zero timers/microtasks/I-O callbacks — every other extension and any language client running in the host is starved.

Evidence (CPU profile)

The startup window (0–35s) is ~95% CPU-saturated (33.4s busy / 35s). Within it there is a single contiguous 13.9s run (+7.8s → +21.7s) where readFileSync/readFileUtf8 is the leaf on the stack, and 100% of the samples in that run (8315/8315) root to the copilot extension. Representative stack (leaf → root):

readFileUtf8                         [native]
readFileSync                         [node:fs]
t.readFileSync                       [node:electron/js2c/node_init]
defaultLoadImpl / loadSource         [node:internal/modules/cjs/loader]
Module._extensions..js / Module.load [node:internal/modules/cjs/loader]
Module._load / wrapModuleLoad        [node:internal/modules/cjs/loader]
Module.require                       [node:internal/modules/cjs/loader]
require                              [resources/app/extensions/copilot/dist/extension.js]
lazyllhttp                           [node_modules/undici/lib/dispatcher/client-h1.js]
connectH1                            [node_modules/undici/lib/dispatcher/client-h1.js]
onConnectSecure                      [node:internal/tls/wrap]
TLSSocket._finishInit                [node:internal/tls/wrap]
ssl.onhandshakedone                  [node:internal/tls/wrap]
(root)

During this whole window the affected language client received only ~0.6s of CPU — i.e. it was effectively frozen, not doing async work of its own.

Impact

  • Any extension or language client sharing the extension host stalls for the duration of the synchronous load.
  • Symptom observed: a language server that has already spawned sits idle for up to ~60s before receiving initialize, looking like a hang.
  • Worse on Windows due to Defender scanning + concurrent child-process disk contention at startup.

Suggested mitigations

  1. Pre-warm undici's llhttp asynchronously (or eagerly import() it) before/at activation, off the hot connection path, so the first HTTPS request doesn't trigger a synchronous module load on the shared thread.
  2. Avoid issuing the first network request synchronously during the busiest part of startup, or defer it until after the host has settled.
  3. (Upstream) consider an async llhttp load path in undici so connectH1 doesn't require() synchronously inside the TLS callback.

Workaround for users

Adding a Microsoft Defender exclusion for the VS Code install directory noticeably reduces the synchronous read time.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions