-
-
Notifications
You must be signed in to change notification settings - Fork 672
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
AOT by default #1743
AOT by default #1743
Conversation
- Always source generate formatters for `[MessagePackObject]`-annotated types, even if no resolver partial class is declared. - Always source generate a resolver when formatters are generated, and declare a new assembly-level attribute that points to it for fast lookup. - Include a new resolver in the `StandardResolver` before `DynamicObjectResolver` in the chain that will discover and use the source-generated formatters via the resolver specified in the assembly-level attribute found in the assembly that declares the type to be formatted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, I understand, that's OK.
By the way, can the resolver cache on a per-assembly?
/// <summary>
/// A resolver that discovers formatters generated by <c>MessagePack.SourceGenerator</c>.
/// </summary>
public sealed class SourceGeneratedFormatterResolver : IFormatterResolver
{
/// <summary>
/// The singleton instance that can be used.
/// </summary>
public static readonly SourceGeneratedFormatterResolver Instance = new();
static readonly ConcurrentDictionary<Assembly, IFormatterResolver?> resolverCache = new();
private SourceGeneratedFormatterResolver()
{
}
/// <inheritdoc/>
public IMessagePackFormatter<T>? GetFormatter<T>() => FormatterCache<T>.Formatter;
private static class FormatterCache<T>
{
internal static readonly IMessagePackFormatter<T>? Formatter = FindPrecompiledFormatter();
private static IMessagePackFormatter<T>? FindPrecompiledFormatter()
{
var resolver = resolverCache.GetOrAdd(typeof(T).Assembly, static assembly =>
{
if (assembly.GetCustomAttributes<GeneratedAssemblyMessagePackResolverAttribute>().FirstOrDefault() is { } att)
{
return (IFormatterResolver?)att.ResolverType.GetField("Instance", BindingFlags.Public | BindingFlags.Static)?.GetValue(null);
}
return null;
});
return resolver?.GetFormatter<T>();
}
}
}
Additionally, regarding our previous discussion about ModuleInitializer,
the fact that it searches for the Assembly belonging to T
suggests that the ModuleInitializer is likely triggered at the moment <T>
is called.
Therefore, I feel it would be safe to add the following code to ResolverTemplate.tt.
[ModuleInitializer]
static void RegisterToSourceGeneratedFormatterResolver()
{
SourceGeneratedFormatterResolver.AddResolver(typeof(<#= ResolverName #>).Assembly, Instance);
}
// in SourceGeneratedFormatterResolver.cs, internal use, call from SourceGenerator ModuleInitializer
public static void AddResolver(Assembly assembly, IFormatterResolver resolver)
{
resolverCache[assembly] = resolver;
}
Yes, I can add that.
No, the module initializer would be invoked as soon as any type in the containing assembly is referenced in code -- long before any serialization may be required. And the module initializer would chain in MessagePack.dll to be loaded. |
I'm not particularly insistent on using ModuleInitializer, but I don't quite understand your explanation. Can we stop the Core's AotResolver and mixing with the AotResolver included in the generated code in the Options? CompositeResolver.Create is still being used, and this, combined with the recent SourceGeneratedFormatterResolver, is making its purpose unclear. I think this AOT by default is excellent! |
The ModuleInitializer will make every assembly that consumes MessagePack force-load MessagePack.dll as soon as that other assembly loads. That'll make it difficult for some applications (including Visual Studio) which use MessagePack to upgrade to v3, because we closely watch assembly loads and want every one to be justified. But I can't justify this assembly load because it'll happen before serialization is required.
I don't understand what you're asking here. Do you want to remove
We can avoid using
The
I'm still holding my breath on how well AOT will work, given historically it has been a source of a lot of bugs, not all of which have been fixed. I hope it'll work great. Looking up a single assembly-level attribute though won't slow things down much, and where it is cause for concern, folks can always add the line of code that switches the resolver to use directly to their source-generated one, which should eliminate all reflection. |
I understand about ModuleInitializer.
StandardAotResolver is introduced in 2.6-alpha track so not shiped yet.
SourceGeneratedFormatterResolver appears to be a completely independent Resolver, and it seems to be integrated into the DefaultResolver. Furthermore, the fact that SourceGeneratedFormatterResolver is not integrated into StandardAotResolver seems likely to cause additional confusion. |
Ah! Good point I hadn't thought of. Then yes, we could remove it. Your last comments helped clarify in my mind your reasoning, and it makes a lot of sense. I'll make the change. |
It is no longer needed, since `StandardResolver` now includes the ability to use the generated resolver automatically.
@neuecc Changes applied. |
[MessagePackObject]
-annotated types, even if no resolver partial class is declared.StandardResolver
beforeDynamicObjectResolver
in the chain that will discover and use the source-generated formatters via the resolver specified in the assembly-level attribute found in the assembly that declares the type to be formatted.Also in this PR:
Closes #1738
This will cause problems with existing code that uses
[MessagePackObject]
on types that serialize with theAllowPrivate
resolvers today, since source generated formatters do not support private access. #1745 tracks fixing that and will come as a separate pull request.