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

Implement RandomAccess.FlushToDisk() #89100

Merged
merged 9 commits into from
Aug 7, 2023
Merged

Conversation

ericmutta
Copy link
Contributor

Fix #86836

@dotnet-issue-labeler
Copy link

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change.

@ghost ghost added the community-contribution Indicates that the PR has been added by a community member label Jul 18, 2023
@ghost
Copy link

ghost commented Jul 18, 2023

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

Issue Details

Fix #86836

Author: ericmutta
Assignees: -
Labels:

area-System.IO, new-api-needs-documentation

Milestone: -

@@ -10023,6 +10023,7 @@ public static partial class RandomAccess
public static void Write(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.ReadOnlySpan<byte> buffer, long fileOffset) { }
public static System.Threading.Tasks.ValueTask WriteAsync(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Collections.Generic.IReadOnlyList<System.ReadOnlyMemory<byte>> buffers, long fileOffset, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.ValueTask WriteAsync(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.ReadOnlyMemory<byte> buffer, long fileOffset, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static void FlushToDisk(Microsoft.Win32.SafeHandles.SafeFileHandle handle) { throw null; }
Copy link
Member

Choose a reason for hiding this comment

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

nit - please put in alpha order. we have tools that regenerate this and it will otherwise cause a diff

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK...before I do, I notice that SetLength appears before the various Read and ReadAsync methods, when it should appear after them in order to preserve alpha order. Should I make that change in this PR too or is it best to use a separate issue?

Copy link
Member

Choose a reason for hiding this comment

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

ericmutta and others added 2 commits July 18, 2023 20:58
This "platform-depedent" should read "platform-dependent".

Co-authored-by: Dan Moseley <danmose@microsoft.com>
Declarations of class members in System.Runtime.cs should be in
alphabetic order (see dotnet#89100 (comment)).

Simplify comments on the call to "FileStreamHelpers.FlushToDisk()"
(see dotnet#89100 (comment)).
Copy link
Member

@adamsitnik adamsitnik left a comment

Choose a reason for hiding this comment

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

@ericmutta great job, thank you for your contribution! PTAL at my comments

@adamsitnik adamsitnik self-assigned this Jul 19, 2023
@ericmutta
Copy link
Contributor Author

Many thanks @adamsitnik for the detailed and encouraging feedback! I will do the necessary over the next 48 hours and push more commits 👍

RandomAccess.Write(handle, randomBytes, fileOffset: 0);

// Get the file time BEFORE flushing to disk.
DateTime fileTimeBeforeFlush = File.GetLastWriteTimeUtc(testFilePath);
Copy link
Member

Choose a reason for hiding this comment

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

consider setting a last write time here that's a little older. otherwise you'll fail when the file system timestamp granularity isn't high enough. at an extreme, this test will fail on a FAT32 partition.

also, this test will fail if a daylight savings change or leap second happens during the test. obviously, that's unlikely, but several of our tests fail every time there's such an event as there's zillions of tests and they run all the time. to fix that, you can set a date that's a year ago, for example.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Interesting points @danmoseley! In this test we are not explicitly setting the last write time so I can't control it in any way. When the file is flushed, it is the operating system that updates the last write time and the test is just checking that the time after flushing is greater than the time before flushing (or that they are at least equal, as explained in the comment near the Assert at the end).

The most important comment in the test says:

 // Flush the file to disk. As a bare minimum test, this should work without throwing an
 // exception on all supported platforms. 

...i.e. we are mostly interested in the fact that flushing works without throwing an exception on all platforms. So I can remove all the date-related asserts to avoid the issues you raised. Please let me know if I should do this 👍

// to. This "implicit flush" would also update the file time and if it happens BEFORE we set the
// fileTimeBeforeFlush variable above, then both fileTimeBeforeFlush and fileTimeAfterFlush will
// end up with the same value, which is why we use the ">=" operator below instead of using ">".
Assert.True(fileTimeAfterFlush >= fileTimeBeforeFlush);
Copy link
Member

Choose a reason for hiding this comment

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

another reason to set your own older timestamp before the flush is you eliminate this race. and then you can use Assert.NotEqual here instead. which also has the advantage that if it fails, it will be clear why, whereas when you use Assert.True it just says false expected true.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Please see this response, I believe the decision we make there will address this too 👍

This includes comments that:

1. Can become outdated if methods are renamed (see dotnet#89100 (comment)).

2. Are not absolutely critical (see dotnet#89100 (comment)).

I also removed the mention of using FileOptions.WriteThrough as an
alternative to RandomAccess.FlushToDisk(). This is an advanced scenario
that can actually degrade performance if done incorrectly (i.e. if the
user doesn't manage their own buffers such that all writes keep hitting
the disk) so it's probably not wise to mention it unless we go into more
detail than is ideal for inclusion in the Remarks section of an XML doc
comment.

To remove the lengthy comment in the ValidateInput() function I had to
rename the optional parameter from "flushingToDisk" to the more generic
and self-describing "allowUnseekableHandles" (see dotnet#89100 (comment)).
The differences between Unix and Windows when it comes to flushing
unseekable handles is worth noting explicitly and the comment that
did this was previously deleted but has now been restored with some
minor edits to fit its new context (see dotnet@a7d6289#r1270505888).
See the following discussions for the rationale:
  - dotnet#89100 (comment)
  - dotnet#89100 (comment)

NOTE: the [MemberData] used by the theories returns one element in order
to reduce the number of tests that need to run per build. Since the element
is random, different builds will likely pick a different value allowing
more variations to be tested over many build cycles.
@ericmutta
Copy link
Contributor Author

Thanks @danmoseley and @adamsitnik for all the feedback on this! I have addressed most of the issues and will be back to resolve the rest over the next few days (contributing to .NET is dangerously addictive, I wish I could do it full time...but until then I have to restrain myself and attend to the day job before I get fired 😄 🏃 )

Copy link
Member

@adamsitnik adamsitnik left a comment

Choose a reason for hiding this comment

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

It looks very good, we are almost there 👍

Again thank you for your contribution @ericmutta !

Copy link
Member

@adamsitnik adamsitnik left a comment

Choose a reason for hiding this comment

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

@ericmutta apologies for the delay

@adamsitnik adamsitnik added the needs-author-action An issue or pull request that requires more info or actions from the author. label Aug 3, 2023
There is a difference in behaviour between FlushFileBuffers() on
Windows and fsync() on Unix when it comes to read-only files. On
Windows it fails with ERROR_ACCESS_DENIED while on Unix it just works.
We made a decision to harmonize the platforms by making Windows behave
like Unix (i.e. we catch and ignore the error if a user attempts to
flush a file opened for reading, so it is effectively a no-op).

See dotnet#89100 (comment)
@ghost ghost removed the needs-author-action An issue or pull request that requires more info or actions from the author. label Aug 4, 2023
Copy link
Member

@adamsitnik adamsitnik left a comment

Choose a reason for hiding this comment

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

LGTM, thank you for your contribution @ericmutta !

@adamsitnik
Copy link
Member

@jeffhandley I request for the permission to merge. This PR adds FlushToDisk method to RandomAccess class. It was already present for years on FileStream, it reuses an existing helper. So the risk for breaking anything else is very small.

Copy link
Member

@jeffhandley jeffhandley left a comment

Choose a reason for hiding this comment

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

I approve of merging this in for .NET 8 RC1.

It adds a targeted new API that was approved a couple months ago, reusing existing code to implement the logic, and there's sufficient testing added.

@adamsitnik
Copy link
Member

The failure is unrelated (#87879), merging!

@adamsitnik adamsitnik merged commit 231b8dc into dotnet:main Aug 7, 2023
164 of 167 checks passed
@adamsitnik adamsitnik added this to the 8.0.0 milestone Aug 7, 2023
@ericmutta
Copy link
Contributor Author

The week is officially off to a great start :-)

My thanks to @danmoseley for encouraging me to take a stab at this; to the API Review Board for approving the name; to @adamsitnik for the many time-saving tips, pointers and code snippets; to both @danmoseley and @adamsitnik for helpful feedback during PR review; and to @jeffhandley for the final review required to merge. You guys are awesome and this has been a fantastic experience! 🚀

@ericmutta ericmutta deleted the issue-86836 branch August 7, 2023 19:32
@danmoseley
Copy link
Member

Thanks @ericmutta ! We hope you will become a regular contributor here.

@ericmutta
Copy link
Contributor Author

Thanks @danmoseley!

I hope to be back after we ship .NET 8 later this year, to contribute to .NET 9.

As part of the database-like project that led me to request RandomAccess.FlushToDisk(), I have had to build some things from scratch that I wish were already in the .NET libraries. Currently, the SemaphoreSlim class is the only synchronization primitive in the System.Threading namespace that has some async support. If there's any interest from the .NET team, I would like to take a stab at adding more async support to other primitives like ReaderWriterLockSlim during the .NET 9 time frame next year 👍

@adamsitnik
Copy link
Member

Currently, the SemaphoreSlim class is the only synchronization primitive in the System.Threading namespace that has some async support. If there's any interest from the .NET team, I would like to take a stab at adding more async support to other primitives like ReaderWriterLockSlim during the .NET 9 time frame next year

cc @stephentoub

@danmoseley
Copy link
Member

If there's any interest from the .NET team, I would like to take a stab at adding more async support to other primitives like ReaderWriterLockSlim during the .NET 9 time frame next year 👍

the best way to see whether there's interest is to start an API proposal. Be sure to search existing issues though, stuff like this has likely been proposed before. (in which case, if it wasn't rejected, it may just need someone to take an interest)

@ghost ghost locked as resolved and limited conversation to collaborators Sep 10, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.IO community-contribution Indicates that the PR has been added by a community member new-api-needs-documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

RandomAccess.FlushToDisk
4 participants