Skip to content
Merged
Show file tree
Hide file tree
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 Aug 5, 2022
9e17307
Add filesystem provider path resolution
SeeminglyScience Oct 7, 2022
b023bb9
Fix path resolution
SeeminglyScience Nov 30, 2022
56041ab
Added experimental feature
SeeminglyScience Nov 30, 2022
a6f0b1c
Fix pipeline handling outside of the compiler
SeeminglyScience Nov 30, 2022
f99f084
Merge branch 'master' into preserve-native-stdout
SeeminglyScience Nov 30, 2022
6586f43
Fix merging redirection fallback behavior
SeeminglyScience Jan 4, 2023
2f0519a
Add byte read/write to TestExe and tests
SeeminglyScience Jan 4, 2023
6ce4861
Fix for steppable pipeline
SeeminglyScience Jan 12, 2023
f0ba2d4
Remove the bits from TestExe I didn't end up using
SeeminglyScience Jan 18, 2023
66556e4
Clean up over abstracted stream interface
SeeminglyScience Jan 19, 2023
9e6da22
Clean up NativeCommandProcessor.cs
SeeminglyScience Jan 19, 2023
a692f40
Add more doc comments
SeeminglyScience Jan 19, 2023
b5b0ff4
Merge branch 'master' into preserve-native-stdout
SeeminglyScience Jan 27, 2023
3f86217
Fix style violation
SeeminglyScience Jan 31, 2023
52dbab8
Fix new line in tests on *nix
SeeminglyScience Jan 31, 2023
bd3a117
Fix non-native merging redirection into file
SeeminglyScience Feb 2, 2023
0e7fdfd
Fix expected bytes in test
SeeminglyScience Feb 8, 2023
537eeff
Rename AsyncByteStreamDrainer
SeeminglyScience Feb 14, 2023
578d468
Rename BeginReadChucks
SeeminglyScience Feb 14, 2023
eb24269
Fix behavior change when feature is turned off
SeeminglyScience Mar 16, 2023
e9065ff
Fix race condition and address feedback
SeeminglyScience Mar 30, 2023
75b8c6f
Fix steppable pipeline
SeeminglyScience Apr 4, 2023
e643222
Get rid of extra UpstreamIsNativeCommand check
SeeminglyScience Apr 13, 2023
6a9fea6
Use PSObject.Base instead of is PSO
SeeminglyScience Apr 13, 2023
8445814
Move native command checks to `PipelineProcessor`
SeeminglyScience Apr 19, 2023
3bff357
Remove unnecessary variable assignment in pattern
SeeminglyScience Apr 19, 2023
56d2fec
Remove unnecessary `StdOutDestination` check
SeeminglyScience Apr 19, 2023
bee0750
Moved `using` statement to after the cr header
SeeminglyScience Apr 19, 2023
6ab9f5e
Removed used parameter
SeeminglyScience Apr 24, 2023
d671ecc
Revert isNativeCommand if statement expansion
SeeminglyScience Apr 24, 2023
eb5cb3f
Remove callbacks from AsyncByteStreamTransfer
SeeminglyScience Apr 24, 2023
0e93d31
Fix compile errors from overload change
SeeminglyScience Apr 24, 2023
bacafce
Merge remote-tracking branch 'upstream/master' into preserve-native-s…
SeeminglyScience Apr 27, 2023
3b483cf
Update src/System.Management.Automation/engine/AsyncByteStreamTransfe…
SeeminglyScience Apr 27, 2023
a33e655
Add experimental feature usage telemetry
SeeminglyScience Apr 27, 2023
972bae2
Apply suggestions from code review
SeeminglyScience Apr 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions src/System.Management.Automation/engine/AsyncByteStreamTransfer.cs
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();
}
}
}
115 changes: 115 additions & 0 deletions src/System.Management.Automation/engine/BytePipe.cs
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
Copy link
Collaborator Author

@SeeminglyScience SeeminglyScience Oct 7, 2022

Choose a reason for hiding this comment

The 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 Out-File without stripping the stack trace. Open to other ideas


ApplicationInsightsTelemetry.SendExperimentalUseData(
ExperimentalFeature.PSNativeCommandPreserveBytePipe,
"f");

return new FileBytePipe(fileStream);
}

public override Task<Stream> GetStream(CancellationToken cancellationToken) => Task.FromResult(_stream);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class ExperimentalFeature

internal const string EngineSource = "PSEngine";
internal const string PSNativeCommandErrorActionPreferenceFeatureName = "PSNativeCommandErrorActionPreference";
internal const string PSNativeCommandPreserveBytePipe = "PSNativeCommandPreserveBytePipe";
internal const string PSModuleAutoLoadSkipOfflineFilesFeatureName = "PSModuleAutoLoadSkipOfflineFiles";
internal const string PSCustomTableHeaderLabelDecoration = "PSCustomTableHeaderLabelDecoration";
internal const string PSFeedbackProvider = "PSFeedbackProvider";
Expand Down Expand Up @@ -126,6 +127,9 @@ static ExperimentalFeature()
new ExperimentalFeature(
name: PSCustomTableHeaderLabelDecoration,
description: "Formatting differentiation for table header labels that aren't property members"),
new ExperimentalFeature(
name: PSNativeCommandPreserveBytePipe,
description: "Byte output is retained when piping between two or more native commands"),
new ExperimentalFeature(
name: PSFeedbackProvider,
description: "Replace the hard-coded suggestion framework with the extensible feedback provider"),
Expand Down
Loading