-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Preserve stdout byte stream for native commands #17857
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
Merged
daxian-dbw
merged 37 commits into
PowerShell:master
from
SeeminglyScience:preserve-native-stdout
Apr 28, 2023
Merged
Changes from all commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
4faa5f9
Preserve stdout byte stream for `native | native`
SeeminglyScience 9e17307
Add filesystem provider path resolution
SeeminglyScience b023bb9
Fix path resolution
SeeminglyScience 56041ab
Added experimental feature
SeeminglyScience a6f0b1c
Fix pipeline handling outside of the compiler
SeeminglyScience f99f084
Merge branch 'master' into preserve-native-stdout
SeeminglyScience 6586f43
Fix merging redirection fallback behavior
SeeminglyScience 2f0519a
Add byte read/write to TestExe and tests
SeeminglyScience 6ce4861
Fix for steppable pipeline
SeeminglyScience f0ba2d4
Remove the bits from TestExe I didn't end up using
SeeminglyScience 66556e4
Clean up over abstracted stream interface
SeeminglyScience 9e6da22
Clean up NativeCommandProcessor.cs
SeeminglyScience a692f40
Add more doc comments
SeeminglyScience b5b0ff4
Merge branch 'master' into preserve-native-stdout
SeeminglyScience 3f86217
Fix style violation
SeeminglyScience 52dbab8
Fix new line in tests on *nix
SeeminglyScience bd3a117
Fix non-native merging redirection into file
SeeminglyScience 0e7fdfd
Fix expected bytes in test
SeeminglyScience 537eeff
Rename AsyncByteStreamDrainer
SeeminglyScience 578d468
Rename BeginReadChucks
SeeminglyScience eb24269
Fix behavior change when feature is turned off
SeeminglyScience e9065ff
Fix race condition and address feedback
SeeminglyScience 75b8c6f
Fix steppable pipeline
SeeminglyScience e643222
Get rid of extra UpstreamIsNativeCommand check
SeeminglyScience 6a9fea6
Use PSObject.Base instead of is PSO
SeeminglyScience 8445814
Move native command checks to `PipelineProcessor`
SeeminglyScience 3bff357
Remove unnecessary variable assignment in pattern
SeeminglyScience 56d2fec
Remove unnecessary `StdOutDestination` check
SeeminglyScience bee0750
Moved `using` statement to after the cr header
SeeminglyScience 6ab9f5e
Removed used parameter
SeeminglyScience d671ecc
Revert isNativeCommand if statement expansion
SeeminglyScience eb5cb3f
Remove callbacks from AsyncByteStreamTransfer
SeeminglyScience 0e93d31
Fix compile errors from overload change
SeeminglyScience bacafce
Merge remote-tracking branch 'upstream/master' into preserve-native-s…
SeeminglyScience 3b483cf
Update src/System.Management.Automation/engine/AsyncByteStreamTransfe…
SeeminglyScience a33e655
Add experimental feature usage telemetry
SeeminglyScience 972bae2
Apply suggestions from code review
SeeminglyScience File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
83 changes: 83 additions & 0 deletions
83
src/System.Management.Automation/engine/AsyncByteStreamTransfer.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| #nullable enable | ||
|
|
||
| using System.Buffers; | ||
| using System.IO; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace System.Management.Automation; | ||
|
|
||
| /// <summary> | ||
| /// Represents the transfer of bytes from one <see cref="Stream" /> to another | ||
| /// asynchronously. | ||
| /// </summary> | ||
| internal sealed class AsyncByteStreamTransfer : IDisposable | ||
| { | ||
| private const int DefaultBufferSize = 1024; | ||
|
|
||
| private readonly BytePipe _bytePipe; | ||
|
|
||
| private readonly BytePipe _destinationPipe; | ||
|
|
||
| private readonly Memory<byte> _buffer; | ||
|
|
||
| private readonly CancellationTokenSource _cts = new(); | ||
|
|
||
| private Task? _readToBufferTask; | ||
|
|
||
| public AsyncByteStreamTransfer( | ||
| BytePipe bytePipe, | ||
| BytePipe destinationPipe) | ||
| { | ||
| _bytePipe = bytePipe; | ||
| _destinationPipe = destinationPipe; | ||
| _buffer = new byte[DefaultBufferSize]; | ||
| } | ||
|
|
||
| public Task EOF => _readToBufferTask ?? Task.CompletedTask; | ||
|
|
||
| public void BeginReadChunks() | ||
| { | ||
| _readToBufferTask = Task.Run(ReadBufferAsync); | ||
| } | ||
|
|
||
| public void Dispose() => _cts.Cancel(); | ||
|
|
||
| private async Task ReadBufferAsync() | ||
| { | ||
| Stream stream; | ||
| Stream? destinationStream = null; | ||
| try | ||
| { | ||
| stream = await _bytePipe.GetStream(_cts.Token); | ||
| destinationStream = await _destinationPipe.GetStream(_cts.Token); | ||
|
|
||
| while (true) | ||
| { | ||
| int bytesRead; | ||
| bytesRead = await stream.ReadAsync(_buffer, _cts.Token); | ||
| if (bytesRead is 0) | ||
| { | ||
| break; | ||
| } | ||
|
|
||
| destinationStream.Write(_buffer.Span.Slice(0, bytesRead)); | ||
| } | ||
| } | ||
| catch (IOException) | ||
| { | ||
| return; | ||
| } | ||
| catch (OperationCanceledException) | ||
| { | ||
| return; | ||
| } | ||
| finally | ||
| { | ||
| destinationStream?.Close(); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| #nullable enable | ||
|
|
||
| using System.Diagnostics; | ||
| using System.IO; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.PowerShell.Telemetry; | ||
|
|
||
| namespace System.Management.Automation; | ||
|
|
||
| /// <summary> | ||
| /// Represents a lazily retrieved <see cref="Stream" /> for transfering bytes | ||
| /// to or from. | ||
| /// </summary> | ||
| internal abstract class BytePipe | ||
| { | ||
| public abstract Task<Stream> GetStream(CancellationToken cancellationToken); | ||
|
|
||
| internal AsyncByteStreamTransfer Bind(BytePipe bytePipe) | ||
| { | ||
| Debug.Assert(bytePipe is not null); | ||
| return new AsyncByteStreamTransfer(bytePipe, destinationPipe: this); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Represents a <see cref="Stream" /> lazily retrieved from the underlying | ||
| /// <see cref="NativeCommandProcessor" />. | ||
| /// </summary> | ||
| internal sealed class NativeCommandProcessorBytePipe : BytePipe | ||
| { | ||
| private readonly NativeCommandProcessor _nativeCommand; | ||
|
|
||
| private readonly bool _stdout; | ||
|
|
||
| internal NativeCommandProcessorBytePipe( | ||
| NativeCommandProcessor nativeCommand, | ||
| bool stdout) | ||
| { | ||
| Debug.Assert(nativeCommand is not null); | ||
| _nativeCommand = nativeCommand; | ||
| _stdout = stdout; | ||
| } | ||
|
|
||
| public override async Task<Stream> GetStream(CancellationToken cancellationToken) | ||
| { | ||
| // If the native command we're wrapping is the upstream command then | ||
| // NativeCommandProcessor.Prepare will have already been called before | ||
| // the creation of this BytePipe. | ||
| if (_stdout) | ||
| { | ||
| return _nativeCommand.GetStream(stdout: true); | ||
| } | ||
|
|
||
| await _nativeCommand.WaitForProcessInitializationAsync(cancellationToken); | ||
| return _nativeCommand.GetStream(stdout: false); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Provides an byte pipe implementation representing a <see cref="FileStream" />. | ||
| /// </summary> | ||
| internal sealed class FileBytePipe : BytePipe | ||
| { | ||
| private readonly Stream _stream; | ||
|
|
||
| private FileBytePipe(Stream stream) | ||
| { | ||
| Debug.Assert(stream is not null); | ||
| _stream = stream; | ||
| } | ||
|
|
||
| internal static FileBytePipe Create(string fileName, bool append) | ||
| { | ||
| FileStream fileStream; | ||
| try | ||
| { | ||
| PathUtils.MasterStreamOpen( | ||
| fileName, | ||
| resolvedEncoding: null, | ||
| defaultEncoding: false, | ||
| append, | ||
| Force: true, | ||
| NoClobber: false, | ||
| out fileStream, | ||
| streamWriter: out _, | ||
| readOnlyFileInfo: out _, | ||
| isLiteralPath: true); | ||
| } | ||
| catch (Exception e) when (e.Data.Contains(typeof(ErrorRecord))) | ||
| { | ||
| // The error record is attached to the exception when thrown to preserve | ||
| // the call stack. | ||
| ErrorRecord? errorRecord = e.Data[typeof(ErrorRecord)] as ErrorRecord; | ||
| if (errorRecord is null) | ||
| { | ||
| throw; | ||
| } | ||
|
|
||
| e.Data.Remove(typeof(ErrorRecord)); | ||
| throw new RuntimeException(null, e, errorRecord); | ||
| } | ||
|
Comment on lines
+93
to
+105
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was the cleanest way I could think of to throw the underlying error record/exception while retaining the same exception type as |
||
|
|
||
| ApplicationInsightsTelemetry.SendExperimentalUseData( | ||
| ExperimentalFeature.PSNativeCommandPreserveBytePipe, | ||
| "f"); | ||
|
|
||
| return new FileBytePipe(fileStream); | ||
| } | ||
|
|
||
| public override Task<Stream> GetStream(CancellationToken cancellationToken) => Task.FromResult(_stream); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.