Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Azure Functions Host appears to be looking for dll's that don't exist #5796

Open
TehWardy opened this issue Mar 16, 2020 · 15 comments
Open

Azure Functions Host appears to be looking for dll's that don't exist #5796

TehWardy opened this issue Mar 16, 2020 · 15 comments
Assignees

Comments

@TehWardy
Copy link

I initially hit this dotnet/core#4390 which in turn led me to dotnet/roslyn#42445 because i noticed this only happened in my functions project and not the rest of my stack.

The roslyn guys have suggested "quote" ...

Microsoft.CodeAnalysis is a metapackage that contains no dlls at all. instead it references all other roslyn packages. It exists as an easy way to take a dependency on a specific version of all roslyn assemblies.

Microsoft.CodeAnalysis.Common is a set of common libraries that all other roslyn packages have a dependency on. It is unlikely you ever need to reference this package directly.

From reading through the issues you posted elsewhere @TehWardy this may be an issue with the Azure Functions and which set of runtimes/dependencies it supports. I would file an issue on https://github.com/Azure/azure-functions-host

... so here I am.
Any help with this would be really appreciated as I have an urgent need to get a deployment through for an important project.

@ghost ghost assigned alrod Mar 16, 2020
@TehWardy
Copy link
Author

TehWardy commented Mar 16, 2020

Ok i've managed to pin it down to a way smaller sample of code.
I don't know if the issue is something that belongs here or in dotnet/roslyn#42445 but here's how to reproduce it ...

Install the .Net Core SDK v3.1 from the current download location on top of VS 2019.
Create a new Azure functions project targeting Core 3.0 and add a reference to the package Microsoft.CodeAnalysis.CSharp.Scripting

... I think this may happen with other stuff too since the project in my main solutoin isn't particularly complex.

but anyway I opened up the project file and replaced the reference from 3.0 to 3.1 (since for some reason the default was 3.0) ...

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <AzureFunctionsVersion>v3</AzureFunctionsVersion>
    <LangVersion>latest</LangVersion>
  </PropertyGroup>

