Skip to content
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

Support Native AOT #1348

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft

Support Native AOT #1348

wants to merge 19 commits into from

Conversation

hwoodiwiss
Copy link
Member

@hwoodiwiss hwoodiwiss commented Feb 17, 2024

This PR is a work in progress to get Native AoT working. Posting early, as I think it'll be good to get eyes on early.

AoT support as added by using a new generic serializer type and serialization factory to create it.

fixes #1347 (eventually)

Things still to do:

  • Work out properly how to get JsonSerializerOptions injected, not sure we should be adding a new dependency on Microsoft.Extensions.Options
  • Add better behaviour switching for .NET 8 builds, currenlty, .NET 8 builds will default to AOT compatible behaviour.
  • Cleanup changes made for local testing
  • Add general test coverage
  • Add some sort of testing to validate the case of AOT compiled builds
  • Bump Version (Major?)

This commit gets AoT working to the point that I can actually test it, and see it work AOT locally
@hwoodiwiss hwoodiwiss changed the title Support Native Aot Support Native AOT Feb 17, 2024
@hwoodiwiss
Copy link
Member Author

This might be a bit cleaner to integrate if we look to add .NET 8 as a first-class TFM first

Copy link

codecov bot commented Feb 17, 2024

Codecov Report

Attention: Patch coverage is 49.36709% with 40 lines in your changes missing coverage. Please review.

Project coverage is 78.70%. Comparing base (54551ab) to head (b74891d).
Report is 149 commits behind head on main.

