-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Added a factory to FileStreamCompletionSource #16190
Added a factory to FileStreamCompletionSource #16190
Conversation
- Change FileStreamCompletionSource to take a ReadOnlyMemory<byte> instead of byte[] - Delegate to memory.Retain if the underlying ReadOnlyMemory<byte> is backed by an array that isn't the stream's internal buffer.
Can run the corefx CI ones; assuming coreclr and corefx are in sync with their apis @dotnet-bot test Windows_NT x64 Checked corefx_baseline |
// If the memory passed in isn't the stream's internal buffer, then delegate to | ||
// memory.Retain instead of passing the buffer to AllocateNativeOverlapped. In | ||
// some cases, this will result in less pinning (in case the underlying memory is backed) | ||
// by pre-pinned buffers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: closing paren should be at the end of the sentence rather than after "backed"
coreclr and corefx out of sync
/cc @tannergooding |
private NativeOverlapped* _overlapped; // Overlapped class responsible for operations in progress when an appdomain unload occurs | ||
private long _result; // Using long since this needs to be used in Interlocked APIs | ||
|
||
private MemoryHandle _handle; // mutable struct; do not make this readonly |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd have to measure again, but just looking at the existing fields, looks like this will increase the size of the object by app 33%. For the common case of the internal buffer being used, that's unfortunate. Can we avoid that? That was one of the reasons for the separate derived type. How about just creating a factory method that contains the below logic and then use that at the two call sites rather than the current naive logic based purely on whether an array is wrapped?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You think the one extra field is worth that complexity?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not one field. It's three ptr-size fields.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this created for every Read, could it be reused across Reads attached to the Stream?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Every non-buffered read and write on an async FileStream.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, when you originally did this was it pre-emptive? What did you measure that made you split the classes? Was it just object size?
You could do something similar with the cancellation registration and remove the _cancellationRegistration field if the CanBeCancelled is false.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What did you measure that made you split the classes? Was it just object size?
Yes, it avoided regressing bytes allocated from previous releases.
You could do something similar with the cancellation registration and remove the _cancellationRegistration field if the CanBeCancelled is false.
Yes, we could, but not without non-trivial extra complexity: it's accessed from the callback, not just Dispose. Also, MemoryHandle is bigger, and from a complexity perspective it's not worth classes for all combinations. I do think it's work two classes, that we already have.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll send an update
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks
- Added a factory method to FileStreamCompletionSource that picks the an impl or FileStreamCompletionSource or MemoryFileStreamCompletionSource based on the buffer matching the underlying stream's internal buffer.
|
||
// Using RunContinuationsAsynchronously for compat reasons (old API used Task.Factory.StartNew for continuations) | ||
internal FileStreamCompletionSource(FileStream stream, int numBufferedBytes, byte[] bytes) | ||
protected FileStreamCompletionSource(FileStream stream, int numBufferedBytes, byte[] bytes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made this protected so that it can't be newed up. Forces callers to find the factory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good
🆙 📅 (updated) |
} | ||
} | ||
|
||
return new MemoryFileStreamCompletionSource(stream, numBufferedBytesRead, memory); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: It's ok if you prefer it this way; if it were me, I'd have written it at least with the condition inverted, and maybe condensed to more like:
// If the memory passed in is the stream's internal buffer, we can use the base FileStreamCompletionSource,
// which has a PreAllocatedOverlapped with the memory already pinned. Otherwise, we use the derived
// MemoryFileStreamCompletionSource, which Retains the memory, which will result in less pinning in the case
// where the underlying memory is backed by pre-pinned buffers.
return MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> arraySegment) && ReferenceEquals(arraySegment.Array, stream._buffer) ?
new FileStreamCompletionSource(stream, numBufferedBytesRead, arraySegment.Array) :
new MemoryFileStreamCompletionSource(stream, numBufferedBytesRead, memory);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is clearer.
// memory.Retain instead of passing the buffer to AllocateNativeOverlapped. In | ||
// some cases, this will result in less pinning (in case the underlying memory is backed | ||
// by pre-pinned buffers) | ||
if (!ReferenceEquals(buffer, stream._buffer)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can remove the condition in the base class now, right? i.e. the code that's currently:
_overlapped = (bytes == null || ReferenceEquals(bytes, _stream._buffer)) && _stream.CompareExchangeCurrentOverlappedOwner(this, null) == null ?
_stream._fileHandle.ThreadPoolBinding.AllocateNativeOverlapped(_stream._preallocatedOverlapped) :
_stream._fileHandle.ThreadPoolBinding.AllocateNativeOverlapped(s_ioCallback, this, bytes);
could instead be:
Debug.Assert(bytes == null || ReferenceEquals(bytes, _stream._buffer));
_overlapped = _stream.CompareExchangeCurrentOverlappedOwner(this, null) == null ?
_stream._fileHandle.ThreadPoolBinding.AllocateNativeOverlapped(_stream._preallocatedOverlapped) :
_stream._fileHandle.ThreadPoolBinding.AllocateNativeOverlapped(s_ioCallback, this, null);
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep., that's right. I'll tweak it. Actually, we don't need to pass the byte[] to FileStreamCompletionSource at all, it's only being used to asset that it matches the internal stream buffer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll leave it for the assert.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for fixing this up, David. A couple remaining comments, otherwise LGTM.
- Cleanup the condition - Flip to Debug.Assert in FileStreamCompletionSource
OK I think it's done forreal this time 😄 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😄
@benaadams, CoreCLR has the change (#16147) and CoreFX has the change consuming that (dotnet/corefx#26776), but it looks like CoreCLR does not have the updated CoreFX bits that contain yet 😄 |
I thought these legs pulled/built live source from the corefx repo. Is that not the case? |
I'm not sure, but given the changes merged and the error message Ben linked, I would guess not. |
I think it pulls version referenced by the build tools update PRs that dotnetbot makes |
I'm assuming somebody will just merge this at some point 😄 |
@dotnet-bot test Windows_NT x64 Checked corefx_baseline |
Not sure what this one is as it just seems to be updating coreclr #16191 Don't see any corefx update PRs |
|
@dotnet-bot test Windows_NT x64 Checked corefx_baseline |
It used to be like that, but it was changed recently. I have submitted #16199 to revert it since it make things clearly worse. |
Is a change, but still out of sync?
|
It needs Update CoreFX PR to get it in sync. The last Update CoreFX was #16180 (2 days ago). |
I am going to merge #16199 as soon as it as gets green to get this unstuck. |
Is my PR being held hostage? 😃 |
If we can't get the automated CI tests to work, someone needs to run the tests locally before merging. If you're not set up to do so, I can, it'll just be a little while, and Jan's PR fixing CI might beat me to it. |
I pulled down the PR and ran the tests locally. There are 37 failing tests out of 6140, but they're all the same root cause:
|
My rough guess: I expect the fix is to just make sure we've initialized the preallocated overlapped. I'll take a look tonight or in the morning. |
@stephentoub thanks for running the tests! I'll take a look as well. |
@davidfowl, the simple fix you can make in this PR is to just add _overlapped = bytes != null && _stream.CompareExchangeCurrentOverlappedOwner(this, null) == null ? With that all of the tests pass. The issue here is that the _preallocatedOverlapped is tied to _buffer: _preallocatedOverlapped is only initialized when _buffer is initialized, because if the _preallocatedOverlapped exists, we trust that it pins _buffer. The tests that failed didn't yet have the buffer because all reads/writes thus far were bigger than the buffer size and thus skipped it. The fix I suggested above leaves things in a state no worse than today, where we only use the _preallocatedOverlapped for operations targeting the internal _buffer. However, it would be nice to do better than that. Using the _preallocatedOverlapped is more efficient than not, and so it'd be nice to be able to take that path even when targeting a Memory that's not _buffer, as in the ASP.NET case where I believe it frequently creates FileStreams with a bufferSize of 1 in order to avoid the buffer ever being used. I see a few options there:
(2) doesn't seem very good. Might be worth experimenting with (4) to see what kind of complexity it adds and whether it's worth it. None of that should hold up this PR, though; we can just add that condition and consider this one done. |
@dotnet-bot test Windows_NT x64 Checked corefx_baseline |
The failures are known runtime bugs: |
* Merged FileStreamCompletionSource and MemoryCompletionSource - Change FileStreamCompletionSource to take a ReadOnlyMemory<byte> instead of byte[] - Delegate to memory.Retain if the underlying ReadOnlyMemory<byte> is backed by an array that isn't the stream's internal buffer. * Brought back MemoryFileStreamCompletionSource - Added a factory method to FileStreamCompletionSource that picks the an impl or FileStreamCompletionSource or MemoryFileStreamCompletionSource based on the buffer matching the underlying stream's internal buffer. * PR feedback - Cleanup the condition - Flip to Debug.Assert in FileStreamCompletionSource * Check for null before using the PreAllocatedOverlapped Signed-off-by: dotnet-bot-corefx-mirror <[email protected]>
* Merged FileStreamCompletionSource and MemoryCompletionSource - Change FileStreamCompletionSource to take a ReadOnlyMemory<byte> instead of byte[] - Delegate to memory.Retain if the underlying ReadOnlyMemory<byte> is backed by an array that isn't the stream's internal buffer. * Brought back MemoryFileStreamCompletionSource - Added a factory method to FileStreamCompletionSource that picks the an impl or FileStreamCompletionSource or MemoryFileStreamCompletionSource based on the buffer matching the underlying stream's internal buffer. * PR feedback - Cleanup the condition - Flip to Debug.Assert in FileStreamCompletionSource * Check for null before using the PreAllocatedOverlapped Signed-off-by: dotnet-bot-corefx-mirror <[email protected]>
* Merged FileStreamCompletionSource and MemoryCompletionSource - Change FileStreamCompletionSource to take a ReadOnlyMemory<byte> instead of byte[] - Delegate to memory.Retain if the underlying ReadOnlyMemory<byte> is backed by an array that isn't the stream's internal buffer. * Brought back MemoryFileStreamCompletionSource - Added a factory method to FileStreamCompletionSource that picks the an impl or FileStreamCompletionSource or MemoryFileStreamCompletionSource based on the buffer matching the underlying stream's internal buffer. * PR feedback - Cleanup the condition - Flip to Debug.Assert in FileStreamCompletionSource * Check for null before using the PreAllocatedOverlapped Signed-off-by: dotnet-bot <[email protected]>
* Merged FileStreamCompletionSource and MemoryCompletionSource - Change FileStreamCompletionSource to take a ReadOnlyMemory<byte> instead of byte[] - Delegate to memory.Retain if the underlying ReadOnlyMemory<byte> is backed by an array that isn't the stream's internal buffer. * Brought back MemoryFileStreamCompletionSource - Added a factory method to FileStreamCompletionSource that picks the an impl or FileStreamCompletionSource or MemoryFileStreamCompletionSource based on the buffer matching the underlying stream's internal buffer. * PR feedback - Cleanup the condition - Flip to Debug.Assert in FileStreamCompletionSource * Check for null before using the PreAllocatedOverlapped Signed-off-by: dotnet-bot <[email protected]>
* Merged FileStreamCompletionSource and MemoryCompletionSource - Change FileStreamCompletionSource to take a ReadOnlyMemory<byte> instead of byte[] - Delegate to memory.Retain if the underlying ReadOnlyMemory<byte> is backed by an array that isn't the stream's internal buffer. * Brought back MemoryFileStreamCompletionSource - Added a factory method to FileStreamCompletionSource that picks the an impl or FileStreamCompletionSource or MemoryFileStreamCompletionSource based on the buffer matching the underlying stream's internal buffer. * PR feedback - Cleanup the condition - Flip to Debug.Assert in FileStreamCompletionSource * Check for null before using the PreAllocatedOverlapped Commit migrated from dotnet/coreclr@39914cd
picks the an impl or FileStreamCompletionSource or MemoryFileStreamCompletionSource based on the buffer matching the underlying stream's internal buffer.
PS: I haven't run any tests locally yet 😄. I will when I find out how to do so.
Addresses https://github.com/dotnet/corefx/issues/26817
/cc @stephentoub