Last active
May 21, 2021 14:36
-
-
Save brettsam/4426f3b2b55bc2d76792fac20a0919e3 to your computer and use it in GitHub Desktop.
ProxyBuilderRepro
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.IO; | |
using System.Reflection; | |
using System.Runtime.Loader; | |
namespace ProxyBuilderRepro | |
{ | |
public class MyLoadContext : AssemblyLoadContext | |
{ | |
public MyLoadContext(string name) | |
: base(name) | |
{ | |
} | |
protected override Assembly Load(AssemblyName assemblyName) | |
{ | |
var path = Path.Combine(Directory.GetCurrentDirectory(), $"{assemblyName.Name}.dll"); | |
if (File.Exists(path)) | |
{ | |
return LoadFromAssemblyPath(path); | |
} | |
return Assembly.Load(assemblyName); | |
} | |
} | |
} |
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.Reflection; | |
namespace ProxyBuilderRepro | |
{ | |
public class MyProxy : DispatchProxy | |
{ | |
public static IMyInterface Create() | |
{ | |
return Create<IMyInterface, MyProxy>(); | |
} | |
protected override object Invoke(MethodInfo targetMethod, object[] args) | |
{ | |
return null; | |
} | |
} | |
public interface IMyInterface | |
{ | |
} | |
} |
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.Reflection; | |
using System.Runtime.Loader; | |
using System.Text; | |
namespace ProxyBuilderRepro | |
{ | |
class Program | |
{ | |
static int _count = 0; | |
static void Main(string[] args) | |
{ | |
// This highlights an issue that some WCF apps hit when running in Azure Functions. Functions uses | |
// a new AssemblyLoadContext whenever a customer makes a change that we're able to "hot-restart". | |
// The proxy generation for WCF fails the second time with an invalid cast because it is using an interface | |
// type from the previous AssemblyLoadContext. | |
var proxy1 = CreateProxyInNewContext(); | |
object proxy2 = null; | |
try | |
{ | |
// This throws an InvalidCastException: 'Unable to cast object of type 'generatedProxy_2' to type 'ProxyBuilderRepro.IMyInterface'.' | |
// It seems the ProxyBuilder uses the same Assembly to build each proxy, even though the second | |
// one is using a new AssemblyLoadContext. This means that the interfaces no longer match. | |
// Suspect: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs#L61 | |
proxy2 = CreateProxyInNewContext(); | |
} | |
catch | |
{ | |
} | |
CheckContexts(); | |
// This writes: | |
// | |
// Context: mine_0: | |
// | |
// ProxyBuilderRepro.IMyInterface(ProxyBuilderRepro), "mine_0" ProxyBuilderRepro.MyLoadContext #0 | |
// | |
// Context: mine_1: | |
// | |
// ProxyBuilderRepro.IMyInterface(ProxyBuilderRepro), "mine_1" ProxyBuilderRepro.MyLoadContext #1 | |
// | |
// Context: Default: | |
// | |
// ProxyBuilderRepro.IMyInterface(ProxyBuilderRepro), "Default" System.Runtime.Loader.DefaultAssemblyLoadContext #2 | |
// | |
// generatedProxy_1(ProxyBuilder), "Default" System.Runtime.Loader.DefaultAssemblyLoadContext #2 | |
// Interface implementations: | |
// ProxyBuilderRepro.IMyInterface(ProxyBuilderRepro), "mine_0" ProxyBuilderRepro.MyLoadContext #0 | |
// generatedProxy_2(ProxyBuilder), "Default" System.Runtime.Loader.DefaultAssemblyLoadContext #2 | |
// Interface implementations: | |
// ProxyBuilderRepro.IMyInterface(ProxyBuilderRepro), "mine_0" ProxyBuilderRepro.MyLoadContext #0 <-- This is wrong. Context should be mine_1. | |
} | |
private static object CreateProxyInNewContext() | |
{ | |
var mine = new MyLoadContext($"mine_{_count++}"); | |
var assm = mine.LoadFromAssemblyName(typeof(Program).Assembly.GetName()); | |
var type = assm.GetType("ProxyBuilderRepro.MyProxy"); | |
var create = type.GetMethod("Create", BindingFlags.Static | BindingFlags.Public); | |
return create.Invoke(null, null); | |
} | |
private static void CheckContexts() | |
{ | |
// Look for the proxies in all contexts. | |
StringBuilder sb = new StringBuilder(); | |
foreach (var context in AssemblyLoadContext.All) | |
{ | |
sb.AppendLine(); | |
sb.AppendLine($"Context: {context.Name}:"); | |
foreach (var assembly in context.Assemblies) | |
{ | |
foreach (var type in assembly.DefinedTypes) | |
{ | |
if (type.FullName.Contains("generatedProxy")) | |
{ | |
sb.AppendLine($" {type.FullName} ({assembly.GetName().Name}), {context}"); | |
sb.AppendLine(" Interface implementations:"); | |
foreach (var i in type.GetInterfaces()) | |
{ | |
var interfaceContext = AssemblyLoadContext.GetLoadContext(i.Assembly); | |
sb.AppendLine($" {i.FullName} ({i.Assembly.GetName().Name}), {interfaceContext}"); | |
} | |
} | |
if (type.Name == "IMyInterface") | |
{ | |
sb.AppendLine(); | |
sb.AppendLine($" {type.FullName} ({assembly.GetName().Name}), {context}"); | |
} | |
} | |
} | |
} | |
Console.WriteLine(sb.ToString()); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment