-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Dispose IServiceScope when window is destroyed to prevent memory leaks #30196
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
Conversation
4b18b04 to
1175419
Compare
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.
Pull Request Overview
This PR addresses a memory leak in .NET MAUI by implementing proper disposal of window-scoped service scopes when windows are destroyed. Previously, service scopes created for windows were never disposed, leading to memory leaks for scoped services.
- Added internal scope tracking methods to
MauiContextto store and dispose window scopes - Modified window destruction flow to dispose service scopes before disconnecting handlers
- Added comprehensive tests to verify scope disposal behavior and integration
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/Core/src/MauiContextExtensions.cs |
Updates MakeWindowScope to store the created scope in the context for later disposal |
src/Core/src/MauiContext.cs |
Adds internal methods SetWindowScope() and DisposeWindowScope() to track window-scoped service scopes |
src/Controls/tests/Core.UnitTests/WindowsTests.cs |
Adds comprehensive tests for scope disposal functionality and edge cases |
src/Controls/src/Core/Window/Window.cs |
Implements scope disposal in IWindow.Destroying() before handler disconnection |
Comments suppressed due to low confidence (2)
src/Controls/tests/Core.UnitTests/WindowsTests.cs:796
- This test validates scope disposal by checking for ObjectDisposedException, but it should also verify that the MauiContext.DisposeWindowScope() method was actually called. Consider adding a mock or spy to verify the disposal method invocation.
Assert.Throws<ObjectDisposedException>(() => scope.ServiceProvider.GetService<TestScopedService>());
src/Controls/tests/Core.UnitTests/WindowsTests.cs:774
- The test creates a service scope manually but doesn't test the actual window creation flow through MakeWindowScope(). Consider testing the complete integration by using MakeWindowScope() to create the window context as it would be used in production.
var serviceCollection = new ServiceCollection();
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
| { | ||
| readonly WrappedServiceProvider _services; | ||
| readonly Lazy<IMauiHandlersFactory> _handlers; | ||
| IServiceScope? _windowScope; |
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.
Should this be a WeakReference ?
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.
@rmarinho no, the service scope's life is already tied to the window, it's not something that's just going to naturally finalize and dispose.
Having this here doesn't cause a memory leak
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.
Using a WeakReference here wouldn't be appropriate because we need to maintain a strong reference to the scope to ensure proper disposal. If we used a WeakReference, the scope could be garbage collected before we dispose it, potentially causing memory leaks. The scope needs to stay alive until we explicitly dispose it when the window is destroyed.
|
|
||
| internal void DisposeWindowScope() | ||
| { | ||
| _windowScope?.Dispose(); |
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.
@copilot set this to null after using this
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.
Done! Set _windowScope = null after disposal to clear the reference. Commit: c0f7dc0
| // Dispose the window-scoped service scope | ||
| if (Handler?.MauiContext is MauiContext mauiContext) | ||
| mauiContext.DisposeWindowScope(); | ||
|
|
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.
@copilot change this block to be like this
var mauiContext = Handler?.MauiContext as MauiContext;
Handler?.DisconnectHandler();
// Dispose the window-scoped service scope
mauiContext?.DisposeWindowScope();we want to call mauiContext.DisposeWindowScope(); after we call Handler?disconnecthandler
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.
Done! Changed the order of operations as requested - now we get the MauiContext reference first, disconnect the handler, and then dispose the scope. Commit: 6f1b547
|
Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details. Warning Firewall rules blocked me from connecting to one or more addressesI tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
Co-authored-by: PureWeen <5375137+PureWeen@users.noreply.github.com> # Conflicts: # src/Controls/tests/Core.UnitTests/WindowsTests.cs
Co-authored-by: PureWeen <5375137+PureWeen@users.noreply.github.com>
Co-authored-by: PureWeen <5375137+PureWeen@users.noreply.github.com>
…r disconnecting handler Co-authored-by: PureWeen <5375137+PureWeen@users.noreply.github.com>
356e45d to
146f4ba
Compare
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
|
@PureWeen can the failure tests be related? |
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Description
This PR implements proper disposal of
IServiceScopeinstances when windows are destroyed, addressing a memory leak where window-scoped services were never being disposed.Problem
Currently, .NET MAUI creates window-scoped service scopes in
MauiContextExtensions.MakeWindowScope()but never disposes them when the associated window is closed or destroyed. This leads to memory leaks for services registered with scoped lifetime.The issue was marked with a TODO comment in the code:
Solution
SetWindowScope()andDisposeWindowScope()to track the window-scoped service scopeWindow.Destroying()to dispose the scope before disconnecting the handlerTesting
Added comprehensive tests to verify:
All existing tests continue to pass (53 window-related tests ✅).
Example Usage
The fix is transparent to users - no code changes required. Services registered with scoped lifetime will now be properly disposed when windows are destroyed:
Fixes #8538.
💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.