mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-17 04:34:21 +00:00
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.
83 lines
2.4 KiB
TypeScript
83 lines
2.4 KiB
TypeScript
import * as vscode from "vscode"
|
|
import { McpHub } from "./McpHub"
|
|
import { ClineProvider } from "../../core/webview/ClineProvider"
|
|
|
|
/**
|
|
* Singleton manager for MCP server instances.
|
|
* Ensures only one set of MCP servers runs across all webviews.
|
|
*/
|
|
export class McpServerManager {
|
|
private static instance: McpHub | null = null
|
|
private static readonly GLOBAL_STATE_KEY = "mcpHubInstanceId"
|
|
private static providers: Set<ClineProvider> = new Set()
|
|
private static initializationPromise: Promise<McpHub> | null = null
|
|
|
|
/**
|
|
* Get the singleton McpHub instance.
|
|
* Creates a new instance if one doesn't exist.
|
|
* Thread-safe implementation using a promise-based lock.
|
|
*/
|
|
static async getInstance(context: vscode.ExtensionContext, provider: ClineProvider): Promise<McpHub> {
|
|
// Register the provider
|
|
this.providers.add(provider)
|
|
|
|
// If we already have an instance, return it
|
|
if (this.instance) {
|
|
return this.instance
|
|
}
|
|
|
|
// If initialization is in progress, wait for it
|
|
if (this.initializationPromise) {
|
|
return this.initializationPromise
|
|
}
|
|
|
|
// Create a new initialization promise
|
|
this.initializationPromise = (async () => {
|
|
try {
|
|
// Double-check instance in case it was created while we were waiting
|
|
if (!this.instance) {
|
|
this.instance = new McpHub(provider)
|
|
// Store a unique identifier in global state to track the primary instance
|
|
await context.globalState.update(this.GLOBAL_STATE_KEY, Date.now().toString())
|
|
}
|
|
return this.instance
|
|
} finally {
|
|
// Clear the initialization promise after completion or error
|
|
this.initializationPromise = null
|
|
}
|
|
})()
|
|
|
|
return this.initializationPromise
|
|
}
|
|
|
|
/**
|
|
* Remove a provider from the tracked set.
|
|
* This is called when a webview is disposed.
|
|
*/
|
|
static unregisterProvider(provider: ClineProvider): void {
|
|
this.providers.delete(provider)
|
|
}
|
|
|
|
/**
|
|
* Notify all registered providers of server state changes.
|
|
*/
|
|
static notifyProviders(message: any): void {
|
|
this.providers.forEach((provider) => {
|
|
provider.postMessageToWebview(message).catch((error) => {
|
|
console.error("Failed to notify provider:", error)
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Clean up the singleton instance and all its resources.
|
|
*/
|
|
static async cleanup(context: vscode.ExtensionContext): Promise<void> {
|
|
if (this.instance) {
|
|
await this.instance.dispose()
|
|
this.instance = null
|
|
await context.globalState.update(this.GLOBAL_STATE_KEY, undefined)
|
|
}
|
|
this.providers.clear()
|
|
}
|
|
}
|