fix win_shell/win_command deadlock on large interleaved stdout/stderr (#5384)
fixes #5229
This commit is contained in:
parent
1ecb63f0e0
commit
8e97539e20
2 changed files with 79 additions and 8 deletions
|
@ -42,8 +42,11 @@ If($removes -and -not $(Test-Path $removes)) {
|
|||
$util_def = @'
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ansible.Command
|
||||
{
|
||||
|
@ -68,6 +71,32 @@ namespace Ansible.Command
|
|||
|
||||
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}
|
||||
}
|
||||
|
||||
# TODO: resolve potential deadlock here if stderr fills buffer (~4k) before stdout is closed,
|
||||
# perhaps some async stream pumping with Process Output/ErrorDataReceived events...
|
||||
$stdout = $stderr = [string] $null
|
||||
|
||||
$result.stdout = $proc.StandardOutput.ReadToEnd()
|
||||
$result.stderr = $proc.StandardError.ReadToEnd()
|
||||
[Ansible.Command.NativeUtil]::GetProcessOutput($proc.StandardOutput, $proc.StandardError, [ref] $stdout, [ref] $stderr) | Out-Null
|
||||
|
||||
$result.stdout = $stdout
|
||||
$result.stderr = $stderr
|
||||
|
||||
$proc.WaitForExit() | Out-Null
|
||||
|
||||
|
|
|
@ -22,6 +22,44 @@
|
|||
Set-StrictMode -Version 2
|
||||
$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
|
||||
|
||||
$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}
|
||||
}
|
||||
|
||||
Add-Type -TypeDefinition $helper_def
|
||||
|
||||
$exec_args = $null
|
||||
|
||||
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}
|
||||
}
|
||||
|
||||
# TODO: resolve potential deadlock here if stderr fills buffer (~4k) before stdout is closed,
|
||||
# perhaps some async stream pumping with Process Output/ErrorDataReceived events...
|
||||
$stdout = $stderr = [string] $null
|
||||
|
||||
$result.stdout = $proc.StandardOutput.ReadToEnd()
|
||||
$result.stderr = $proc.StandardError.ReadToEnd()
|
||||
[Ansible.Shell.ProcessUtil]::GetProcessOutput($proc.StandardOutput, $proc.StandardError, [ref] $stdout, [ref] $stderr) | Out-Null
|
||||
|
||||
$result.stdout = $stdout
|
||||
$result.stderr = $stderr
|
||||
|
||||
# TODO: decode CLIXML stderr output (and other streams?)
|
||||
|
||||
|
|
Loading…
Reference in a new issue