... adding the langversion node is something of a default for me too lately (apparently VS is happier in general when I do this.

I then updated the program.cs file to this ...

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace FunctionApp1
{
    public static class Function1
    {
        [FunctionName("Function1")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;

            var result = await new ScriptRunner().Run<string>("\"Hello \" + Name", new[] { "System" }, new Args { Name = name });

            return name != null
                ? (ActionResult)new OkObjectResult(result)
                : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
        }
    }

    public class Args
    {
        public string Name { get; set; }
    }

    internal class ScriptRunner
    {
        internal static Assembly[] references;

        static ScriptRunner()
        {
            // some of the stack might not have been loaded, lets make sure we load everything so we can give a complete response
            // first grab what's loaded
            var loadedAlready = AppDomain.CurrentDomain
                    .GetAssemblies()
                    .Where(a => !a.IsDynamic)
                    .ToList();

            // then grab the bin directory
            var binDir = Assembly.GetExecutingAssembly().CodeBase
                .Replace(Assembly.GetExecutingAssembly().CodeBase.Split('/').Last(), "")
                .Replace("file:///", "")
                .Replace("/", "\\");

            // from the bin, grab our core dll files
            var stackDlls = Directory.GetFiles(binDir)
                .Select(i => i.ToLowerInvariant())
                .Where(f => f.EndsWith("dll"))
                .ToList();

            // load the missing ones

            var stackPaths = stackDlls.Where(assemblyPath => loadedAlready.All(a => a.CodeBase.ToLowerInvariant() != assemblyPath.ToLowerInvariant()));
            foreach (var assemblyPath in stackPaths)
                loadedAlready.Add(Assembly.LoadFile(assemblyPath));

            references = loadedAlready.ToArray();
        }

        public async Task<T> Run<T>(string code, string[] imports, object args)
        {
            try
            {
                var referencesNeeded = references.Where(r => r.GetExportedTypes().Any(t => imports.Contains(t.Namespace)));
                var options = ScriptOptions.Default
                    .AddReferences(referencesNeeded)
                    .WithImports(imports);

                return await CSharpScript.EvaluateAsync<T>(code, options, args, args.GetType());
            }
            catch (NullReferenceException ex)
            {
                var typeAndCall = $"(({ex.TargetSite.DeclaringType.Name})object).{ex.TargetSite.Name}";
                var data = new List<string>();

                foreach (var k in ex.Data.Keys) data.Add($"{k}: {ex.Data[k]}");

                throw new Exception(ex.Message + $"\nContext: {ex.Source}\nTarget: {typeAndCall}\n{string.Join("\n", data)}");
            }
            catch (CompilationErrorException ex)
            {
                throw new Exception($"Compilation failed:\n{ex.Message}\n{string.Join(Environment.NewLine, ex.Diagnostics)}");
            }
        }

        public Task Run(string code, string[] imports, object args) => Run<bool>(code + ";return true;", imports, args);
    }
}

... build and run this.
You should get (assuming i'm not in some weird system specific situation here) ,,, and exception that reads ...

Could not load file or assembly 'Microsoft.CodeAnalysis, Version=3.4.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'. The system cannot find the file specified.

... looking in the build output folder I can see ...

image

@TehWardy
Copy link
Author

Yeh ok it definitely appears to be a functions framework host issue, here's the same code in a console app that works ...

using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace WorkflowConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            var name = args[0];
            var result = new ScriptRunner().Run<string>("\"Hello \" + Name", new[] { "System" }, new Args { Name = name }).Result;

            Console.WriteLine(
                name != null
                    ? result
                    : "Please pass a name on the query string or in the request body"
            );

            _ = Console.ReadKey();
        }
    }

    public class Args
    { 
        public string Name { get; set; }
    }

    public class ScriptRunner
    {
        internal static Assembly[] references;

        static ScriptRunner()
        {
            // some of the stack might not have been loaded, lets make sure we load everything so we can give a complete response
            // first grab what's loaded
            var loadedAlready = AppDomain.CurrentDomain
                    .GetAssemblies()
                    .Where(a => !a.IsDynamic)
                    .ToList();

            // then grab the bin directory
            var binDir = Assembly.GetExecutingAssembly().CodeBase
                .Replace(Assembly.GetExecutingAssembly().CodeBase.Split('/').Last(), "")
                .Replace("file:///", "")
                .Replace("/", "\\");

            // from the bin, grab our core dll files
            var stackDlls = Directory.GetFiles(binDir)
                .Select(i => i.ToLowerInvariant())
                .Where(f => f.EndsWith("dll"))
                .ToList();

            // load the missing ones

            var stackPaths = stackDlls.Where(assemblyPath => loadedAlready.All(a => a.CodeBase.ToLowerInvariant() != assemblyPath.ToLowerInvariant()));
            foreach (var assemblyPath in stackPaths)
                loadedAlready.Add(Assembly.LoadFile(assemblyPath));

            references = loadedAlready.ToArray();
        }

        public async Task<T> Run<T>(string code, string[] imports, object args)
        {
            try
            {
                var referencesNeeded = references.Where(r => r.GetExportedTypes().Any(t => imports.Contains(t.Namespace)));
                var options = ScriptOptions.Default
                    .AddReferences(referencesNeeded)
                    .WithImports(imports);

                return await CSharpScript.EvaluateAsync<T>(code, options, args, args.GetType());
            }
            catch (NullReferenceException ex)
            {
                var typeAndCall = $"(({ex.TargetSite.DeclaringType.Name})object).{ex.TargetSite.Name}";
                var data = new List<string>();

                foreach (var k in ex.Data.Keys) data.Add($"{k}: {ex.Data[k]}");

                throw new Exception(ex.Message + $"\nContext: {ex.Source}\nTarget: {typeAndCall}\n{string.Join("\n", data)}");
            }
            catch (CompilationErrorException ex)
            {
                throw new Exception($"Compilation failed:\n{ex.Message}\n{string.Join(Environment.NewLine, ex.Diagnostics)}");
            }
        }

        public Task Run(string code, string[] imports, object args) => Run<bool>(code + ";return true;", imports, args);
    }
}

@alrod alrod removed their assignment Apr 3, 2020
@alrod alrod added this to the Triaged milestone Apr 3, 2020
@andrew-vdb
Copy link

image

Got this issue too

What i did simply updating these dll

    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.DurableTask" Version="2.2.1" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="3.0.11" />
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.6" />

from

  <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.DurableTask" Version="2.1.0" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="3.0.10" />
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.2" />

Doing a rollback now and see if the issue is gone

@andrew-vdb
Copy link

it works again after rolling back, i think Microsoft.NET.Sdk.Functions somewhere between 3.03 - 3.0.6 is broken

@pseabury
Copy link

Same issue after upgrading the Functions SDK beyond 3.0.2. Rolling back to 3.0.2 allows the Functions app to run properly again.

@pedromvgomes
Copy link

pedromvgomes commented Jul 28, 2020

I confirm the same. I've bumped into this issue while using version 3.0.9 of Microsoft.NET.Sdk.Functions. Changing to 3.0.2 get's it fixed.

Wondering when or if it will be fixed.

@DenisBalan
Copy link

