fix win_shell/win_command deadlock on large interleaved stdout/stderr (#5384)

fixes #5229
This commit is contained in:
Matt Davis 2016-10-25 17:21:00 -07:00 committed by Matt Clay
parent 1ecb63f0e0
commit 8e97539e20
2 changed files with 79 additions and 8 deletions

View file

@ -42,8 +42,11 @@ If($removes -and -not $(Test-Path $removes)) {
$util_def = @' $util_def = @'
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
namespace Ansible.Command namespace Ansible.Command
{ {
@ -68,6 +71,32 @@ namespace Ansible.Command
return cmdlineParts; return cmdlineParts;
} }
public static void GetProcessOutput(StreamReader stdoutStream, StreamReader stderrStream, out string stdout, out string stderr)
{
var sowait = new EventWaitHandle(false, EventResetMode.ManualReset);
var sewait = new EventWaitHandle(false, EventResetMode.ManualReset);
string so = null, se = null;
ThreadPool.QueueUserWorkItem((s)=>
{
so = stdoutStream.ReadToEnd();
sowait.Set();
});
ThreadPool.QueueUserWorkItem((s) =>
{
se = stderrStream.ReadToEnd();
sewait.Set();
});
foreach(var wh in new WaitHandle[] { sowait, sewait })
wh.WaitOne();
stdout = so;
stderr = se;
}
} }
} }
'@ '@
@ -112,11 +141,12 @@ Catch [System.ComponentModel.Win32Exception] {
Exit-Json @{failed=$true;changed=$false;cmd=$raw_command_line;rc=$excep.Exception.NativeErrorCode;msg=$excep.Exception.Message} Exit-Json @{failed=$true;changed=$false;cmd=$raw_command_line;rc=$excep.Exception.NativeErrorCode;msg=$excep.Exception.Message}
} }
# TODO: resolve potential deadlock here if stderr fills buffer (~4k) before stdout is closed, $stdout = $stderr = [string] $null
# perhaps some async stream pumping with Process Output/ErrorDataReceived events...
$result.stdout = $proc.StandardOutput.ReadToEnd() [Ansible.Command.NativeUtil]::GetProcessOutput($proc.StandardOutput, $proc.StandardError, [ref] $stdout, [ref] $stderr) | Out-Null
$result.stderr = $proc.StandardError.ReadToEnd()
$result.stdout = $stdout
$result.stderr = $stderr
$proc.WaitForExit() | Out-Null $proc.WaitForExit() | Out-Null

View file

@ -22,6 +22,44 @@
Set-StrictMode -Version 2 Set-StrictMode -Version 2
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
$helper_def = @"
using System.Diagnostics;
using System.IO;
using System.Threading;
namespace Ansible.Shell
{
public class ProcessUtil
{
public static void GetProcessOutput(StreamReader stdoutStream, StreamReader stderrStream, out string stdout, out string stderr)
{
var sowait = new EventWaitHandle(false, EventResetMode.ManualReset);
var sewait = new EventWaitHandle(false, EventResetMode.ManualReset);
string so = null, se = null;
ThreadPool.QueueUserWorkItem((s)=>
{
so = stdoutStream.ReadToEnd();
sowait.Set();
});
ThreadPool.QueueUserWorkItem((s) =>
{
se = stderrStream.ReadToEnd();
sewait.Set();
});
foreach(var wh in new WaitHandle[] { sowait, sewait })
wh.WaitOne();
stdout = so;
stderr = se;
}
}
}
"@
$parsed_args = Parse-Args $args $false $parsed_args = Parse-Args $args $false
$raw_command_line = $(Get-AnsibleParam $parsed_args "_raw_params" -failifempty $true).Trim() $raw_command_line = $(Get-AnsibleParam $parsed_args "_raw_params" -failifempty $true).Trim()
@ -40,6 +78,8 @@ If($removes -and -not $(Test-Path $removes)) {
Exit-Json @{cmd=$raw_command_line; msg="skipped, since $removes does not exist"; changed=$false; skipped=$true; rc=0} Exit-Json @{cmd=$raw_command_line; msg="skipped, since $removes does not exist"; changed=$false; skipped=$true; rc=0}
} }
Add-Type -TypeDefinition $helper_def
$exec_args = $null $exec_args = $null
If(-not $executable -or $executable -eq "powershell") { If(-not $executable -or $executable -eq "powershell") {
@ -80,11 +120,12 @@ Catch [System.ComponentModel.Win32Exception] {
Exit-Json @{failed=$true;changed=$false;cmd=$raw_command_line;rc=$excep.Exception.NativeErrorCode;msg=$excep.Exception.Message} Exit-Json @{failed=$true;changed=$false;cmd=$raw_command_line;rc=$excep.Exception.NativeErrorCode;msg=$excep.Exception.Message}
} }
# TODO: resolve potential deadlock here if stderr fills buffer (~4k) before stdout is closed, $stdout = $stderr = [string] $null
# perhaps some async stream pumping with Process Output/ErrorDataReceived events...
$result.stdout = $proc.StandardOutput.ReadToEnd() [Ansible.Shell.ProcessUtil]::GetProcessOutput($proc.StandardOutput, $proc.StandardError, [ref] $stdout, [ref] $stderr) | Out-Null
$result.stderr = $proc.StandardError.ReadToEnd()
$result.stdout = $stdout
$result.stderr = $stderr
# TODO: decode CLIXML stderr output (and other streams?) # TODO: decode CLIXML stderr output (and other streams?)