Skip to content

Stop NativeImageList GDI leak#4952

Merged
RussKie merged 6 commits intodotnet:mainfrom
RussKie:fix_3567_gdi_leak
May 25, 2021
Merged

Stop NativeImageList GDI leak#4952
RussKie merged 6 commits intodotnet:mainfrom
RussKie:fix_3567_gdi_leak

Conversation

@RussKie
Copy link
Contributor

@RussKie RussKie commented May 21, 2021

Fixes #3567
Fixes #4913
Closes #4316
Closes #4928

Proposed changes

There are certain code paths where we are unable to track the lifetime of the object,
for example in the following scenarios:

 this.imageList1.ImageStream = (System.Windows.Forms.ImageListStreamer)(resources.GetObject("imageList1.ImageStream"));

or

 resources.ApplyResources(this.listView1, "listView1");

In those cases the loose instances will be collected by the GC.

Undo the regression introduced in #3526.

Customer Impact

  • Customer applications should no longer crash due to exhausted pool of GDI handles used by NativeImageList objects.

Regression?

  • Yes

Risk

  • Minimal

Test methodology

  • added unit and MAUI tests to verify both disposal and GC scenarios
  • a lot of manual tests

Manual tests harness:

diff --git a/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/MainForm.cs b/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/MainForm.cs
index 02ed97ff4..68d0ef6db 100644
--- a/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/MainForm.cs
+++ b/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/MainForm.cs
@@ -123,7 +123,18 @@ namespace WinformsControlsTest
             },
             {
                 MainFormControlsTabOrder.ListViewButton,
-                new InitInfo("ListVew", (obj, e) => new ListViewTest().Show())
+                new InitInfo("ListVew", (obj, e) =>
+                {
+                    int gdiHandleCount = Interop.User32.GetGuiResources(Process.GetCurrentProcess().Handle, Interop.User32.GR.GDIOBJECTS);
+                    Debug.WriteLine($"before: {gdiHandleCount}");
+                    using ListViewTest form = new ();
+                    form.ShowDialog();
+                    GC.Collect();
+                    GC.WaitForPendingFinalizers();
+                    GC.Collect(0);
+                    gdiHandleCount = Interop.User32.GetGuiResources(Process.GetCurrentProcess().Handle, Interop.User32.GR.GDIOBJECTS);
+                    Debug.WriteLine($"after: {gdiHandleCount}");
+                })
             },

Before

gdi1

After

gdi2

Microsoft Reviewers: Open in CodeFlow

@RussKie RussKie requested a review from a team as a code owner May 21, 2021 07:28
@ghost ghost assigned RussKie May 21, 2021
@RussKie
Copy link
Contributor Author

RussKie commented May 21, 2021

/cc: @kirsan31 @weltkante

@RussKie RussKie added the servicing-consider .NET Shiproom label indicating a PR seeks to enter into a branch under Tell-Mode criteria label May 21, 2021
@RussKie RussKie added this to the 5.0.8 milestone May 21, 2021
There are certain code paths where we are unable to track the lifetime of the object,
for example in the following scenarios:

     this.imageList1.ImageStream = (System.Windows.Forms.ImageListStreamer)(resources.GetObject("imageList1.ImageStream"));
or
     resources.ApplyResources(this.listView1, "listView1");

In those cases the loose instances will be collected by the GC.

Undo the regression introduced in  dotnet#3526.

Fixes dotnet#3567
Closes dotnet#4928
@RussKie RussKie force-pushed the fix_3567_gdi_leak branch from 104a4c5 to 706b64a Compare May 21, 2021 07:35
@kirsan31
Copy link
Contributor

kirsan31 commented May 21, 2021

I think this in scope (may be I am wrong). Was introduced in same PR #3526. Is it does anything?


Due to ImageStream property implementation:
public ImageListStreamer ImageStream
{
get
{
if (Images.Empty)
{
return null;
}
// No need for us to create the handle, because any serious attempts to use the
// ImageListStreamer will do it for us.
return new ImageListStreamer(this);
}

@RussKie
Copy link
Contributor Author

RussKie commented May 24, 2021

I think this in scope (may be I am wrong). Was introduced in same PR #3526. Is it does anything?

Apologies, I am struggling to follow your train of thought/question. Could you please rephrase?

@kirsan31
Copy link
Contributor

I think this in scope (may be I am wrong). Was introduced in same PR #3526. Is it does anything?

Apologies, I am struggling to follow your train of thought/question. Could you please rephrase?

The regression was introduced in #3526. Also in this PR this line of code was added to ImageList.Dispose(bool disposing) method:


and this line does nothing (useful), due to ImageStream property implementation:
public ImageListStreamer ImageStream
{
get
{
if (Images.Empty)
{
return null;
}
// No need for us to create the handle, because any serious attempts to use the
// ImageListStreamer will do it for us.
return new ImageListStreamer(this);
}

🤗

@RussKie
Copy link
Contributor Author

RussKie commented May 24, 2021

The regression was introduced in #3526. Also in this PR this line of code was added to ImageList.Dispose(bool disposing) method:
...
and this line does nothing (useful), due to ImageStream property implementation:

Ah, yes! Thank you. Removed

Copy link
Member

@JeremyKuhne JeremyKuhne left a comment

Choose a reason for hiding this comment

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

It would be nice if we could avoid introducing more BinaryFormatter usage here (even in tests). We'll need an answer at some point for ImageListStreamer. I'd suggest adding an internal void ImageListStreamer.ToStream(Stream stream) and internal ImageListStream(Stream stream) to avoid the BF. Should be straight forward to implement and we can follow up with an API proposal for it.

Outside of that there are a few minor comments.

@ghost ghost added waiting-author-feedback The team requires more information from the author and removed waiting-author-feedback The team requires more information from the author labels May 24, 2021
@RussKie
Copy link
Contributor Author

RussKie commented May 25, 2021

It would be nice if we could avoid introducing more BinaryFormatter usage here (even in tests). We'll need an answer at some point for ImageListStreamer. I'd suggest adding an internal void ImageListStreamer.ToStream(Stream stream) and internal ImageListStream(Stream stream) to avoid the BF. Should be straight forward to implement and we can follow up with an API proposal for it.

I'm reusing the existing test infra that is relying on the BF. Since this is a separate piece of work not directly related to this fix I'm going to do this in a separate PR.

@RussKie RussKie merged commit 10babcc into dotnet:main May 25, 2021
@RussKie RussKie deleted the fix_3567_gdi_leak branch May 25, 2021 02:59
@ghost ghost modified the milestones: 5.0.8, 6.0 Preview6 May 25, 2021
@RussKie
Copy link
Contributor Author

RussKie commented May 25, 2021

Reworked the BF in #4966

@kirsan31
Copy link
Contributor

image
@RussKie Is this still servicing-consider for 5.0.x?

@RussKie
Copy link
Contributor Author

RussKie commented May 25, 2021

I've requested a permission to service, will find out soon.

@dreddy-work
Copy link
Member

dreddy-work commented May 25, 2021

Approved for servicing. We need a servicing PR. @RussKie

@dreddy-work dreddy-work removed the servicing-consider .NET Shiproom label indicating a PR seeks to enter into a branch under Tell-Mode criteria label May 25, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Jan 28, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NET5 ImageList GDI object leak [regression] PropertyGrid GDI leak NativeImageList not disposed by ResourceManager Leak of NativeImageList

5 participants