Skip to content

'ComWrappers.ReleaseObjects' also passes aggregated objects #114043

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

Closed
Tracked by #114179
Sergio0694 opened this issue Mar 30, 2025 · 5 comments
Closed
Tracked by #114179

'ComWrappers.ReleaseObjects' also passes aggregated objects #114043

Sergio0694 opened this issue Mar 30, 2025 · 5 comments

Comments

@Sergio0694
Copy link
Contributor

Sergio0694 commented Mar 30, 2025

Related to microsoft/CsWinRT#1955

Description

It seems that ComWrappers.ReleaseObjects is called with some objects also potentially being aggregated objects (eg. a type deriving from Windows.UI.Xaml.Controls.Page). This currently causes CsWinRT to throw an exception, as we can't unwrap the object. That is by design, as our understanding was that ReleaseObjects would only be called with unwrappable RCWs from our own ComWrappers.

I'm not sure whether this is a bug or expected. It feels like at the very least we could improve the docs for ReleaseObjects? Currently the method is pretty cryptic, as it just takes some IEnumerable and gives you no info whatsoever on when would the method actually be called, or what objects an implementing type should expected to be passed to it, and/or what should the behavior be for objects you can't manually release.

Reproduction Steps

  1. Run https://github.com/user-attachments/files/19464189/App1.zip
  2. Click the button to open a page
  3. Click the button to close the window
  4. Observe the crash

Image

Expected behavior

Not clear.

Actual behavior

ComWrappers.ReleaseObject is also receiving some aggregated objects.

@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Mar 30, 2025
@AaronRobinsonMSFT
Copy link
Member

This is expected from the runtime perspective. These objects are all associated with the current thread COM apartment and created with the CreateObjectFlags.TrackerObject.

This API is only called when the Jupiter runtime calls IReferenceTrackerHost::NotifyEndOfReferenceTrackingOnThread(). It wasn't functional in .NET Framework due to implementation issues. It was re-enabled in .NET 5 since we were fixing a few issues.

This API is about cleaning up thread affinitized objects that require the associated thread for clean-up.

@Sergio0694
Copy link
Contributor Author

Oh, interesting. The docs for that say that the method is only called "when a FrameworkView is uninitialized". However we're not seeing this being called when just closing a normal UWP app (or we would've noticed the crash in the Store as well). Does this mean that this method is basically only ever called specifically when closing a secondary CoreWindow in a UWP app? 🤔

"This API is about cleaning up thread affinitized objects that require the associated thread for clean-up."

Out of curiosity, why is this called in the same context in this case, yet all other tracker objects we create are just finalized on whatever thread the finalizer happens to run on? Why aren't those also finalized on the same original context?

@AaronRobinsonMSFT
Copy link
Member

Does this mean that this method is basically only ever called specifically when closing a secondary CoreWindow in a UWP app?

Great question. I defer to the Jupiter team on this. We simply provide the callout.

Out of curiosity, why is this called in the same context in this case, yet all other tracker objects we create are just finalized on whatever thread the finalizer happens to run on? Why aren't those also finalized on the same original context?

That I do not know. Again, this is an API that is exposed for the Jupiter runtime. I will circle back today and confirm the state in .NET Framework.

@AaronRobinsonMSFT
Copy link
Member

Looking at the .NET Framework version, there is a slight nuance that isn't captured in the documentation. RCWs are cleaned up in a 2 pass operation.

In the first pass, all non-aggregated RCWs are released. In the second pass, all aggregated RCWs are released. This is something that would need to be implemented in the CsWinRT ReleaseObjects(), but I'm not sure it is required.

Out of curiosity, why is this called in the same context in this case, yet all other tracker objects we create are just finalized on whatever thread the finalizer happens to run on? Why aren't those also finalized on the same original context?

Based on the various snippets of comments, both in the Jupiter runtime and in .NET Framework, it looks like this is an optimization to avoid releasing too much on the finalizer.

@Sergio0694
Copy link
Contributor Author

Talked with Aaron offline. Closing this, as the behavior is expected. We'll look into changing impl on the CsWinRT side.
Thank you! 🙂

@dotnet-policy-service dotnet-policy-service bot removed the untriaged New issue has not been triaged by the area owner label Apr 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Archived in project
Development

No branches or pull requests

2 participants