-
Notifications
You must be signed in to change notification settings - Fork 10k
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
Annotate SignalR client for native AOT #56079
Conversation
Addresses the following problems: 0. Fix up some infrastructure around TrimmingAttributes to be cleaner. - Don't need to condition when to include them. Just always include them if you need it, and the TFM checks will be done for you. - Remove duplicate, standalone attribute files 1. HubConnection and ReflectionHelper's usage of finding IAsyncEnumerable interface 2. HubConnection's usage of MakeGenericMethod when using a streaming reader (IAsyncEnumerable or ChannelReader). - The only idea I have here is to follow the same approach as we do in DependencyInjection and elsewhere, which is to check for `IsDynamicCodeSupported == false` && `IsValueType` and throw an exception. This enables people to get exceptions during F5, and not only after publishing.
Odd statement to make, do you have data here that points to this? I'd be surprised if value types were uncommon. |
public static bool IsIAsyncEnumerable(Type type) => GetIAsyncEnumerableInterface(type) is not null; | ||
|
||
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern", | ||
Justification = "The 'IAsyncEnumerable<>' Type must exist and so trimmer kept it. In which case " + |
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.
The 'IAsyncEnumerable<>' Type must exist
Is this because we explicitly reference it via typeof(IAsyncEnumerable<>)
below?
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.
Correct. This method references typeof(IAsyncEnumerable<>)
. If this method is kept, then IAsyncEnumerable<>
will be kept as well.
@@ -47,26 +47,28 @@ public static bool TryGetStreamType(Type streamType, [NotNullWhen(true)] out Typ | |||
return false; | |||
} | |||
|
|||
public static bool IsIAsyncEnumerable([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type type) |
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.
What was wrong here? Was this path not being hit by a trimmed library so the annotation was just wrong?
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.
The annotation wasn't "wrong" necessarily. The problem was that callers of this API can't guarantee statically which Types are being passed in - they use someObject.GetType()
to obtain the type. So they were getting warnings they can't address.
This method doesn't need all interfaces on the Type. It just needs to check for 1 interface - IAsyncEnumerable
. And so referencing that interface directly solves the trimming concern. The trimmer can't remove this interface, because it is being referenced directly.
@@ -914,6 +911,15 @@ private static HubMessage ApplyHeaders(HubMessage message, Dictionary<string, st | |||
return message; | |||
} | |||
|
|||
[UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode", | |||
Justification = "The 'JsonSerializer.IsReflectionEnabledByDefault' feature switch, which is set to false by default for trimmed .NET apps, ensures the JsonSerializer doesn't use Reflection.")] |
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.
What's the exception the user gets back? Is it actionable? i.e. does it mention what type caused the problem?
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.
You get 2 different exceptions, depending on exactly what you did wrong.
- If you didn't configure the source generator at all. You get:
Failed to bind arguments received in invocation '(null)' of 'ReceiveMessage'.
System.IO.InvalidDataException: Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.
---> System.InvalidOperationException: Reflection-based serialization has been disabled for this application. Either use the source generator APIs or explicitly configure the 'JsonSerializerOptions.TypeInfoResolver' property.
at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_JsonSerializerIsReflectionDisabled()
at System.Text.Json.JsonSerializerOptions.ConfigureForJsonSerializer()
at System.Text.Json.JsonSerializer.GetTypeInfo(JsonSerializerOptions options, Type inputType)
at System.Text.Json.JsonSerializer.Deserialize(Utf8JsonReader& reader, Type returnType, JsonSerializerOptions options)
at Microsoft.AspNetCore.SignalR.Protocol.JsonHubProtocol.BindTypes(Utf8JsonReader& reader, IReadOnlyList`1 paramTypes)
--- End of inner exception stack trace ---
at Microsoft.AspNetCore.SignalR.Protocol.JsonHubProtocol.BindTypes(Utf8JsonReader& reader, IReadOnlyList`1 paramTypes)
at Microsoft.AspNetCore.SignalR.Protocol.JsonHubProtocol.ParseMessage(ReadOnlySequence`1 input, IInvocationBinder binder)
Which tells you that you need to enable the source generator.
- Then once you have the source generator enabled, but didn't add the right Type to the generator, you get:
Failed to bind arguments received in invocation '(null)' of 'ReceiveMessage'.
System.IO.InvalidDataException: Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.
---> System.NotSupportedException: JsonTypeInfo metadata for type 'System.String' was not provided by TypeInfoResolver of type '[SignalRWorker.AppJsonSerializerContext]'. If using source generation, ensure that all root types passed to the serializer have been annotated with 'JsonSerializableAttribute', along with any types that might be serialized polymorphically.
at System.Text.Json.ThrowHelper.ThrowNotSupportedException_NoMetadataForType(Type type, IJsonTypeInfoResolver resolver)
at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Nullable`1 ensureNotNull, Boolean resolveIfMutable, Boolean fallBackToNearestAncestorType)
at System.Text.Json.JsonSerializerOptions.GetTypeInfoForRootType(Type type, Boolean fallBackToNearestAncestorType)
at System.Text.Json.JsonSerializer.Deserialize(Utf8JsonReader& reader, Type returnType, JsonSerializerOptions options)
at Microsoft.AspNetCore.SignalR.Protocol.JsonHubProtocol.BindTypes(Utf8JsonReader& reader, IReadOnlyList`1 paramTypes)
--- End of inner exception stack trace ---
at Microsoft.AspNetCore.SignalR.Protocol.JsonHubProtocol.BindTypes(Utf8JsonReader& reader, IReadOnlyList`1 paramTypes)
at Microsoft.AspNetCore.SignalR.Protocol.JsonHubProtocol.ParseMessage(ReadOnlySequence`1 input, IInvocationBinder binder)
Unfortunately I don't have any data here. Do you know how to get some? My opinion is that we don't try to solve this case preemptively. If/when we get feedback that it's important to support Thoughts? |
Can we make streams work by using |
From my understanding, not without a bunch of reflection code to call methods on |
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.
Consider preemptively filing an issue for value type streaming that we'll backlog waiting for feedback?
Done. #56179 |
Addresses the following problems:
JsonHubProtocolOptions.PayloadSerializerOptions
. We have to suppress the warnings here and let the runtime exception catch cases where the source generator isn't being used.IsDynamicCodeSupported == false
&&IsValueType
and throw an exception. This enables people to get exceptions during F5, and not only after publishing.Alternative approach
One idea I had to support streaming ValueTypes is to fall back to pure reflection over the
IAsyncEnumerable
andChannelReader
objects when we are in native AOT and the item is a ValueType. I didn't feel this was worth it, since the odds someone is using ValueTypes here is low. We can always implement that later, if we see a need.