DenisBalan commented Aug 31, 2020

Its important which version you have referenced in csproj.

For this, please have a look on tools folder from azure-functions-core-tools-3 (somewhere on your drive)
and extract file version

[System.Diagnostics.FileVersionInfo]::GetVersionInfo("Microsoft.CodeAnalysis.dll")

image

after that reference in your csproj expected version
image

And module gets referenced in runtime.

used this with codedom and cs-script (csscript for function app)

ping for @alrod to update tags.

@DenisBalan
Copy link

@TehWardy the files you see in your bin / obj directories are not the dll which will be used in runtime binding.

Azure function host decide on their own half based on version, and may override out-of-the-box libraries.

Thats why this is happening, you version is being replaced by azure function host.

@TehWardy
Copy link
Author

TehWardy commented Aug 31, 2020

General .Net etiquette

If I'm dependent on an assembly directly then I should be able to deploy that assembly with my application code and force usage of that particular version.
If the runtime wants to replace something then breaks it, that's on the runtime.

The complications as I see them

Forcing me to use specific versions of assemblies is against everything .Net is built on.
My understanding from my conversation with @brettsam over on #6512 (comment) is that The functions framework manages multiple Assembly load contexts and is therefore able to run code in each which should allow for multiple version if the framework is building everything correctly.

That said, as per that same issue I ended up over on dotnet/aspnetcore#25305 which highlighted that some packages may not report their dependencies correctly further complicating the process.

Clearly there's a lot of different things at play here but my understanding from "general advice" I receive on most tickets is that to resolve any reference issues directly reference the version of the problem assembly directly in my project and the build process should honour that.

So @DenisBalan: the bottom line

We presumably need some way that we as consumers of the Azure functions framework can reliably control or derive the versions needed explicitly by the framework to enforce the same versioning constraints on our deployments?

Or am I missing something here?

@DenisBalan
Copy link

@TehWardy this is.
Whether you accept framework’s rules and bottleneck and just do the job, or trying to knock on the ecosystem and pass their checks .

The whole process of building function involves a lot of msbuild target files, where, if you want, you could replace libraries that came out of the box with your own ones, but there is no compatibility guarantee.

There is a lot to say on what should and should not allow the host to do, but starting from the point that there is a way to deal with those issues, you just have to comply with those. IMHO.

@TehWardy
Copy link
Author

I'm not sure how that helps / what you're trying to say exactly but my take from it is ...
I'm not asking for any rules to be broken just for the tooling to enable me to clearly see what they are going to be when my code stack is deployed with it's dependencies to the cloud so I can correctly follow them or if need be make a decision that the tooling won't be informed enough to make to fix known issues in Microsoft's dependency stacking.

In some cases like my SignalR case above I can clearly do everything right and still not have it all work.
The relevant rules of course being ...

  • only 1 version of an assembly can be loaded in to an app domain
  • all dependencies must agree to a version to use".

In addition to that you're saying the rules that Azure Functions adds are something like ...

  • It'll use whatever version of it's internal stuff it wants (and I have no control over that)
  • If it and I share a dependency then we must agree on the version of that dep.

I'm simply asking if there's a means to allow for some ability to "manually resolve" or "advise" azure functions that I "prefer" a given version of something because other parts of .Net "require" that version (which I also have no control over).
When all the rules being followed clearly doesn't work as intended there has to be a fix or I end up waiting (as shown here) for months to get a working build because you guys have to change something in the framework on my behalf.

I've been told in other tickets that all .Net Core 3.x assemblies that are part of the Microsoft ecosystem should be compatible.
So if I see a package that incorrectly wants to depend on a 2.x assembly I should be able force it to use a 3.x equiv.
The build process and everything that then gets consumed on deployment should also follow that "rule".

I don't see how I'm breaking any rules here by trying to force consistency in my assembly stack when some of it isn't correctly linked on your (Microsofts) end.

I also hit issues like:

If Microsoft wants to have a framework dependency "recommendation" based on what is known to be tested and working or a "minimum version" requirement that's fine with me.
If I want to override that though I should be able to and know that the tooling will honour that because the assumption from the tool should be that I as a developer know better for some reason.

My reasoning is simple
You guys are human too, you make mistakes just as I do, packages "report incorrect information" or have inconsistencies and incorrect references. I should be able to correct that (and report that I have had to for you guys to fix in your own time) instead of having to wait weeks (or in some cases years) for you guys to fix things.

Alternative suggestion
Allow the functions framework to "do it's own thing" and "host" our code which can then have entirely it's own stack not requiring the Functions Framework to break our code as is being asked for over at #4543

@TehWardy
Copy link
Author

