Skip to content

Exception when used locally in 4.0.0. Kestrel related? #401

Description

@marklagendijk

Prerequisites

  • I have written a descriptive issue title
  • I have verified that I am running the latest version of ImageSharp.Web
  • I have verified if the problem exist in both DEBUG and RELEASE mode
  • I have searched open and closed issues to ensure it has not already been reported

ImageSharp.Web version

4.0.0

Other Six Labors packages and versions

ImageSharp 4.0.0

Environment (Operating system, version and so on)

Windows 11, Kestrel

.NET version

10.0

Description

Since 4.0.0, when running ImageSharp.Web locally or in the CI/CD pipelines the following exception is thrown intermittently. On our production system (Azure App Service, Windows) it is not thrown at all.

fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HNM646UV6KOA", Request id "0HNM646UV6KOA:00000017": An unhandled exception was thrown by the application.
      System.ArgumentException: An item with the same key has already been added. Key: autoorient
         at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
         at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
         at System.Collections.ObjectModel.KeyedCollection`2.AddKey(TKey key, TItem item)
         at System.Collections.ObjectModel.KeyedCollection`2.InsertItem(Int32 index, TItem item)
         at System.Collections.ObjectModel.Collection`1.Insert(Int32 index, T item)
         at SixLabors.ImageSharp.Web.Middleware.ImageSharpMiddlewareOptions.<>c.<.ctor>b__56_1(ImageCommandContext context)
         at SixLabors.ImageSharp.Web.Middleware.ImageSharpMiddleware.Invoke(HttpContext httpContext, Boolean retry)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)

Based on the stacktrace, it seems like the following line is causing the issue:

context.Commands.Insert(0, new KeyValuePair<string, string?>(AutoOrientWebProcessor.AutoOrient, bool.TrueString));

I have no clue why, could it be a concurrency issue?

Steps to Reproduce

Unknown at this point.

Images

Not applicable.

Activity

  1. changed the title [-]Exception when used locally[/-] [+]Exception when used locally in 4.0.0[/+] on Jun 9, 2026
  2. changed the title [-]Exception when used locally in 4.0.0[/-] [+]Exception when used locally in 4.0.0. Kestrel related?[/+] on Jun 9, 2026
  3. JimBobSquarePants commented on Jun 11, 2026

    @JimBobSquarePants
    Member

    Are you using a custom IRequestParser, custom OnParseCommandsAsync, or any middleware that branches/re-executes the pipeline for image requests?

    The default parser creates a new CommandCollection per request, and normal ASP.NET Core middleware execution should be sequential for a single request. The exception implies that the CommandCollection changed between Contains("autoorient") and Insert(...), or that the same collection/context is being reused or mutated concurrently. HttpContext itself is not thread-safe, but this does not by itself suggest Kestrel is invoking our middleware concurrently for the same request.

  4. marklagendijk commented on Jun 11, 2026

    @marklagendijk
    ContributorAuthor

    Very good analysis and description!

    Based on your reply I investigated further and I think I found the issue. In our code we use the PresetOnlyQueryCollectionRequestParser that I contributed. I looked at its code and found this:

    if (requestedPreset is not null && this.presets.TryGetValue(requestedPreset, out CommandCollection? collection))

    1. At construction the PresetOnlyQueryCollectionRequestParser parses the presets from configuration, into a Dictionary<string, CommandCollection>
    2. In ParseRequestCommands it returns the matched CommandCollection from that Dictionary.
    3. Therefore the returned CommandCollection is shared among different requests that use the same preset.
    4. It should instead return a copy / clone of the CommandCollection.
  5. marklagendijk commented on Jun 11, 2026

    @marklagendijk
    ContributorAuthor

    I tested my theory by copying the PresetOnlyQueryCollectionRequestParser and adjusting the code to:

    if (requestedPreset is not null && presets.TryGetValue(requestedPreset, out var collection))
    {
        CommandCollection copy = [];
    
        // I didn't see a better way to create a copy, so I used a loop
        foreach (var pair in collection)
            copy.Add(pair.Key, pair.Value!);
    
        return copy;
    }

    This indeed fixes the issue!

  6. JimBobSquarePants commented on Jun 11, 2026

    @JimBobSquarePants
    Member

    I think we should create a DeepClone method.

  7. marklagendijk commented on Jun 11, 2026

    @marklagendijk
    ContributorAuthor

    I think we should create a DeepClone method.

    Makes sense.
    Btw, I'm currently not able to contribute a PR for this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions