{"record":{"id":"450d7a2c82f04f94","repo":"microsoft/semantic-kernel","slug":"runtime-is-already-stopping","errorCode":null,"errorMessage":"Runtime is already stopping.","messagePattern":"Runtime is already stopping\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"dotnet/src/Agents/Runtime/InProcess/InProcessRuntime.cs","lineNumber":77,"sourceCode":"        this._shutdownSource = new CancellationTokenSource();\n        this._messageDeliveryTask = Task.Run(() => this.RunAsync(this._shutdownSource.Token), cancellationToken);\n\n        return Task.CompletedTask;\n    }\n\n    /// <summary>\n    /// Stops the runtime service.\n    /// </summary>\n    /// <param name=\"cancellationToken\">Token to propagate when stopping the runtime.</param>\n    /// <returns>A task representing the asynchronous operation.</returns>\n    /// <exception cref=\"InvalidOperationException\">Thrown if the runtime is in the process of stopping.</exception>\n    public Task StopAsync(CancellationToken cancellationToken = default)\n    {\n        if (this._shutdownSource != null)\n        {\n            if (this._finishSource != null)\n            {\n                throw new InvalidOperationException(\"Runtime is already stopping.\");\n            }\n\n            this._finishSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);\n\n            this._shutdownSource.Cancel();\n        }\n\n        return Task.CompletedTask;\n    }\n\n    /// <summary>\n    /// This will run until the message queue is empty and then stop the runtime.\n    /// </summary>\n    public async Task RunUntilIdleAsync()\n    {\n        Func<bool> oldShouldContinue = this._shouldContinue;\n        this._shouldContinue = () => !this._messageDeliveryQueue.IsEmpty;\n","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/microsoft/semantic-kernel/blob/c028a0c7dc4f0814cdcbaba9d998f187a41197bf/dotnet/src/Agents/Runtime/InProcess/InProcessRuntime.cs#L59-L95","documentation":"InProcessRuntime.StopAsync throws InvalidOperationException when _finishSource is already non-null, meaning a stop is already in progress. StopAsync is idempotent only before the first stop; once _finishSource is created it refuses concurrent/duplicate stop calls.","triggerScenarios":"Calling StopAsync twice in quick succession; concurrent shutdown from multiple threads/tasks; calling StopAsync while RunUntilIdleAsync is still draining.","commonSituations":"Race between an explicit StopAsync and DisposeAsync (which calls RunUntilIdleAsync then disposes sources); multiple cancellation signals firing shutdown; finally blocks on concurrent paths.","solutions":["Call StopAsync once and await the resulting drain; do not stop concurrently.","Coordinate shutdown through a single path (e.g. one CancellationTokenSource).","Prefer DisposeAsync for teardown; it runs until idle and disposes the sources."],"exampleFix":"// before\nawait Task.WhenAll(runtime.StopAsync(), runtime.StopAsync()); // second throws\n\n// after\nawait runtime.StopAsync();\nawait runtime.RunUntilIdleAsync();","handlingStrategy":"validation","validationCode":"bool stopping = false;\nvoid SafeStop(InProcessRuntime rt)\n{\n    if (stopping) return;\n    try { rt.StopAsync(); stopping = true; }\n    catch (InvalidOperationException) { /* already stopping */ }\n}","typeGuard":null,"tryCatchPattern":"try { runtime.StopAsync(); }\ncatch (InvalidOperationException) { /* already stopping */ }","preventionTips":["Stop the runtime from a single ownership path.","Do not call StopAsync concurrently with DisposeAsync.","Prefer DisposeAsync for full teardown."],"tags":["inprocess-runtime","lifecycle","shutdown","concurrency","dotnet"],"backgroundTag":null,"analyzedSha":"c028a0c7dc4f0814cdcbaba9d998f187a41197bf","analyzedAt":"2026-08-13T13:48:05.040Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}