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

Update position before ReadAsync starts, but fix it after incomplete read #56531

Merged
merged 6 commits into from
Jul 31, 2021

Conversation

adamsitnik
Copy link
Member

No description provided.

@adamsitnik adamsitnik added this to the 6.0.0 milestone Jul 29, 2021
@ghost
Copy link

ghost commented Jul 29, 2021

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

Issue Details
Author: adamsitnik
Assignees: -
Labels:

area-System.IO

Milestone: 6.0.0

@@ -99,6 +99,10 @@ public unsafe sealed override long Length
}
}

// in case of concurrent incomplete reads, there can be multiple threads trying to update the position
// at the same time. That is why we are using Interlocked here.
internal void OnIncompleteRead(int expectedBytesRead, int actualBytesRead) => Interlocked.Add(ref _filePosition, actualBytesRead - expectedBytesRead);
Copy link
Member Author

Choose a reason for hiding this comment

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

@stephentoub I've used Interlocked here as the new IncompleteReadCantSetPositionBeyondEndOfFile test was simply failing without it. I know that we are never going to be 100% thread safe, but the alternative was to call Length which would result in an extra sys-call.

_filePosition = Math.Min(Length, _filePosition - expectedBytesRead + actualBytesRead);

@adamsitnik
Copy link
Member Author

the test is failing on Windows 7 and 8, I am going to apply no merge until I fix it

@adamsitnik adamsitnik added NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) and removed NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) labels Jul 29, 2021
@adamsitnik adamsitnik removed the NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) label Jul 30, 2021
@adamsitnik
Copy link
Member Author

Setting file position and length via UpdateLengthOnChangePosition before starting the async write operation could lead to potentially invalid Length value in cases when WriteAsync has failed (with an error or was cancelled)

I've disabled Length caching for files open for writing. We no longer need Length in ReadAsync so we are not regressing any important scenario.

@adamsitnik
Copy link
Member Author

@stephentoub all tests are passing, PTAL

@@ -286,6 +288,13 @@ internal static unsafe (SafeFileHandle.OverlappedValueTaskSource? vts, int error
vts.Dispose();
throw;
}
finally
{
if (errorCode != Interop.Errors.ERROR_IO_PENDING && errorCode != Interop.Errors.ERROR_SUCCESS)
Copy link
Member

Choose a reason for hiding this comment

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

Nit: this function is initializing errorCode to '0' but then checking against 'ERROR_SUCCESS'. Style-wise, it'd be nice if they were consistent.

{
// Update Position... it could be anywhere.
_filePosition = positionBefore;
Interlocked.Exchange(ref _filePosition, positionBefore);
}
Copy link
Member

Choose a reason for hiding this comment

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

This is precarious regardless of the interlocked, e.g. if someone did kick off concurrent operations, they could all fight to reset this to their starting position. It's not clear there's a "right" answer.

@gewarren
Copy link
Contributor

gewarren commented May 2, 2024

Did this PR effectively negate the breaking change listed here? dotnet/docs#23858

@adamsitnik
Copy link
Member Author

Did this PR effectively negate the breaking change listed here? dotnet/docs#23858

Yes and no.

IIRC we wanted to update the position after the operations are completed, but then we realized that some users use FileStream for parallel reads and writes despite the fact it's not supported (FileStream is officially not thread safe). We did not want to break these users, so we've decided to optimistically set the position when the operation is being started. We just assume that whole buffer will be read/written, but when the operation finishes and it turns out to not be true, we update the position accordingly.

My main goal with dotnet/docs#23858 was to give the users a clear message: stop relying on that, don't use FileStream for parallel file operations (we have provided a new API designed for that: RandomAccess).

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

Successfully merging this pull request may close these issues.

3 participants