Skip to content

Concurrency / Thread-Safety Issue: Setting UrlEndpoint(...) in Parallel Triggers InvalidOperationException #67

@ahnv

Description

@ahnv

A user has reported that invoking UrlEndpoint(...) on a shared ServerImagekit (or Imagekit) instance from multiple threads can result in a System.InvalidOperationException. The error indicates that a non-concurrent internal collection or state is being mutated by multiple threads simultaneously.

System.InvalidOperationException: Operations that change non-concurrent collections must have exclusive access. 
A concurrent update was performed on this collection and corrupted its state. 
The collection's state is no longer correct.
It appears the library is mutating some shared, non-thread-safe state (perhaps a collection or internal field) when UrlEndpoint(...) is invoked in parallel.

Note: Although one might typically set the endpoint once at initialization, some use cases require dynamically switching endpoints in parallel. In such scenarios, the SDK can throw the above exception.

Steps to Reproduce

  1. Create a single ServerImagekit instance with an initial endpoint.
var _imagekit = new ServerImagekit(
    "public_key_here",
    "private_key_here",
    "https://ik.imagekit.io/realedo/"
);
  1. In parallel (e.g., using Parallel.For or multiple threads), alternate calls to .UrlEndpoint(".../tera/") and .UrlEndpoint(".../another/").
  2. Generate URLs (e.g. kit.Url(...).Generate()) between these calls to simulate typical usage in a concurrent environment.

Here’s a minimal code snippet that triggers the error on many machines:

using System;
using System.Threading.Tasks;
using Imagekit; // or relevant namespace

public class Program
{
    public static void Main()
    {
        var _imagekit = new ServerImagekit("public_key", "private_key", "https://ik.imagekit.io/realedo/");
        
        Parallel.For(0, 10000, new ParallelOptions { MaxDegreeOfParallelism = 16 }, i =>
        {
            try
            {
                // Randomly switch endpoint
                if (i % 2 == 0)
                {
                    _imagekit.UrlEndpoint("https://ik.imagekit.io/realedo/tera/");
                }
                else
                {
                    _imagekit.UrlEndpoint("https://ik.imagekit.io/realedo/another/");
                }

                // Optional small delay to increase concurrency
                Task.Delay(1).Wait();

                var url = _imagekit.Url().Path($"test{i}.jpg").Generate();
                Console.WriteLine($"{i}: {url}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"{i}: {ex.GetType().Name} -> {ex.Message}");
            }
        });
        
        Console.WriteLine("Done.");
        Console.ReadKey();
    }
}

Observed Behavior

In some runs (especially with higher iteration counts and parallelism), System.InvalidOperationException is thrown, complaining about concurrent access to an internal, non-concurrent collection.

Expected Behavior

  • Either the SDK handles concurrent modifications safely (e.g., using thread-safe data structures or internal locking), or
  • The documentation explicitly states that changing UrlEndpoint(...) in parallel is unsupported, guiding users to create separate Imagekit instances or synchronize their calls.

Suggested Solutions

  1. Immutability: Make methods like UrlEndpoint(...) return a new ServerImagekit object instead of mutating the existing instance.
  2. Thread-Safe Mutations: Use lock statements or thread-safe collections internally so multiple threads can safely modify the endpoint configuration.
  3. Documentation Update: If dynamic endpoint changes aren’t intended to be thread-safe, clarify that in the docs, suggesting patterns like “create separate clients” or “lock around calls.”

Additional Context

Some users have valid scenarios requiring multiple endpoints (e.g., multi-tenant setups) and may switch them at runtime. If the SDK cannot support concurrent reconfiguration, it should at least guide developers toward safe usage patterns.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions