Skip to content

FileService activateProvider('user-storage') hangs during frontend startup in 1.71 #17506

Description

@daviwil

Bug Description:

In a Theia 1.71.1 application, frontend startup hangs forever waiting on PreferenceServiceImpl.initializeProviders to settle _ready. Tracing through the runtime container:

  • PreferenceServiceImpl.initializeProviders awaits each scope provider's ready. It blocks on the User scope.
  • UserConfigsPreferenceProvider.ready is forever pending.
  • Its four section providers (settings.json, tasks.json, launch.json, extensions.json) are all AbstractResourcePreferenceProvider instances whose _ready is pending — they're stuck on await this.readPreferencesFromFile() inside doInit().
  • readPreferencesFromFile() ultimately awaits fileService.read(uri) where uri has scheme user-storage. That call awaits fileService.activateProvider('user-storage').
  • fileService.activations.get('user-storage') is a pending Deferred that never settles.

The activation deferred is resolved/rejected inside activateProvider() after WaitUntilEvent.fire(this.onWillActivateFileSystemProviderEmitter, { scheme }) resolves. So fire(...) is the hung promise — one of the listeners' event.waitUntil(...) promises never settles.

The puzzle: when probed from the running (hung) frontend, every component the listener depends on works in isolation:

// All of these succeed:
await env.getConfigDirUri();                                  // → "file:///home/runner/.theia" (~4 ms)
await userStorageContribution.getDelegate(fileService);        // service.activateProvider('file')
await userStorageContribution.createProvider(fileService);     // the exact body of the listener's IIFE

Calling fileService.registerProvider('user-storage', providerCreatedManually) even adds the provider to fileService.providers, but the original activations Deferred for 'user-storage' remains pending. The listener's event.waitUntil(...) Promise was created at startup but never settled, even though running the same logic now completes in milliseconds.

This is the dangling-promise pattern. It looks like the same family that #17334 addressed (RPC and channel-multiplex paths), but along a different code path — WaitUntilEvent.fire / Promise.all(waitables) in FileService.activateProvider.

Steps to Reproduce:

I don't have a minimal upstream-only repro yet. The hang is consistent on every reload in our setup but I haven't been able to isolate it to examples/browser. The following diagnostic snippet, evaluated against the live container of a hung frontend, demonstrates the state and the puzzle:

const c = window.theia.container;
const fs = /* the FileService instance (has .providers, .activations, .onWillActivateFileSystemProvider) */;
const ps = /* the PreferenceServiceImpl (has .ready, ._isReady, .preferenceProviders) */;
const userProvider = ps.providerProvider(PreferenceScope.User); // UserConfigsPreferenceProvider

// PreferenceService is hung:
ps._isReady;                                        // false
await Promise.race([ps.ready, new Promise(r => setTimeout(() => r('PENDING'), 500))]); // 'PENDING'

// User scope provider's ready is hung:
await Promise.race([userProvider.ready, new Promise(r => setTimeout(() => r('PENDING'), 500))]); // 'PENDING'

// All four inner section providers are pending:
for (const [uri, p] of userProvider.providers.entries()) {
  await Promise.race([p.ready, new Promise(r => setTimeout(() => r('PENDING'), 200))]); // 'PENDING' for all
}

// The activation that started it all is pending:
await Promise.race([fs.activations.get('user-storage'), new Promise(r => setTimeout(() => r('PENDING'), 500))]); // 'PENDING'

// But every component now works in isolation:
await env.getConfigDirUri();                        // → "file:///home/runner/.theia"
await userStorageContribution.createProvider(fs);   // → ProviderInstance (~4 ms)

Additional Information

  • Theia version: 1.71.1
  • Frontend: Chrome (latest stable), browser application
  • Backend: Node on Linux

State observed at the hang:

  • window.theia.application is undefinedFrontendApplication.start() is hung on a contribution's onStart.
  • fileService.providers contains vscode and file. fileService.activations contains file (resolved) and user-storage (pending forever).
  • RemoteFileSystemProvider._capabilities = 50336798 (non-zero) — the backend's getCapabilities() RPC for the file scheme completed successfully, so the basic RPC layer is healthy.
  • onWillActivateFileSystemProviderEmitter has 5 listeners registered when the hang occurs, so UserStorageContribution.registerFileSystemProviders did run before the activation was attempted.

What I ruled out:

  • PR fix(core): unbind all services in connection container on close #17384 (unbind all services in connection container on close): patched out the socket.onClose(() => connectionContainer.unbindAllAsync()) line in the running backend's default-messaging-service.js — hang persists identically.
  • Stale node_modules: verified @theia/filesystem, @theia/userstorage, @theia/preferences, @theia/core are all 1.71.1 at runtime.
  • Wrong injection of EnvVariablesServer: userStorageContribution.environments is the proper EnvVariablesServer proxy object; calling .getConfigDirUri() on it succeeds.

Hypothesis: a startup-time race where the event.waitUntil(asyncFn()) promise created during WaitUntilEvent.fire('user-storage') somehow never settles — possibly the IIFE's first await resolves to a state whose continuation never runs (microtask cancelled / detached). Same family as the dangling-promise bugs addressed by #17334, but on a different path. Note that in 1.71's refactored preferences code, AbstractResourcePreferenceProvider.doInit() now constructs FrontendPreferenceStorage, whose constructor synchronously calls fileService.watch(uri) — kicking off a fire-and-forget doWatch(...) → activateProvider(scheme) before the awaited readPreferencesFromFile() call. That doubled activation pressure during startup might be where the race is exposed.

Happy to provide more instrumented traces or test patch suggestions.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

Status
Backlog

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions