-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Always use non-threadpool threads for blocking process IO.
fix #94
- Loading branch information
Showing
18 changed files
with
624 additions
and
420 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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,68 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Linq; | ||
using System.Net.NetworkInformation; | ||
using System.Text; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using NUnit.Framework; | ||
|
||
namespace Medallion.Shell.Tests; | ||
|
||
[NonParallelizable] // performs global ThreadPool configuration | ||
internal class ThreadUsageTest | ||
{ | ||
/// <summary> | ||
/// Tests the fix to https://github.com/madelson/MedallionShell/issues/94; prior to this change this test | ||
/// would fail for small-ish thread pool values (both 2 and 8 on my machine, for example). | ||
/// </summary> | ||
[Test] | ||
public void TestPipeline([Values(2, 8)] int minThreads) | ||
{ | ||
const int ProcessCount = 10; | ||
|
||
ThreadPool.GetMinThreads(out var originalMinWorkerThreads, out var originalMinCompletionPortThreads); | ||
ThreadPool.GetMaxThreads(out var originalMaxWorkerThreads, out var originalMaxCompletionPortThreads); | ||
Command? pipeline = null; | ||
try | ||
{ | ||
ThreadPool.SetMinThreads(minThreads, minThreads); | ||
ThreadPool.SetMaxThreads(minThreads, minThreads); | ||
|
||
var task = Task.Factory.StartNew( | ||
() => | ||
{ | ||
pipeline = Enumerable.Range(0, ProcessCount) | ||
.Select(_ => UnitTestHelpers.TestShell.Run(UnitTestHelpers.SampleCommand, "pipebytes")) | ||
.Aggregate((first, second) => first | second); | ||
for (var i = 0; i < 10; ++i) | ||
{ | ||
var @char = (char)('a' + i); | ||
pipeline.StandardInput.AutoFlush.ShouldEqual(true); | ||
var writeTask = pipeline.StandardInput.WriteAsync(@char); | ||
writeTask.Wait(TimeSpan.FromSeconds(30)).ShouldEqual(true, $"write {i} should complete"); | ||
var buffer = new char[10]; | ||
var readTask = pipeline.StandardOutput.ReadAsync(buffer, 0, buffer.Length); | ||
readTask.Wait(TimeSpan.FromSeconds(30)).ShouldEqual(true, $"read {i} should complete"); | ||
readTask.Result.ShouldEqual(1); | ||
buffer[0].ShouldEqual(@char); | ||
} | ||
pipeline.StandardInput.Dispose(); | ||
pipeline.Task.Wait(TimeSpan.FromSeconds(30)).ShouldEqual(true, "pipeline should exit"); | ||
}, | ||
TaskCreationOptions.LongRunning | ||
); | ||
Assert.IsTrue(task.Wait(TimeSpan.FromSeconds(10))); | ||
} | ||
finally | ||
{ | ||
ThreadPool.SetMinThreads(originalMinWorkerThreads, originalMinCompletionPortThreads); | ||
ThreadPool.SetMaxThreads(originalMaxWorkerThreads, originalMaxCompletionPortThreads); | ||
pipeline?.Kill(); | ||
} | ||
} | ||
} |
This file contains 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
This file contains 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
This file contains 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
This file contains 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,36 @@ | ||
// note: we use the shim implementation for our NETFRAMEWORK tests in DEBUG just to get coverage | ||
#if NET45 || (DEBUG && NETFRAMEWORK) | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Runtime.CompilerServices; | ||
using System.Runtime.Remoting.Messaging; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace Medallion.Shell; | ||
|
||
internal sealed class AsyncLocal<T> where T : class? // needed for ConditionalWeakTable impl; ok for our purposes | ||
{ | ||
// CallContext values must be serializable; this indirection should eliminate any chance of that burning us | ||
private static readonly ConditionalWeakTable<object, T> Storage = new(); | ||
|
||
private readonly string Key = $"AsyncLocal<{typeof(T)}>_{Guid.NewGuid()}"; | ||
|
||
public T? Value | ||
{ | ||
get => CallContext.LogicalGetData(Key) is { } storageKey | ||
? Storage.TryGetValue(storageKey, out var value) | ||
? value | ||
: throw new KeyNotFoundException() | ||
: default; | ||
set | ||
{ | ||
object? storageKey; | ||
if (value is null) { storageKey = null; } | ||
else { Storage.Add(storageKey = new(), value); } | ||
CallContext.LogicalSetData(Key, storageKey); | ||
} | ||
} | ||
} | ||
#endif |
This file contains 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,44 @@ | ||
using System; | ||
using System.Diagnostics; | ||
using System.Runtime.CompilerServices; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Medallion.Shell; | ||
|
||
internal static class Shims | ||
{ | ||
public static T[] EmptyArray<T>() => | ||
#if NET45 || (NETFRAMEWORK && DEBUG) // for test coverage | ||
Empty<T>.Array; | ||
|
||
private static class Empty<T> | ||
{ | ||
public static readonly T[] Array = new T[0]; | ||
} | ||
#else | ||
Array.Empty<T>(); | ||
#endif | ||
|
||
public static Task<T> FaultedTask<T>(Exception exception) | ||
{ | ||
#if NET45 || (NETFRAMEWORK && DEBUG) // for test coverage | ||
TaskCompletionSource<T> taskCompletionSource = new(); | ||
taskCompletionSource.SetException(exception); | ||
return taskCompletionSource.Task; | ||
#else | ||
return Task.FromException<T>(exception); | ||
#endif | ||
} | ||
|
||
public static Task<T> CanceledTask<T>(CancellationToken cancellationToken) | ||
{ | ||
#if NET45 || (NETFRAMEWORK && DEBUG) // for test coverage | ||
TaskCompletionSource<T> taskCompletionSource = new(); | ||
taskCompletionSource.SetCanceled(); | ||
return taskCompletionSource.Task; | ||
#else | ||
return Task.FromCanceled<T>(cancellationToken); | ||
#endif | ||
} | ||
} |
26 changes: 5 additions & 21 deletions
26
MedallionShell/Shims.cs → MedallionShell/Shims/SpanShims.cs
This file contains 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
This file contains 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,19 @@ | ||
#if !NETCOREAPP && !NET47_OR_GREATER && !NETSTANDARD2_0_OR_GREATER | ||
#pragma warning disable SA1649 // File name should match first type name | ||
|
||
namespace System; | ||
|
||
internal struct ValueTuple<T1, T2, T3> | ||
{ | ||
public T1 Item1; | ||
public T2 Item2; | ||
public T3 Item3; | ||
|
||
public ValueTuple(T1 item1, T2 item2, T3 item3) | ||
{ | ||
this.Item1 = item1; | ||
this.Item2 = item2; | ||
this.Item3 = item3; | ||
} | ||
} | ||
#endif |
Oops, something went wrong.