{"record":{"id":"af9dfed632a4e514","repo":"dotnet/runtime","slug":"multiple-clients-in-parallel-are-not-allowed","errorCode":null,"errorMessage":"multiple clients in parallel are not allowed","messagePattern":"multiple clients in parallel are not allowed","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/native/libs/System.Native.Browser/diagnostics/diagnostic-server-js.ts","lineNumber":159,"sourceCode":"}\n\nexport function initializeJsClient() {\n    nextJsClient = dotnetLoaderExports.createPromiseCompletionSource<IDiagnosticClient>();\n    startupJsClient = undefined;\n}\n\nexport function setupJsClient(client: IDiagnosticClient, startup?: boolean) {\n    if (!startup && !dotnetLoaderExports.isRuntimeRunning()) {\n        throw new Error(\"Runtime is not running\");\n    }\n    if (startup) {\n        if (startupJsClient) {\n            throw new Error(\"startup diagnostic client already registered\");\n        }\n        startupJsClient = client;\n    } else {\n        if (nextJsClient.isDone) {\n            throw new Error(\"multiple clients in parallel are not allowed\");\n        }\n        nextJsClient.resolve(client);\n    }\n}\n\nexport function createDiagConnectionJs(socketHandle: number, scenarioName: string): DiagnosticSession {\n    if (!fromScenarioNameOnce) {\n        fromScenarioNameOnce = true;\n        if (scenarioName.startsWith(\"js://gcdump\")) {\n            collectGcDump({}, true);\n        }\n        if (scenarioName.startsWith(\"js://counters\")) {\n            collectMetrics({}, true);\n        }\n        if (scenarioName.startsWith(\"js://cpu-samples\")) {\n            collectCpuSamples({}, true);\n        }\n        const dotnetDiagnosticClient: FnClientProvider = (globalThis as any).dotnetDiagnosticClient;","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/dotnet/runtime/blob/290d5ab72cc1102813fbc0406fb186ceadabc340/src/native/libs/System.Native.Browser/diagnostics/diagnostic-server-js.ts#L141-L177","documentation":"Thrown by setupJsClient (runtime path) when nextJsClient.isDone is already true, meaning a runtime diagnostic client is already pending or active. The JS diagnostic protocol supports one client at a time per session.","triggerScenarios":"Calling collectMetrics()/collectGcDump()/collectCpuSamples() a second time while the first session is still open and its promise has not resolved.","commonSituations":"UI buttons triggering overlapping traces; forgetting to await the previous collect promise; a retry fired before the first attempt settled.","solutions":["Await the previous collect* promise before starting a new diagnostic collection.","Serialize diagnostic requests through a single-slot queue."],"exampleFix":"// before: overlapping runtime clients\ncollectMetrics();\ncollectGcDump(); // throws: parallel not allowed\n// after: await before starting the next\nawait collectMetrics();\nawait collectGcDump();","handlingStrategy":"validation","validationCode":"// Serialize runtime diagnostic collections through a single-slot lock\nlet diagInProgress = null;\nasync function runOneAtATime(fn, opts) {\n  while (diagInProgress) await diagInProgress;\n  diagInProgress = fn(opts);\n  try { return await diagInProgress; } finally { diagInProgress = null; }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Await the previous collect* promise before starting the next.","Wrap diagnostics in a queue/lock so UI cannot trigger overlapping sessions.","Disable the trigger button while a collection is in flight."],"tags":["diagnostics","concurrency","singleton"],"analyzedSha":"290d5ab72cc1102813fbc0406fb186ceadabc340","analyzedAt":"2026-08-06T19:57:01.276Z","schemaVersion":2},"datasetVersion":"2026-08-06T23:17:07.152Z"}