microsoft/semantic-kernel · error · InvalidOperationException
Runtime is already stopping.
Error message
Runtime is already stopping.
What it means
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.
Source
Thrown at dotnet/src/Agents/Runtime/InProcess/InProcessRuntime.cs:77
this._shutdownSource = new CancellationTokenSource();
this._messageDeliveryTask = Task.Run(() => this.RunAsync(this._shutdownSource.Token), cancellationToken);
return Task.CompletedTask;
}
/// <summary>
/// Stops the runtime service.
/// </summary>
/// <param name="cancellationToken">Token to propagate when stopping the runtime.</param>
/// <returns>A task representing the asynchronous operation.</returns>
/// <exception cref="InvalidOperationException">Thrown if the runtime is in the process of stopping.</exception>
public Task StopAsync(CancellationToken cancellationToken = default)
{
if (this._shutdownSource != null)
{
if (this._finishSource != null)
{
throw new InvalidOperationException("Runtime is already stopping.");
}
this._finishSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
this._shutdownSource.Cancel();
}
return Task.CompletedTask;
}
/// <summary>
/// This will run until the message queue is empty and then stop the runtime.
/// </summary>
public async Task RunUntilIdleAsync()
{
Func<bool> oldShouldContinue = this._shouldContinue;
this._shouldContinue = () => !this._messageDeliveryQueue.IsEmpty;
View on GitHub (pinned to c028a0c7dc)
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.
Example fix
// before await Task.WhenAll(runtime.StopAsync(), runtime.StopAsync()); // second throws // after await runtime.StopAsync(); await runtime.RunUntilIdleAsync();
Defensive patterns
Strategy: validation
Validate before calling
bool stopping = false;
void SafeStop(InProcessRuntime rt)
{
if (stopping) return;
try { rt.StopAsync(); stopping = true; }
catch (InvalidOperationException) { /* already stopping */ }
} Try / catch
try { runtime.StopAsync(); }
catch (InvalidOperationException) { /* already stopping */ } Prevention
- Stop the runtime from a single ownership path.
- Do not call StopAsync concurrently with DisposeAsync.
- Prefer DisposeAsync for full teardown.
When it happens
Trigger: Calling StopAsync twice in quick succession; concurrent shutdown from multiple threads/tasks; calling StopAsync while RunUntilIdleAsync is still draining.
Common situations: Race between an explicit StopAsync and DisposeAsync (which calls RunUntilIdleAsync then disposes sources); multiple cancellation signals firing shutdown; finally blocks on concurrent paths.
Related errors
- Application is already stopped.
- Runtime is already running.
- Application is already running.
- Delivery of message {messageId} was cancelled.
- Agent with name {agentId.Type} is not of type {typeof(TAgent
AI-assisted analysis of microsoft/semantic-kernel@c028a0c7dc (2026-08-13).
Data as JSON: /api/errors/450d7a2c82f04f94.
Report an issue: GitHub.