Ping ... to prevent bot closes¬

@edgett
Copy link

edgett commented Dec 15, 2020

This is still a problem in Microsoft.NET.Sdk.Functions 3.0.11

Microsoft.CodeAnalysis 3.8.0 works with Microsoft.NET.Sdk.Functions 3.0.2

The problem: 'Could not load file or assembly 'Microsoft.CodeAnalysis, Version=3.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.

Can be resolved in Azure Functions by referencing: Microsoft.NET.Sdk.Functions 3.0.2 instead of 3.0.>=3

Code to demo problem, this will work in Functions 3.0.2 but fail between 3.0.3 to 3.0.11, latest at time of writing:

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace MMIT.DiffResearch.DiffTestRunner
{
    public static class Function1
    {
        [FunctionName("Function1")]
        public static void Run([TimerTrigger("0 0 29 2 1", RunOnStartup = true)] TimerInfo timerInfo, ILogger log)
        {
            var bc = new BasicCSharpCompile();
            var code = bc.GetDiffCodeFromGithub();
            var assembly = bc.CompileCodeAndCreateAssembly(code);

            var diffAlgorithmClassInfo = assembly.GetType("DiffMatchPatch.diff_match_patch");
            //Create an instance of diff_match_patch.
            var instanceOf_diff_match_patch = assembly.CreateInstance(diffAlgorithmClassInfo.FullName);
            //Invoke the diff_main function on the above instance of diff_match_patch.
            var diffs = instanceOf_diff_match_patch.GetType().InvokeMember("diff_main", BindingFlags.InvokeMethod, null, instanceOf_diff_match_patch, new[] { "This is code.", "This is not code." });


            Console.WriteLine(JsonConvert.SerializeObject(diffs));
        }

        public class BasicCSharpCompile
        {

            public Assembly CompileCodeAndCreateAssembly(string code)
            {

                var parsedCSharpCode = CSharpSyntaxTree.ParseText(code);
                var dependencyInfoForCSharpCode = GetNecessaryDllsToRunCode();
                var compileOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithOverflowChecks(true).WithOptimizationLevel(OptimizationLevel.Release);
                var compileOperation = CSharpCompilation.Create("DiffPatch", new List<SyntaxTree>() { parsedCSharpCode }, references: dependencyInfoForCSharpCode, compileOptions);

                using (var compiledCodeStream = new MemoryStream())
                {

                    EmitResult compileResult = compileOperation.Emit(compiledCodeStream);

                    if (compileResult.Success)
                    {
                        compiledCodeStream.Position = 0;
                        return Assembly.Load(compiledCodeStream.ToArray());
                    }
                    else
                    {
                        throw new Exception("Error compiling code file.");
                    }
                }
            }

            //Get nesessary dependencies to run code
            IEnumerable<MetadataReference> GetNecessaryDllsToRunCode()
            {
                var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);

                IEnumerable<MetadataReference> defaultReferences = new[]
                    {
            MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.dll")),
            MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Core.dll")),
            MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")),
            MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Text.RegularExpressions.dll")),
            MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Collections.dll")),
            MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Web.HttpUtility.dll")),
            MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Linq.dll")),
			//When you have problems for no reason add this:
			MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Private.CoreLib.dll")),
        };

                return defaultReferences;
            }


            public string GetDiffCodeFromGithub()
            {
                var wc = new System.Net.WebClient();
                return wc.DownloadString("https://raw.githubusercontent.com/google/diff-match-patch/master/csharp/DiffMatchPatch.cs");
            }

        }

    }
}

@v-bbalaiagar v-bbalaiagar self-assigned this Oct 26, 2021
@v-bbalaiagar
Copy link

Hi @TehWardy , Can we know if you are seeing this issue still and requires further follow up.

@TehWardy
Copy link
Author

I no longer have this issue but there is something still about this i'm not confident is "quite right" ...

I think this got passed around ... I never really got an answer to it other than "there's some stuff you just can't do in Azure Functions when it comes to the reflection API's".
I don't really understand why / what this stuff is but I recall someone suggested a fix to my dynamic assembly loading function that resolved the issue at the time.

I think the real underlying issue here is dependency graphing across Microsoft's teams is inconsistent because Microsoft's reference policies are quite strict regarding versioning (in places) and very relaxed in others.

I find the biggest issues with this is when a project is migrated from a given .Net version to the next, the dependency tree isn't properly recomputed and VS does a decent job compared to something like VS Code of tryign to keep it all together but the net result that is that I was told "explicitly specify the version when you can", which doesn't work on dependencies of dependencies (the root cause of this issue).

@fabiocav fabiocav removed this from the Triaged milestone Jan 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants