Created
August 19, 2020 15:20
-
-
Save brettsam/1376f531ff84430df109a921341c3167 to your computer and use it in GitHub Desktop.
LoadContext Repro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Net.Http; | |
using System.Threading.Tasks; | |
using Microsoft.Azure.WebJobs; | |
using Microsoft.Azure.WebJobs.Extensions.Http; | |
using Microsoft.Extensions.Logging; | |
namespace FunctionApp75 | |
{ | |
public static class Function1 | |
{ | |
[FunctionName("Function1")] | |
public static async Task Run( | |
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequestMessage req, | |
ILogger log) | |
{ | |
string c = "hello"; | |
string[] i = new[] { "using System;" }; | |
await new ScriptRunner().Run(c, i, null); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Project Sdk="Microsoft.NET.Sdk"> | |
<PropertyGroup> | |
<TargetFramework>netcoreapp3.1</TargetFramework> | |
<AzureFunctionsVersion>v3</AzureFunctionsVersion> | |
<_FunctionsSkipCleanOutput>true</_FunctionsSkipCleanOutput> | |
</PropertyGroup> | |
<ItemGroup> | |
<PackageReference Include="Microsoft.CodeAnalysis.Scripting" Version="3.7.0" /> | |
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.9" /> | |
</ItemGroup> | |
<ItemGroup> | |
<None Update="host.json"> | |
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | |
</None> | |
<None Update="local.settings.json"> | |
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | |
<CopyToPublishDirectory>Never</CopyToPublishDirectory> | |
</None> | |
</ItemGroup> | |
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Reflection; | |
using System.Runtime.Loader; | |
using System.Threading.Tasks; | |
using Microsoft.CodeAnalysis.CSharp.Scripting; | |
using Microsoft.CodeAnalysis.Scripting; | |
namespace FunctionApp75 | |
{ | |
internal class ScriptRunner | |
{ | |
internal static Assembly[] references; | |
public ScriptRunner() | |
{ | |
try | |
{ | |
if (references == null) | |
{ | |
var loadCtx = AssemblyLoadContext.GetLoadContext(typeof(Function1).Assembly); | |
// 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 = loadCtx.Assemblies | |
.Where(a => !a.IsDynamic) | |
.ToList(); | |
// then grab the bin directory | |
var thisAssembly = typeof(Function1).Assembly; | |
var binDir = thisAssembly.Location | |
.Replace(thisAssembly.ManifestModule.Name, ""); | |
// 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 toLoad = stackDlls | |
.Where(assemblyPath => loadedAlready.All(a => a.CodeBase.ToLowerInvariant() != assemblyPath.ToLowerInvariant())) | |
.Where(a => !a.Contains("api-ms-win")) | |
.ToArray(); | |
foreach (var assemblyPath in toLoad) | |
{ | |
try | |
{ | |
var a = loadCtx.LoadFromAssemblyPath(assemblyPath); | |
loadedAlready.Add(a); | |
} | |
catch (Exception ex) | |
{ | |
} | |
} | |
references = loadedAlready.ToArray(); | |
} | |
} | |
catch (Exception ex) | |
{ | |
} | |
} | |
public async Task<T> BuildScript<T>(string code, string[] imports) | |
{ | |
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); | |
} | |
catch (Exception ex) | |
{ | |
return default; | |
} | |
} | |
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); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment