diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index ab9c299b71..cb2f37a0ef 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -62,10 +62,6 @@ namespace Microsoft.PowerShell.Commands private Ping? _sender; - private readonly ManualResetEventSlim _pingComplete = new ManualResetEventSlim(); - - private PingCompletedEventArgs? _pingCompleteArgs; - #endregion #region Parameters @@ -797,7 +793,6 @@ namespace Microsoft.PowerShell.Commands if (disposing) { _sender?.Dispose(); - _pingComplete?.Dispose(); } _disposed = true; @@ -815,50 +810,26 @@ namespace Microsoft.PowerShell.Commands try { _sender = new Ping(); - _sender.PingCompleted += OnPingComplete; timer?.Start(); - _sender.SendAsync(targetAddress, timeout, buffer, pingOptions, this); - _pingComplete.Wait(); - timer?.Stop(); - _pingComplete.Reset(); - - if (_pingCompleteArgs == null) - { - throw new PingException(string.Format( - TestConnectionResources.NoPingResult, - targetAddress, - IPStatus.Unknown)); - } - - if (_pingCompleteArgs.Cancelled) - { - // The only cancellation we have implemented is on pipeline stops via StopProcessing(). - throw new PipelineStoppedException(); - } - - if (_pingCompleteArgs.Error != null) - { - throw new PingException(_pingCompleteArgs.Error.Message, _pingCompleteArgs.Error); - } - - return _pingCompleteArgs.Reply; + // 'SendPingAsync' always uses the default synchronization context (threadpool). + // This is what we want to avoid the deadlock resulted by async work being scheduled back to the + // pipeline thread due to a change of the current synchronization context of the pipeline thread. + return _sender.SendPingAsync(targetAddress, timeout, buffer, pingOptions).GetAwaiter().GetResult(); + } + catch (PingException ex) when (ex.InnerException is TaskCanceledException) + { + // The only cancellation we have implemented is on pipeline stops via StopProcessing(). + throw new PipelineStoppedException(); } finally { + timer?.Stop(); _sender?.Dispose(); _sender = null; } } - // This event is triggered when the ping is completed, and passes along the eventargs so that we know - // if the ping was cancelled, or an exception was thrown. - private static void OnPingComplete(object sender, PingCompletedEventArgs e) - { - ((TestConnectionCommand)e.UserState)._pingCompleteArgs = e; - ((TestConnectionCommand)e.UserState)._pingComplete.Set(); - } - /// /// The class contains information about the source, the destination and ping results. /// diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Test-Connection.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Test-Connection.Tests.ps1 index a4343e27a0..93608811ff 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Test-Connection.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Test-Connection.Tests.ps1 @@ -299,3 +299,24 @@ Describe "Connection" -Tag "CI", "RequireAdminOnWindows" { Test-Connection $UnreachableAddress -TcpPort 80 -TimeOut 1 | Should -BeFalse } } + +Describe "Test-Connection should run in the default synchronization context (threadpool)" -Tag "CI" { + It "Test-Connection works after constructing a WindowsForm object" -Skip:(!$IsWindows) { + $pwsh = Join-Path $PSHOME "pwsh" + $pingResults = & $pwsh -NoProfile { + Add-Type -AssemblyName System.Windows.Forms + $null = New-Object System.Windows.Forms.Form + Test-Connection localhost + } + + $pingResults.Length | Should -Be 4 + $result = $pingResults | Select-Object -First 1 + + $result.Ping | Should -Be 1 + $result.Source | Should -BeExactly ([System.Net.Dns]::GetHostName()) + $result.Destination | Should -BeExactly localhost + $result.Latency | Should -BeOfType "long" + $result.Reply.Status | Should -BeExactly "Success" + $result.BufferSize | Should -Be 32 + } +}