Skip to content

Instantly share code, notes, and snippets.

@brettsam
Created August 19, 2020 15:20
Show Gist options
  • Save brettsam/1376f531ff84430df109a921341c3167 to your computer and use it in GitHub Desktop.
Save brettsam/1376f531ff84430df109a921341c3167 to your computer and use it in GitHub Desktop.
LoadContext Repro
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);
}
}
}
<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>
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