Skip to content

Fix issue 15213: Bitmap finalized during Image.Save(Stream, ImageFormat) leaks native GDI+ memory - #15254

Open
SimonZhao888 wants to merge 3 commits into
dotnet:mainfrom
SimonZhao888:Fix_Issue_15213
Open

SimonZhao888 wants to merge 3 commits into
dotnet:mainfrom
SimonZhao888:Fix_Issue_15213

Conversation

@SimonZhao888

@SimonZhao888 SimonZhao888 commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Fixes #15213

Root Cause

When Image.Save(Stream, ImageFormat) executes the internal stream-saving path, the Image object is not guaranteed to remain alive during the call to the native GdipSaveImageToStream function. Under specific timing conditions, the JIT compiler or Garbage Collector (GC) may trigger finalization of the Image object prematurely, creating a race condition with the native save operation; this results in the failure to release—or the loss of—underlying GDI+ image resources, manifesting as a native memory leak (where significant memory remains unrecovered).

Proposed changes

Within CoreImageExtensions.Save(this IImage image, Stream stream, ...) of System.Private.Windows.GdiPlus, wrap GdipSaveImageToStream in a try/finally block and call GC.KeepAlive(image) in the finally block.

Customer Impact

The correction avoids abnormal native memory growth in Save(Stream, ImageFormat) under high-pressure/specific GC timings, reducing memory risks and potential stability issues in long-duration or batch image processing scenarios.

The impact on existing behavioral compatibility is minimal (only extending object survival to the end of the method), and should not change functional results or introduce visible behavioral differences.

Regression?

  • Yes

Risk

  • Mini

Screenshots

Before

image

After

image

Test methodology

  • Manual

Test environment(s)

  • 11.0.0-rc.2.26509.113
Microsoft Reviewers: Open in CodeFlow

Simon Zhao (BEYONDSOFT CONSULTING INC) added 2 commits October 12, 2026 16:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The regression test baseline can be contaminated by pending finalizers from earlier tests.

1 open finding
What changed in this PR

Prevents premature Image finalization during native GDI+ stream saves.

Changes:

  • Keeps IImage alive through GdipSaveImageToStream.
  • Adds native-memory regression coverage.
File Description
CoreImageExtensions.cs Extends image lifetime through native save.
ImageTests.cs Tests memory growth across collections.

🧠 Review effort: Balanced


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/System.Drawing.Common/tests/System/Drawing/ImageTests.cs
@codecov

codecov Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 37.84861%. Comparing base (77d6ad3) to head (7b84515).

Additional details and impacted files
@@              Coverage Diff              @@
##                main      #15254   +/-   ##
=============================================
  Coverage   37.84861%   37.84861%           
=============================================
  Files            246         246           
  Lines           9789        9789           
  Branches        1031        1031           
=============================================
  Hits            3705        3705           
  Misses          5920        5920           
  Partials         164         164           
Flag Coverage Δ
Debug 37.84861% <ø> (ø)
production 40.04608% <ø> (ø)
test 20.64923% <ø> (ø)
unit 40.04608% <ø> (ø)

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Regression: Bitmap finalized during Image.Save(Stream, ImageFormat) leaks native GDI+ memory

3 participants