diff --git a/module/PSParallelPipeline.psd1 b/module/PSParallelPipeline.psd1 index 675434f..43db821 100644 --- a/module/PSParallelPipeline.psd1 +++ b/module/PSParallelPipeline.psd1 @@ -11,7 +11,7 @@ RootModule = 'bin/netstandard2.0/PSParallelPipeline.dll' # Version number of this module. - ModuleVersion = '1.2.4' + ModuleVersion = '1.2.5' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/src/PSParallelPipeline/Commands/InvokeParallelCommand.cs b/src/PSParallelPipeline/Commands/InvokeParallelCommand.cs index 9c4a6eb..ccd034e 100644 --- a/src/PSParallelPipeline/Commands/InvokeParallelCommand.cs +++ b/src/PSParallelPipeline/Commands/InvokeParallelCommand.cs @@ -3,7 +3,6 @@ using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Threading; -using PSParallelPipeline.Poly; namespace PSParallelPipeline.Commands; @@ -80,12 +79,15 @@ protected override void BeginProcessing() ScriptBlock.GetUsingParameters(this)); _worker = new Worker(poolSettings, workerSettings, _cts.Token); - _worker.Run(); } protected override void ProcessRecord() { - Dbg.Assert(_worker is not null); + if (_worker is null) + { + return; + } + InputObject.ThrowIfInputObjectIsScriptBlock(this); try @@ -103,14 +105,17 @@ protected override void ProcessRecord() } catch (OperationCanceledException exception) { - CancelAndWait(); + _worker.WaitForCompletion(); exception.WriteTimeoutError(this); } } protected override void EndProcessing() { - Dbg.Assert(_worker is not null); + if (_worker is null) + { + return; + } try { @@ -129,7 +134,7 @@ protected override void EndProcessing() } catch (OperationCanceledException exception) { - CancelAndWait(); + _worker.WaitForCompletion(); exception.WriteTimeoutError(this); } } diff --git a/src/PSParallelPipeline/Extensions.cs b/src/PSParallelPipeline/Extensions.cs index 2051b10..e0e326d 100644 --- a/src/PSParallelPipeline/Extensions.cs +++ b/src/PSParallelPipeline/Extensions.cs @@ -209,14 +209,28 @@ private static string ResolvePath(this PSCmdlet cmdlet, string path) .InvokeReturnAsIs(); } - internal static Task InvokePowerShellAsync( + internal static Task InvokePowerShellAsync( this PowerShell powerShell, - PSDataCollection output) + PSDataCollection output) => Task.Factory.FromAsync( - powerShell.BeginInvoke(null, output), + powerShell.BeginInvoke(null, output), powerShell.EndInvoke); internal static ConfiguredTaskAwaitable NoContext(this Task task) => task.ConfigureAwait(false); internal static ConfiguredTaskAwaitable NoContext(this Task task) => task.ConfigureAwait(false); + + public static IEnumerable DistinctBy( + this IEnumerable source, + Func keySelector) + { + HashSet seenKeys = []; + foreach (TSource element in source) + { + if (seenKeys.Add(keySelector(element))) + { + yield return element; + } + } + } } diff --git a/src/PSParallelPipeline/ModuleCompleter.cs b/src/PSParallelPipeline/ModuleCompleter.cs index f69317a..47431eb 100644 --- a/src/PSParallelPipeline/ModuleCompleter.cs +++ b/src/PSParallelPipeline/ModuleCompleter.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Management.Automation; using System.Management.Automation.Language; @@ -15,17 +16,16 @@ public IEnumerable CompleteArgument( CommandAst commandAst, IDictionary fakeBoundParameters) { - using PowerShell ps = PowerShell + using PowerShell powershell = PowerShell .Create(RunspaceMode.CurrentRunspace) .AddCommand("Get-Module") .AddParameter("ListAvailable"); - foreach (PSModuleInfo module in ps.Invoke()) - { - if (module.Name.StartsWith(wordToComplete, StringComparison.InvariantCultureIgnoreCase)) - { - yield return new CompletionResult(module.Name); - } - } + return powershell + .Invoke() + .DistinctBy(e => e.Name) + .Where(e => e.Name.StartsWith(wordToComplete, StringComparison.InvariantCultureIgnoreCase)) + .OrderBy(e => e.Name) + .Select(e => new CompletionResult(e.Name)); } } diff --git a/src/PSParallelPipeline/PSTask.cs b/src/PSParallelPipeline/PSTask.cs index 9942297..f52e406 100644 --- a/src/PSParallelPipeline/PSTask.cs +++ b/src/PSParallelPipeline/PSTask.cs @@ -114,13 +114,13 @@ private PSTask AddUsingStatements(Dictionary usingParams) private void CompleteTask() { + _powershell.Dispose(); if (_canceled) { _runspace?.Dispose(); return; } - _powershell.Dispose(); if (_runspace is not null) { _pool.PushRunspace(_runspace); @@ -129,7 +129,7 @@ private void CompleteTask() internal void Cancel() { - _powershell.Dispose(); + _powershell.BeginStop(null, null); _canceled = true; } } diff --git a/src/PSParallelPipeline/Poly/Dbg.cs b/src/PSParallelPipeline/Poly/Dbg.cs deleted file mode 100644 index d482eb9..0000000 --- a/src/PSParallelPipeline/Poly/Dbg.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; - -namespace PSParallelPipeline.Poly; - -internal static class Dbg -{ - [Conditional("DEBUG")] - public static void Assert([DoesNotReturnIf(false)] bool condition) => - Debug.Assert(condition); -} diff --git a/src/PSParallelPipeline/Poly/Nullable.cs b/src/PSParallelPipeline/Poly/Nullable.cs deleted file mode 100644 index ad74d3b..0000000 --- a/src/PSParallelPipeline/Poly/Nullable.cs +++ /dev/null @@ -1,150 +0,0 @@ -#if !NETCOREAPP - -namespace System.Diagnostics.CodeAnalysis; - -/// Specifies that null is allowed as an input even if the corresponding type disallows it. -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] -[ExcludeFromCodeCoverage] -internal sealed class AllowNullAttribute : Attribute { } - -/// Specifies that null is disallowed as an input even if the corresponding type allows it. -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] -[ExcludeFromCodeCoverage] -internal sealed class DisallowNullAttribute : Attribute { } - -/// Specifies that an output may be null even if the corresponding type disallows it. -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] -[ExcludeFromCodeCoverage] -internal sealed class MaybeNullAttribute : Attribute { } - -/// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns. -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] -[ExcludeFromCodeCoverage] -internal sealed class NotNullAttribute : Attribute { } - -/// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. -[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] -[ExcludeFromCodeCoverage] -internal sealed class MaybeNullWhenAttribute : Attribute -{ - /// Initializes the attribute with the specified return value condition. - /// - /// The return value condition. If the method returns this value, the associated parameter may be null. - /// - public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; - - /// Gets the return value condition. - public bool ReturnValue { get; } -} - -/// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. -[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] -[ExcludeFromCodeCoverage] -internal sealed class NotNullWhenAttribute : Attribute -{ - /// Initializes the attribute with the specified return value condition. - /// - /// The return value condition. If the method returns this value, the associated parameter will not be null. - /// - public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; - - /// Gets the return value condition. - public bool ReturnValue { get; } -} - -/// Specifies that the output will be non-null if the named parameter is non-null. -[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] -[ExcludeFromCodeCoverage] -internal sealed class NotNullIfNotNullAttribute : Attribute -{ - /// Initializes the attribute with the associated parameter name. - /// - /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. - /// - public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; - - /// Gets the associated parameter name. - public string ParameterName { get; } -} - -/// Applied to a method that will never return under any circumstance. -[AttributeUsage(AttributeTargets.Method, Inherited = false)] -[ExcludeFromCodeCoverage] -internal sealed class DoesNotReturnAttribute : Attribute { } - -/// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. -[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] -[ExcludeFromCodeCoverage] -internal sealed class DoesNotReturnIfAttribute : Attribute -{ - /// Initializes the attribute with the specified parameter value. - /// - /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to - /// the associated parameter matches this value. - /// - public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; - - /// Gets the condition parameter value. - public bool ParameterValue { get; } -} - -/// Specifies that the method or property will ensure that the listed field and property members have not-null values. -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] -[ExcludeFromCodeCoverage] -internal sealed class MemberNotNullAttribute : Attribute -{ - /// Initializes the attribute with a field or property member. - /// - /// The field or property member that is promised to be not-null. - /// - public MemberNotNullAttribute(string member) => Members = new[] { member }; - - /// Initializes the attribute with the list of field and property members. - /// - /// The list of field and property members that are promised to be not-null. - /// - public MemberNotNullAttribute(params string[] members) => Members = members; - - /// Gets field or property member names. - public string[] Members { get; } -} - -/// Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition. -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] -[ExcludeFromCodeCoverage] -internal sealed class MemberNotNullWhenAttribute : Attribute -{ - /// Initializes the attribute with the specified return value condition and a field or property member. - /// - /// The return value condition. If the method returns this value, the associated parameter will not be null. - /// - /// - /// The field or property member that is promised to be not-null. - /// - public MemberNotNullWhenAttribute(bool returnValue, string member) - { - ReturnValue = returnValue; - Members = new[] { member }; - } - - /// Initializes the attribute with the specified return value condition and list of field and property members. - /// - /// The return value condition. If the method returns this value, the associated parameter will not be null. - /// - /// - /// The list of field and property members that are promised to be not-null. - /// - public MemberNotNullWhenAttribute(bool returnValue, params string[] members) - { - ReturnValue = returnValue; - Members = members; - } - - /// Gets the return value condition. - public bool ReturnValue { get; } - - /// Gets field or property member names. - public string[] Members { get; } -} - -#endif diff --git a/src/PSParallelPipeline/Worker.cs b/src/PSParallelPipeline/Worker.cs index 52f88b8..d6f16a8 100644 --- a/src/PSParallelPipeline/Worker.cs +++ b/src/PSParallelPipeline/Worker.cs @@ -8,7 +8,7 @@ namespace PSParallelPipeline; internal sealed class Worker { - private Task? _worker; + private readonly Task _worker; private readonly TaskSettings _taskSettings; @@ -31,9 +31,10 @@ internal Worker( _taskSettings = taskSettings; _streams = new PSOutputStreams(_output); _pool = new RunspacePool(poolSettings, _streams, _token); + _worker = Task.Run(Start, cancellationToken: _token); } - internal void WaitForCompletion() => _worker?.GetAwaiter().GetResult(); + internal void WaitForCompletion() => _worker.GetAwaiter().GetResult(); internal void Enqueue(object? input) => _input.Add(input, _token); @@ -43,8 +44,6 @@ internal Worker( internal IEnumerable GetConsumingEnumerable() => _output.GetConsumingEnumerable(_token); - internal void Run() => _worker = Task.Run(Start, cancellationToken: _token); - private async Task Start() { List tasks = new(_pool.MaxRunspaces); @@ -55,10 +54,7 @@ private async Task Start() { if (tasks.Count == tasks.Capacity) { - Task task = await Task - .WhenAny(tasks) - .NoContext(); - + Task task = await Task.WhenAny(tasks).NoContext(); tasks.Remove(task); await task.NoContext(); }