stride3d/stride · error · InvalidOperationException

Bundle is being loaded twice (either cyclic dependency or…

Error message

Bundle {0} is being loaded twice (either cyclic dependency or concurrency issue)

What it means

BundleOdbBackend.ResolveBundle tracks in-progress bundle resolutions in resolvedBundles, storing null as an 'in progress' marker. If a resolution request arrives for a bundle already marked in progress, it means a dependency cycle or re-entrant/concurrent load of the same bundle, and Stride throws InvalidOperationException.

Solutions

  1. Inspect bundle dependency lists and remove the cycle (bundle A -> B -> A).
  2. Serialize bundle loading: ensure vfsUrl / LoadBundle calls are made from one thread or guarded by an external lock during startup.
  3. Check for duplicate mounts of the same bundle under the same name and deduplicate.

Example fix

// before (two threads)
Task.Run(() => backend.LoadBundle("assets.bundle", ...));
Task.Run(() => backend.LoadBundle("assets.bundle", ...));
// after
lock (loadLock) { backend.LoadBundle("assets.bundle", ...); } // or await once, share result
Defensive patterns

Strategy: try-catch

Validate before calling

if (IsBundleResolutionInProgress(bundleName))
    throw new InvalidOperationException($"Cycle detected while resolving {bundleName}");

Try / catch

try
{
    return backend.vfsUrl(bundleName, throwExceptionIfNotFound: true);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("being loaded twice"))
{
    // break the cycle or serialize access, then retry
    breakDependencyCycle(bundleName);
    return null;
}

Prevention

When it happens

Trigger: vfsUrl is called for a bundle whose resolution has already started but not finished — i.e. bundle A depends on B and B (directly or transitively) depends on A, or two threads simultaneously request vfsUrl for the same bundle name outside the lock.

Common situations: Cyclic .bundle dependencies authored in a build script; multiple threads/clients hitting the same virtual file system URL concurrently during startup.

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 stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/876adcf4479a623b. Report an issue: GitHub.

Appendix: source

Thrown at sources/core/Stride.Core.Serialization/Storage/BundleOdbBackend.cs:94

    private Task<string?> DefaultBundleResolve(string bundleName)
    {
        // Try to find [bundleName].bundle
        var bundleFile = VirtualFileSystem.Combine(BundleDirectory, bundleName + BundleExtension);
        if (VirtualFileSystem.FileExists(bundleFile))
            return Task.FromResult<string?>(bundleFile);

        return Task.FromResult<string?>(null);
    }

    private async Task<string> ResolveBundle(string bundleName, bool throwExceptionIfNotFound)
    {
        string? bundleUrl;
        lock (resolvedBundles)
        {
            if (resolvedBundles.TryGetValue(bundleName, out bundleUrl))
            {
                if (bundleUrl == null)
                    throw new InvalidOperationException(string.Format("Bundle {0} is being loaded twice (either cyclic dependency or concurrency issue)", bundleName));
                return bundleUrl;
            }

            // Store null until resolved (to detect cyclic dependencies)
            resolvedBundles[bundleName] = null;
        }

        if (BundleResolve != null)
        {
            // Iterate over each handler and find the first one that returns non-null result
            foreach (var bundleResolvedHandler in BundleResolve.GetInvocationList().Cast<BundleResolveDelegate>())
            {
                // Use handler to resolve package
                bundleUrl = await bundleResolvedHandler(bundleName);
                if (bundleUrl != null)
                    break;
            }
        }

View on GitHub (pinned to 96fad776d2)