microsoft/aspire · error · InvalidOperationException

Failed to register cancellation token with ID

Error message

Failed to register cancellation token with ID '{id}'

What it means

CancellationTokenRegistry.Create generates an id (ct_N) and a fresh CancellationTokenSource, then atomically inserts them into a ConcurrentDictionary. If TryAdd fails — meaning a token with that same id is somehow already registered — it disposes the CTS and throws this InvalidOperationException. Under normal operation the incrementing counter makes collisions impossible, so this is a defensive invariant check against registry corruption or reuse.

Solutions

  1. Do not manipulate the registry's counter or dictionary from outside; create tokens only via Create/CreateLinked.
  2. Replace the registry instance (or recreate it) if its internal state has been corrupted.
  3. Verify no custom code adds 'ct_*' keys directly into the underlying ConcurrentDictionary.
  4. If seen after patching assemblies, restore the stock CancellationTokenRegistry implementation.
Defensive patterns

Strategy: try-catch

Try / catch

try { var (id, token) = registry.Create(); }
catch (InvalidOperationException ex) when (ex.Message.StartsWith("Failed to register cancellation token"))
{ log.LogError(ex, "Cancellation registry state corrupted; recreating registry"); registry = new CancellationTokenRegistry(); throw; }

Prevention

When it happens

Trigger: Calling CancellationTokenRegistry.Create when _sources already contains the generated ct_N id — only possible if the counter and dictionary got out of sync (e.g. a custom subclass reseeded the counter, or entries were manually injected).

Common situations: Practically never hit in production; seen when tests or tools manipulate registry internals, or when a patched/replaced registry implementation breaks the monotonic-counter assumption.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/9991c3e1ea8eeaa3. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting.RemoteHost/CancellationTokenRegistry.cs:35

    private int _counter;
    private bool _disposed;

    /// <summary>
    /// Creates a new CancellationTokenSource and returns its ID and token.
    /// The ID can be passed to the guest, which can use it to cancel the token.
    /// </summary>
    /// <returns>A tuple of (tokenId, cancellationToken).</returns>
    public (string TokenId, CancellationToken Token) Create()
    {
        ObjectDisposedException.ThrowIf(_disposed, this);

        var id = $"ct_{Interlocked.Increment(ref _counter)}";
        var cts = new CancellationTokenSource();

        if (!_sources.TryAdd(id, cts))
        {
            cts.Dispose();
            throw new InvalidOperationException($"Failed to register cancellation token with ID '{id}'");
        }

        return (id, cts.Token);
    }

    /// <summary>
    /// Creates a new CancellationTokenSource linked to an existing token.
    /// This is useful when you need to combine multiple cancellation sources.
    /// </summary>
    /// <param name="linkedToken">The token to link to.</param>
    /// <returns>A tuple of (tokenId, cancellationToken).</returns>
    public (string TokenId, CancellationToken Token) CreateLinked(CancellationToken linkedToken)
    {
        ObjectDisposedException.ThrowIf(_disposed, this);

        var id = $"ct_{Interlocked.Increment(ref _counter)}";
        var cts = CancellationTokenSource.CreateLinkedTokenSource(linkedToken);

View on GitHub (pinned to 25830f84bd)