Skip to content

Commit c9be470

Browse files
committed
fix: prevent race conditions in McpServerManager singleton initialization
Added thread-safe initialization to the McpServerManager singleton pattern to prevent potential race conditions when getInstance is called concurrently. The changes include: 1. Added initializationPromise to track ongoing initialization 2. Implemented double-checked locking pattern: - First check: Return existing instance if available - Second check: Wait for any ongoing initialization - Third check: Double-check inside initialization block 3. Added proper cleanup in finally block to prevent deadlocks This ensures that: - Only one McpHub instance is ever created - Concurrent calls wait for initialization to complete - Resources are properly initialized and cleaned up - No memory leaks from incomplete initialization The fix maintains the existing functionality while making it safe for concurrent access in VS Code's multi-window environment.
1 parent ef95562 commit c9be470

File tree

1 file changed

+28
-5
lines changed

1 file changed

+28
-5
lines changed

src/services/mcp/McpServerManager.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,44 @@ export class McpServerManager {
1010
private static instance: McpHub | null = null
1111
private static readonly GLOBAL_STATE_KEY = "mcpHubInstanceId"
1212
private static providers: Set<ClineProvider> = new Set()
13+
private static initializationPromise: Promise<McpHub> | null = null
1314

1415
/**
1516
* Get the singleton McpHub instance.
1617
* Creates a new instance if one doesn't exist.
18+
* Thread-safe implementation using a promise-based lock.
1719
*/
1820
static async getInstance(context: vscode.ExtensionContext, provider: ClineProvider): Promise<McpHub> {
1921
// Register the provider
2022
this.providers.add(provider)
2123

22-
if (!this.instance) {
23-
this.instance = new McpHub(provider)
24-
// Store a unique identifier in global state to track the primary instance
25-
await context.globalState.update(this.GLOBAL_STATE_KEY, Date.now().toString())
24+
// If we already have an instance, return it
25+
if (this.instance) {
26+
return this.instance
27+
}
28+
29+
// If initialization is in progress, wait for it
30+
if (this.initializationPromise) {
31+
return this.initializationPromise
2632
}
27-
return this.instance
33+
34+
// Create a new initialization promise
35+
this.initializationPromise = (async () => {
36+
try {
37+
// Double-check instance in case it was created while we were waiting
38+
if (!this.instance) {
39+
this.instance = new McpHub(provider)
40+
// Store a unique identifier in global state to track the primary instance
41+
await context.globalState.update(this.GLOBAL_STATE_KEY, Date.now().toString())
42+
}
43+
return this.instance
44+
} finally {
45+
// Clear the initialization promise after completion or error
46+
this.initializationPromise = null
47+
}
48+
})()
49+
50+
return this.initializationPromise
2851
}
2952

3053
/**

0 commit comments

Comments
 (0)