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

[release/6.0] 1388 xml serializer assembly load context awareness #61266

Merged
merged 15 commits into from
Jan 10, 2022

Conversation

github-actions[bot]
Copy link
Contributor

@github-actions github-actions bot commented Nov 5, 2021

Backport of #58932 to release/6.0

/cc @StephenMolloy

Customer Impact

Prior to this fix, customers could not use XmlSerializer with types that exist in collectible AssemblyLoadContexts. Customers expect to be able to use XmlSerializer in their projects as they have since the days of the full .Net Framework, but if they do, this limitation can prevent them from using a useful feature like ALC in .Net Core. This has been something that customers have been asking for for quite some time and we had intended to target 6.0 with the fix, but did not want to rush a check-in as we came up to the rtm deadline.

Testing

This PR enables new functionality which is only subtly different from the old. Mostly just a difference in which lookup tables to use. All the old XmlSerializer tests cover the old and most common expectation of working primarily in the default ALC. We have added a new test to verify XmlSerializer can work with an unloadable ALC as well. Lots of ad-hoc scenarios were tested while developing this PR and a quick check of very basic serializer performance was done on the final PR before merging in 7.0 with no obvious regression seen.

Risk

Low. The PR enables a currently-broken scenario. The much more common non-broken scenario is pretty well covered by existing tests. We are confident the PR addresses the broken scenario, but if there is a problem here we haven't identified, then the scenario is still broken... which is the same as before.

@StephenMolloy StephenMolloy added this to the 6.0.x milestone Nov 5, 2021
@StephenMolloy StephenMolloy added the Servicing-consider Issue for next servicing release review label Nov 5, 2021
@StephenMolloy
Copy link
Member

The test failures are due to a bug in coreclr that we discovered while working on this PR. It has been fixed in 7.0. I don't know if the CLR team wants to backport the fix to 6.0.

Without the CLR fix, this PR still enables the scenario of exception-free use of XmlSerializer with types in an unloadable ALC. However, the ALC in some cases will end up becoming not unloadable.

@agocke
Copy link
Member

agocke commented Nov 11, 2021

@mconnew Can you also give a risk assessment here?

@mconnew
Copy link
Member

mconnew commented Nov 11, 2021

I agree with what @StephenMolloy said. The only behavior change is for scenarios which were previously failing scenarios. The code maintains the same behavior for all existing working scenarios.

The nature of this change is very binary for existing working scenarios. It will either always work, or always won't work. If there was a mistake made, it would always fail as there are no edge cases. All our test scenarios are passing so there is very high confidence we have zero functional regression. The new enabled scenarios will have slightly poorer performance due to use of ConditionalWeakTable, but this isn't on a hot code path, only serializer instantiation. Existing working scenarios avoid the more expensive ConditionalWeakTable and continue to use the same collections so will be unaffected.

@agocke
Copy link
Member

agocke commented Nov 11, 2021

Great! Could you also review the backport?

Copy link
Member

@vitek-karas vitek-karas left a comment

Choose a reason for hiding this comment

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

Approve for servicing as the risk of the change is low.

That said, the original feature (sorry I wasn't able to review that in main) has some issues which should be fixed as it will not work correctly in some cases. But the incorrect behavior is only going to happen in the cases where it originally didn't work at all. That said, somebody might take a dependency on the incorrect behavior and it would make it harder for us to fix this in future releases.

s_xmlSerializerTable[type] = typedMappingTable;
}
}
AssemblyLoadContext? alc = AssemblyLoadContext.GetLoadContext(type.Assembly);
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't seem to be used anywhere.

Copy link
Member

Choose a reason for hiding this comment

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

Ahh, a remnant from a previous iteration where we spread logic out instead of encapsulating these repetitive patterns in our new ContextAwareTable. I will remove this line.

var obj = Activator.CreateInstance(type);
var rtobj = SerializeAndDeserialize(obj, null, () => serializer, true);
Assert.NotNull(rtobj);
Assert.True(rtobj.Equals(obj));
Copy link
Member

Choose a reason for hiding this comment

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

Worth a comment that this assumes that the tested type implements equality correctly.
I would still probably add a check that the deserialized type is from the test ALC (same type as we serialized, not just the same name).

Copy link
Member

Choose a reason for hiding this comment

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

The test I added does the right thing with Equals(). I chose the type to use in part because I could make Equals() work here. But I see how this method is not restricted to the type that I choose to pass in in the one test I added. I can add the extra check.

{
throw;
serializer = Assembly.Load(name);
Copy link
Member

Choose a reason for hiding this comment

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

Instead of using contextual reflection it would be cleaner to just call AssemblyLoadContext.GetLoadContext(type.Assembly).LoadFromAssemblyName.

We recommend people stop using the Assembly.Load because of its weird behavior in terms of which ALC it loads into (without contextual reflection it relies on caller's ALC which is often times wrong).

else if (assemblyAttribute.CodeBase != null && assemblyAttribute.CodeBase.Length > 0)
{
serializerName = assemblyAttribute.CodeBase;
serializer = Assembly.LoadFrom(serializerName);
Copy link
Member

Choose a reason for hiding this comment

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

Not sure what the intention is here, but this will probably not work correctly with custom ALCs.
Assembly.LoadFrom ALWAYS loads into the default ALC. I think this should call AssemblyLoadContext.GetLoadContext(type.Assembly).LoadFromAssemblyPath instead.

In short - it would be better to not use contextual reflection for this method at all.

@safern
Copy link
Member

safern commented Dec 15, 2021

@StephenMolloy it seems like Xml_TypeInCollectibleALC test is failing consistently, we nee d to fix that in order to be able to merge.

There is a bug in AppDomain::FindAssembly code path that iterates over
the domain assemblies. The iteration loop leaves the refcount of the
LoaderAllocator related to the returned DomainAssembly bumped, but
nothing ever decrements it. So when a code that needs to be unloaded
ends up in that code path, all the managed things like managed
LoaderAllocator, LoaderAllocatorScout are destroyed, but the unloading
doesn't complete due to the refcount.

We have never found it before as this code path is never executed in any
of the coreclr tests even with unloadability testing option.

(cherry picked from commit 8b38c19)
@agocke agocke added Servicing-approved Approved for servicing release and removed Servicing-consider Issue for next servicing release review labels Jan 10, 2022
@jkotas jkotas merged commit 04a5836 into release/6.0 Jan 10, 2022
@agocke agocke deleted the backport/pr-58932-to-release/6.0 branch January 10, 2022 23:32
@ghost ghost locked as resolved and limited conversation to collaborators Feb 10, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-Serialization Servicing-approved Approved for servicing release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants