private async Task <Assembly?> LoadAssemblyByNameAsync(
            AssemblyName assemblyName,
            AssemblyLoaderContext?context)
        {
            if (_assemblyLoadContext == null)
            {
                return(null);
            }

            IAssemblyComparer comparer = GetAssemblyNameComparer(assemblyName);

            if (TryGetAlreadyLoadedAssembly(assemblyName, comparer, out var alreadyLoadedAssembly))
            {
                return(alreadyLoadedAssembly);
            }

            if (TryGetAlreadyLoadingAssembly(assemblyName, comparer, out var assemblyLoadingTask))
            {
                Debug.WriteLine($"Waiting for Loading Assembly '{assemblyName}'");

                return(await assemblyLoadingTask !.ConfigureAwait(false));
            }

            AssemblyLoaderContext contextScope = context == null
                ? new AssemblyLoaderContext(assemblyName)
                : context.NewScope(assemblyName);

            Assembly?assembly = await PerformAssemblyLoad(assemblyName, comparer, contextScope).ConfigureAwait(false);

            return(assembly);
        }
        public async Task <AssemblyData?> GetAssemblyDataAsync(
            AssemblyName assemblyName,
            AssemblyLoaderContext context)
        {
            if (_assemblyDataCache.TryGetValue(assemblyName, out AssemblyData? data))
            {
                return(data);
            }

            var paths = _assemblyDataLocator.GetFindPaths(assemblyName, context);

            foreach (var path in paths)
            {
                data = await GetAssemblyDataAsync(path).ConfigureAwait(false);

                if (data != null)
                {
                    _assemblyDataCache.TryAdd(assemblyName, data);

                    return(data);
                }
            }

            return(null);
        }
        private async Task <Assembly?> ResolveAssembly(
            AssemblyName assemblyName,
            IAssemblyComparer comparer,
            AssemblyLoaderContext context)
        {
            if (_assemblyLoadContext == null)
            {
                return(null);
            }

            // Try loading the assembly by name (this works when the assembly is part of the bootloader, but never used explicitly)
            Assembly?assembly = _assemblyLoadContext.Load(assemblyName);

            if (assembly != null)
            {
                return(assembly);
            }

            AssemblyData?data = await _assemblyDataProvider.GetAssemblyDataAsync(assemblyName, context).ConfigureAwait(false);

            if (data == null)
            {
                Debug.WriteLine($"Failed to resolve Assembly: '{assemblyName}'");
                return(null);
            }

            var dependencies = await ResolveDependencies(assemblyName, data, context).ConfigureAwait(false);

            if (dependencies == null)
            {
                return(null);
            }

            return(_assemblyLoadContext.Load(data));
        }
        /// <inheritdoc/>
        public IEnumerable <AssemblyLocation> GetFindPaths(
            AssemblyName assemblyName,
            AssemblyLoaderContext context)
        {
            foreach (var module in _lazyModuleNamesProvider.ModuleNameHints)
            {
                yield return(CreateAssemblyLocation($"_content/{module}/_lazy", assemblyName));
            }

            yield return(CreateAssemblyLocation($"_content/{assemblyName}/_lazy", assemblyName));

            yield return(CreateAssemblyLocation($"_framework/_bin", assemblyName));
        }
        private ICollection <AssemblyName> GetAssemblyDependencies(
            AssemblyData assemblyData,
            AssemblyLoaderContext context)
        {
            if (_assemblyLoadContext == null || assemblyData.DllBytes == null)
            {
                return(Array.Empty <AssemblyName>());
            }

            using MemoryStream dllStream = new MemoryStream(assemblyData.DllBytes);
            using PEReader peReader      = new PEReader(dllStream);
            MetadataReader mdReader = peReader.GetMetadataReader(MetadataReaderOptions.None);

            // TODO: Throw error if the loaded assembly doesn't match the version requested

            return(mdReader.AssemblyReferences
                   .Select(x => mdReader.GetAssemblyReference(x).GetAssemblyName())
                   .Except(_assemblyLoadContext.AllAssemblies.Select(a => a.GetName()), AssemblyByNameAndVersionComparer.Default)
                   .ToList());
        }
        private async Task <Assembly?> PerformAssemblyLoad(
            AssemblyName assemblyName,
            IAssemblyComparer comparer,
            AssemblyLoaderContext context)
        {
            var assemblyLoadingTaskSource = new TaskCompletionSource <Assembly?>();

            if (!TryActionRepeteadly(() => _loadingAssemblies.TryAdd(assemblyName, assemblyLoadingTaskSource.Task), 5))
            {
                throw new InvalidOperationException($"Unable to Load Assembly '{assemblyName}': Concurrency error (adding)");
            }

            Debug.WriteLine($"Loading Assembly: '{assemblyName}'");

            Assembly?assembly = await ResolveAssembly(assemblyName, comparer, context).ConfigureAwait(false);

            if (assembly != null)
            {
                Debug.WriteLine($"Loaded Assembly: '{assemblyName}'");

                foreach (var assemblyLoadCallback in _onAssemblyLoad)
                {
                    await assemblyLoadCallback.Invoke(assembly).ConfigureAwait(false);
                }
            }
            else
            {
                Debug.WriteLine($"Assembly '{assemblyName}' failed to load");
            }

            assemblyLoadingTaskSource.SetResult(assembly);

            if (!TryActionRepeteadly(() => _loadingAssemblies.TryRemove(assemblyName, out var _), 5))
            {
                throw new InvalidOperationException($"Unable to Load Assembly '{assemblyName}': Concurrency error (removing)");
            }

            return(assembly);
        }
        private async Task <ICollection <Assembly>?> ResolveDependencies(
            AssemblyName assemblyName,
            AssemblyData data,
            AssemblyLoaderContext context)
        {
            var dependencies          = GetAssemblyDependencies(data, context);
            var loadDependenciesTasks = dependencies
                                        .Select(async n => new
            {
                Name     = n,
                Assembly = await LoadAssemblyByNameAsync(n, context).ConfigureAwait(false),
            })
                                        .ToList();

            await Task.WhenAll(loadDependenciesTasks).ConfigureAwait(false);

            var resolvedAssemblies = loadDependenciesTasks
                                     .Select(t => t.Result)
                                     .ToList();

            var erroredAssemblies = resolvedAssemblies
                                    .Where(a => a.Assembly == null)
                                    .ToList();

            foreach (var missingDependency in erroredAssemblies)
            {
                Debug.WriteLine($"Dependency load failed: '{missingDependency.Name}' required by '{assemblyName}'");
            }

            if (erroredAssemblies.Any())
            {
                return(null);
            }

            return(resolvedAssemblies
                   .Select(a => a.Assembly !)
                   .ToList());
        }