Description
Description
TypeBuilder
could throw a random exception when multiple instances, that are defined in the same ModuleBuilder
, are generated in different threads. It seems like a dynamic module itself got corrupted. It happens on a fresh instance only, meaning in the code sample below, increasing a number of iteration doesn't increase probability of an error, if the root module is not re-initialized at the beginning.
Reproduction Steps
using System;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading.Tasks;
public class TestBase
{
public readonly TextReader reader;
public TestBase(TextReader reader)
{
this.reader = reader;
}
}
public interface ITest
{
string Test(int num);
}
public class Program
{
private static object syncroot = new object();
private const int N = 10;
public static void Main()
{
try
{
for (int step = 0; ; step++)
{
var assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("test" + step.ToString()), AssemblyBuilderAccess.Run);
var module = assembly.DefineDynamicModule("test" + step++.ToString());
var types = new Type[N];
Console.Write(step.ToString() + '\r');
Parallel.For(0, N, (i) =>
{
TypeBuilder tb;
lock (syncroot)
{
tb = module.DefineType("test_" + i.ToString(), TypeAttributes.Public, typeof(TestBase), new[] { typeof(ITest) });
}
MethodBuilder mb;
// The following lock sections commented are enough to workaroud the issue.
// Basically an error occurs when a new assembly reference is internally added in the assembly builder.
// Note, each thread is working with its own TypeBuilder, no shared context, except the initial DefineType call.
//lock (syncroot)
{
mb = tb.DefineMethod("Test", MethodAttributes.Public | MethodAttributes.Virtual, typeof(string), new[] { typeof(int) });
}
var il = mb.GetILGenerator();
var label = il.DefineLabel();
for (int j = 0; j < 50; j++)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldc_I4, j);
il.Emit(OpCodes.Bne_Un, label);
il.Emit(OpCodes.Ldstr, j.ToString());
il.Emit(OpCodes.Ret);
il.MarkLabel(label);
label = il.DefineLabel();
}
il.MarkLabel(label);
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Ret);
ConstructorBuilder cb;
//lock (syncroot)
{
cb = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new[] { typeof(TextReader) });
}
il = cb.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
//lock (syncroot)
{
il.Emit(OpCodes.Call, typeof(TestBase).GetConstructor(new[] { typeof(TextReader) }));
}
il.Emit(OpCodes.Ret);
types[i] = tb.CreateType();
});
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
Expected behavior
TypeBuilder instances manage their internal shared context in a thread-safe manner.
Actual behavior
Examples of exception types could be thrown, though, seems, their just indicates a corrupted state :
System.BadImageFormatException: An attempt was made to load a program with an incorrect format. (0x8007000B)
at System.Reflection.Emit.ModuleBuilder.GetMemberRefOfMethodInfo(QCallModule module, Int32 tr, RuntimeMethodHandleInternal method)
Fatal error. Internal CLR error. (0x80131506)
at System.Reflection.Emit.TypeBuilder.DefineType(System.Runtime.CompilerServices.QCallModule, System.String, Int32, System.Reflection.TypeAttributes, Int32, Int32[])
System.TypeLoadException: The signature is incorrect.
at System.Reflection.Emit.TypeBuilder.CreateTypeNoLock()
Regression?
No response
Known Workarounds
No response
Configuration
Reproduced with .Net Core 3.1, .Net 5, .Net 6
Windows 10, x64
Other information
No response