Repository navigation
Make compat binary console output non-blocking #736
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| using System; | ||
| using System.Collections.Concurrent; | ||
| using System.IO; | ||
| using System.Runtime.InteropServices; | ||
| using Microsoft.Win32.SafeHandles; | ||
| using System.Text; | ||
| using System.Threading; | ||
|
|
||
| namespace Halibut.TestUtils.SampleProgram.Base.LogUtils | ||
| { | ||
| /// <summary> | ||
| /// The test reads our stdout through a pipe. If it stops reading, a blocking Console.Write | ||
| /// freezes Halibut mid-exchange, so writes are queued and dropped when the queue is full. | ||
| /// </summary> | ||
| public class NonBlockingConsoleWriter : TextWriter | ||
| { | ||
| const int MaxQueuedWrites = 10_000; | ||
|
|
||
| readonly TextWriter destination; | ||
| readonly BlockingCollection<string> queue = new(MaxQueuedWrites); | ||
| long droppedWrites; | ||
|
|
||
| NonBlockingConsoleWriter(Stream destination) | ||
| { | ||
| this.destination = new StreamWriter(destination, new UTF8Encoding(false)) { AutoFlush = false }; | ||
| new Thread(Drain) { IsBackground = true, Name = "NonBlockingConsoleWriter" }.Start(); | ||
| } | ||
|
|
||
| public static void Install() | ||
|
Contributor
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. Probably not a big deal, but if the process exists, nothing will drain what we currently have in th equeue. We could hook into the
Contributor
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. We might want to consider overriding |
||
| { | ||
| Console.SetOut(new NonBlockingConsoleWriter(OpenStandardStream(1, Console.OpenStandardOutput))); | ||
| Console.SetError(new NonBlockingConsoleWriter(OpenStandardStream(2, Console.OpenStandardError))); | ||
| } | ||
|
|
||
| // On Unix the console stream locks Console.Out while writing, so a stalled pipe would | ||
| // still block every Console.WriteLine. Write to the raw file descriptor instead. | ||
| static Stream OpenStandardStream(int fileDescriptor, Func<Stream> openConsoleStream) | ||
| { | ||
| if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return openConsoleStream(); | ||
| return new FileStream(new SafeFileHandle((IntPtr)fileDescriptor, ownsHandle: false), FileAccess.Write, 1); | ||
| } | ||
|
|
||
| public override Encoding Encoding => destination.Encoding; | ||
|
|
||
| public override void Write(char value) => Enqueue(value.ToString()); | ||
|
|
||
| public override void Write(string? value) | ||
|
Contributor
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. We should consider overriding |
||
| { | ||
| if (value is not null) Enqueue(value); | ||
| } | ||
|
|
||
| public override void WriteLine(string? value) => Enqueue(value + new string(CoreNewLine)); | ||
|
|
||
| void Enqueue(string value) | ||
| { | ||
| if (!queue.TryAdd(value)) Interlocked.Increment(ref droppedWrites); | ||
| } | ||
|
|
||
| void Drain() | ||
| { | ||
| foreach (var value in queue.GetConsumingEnumerable()) | ||
| { | ||
| var dropped = Interlocked.Exchange(ref droppedWrites, 0); | ||
| if (dropped > 0) destination.WriteLine($"[NonBlockingConsoleWriter dropped {dropped} writes]"); | ||
|
Contributor
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. If we write the drops here, then the text will appear well after the drop actually happened (likely 10000 lines out of place). We could do this closer to when it happened, when the |
||
|
|
||
| destination.Write(value); | ||
| if (queue.Count == 0) destination.Flush(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
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.
Claude made this, seems legit.
If we hate it in the future, we can simply delete it and end up in a slightly worse place if we decide we don't want to maintain this.