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

Add missing GC.SuppressFinalize #64997

Merged
merged 7 commits into from
Feb 23, 2022
Merged

Add missing GC.SuppressFinalize #64997

merged 7 commits into from
Feb 23, 2022

Conversation

adamsitnik
Copy link
Member

fixes #64741

@adamsitnik adamsitnik added this to the 7.0.0 milestone Feb 8, 2022
@ghost ghost assigned adamsitnik Feb 8, 2022
@ghost
Copy link

ghost commented Feb 8, 2022

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

Issue Details

fixes #64741

Author: adamsitnik
Assignees: -
Labels:

area-System.IO

Milestone: 7.0.0

{
GC.SuppressFinalize(this);
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Do we need DisposeInternal at all? Could FileStream's Dispose(bool) instead do:

if (disposing) _strategy.Dispose();

?

Copy link
Member Author

@adamsitnik adamsitnik Feb 9, 2022

Choose a reason for hiding this comment

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

@stephentoub to answer your question I've written some additional tests (to cover all scenarios). And I've discovered a new bug: for types that derive from FileStream and have buffering enabled, the buffer is currently not getting flushed when the instance is finalized. Repro:

public class DerivedFileStream : FileStream
{
    public DerivedFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
        : base(path, mode, access, share, bufferSize, options)
    {
    }
}

[Fact]
public static void Repro()
{
    string filePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName());
    Separate(filePath);
    
    GC.Collect();
    GC.WaitForPendingFinalizers();
    
    Assert.Equal(new byte [] { 1} , File.ReadAllBytes(filePath));

    // use a separate method to be sure that fs isn't rooted at time of GC.
    static void Separate(string filePath)
    {
        FileStream fs = new DerivedFileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, FileOptions.None);
        fs.WriteByte(1);
    }
}

It's caused by the fact that ~DerivedFileStreamStrategy is currently passing false to Dispose:

~DerivedFileStreamStrategy()
{
// Preserved for compatibility since FileStream has defined a
// finalizer in past releases and derived classes may depend
// on Dispose(false) call.
_fileStream.DisposeInternal(false);

which is perfectly fine, because derived type is expecting that.

However, when it's passed back to BufferedFileStreamStrategy.Dispose the Flush is not performed for !disposing:

protected override void Dispose(bool disposing)
{
try
{
if (disposing && !_strategy.IsClosed)
{
try
{
Flush();

~BufferedFileStreamStrategy workarounds it by always passing true to Dispose:

~BufferedFileStreamStrategy()
{
try
{
// the finalizer must at least try to flush the write buffer
// so we enforce it by passing always true
Dispose(true);

My current take on it is that removing finalizer from FileStream was a bad idea as it actually got things more complicated and regressed the performance instead of improving it. I think that we should restore it, let it run for a few weeks on main and backport it to 6.0. PTAL at my recent changes and let me know what do you think.

@rpnetcoder
Copy link

Dear Team, this is just a quick follow-up.

As the author of original issue, I would like to emphasize the significance of the bug. In my particular case, there are hundreds of thousands of FileStream objects created daily which are properly disposed after being used, but not GC collected in Gen 0 and moved to the Finalization queue. I can imagine even much larger services which might use FileStream under the hood like Microsoft Azure, where, if this bug gets fixed, the whole .NET ecosystem will benefit from it.

Thanks

@adamsitnik
Copy link
Member Author

@rpnetcoder thank you for your feedback! I am definitely willing to fix the bug and do my best to backport it to 6.0.

@stephentoub could you please take a look at the PR? I've addressed your feedback

@adamsitnik adamsitnik merged commit a1a653c into dotnet:main Feb 23, 2022
@adamsitnik adamsitnik deleted the issue64741 branch February 23, 2022 09:24
@adamsitnik
Copy link
Member Author

/backport to release/6.0

@github-actions
Copy link
Contributor

Started backporting to release/6.0: https://github.com/dotnet/runtime/actions/runs/1886417132

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
3 participants