From 01b36e80c26b14383aacf34371c4a723afe51b17 Mon Sep 17 00:00:00 2001 From: j4rviscmd Date: Mon, 3 Aug 2026 10:40:36 +0900 Subject: [PATCH] feat: isolate EH resource separation and registry symmetry (Phase 7-A) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 7-A prerequisite for Issue #246. Separates the isolated extension host storage/logs from LocalProcess and fixes the registry symmetry bug that silently dropped isolated extensions from the snapshot — the root cause of Phase 6's activation failure. - logsLocation and workspaceStorageHome are now isolated-specific so the isolated EH never contends with LocalProcess for the same storage lock - workspaceStorageHome uses a sibling dir (workspaceStorage-isolated/) to stay outside VS Code's storage cleanup scan - --skipWorkspaceStorageLock is applied only to non-isolated EHs; the isolated EH acquires the lock normally since its storage is separated - _resolveAndProcessExtensions now includes a LocalIsolatedProcess bucket in allExtensions, so isolated-routed extensions register symmetrically with LocalProcess/LocalWebWorker/Remote Co-Authored-By: Claude --- .../electron-main/extensionHostStarter.ts | 9 +++++++- .../common/abstractExtensionService.ts | 15 +++++++++++- .../localIsolatedProcessExtensionHost.ts | 23 +++++++++++++++++-- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/extensions/electron-main/extensionHostStarter.ts b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts index e10bd7acba4..5aa125d198a 100644 --- a/src/vs/platform/extensions/electron-main/extensionHostStarter.ts +++ b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts @@ -107,7 +107,14 @@ export class ExtensionHostStarter extends Disposable implements IDisposable, IEx throw canceled(); } const extHost = this._getExtHost(id); - const args = ['--skipWorkspaceStorageLock']; + // --- Coderm start: Phase 7-A isolated EH storage lock --- + // The isolated EH runs with a separated workspaceStorageHome (set by the + // workbench side), so it can acquire the workspace storage lock normally + // instead of skipping it — giving it proper storage consistency on crash + // recovery. The LocalProcess EH keeps the skip flag to preserve upstream + // behavior, where multiple LocalProcess EHs may share one storage home. + const args = opts.kind === 'isolatedExtensionHost' ? [] : ['--skipWorkspaceStorageLock']; + // --- Coderm end --- if (this._configurationService.getValue('extensions.supportNodeGlobalNavigator')) { args.push('--supportGlobalNavigator'); } diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index b4e19e4091b..1abed79be3e 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -545,6 +545,16 @@ export abstract class AbstractExtensionService extends Disposable implements IEx const localProcessExtensions = (this._hasLocalProcess ? this._runningLocations.filterByExtensionHostKind(localExtensions, ExtensionHostKind.LocalProcess) : []); const localWebWorkerExtensions = this._runningLocations.filterByExtensionHostKind(localExtensions, ExtensionHostKind.LocalWebWorker); remoteExtensions = this._runningLocations.filterByExtensionHostKind(remoteExtensions, ExtensionHostKind.Remote); + // --- Coderm start: isolated language EH kind --- + // VS Code's _resolveAndProcessExtensions builds the registry from per-kind buckets. + // Upstream covers LocalProcess / LocalWebWorker / Remote only — without the isolated + // bucket here, isolated-routed extensions (e.g. typescript-language-features) are + // dropped from the registry. That makes ExtensionHostManager.start() receive empty + // myExtensions for the isolated EH, which overwrites the correct init data and breaks + // activation (Phase 6 / Phase 7-A root cause). Add the isolated bucket so isolated + // extensions are registered symmetrically with the other kinds. + const localIsolatedProcessExtensions = this._runningLocations.filterByExtensionHostKind(localExtensions, ExtensionHostKind.LocalIsolatedProcess); + // --- Coderm end --- // Add locally the remote extensions that need to run locally in the web worker for (const ext of remoteExtensionsThatNeedToRunLocally) { @@ -553,7 +563,10 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } } - const allExtensions = remoteExtensions.concat(localProcessExtensions).concat(localWebWorkerExtensions); + const allExtensions = remoteExtensions.concat(localProcessExtensions).concat(localWebWorkerExtensions) + // --- Coderm start: isolated language EH kind --- + .concat(localIsolatedProcessExtensions); + // --- Coderm end --- let toAdd = allExtensions; if (resolverExtensions.length) { diff --git a/src/vs/workbench/services/extensions/electron-browser/localIsolatedProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/localIsolatedProcessExtensionHost.ts index ee6ccc1c1a5..d45e2b1fede 100644 --- a/src/vs/workbench/services/extensions/electron-browser/localIsolatedProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/localIsolatedProcessExtensionHost.ts @@ -532,7 +532,21 @@ export class NativeLocalIsolatedProcessExtensionHost extends Disposable implemen extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, globalStorageHome: this._userDataProfilesService.defaultProfile.globalStorageHome, - workspaceStorageHome: this._environmentService.workspaceStorageHome, + // --- Coderm start: Phase 7-A isolated EH resource isolation --- + // Separate workspaceStorageHome so the isolated EH never contends with the + // LocalProcess EH for the same // lock + // and meta.json. Sharing that path was the strong candidate for Phase 6's + // silent activate failure (exthost.log showed 3 EHs "Skipping acquiring lock" + // on the same storage). + // + // IMPORTANT: must be a SIBLING of workspaceStorage/, not a child. VS Code's + // storage cleanup (storageDataCleaner) reclaims workspace storage directories + // it no longer considers in-use (matched by workspace.id). A child like + // workspaceStorage/isolated/ is not a recognized workspace entry and gets + // reclaimed, dropping the storage lock mid-session (lock-lost) and breaking + // activation. workspaceStorage-isolated/ sits outside that scan. + workspaceStorageHome: this._environmentService.workspaceStorageHome.with({ path: this._environmentService.workspaceStorageHome.path + '-isolated' }), + // --- Coderm end --- extensionLogLevel: this._defaultLogLevelsService.defaultLogLevels.extensions, isSessionsWindow: this._environmentService.isSessionsWindow }, @@ -565,7 +579,12 @@ export class NativeLocalIsolatedProcessExtensionHost extends Disposable implemen virtualWorkspaceExtensionTips: this._productService.virtualWorkspaceExtensionTips, logLevel: this._logService.getLevel(), loggers: [...this._loggerService.getRegisteredLoggers()], - logsLocation: this._environmentService.extHostLogsPath, + // --- Coderm start: Phase 7-A isolated EH resource isolation --- + // Separate logsLocation so the isolated EH writes logs under a dedicated + // subtree (/isolated//), mirroring the + // workspaceStorageHome split. Avoids log path races with the LocalProcess EH. + logsLocation: URI.joinPath(this._environmentService.extHostLogsPath, 'isolated'), + // --- Coderm end --- autoStart: (this.startup === ExtensionHostStartup.EagerAutoStart), uiKind: UIKind.Desktop, handle: this._environmentService.window.handle ? encodeBase64(this._environmentService.window.handle) : undefined