Files with missing lines Patch % Lines
...MessageSerialization/SystemTextJsonSerializer`1.cs 48.21% 26 Missing and 3 partials ⚠️
src/JustSaying/Extensions/JsonElementExtensions.cs 0.00% 7 Missing ⚠️
...g/Fluent/ServiceResolver/DefaultServiceResolver.cs 33.33% 1 Missing and 1 partial ⚠️
...njection.Microsoft/IServiceCollectionExtensions.cs 0.00% 1 Missing ⚠️
...erialization/SystemTextJsonSerializationFactory.cs 66.66% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1348      +/-   ##
==========================================
- Coverage   79.43%   78.70%   -0.74%     
==========================================
  Files         138      142       +4     
  Lines        3234     3306      +72     
  Branches      450      462      +12     
==========================================
+ Hits         2569     2602      +33     
- Misses        434      468      +34     
- Partials      231      236       +5     
Flag Coverage Δ
linux 78.70% <49.36%> (-0.74%) ⬇️
macos 47.88% <48.10%> (?)
windows 47.79% <48.10%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@hwoodiwiss hwoodiwiss added enhancement dependencies Pull requests that update a dependency file .NET Pull requests that update .net code labels Feb 17, 2024
@martincostello
Copy link
Member

  • Add some sort of testing to validate the case of AOT compiled builds

We use this test project in Polly. In theory compiling it with the assemblies trim-rooted is enough to flush out issues that the analysers can't detect.

Bump Version (Major?)

Unless we have to break stuff, I'd class it just as a minor. I'd a minor planned for #1335 once I've validated it internally, so depending on how long both take to complete, we could have either an 8.2 then an 8.3, or ship them together as 8.2.

public override string ToString()
#if NET8_0_OR_GREATER
=> System.Text.Json.JsonSerializer.Serialize(this, JustSayingSerializationContext.Default.RedrivePolicy);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this might be problematic for AoT in case it's a derived type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's a good point, I think it'll end up serializing the base fields, but none added by the derived type.
At the moment, RedrivePolicy is only used internally, and I can't see a way that an API user could derive it and pass it through to any internal usage, but I could be missing it.

If that is the case, I don't think it would be a bad idea to internal sealed it.

Comment on lines 50 to 56
if (RuntimeFeature.IsDynamicCodeSupported)
{
#pragma warning disable IL2026
#pragma warning disable IL3050
return new NewtonsoftSerializationFactory();
#pragma warning restore
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slightly surprised it isn't smart enough to not warn about this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll check, I think I had the check backwards when I added the #pragma warning disable's

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, even with that fixed, it still warns.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've found an issue related to this: dotnet/runtime#97273, looks like the supression in runtime checks just didn't make it into .NET 8, not sure if they plan to backport either.

It also looks like there are plans for a wider design proposal to support feature checks suppressing analyzers: dotnet/runtime#96859

Comment on lines 73 to 74
var dataType = obj.Value.GetProperty("Type").GetString();
var dataValue = obj.Value.GetProperty("Value").GetString();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make these defensive against the possibility of the properties not being there so we don't NRE.


namespace JustSaying.Messaging.MessageSerialization;

public class TypedSystemTextJsonSerializationFactory(JsonSerializerOptions options) : IMessageSerializationFactory
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the biggest fan of the Typed prefix.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, same, I was at the "get it working at any cost" stage by then. I'll try and think of something a bit sleaker, or merge this back in with the current STJSerializationFactory, and have it use the typed serializer when dynamic code isn't supported.

If we go this way, in the next breaking version, I think it'll make sense to deprecate the non-typed serializer, and to that end, maybe decorate it now.

var typeInfo = options.GetTypeInfo(typeof(T));
if (typeInfo is not JsonTypeInfo<T> genericTypeInfo)
{
throw new JsonException($"Could not find type info for the specified type {typeof(T).Name}");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could probably be a bit more informative.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It ends up being redundant anyway tbh, GetTypeInfo(Type) throws if it can't find the type info anyway.

@hwoodiwiss
Copy link
Member Author

Unless we have to break stuff, I'd class it just as a minor. I'd a minor planned for #1335 once I've validated it internally, so depending on how long both take to complete, we could have either an 8.2 then an 8.3, or ship them together as 8.2.

That makes sense. It makes sense to me that we should add the net8.0 tfm before this, in part just to reduce the overall size/complexity of this change, so maybe we could do:
8.2 - Batch Publish and net8.0 tfm
8.3 - AOT Support if it doesn't require breaking changes.

(Pushing rather than stashing)
@hwoodiwiss hwoodiwiss mentioned this pull request Feb 17, 2024
@slang25
Copy link
Member

slang25 commented Feb 18, 2024

This is looking very nice 🙂 I had looked at this a while back in a local branch and the thing that I was concerned about was the JsonStringEnumConverter behaviour, which in an AOT environment will need to be added to the source generated serializer per-enum using JsonStringEnumConverter<TEnum>, which is fine but I think something that will be easily missed by consumers.

@hwoodiwiss
Copy link
Member Author

This is looking very nice 🙂 I had looked at this a while back in a local branch and the thing that I was concerned about was the JsonStringEnumConverter behaviour, which in an AOT environment will need to be added to the source generated serializer per-enum using JsonStringEnumConverter<TEnum>, which is fine but I think something that will be easily missed by consumers.

Yeah, I'd agree, I think this is a known enough sharp edge of AOT compilation, that we could just guard the addition of the default JsonStringEnumConverter with RuntimeFeature.IsDynamicCodeSupported, I'm not sure if there's a better way that we could surface a warning around this to API consumers.

@slang25
Copy link
Member

slang25 commented Feb 18, 2024

Another thought I had while browsing this change, now might be the time to create separate interfaces and implementation for the "message envelope" and message serializer/deserializer.

@hwoodiwiss
Copy link
Member Author

Another thought I had while browsing this change, now might be the time to create separate interfaces and implementation for the "message envelope" and message serializer/deserializer.

Yeah, I had originally thought this change would require doing that anyway but found a way to avoid it.
I think I'd prefer a v1 of AoT support to not alter the public API, at the cost of ergonomics for the AoT use-case, then we can look at building first-class support as part of the API changes that come for v8.

#if NET8_0_OR_GREATER
[RequiresUnreferencedCode(Constants.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(Constants.SerializationDynamicCodeMessage)]
#endif
public class NewtonsoftSerializer : IMessageSerializer
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an assumption at the moment. Newtonsoft.Json isn't currently annotated for AOT/Trimming compatibility.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dependencies Pull requests that update a dependency file enhancement .NET Pull requests that update .net code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Native AOT Compatibility
3 participants