| File: Backchannel\TrayProtocolOutput.cs | Web Access |
| Project: src\src\Aspire.Cli\Aspire.Cli.csproj (aspire) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.InteropServices; using System.Text; using Microsoft.Win32.SafeHandles; namespace Aspire.Cli.Backchannel; /// <summary> /// Writes protocol lines without the console stream's broken-pipe error suppression. /// </summary> internal sealed partial class TrayProtocolOutput(Func<TextWriter> createWriter) : IDisposable { private readonly Lazy<TextWriter> _writer = new(createWriter); public static TrayProtocolOutput CreateStandardOutput() => new(() => { // Console.Out / Console.OpenStandardOutput intentionally ignore EPIPE on Unix. The // heartbeat must observe it to terminate a watcher whose consumer has disappeared. // A non-owning FileStream preserves errors without closing the process's stdout handle. // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Console/src/System/ConsolePal.Unix.cs var handle = OperatingSystem.IsWindows() ? GetStdHandle(-11) : (nint)1; return CreateWriter(new SafeFileHandle(handle, ownsHandle: false)); }); internal static TextWriter CreateWriter(SafeFileHandle handle) => new StreamWriter(new FileStream(handle, FileAccess.Write, bufferSize: 4096, isAsync: false), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); public async Task WriteLineAsync(string json, CancellationToken cancellationToken) { await _writer.Value.WriteLineAsync(json.AsMemory(), cancellationToken).ConfigureAwait(false); await _writer.Value.FlushAsync(cancellationToken).ConfigureAwait(false); } public void Dispose() { if (_writer.IsValueCreated) { try { _writer.Value.Dispose(); } catch (IOException) { // The command already handled a failed output write. Disposal can retry the flush. } } } [LibraryImport("kernel32.dll")] private static partial nint GetStdHandle(int standardHandle); }