-
Notifications
You must be signed in to change notification settings - Fork 2.5k
fix: add timeout protection to ProviderSettingsManager initialization #8463
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
Conversation
- Add ensureInitialized() method with configurable timeout (default 10s) - Implement initialization tracking to prevent multiple init attempts - Handle initialization errors gracefully to prevent infinite retries - Update constructor to call ensureInitialized() with error handling - Update tests to handle async initialization properly This prevents the extension from freezing indefinitely during startup when the ProviderSettingsManager initialization gets stuck. Fixes #8462
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.
Self-review: performed a deadpan stare into my own constructor and found a race with a timeout having an existential crisis.
| const timeoutPromise = new Promise<never>((_, reject) => { | ||
| setTimeout(() => { | ||
| reject(new Error(`ProviderSettingsManager initialization timed out after ${timeoutMs}ms`)) | ||
| }, timeoutMs) | ||
| }) | ||
|
|
||
| const initPromise = this.initialize() | ||
|
|
||
| try { | ||
| await Promise.race([initPromise, timeoutPromise]) | ||
| this.isInitialized = true | ||
| } catch (error) { | ||
| console.error("ProviderSettingsManager initialization failed:", error) | ||
| // Set to initialized even on error to prevent infinite retries | ||
| this.isInitialized = true | ||
| throw error | ||
| } | ||
| } |
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.
[P1] Timeout promise can cause unhandled rejection and isInitialized is set true on error, allowing calls to bypass init. Suggest clearing the timer and not marking initialized on failure.
| const timeoutPromise = new Promise<never>((_, reject) => { | |
| setTimeout(() => { | |
| reject(new Error(`ProviderSettingsManager initialization timed out after ${timeoutMs}ms`)) | |
| }, timeoutMs) | |
| }) | |
| const initPromise = this.initialize() | |
| try { | |
| await Promise.race([initPromise, timeoutPromise]) | |
| this.isInitialized = true | |
| } catch (error) { | |
| console.error("ProviderSettingsManager initialization failed:", error) | |
| // Set to initialized even on error to prevent infinite retries | |
| this.isInitialized = true | |
| throw error | |
| } | |
| } | |
| let timer: ReturnType<typeof setTimeout> | undefined | |
| const timeoutPromise = new Promise<never>((_, reject) => { | |
| timer = setTimeout(() => { | |
| reject(new Error(`ProviderSettingsManager initialization timed out after ${timeoutMs}ms`)) | |
| }, timeoutMs) | |
| }) | |
| const initPromise = this.initialize() | |
| try { | |
| await Promise.race([initPromise, timeoutPromise]) | |
| this.isInitialized = true | |
| } catch (error) { | |
| console.error("ProviderSettingsManager initialization failed:", error) | |
| throw error | |
| } finally { | |
| if (timer) clearTimeout(timer) | |
| this.initializationPromise = null | |
| } |
| this.initialize().catch(console.error) | ||
| // Initialize asynchronously with timeout protection | ||
| this.ensureInitialized().catch((error) => { | ||
| console.error("ProviderSettingsManager initialization failed:", error) |
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.
[P2] Duplicate error logging and swallowed rejection. initializeWithTimeout already logs the failure; catching here logs again and swallows the rejection. Consider no-op catch to avoid duplicate logs while preventing unhandled rejection warnings.
| console.error("ProviderSettingsManager initialization failed:", error) | |
| this.ensureInitialized().catch(() => { | |
| // already logged inside initializeWithTimeout | |
| }) |
Description
This PR fixes the extension freeze issue reported in #8462 where the extension would hang indefinitely during initialization, particularly affecting Windows users with WSL2.
Problem
The ProviderSettingsManager was calling an async
initialize()method in its constructor without proper timeout handling. If the initialization process got stuck (e.g., due to I/O issues or race conditions), the extension would freeze indefinitely with a spinner.Solution
ensureInitialized()method with configurable timeout (default 10 seconds)ensureInitialized()asynchronously with error handlingTesting
Impact
This change prevents the extension from freezing during startup while maintaining backward compatibility. The initialization is now non-blocking and will timeout after 10 seconds if it gets stuck, allowing the extension to continue functioning (with potential limitations) rather than hanging indefinitely.
Fixes #8462
Important
Adds timeout protection to
ProviderSettingsManagerinitialization to prevent indefinite hangs, ensuring idempotent and error-handled startup.ensureInitialized()method with a 10-second timeout toProviderSettingsManagerto prevent indefinite hangs during initialization.ensureInitialized()asynchronously with error handling.ensureInitialized()to ensure proper initialization.initializeWithTimeout()to handle initialization with timeout logic inProviderSettingsManager.ts.initializationPromiseandisInitializedflags.ProviderSettingsManager.spec.tsto handle async initialization.This description was created by
for a0a75fe. You can customize this summary. It will automatically update as commits are pushed.