Make run_command process communication smarter
The run_command module function will now poll stdout/stderr for data rather than using the builtin command communicate(), which can hang under certain circumstances. Fixes #7452 Fixes #7748 Fixes #8163
This commit is contained in:
parent
74cbeb1292
commit
5d0bb33ede
1 changed files with 45 additions and 6 deletions
|
@ -53,6 +53,7 @@ import sys
|
||||||
import syslog
|
import syslog
|
||||||
import types
|
import types
|
||||||
import time
|
import time
|
||||||
|
import select
|
||||||
import shutil
|
import shutil
|
||||||
import stat
|
import stat
|
||||||
import tempfile
|
import tempfile
|
||||||
|
@ -1163,7 +1164,7 @@ class AnsibleModule(object):
|
||||||
# rename might not preserve context
|
# rename might not preserve context
|
||||||
self.set_context_if_different(dest, context, False)
|
self.set_context_if_different(dest, context, False)
|
||||||
|
|
||||||
def run_command(self, args, check_rc=False, close_fds=False, executable=None, data=None, binary_data=False, path_prefix=None, cwd=None, use_unsafe_shell=False):
|
def run_command(self, args, check_rc=False, close_fds=True, executable=None, data=None, binary_data=False, path_prefix=None, cwd=None, use_unsafe_shell=False):
|
||||||
'''
|
'''
|
||||||
Execute a command, returns rc, stdout, and stderr.
|
Execute a command, returns rc, stdout, and stderr.
|
||||||
args is the command to run
|
args is the command to run
|
||||||
|
@ -1237,7 +1238,7 @@ class AnsibleModule(object):
|
||||||
executable=executable,
|
executable=executable,
|
||||||
shell=shell,
|
shell=shell,
|
||||||
close_fds=close_fds,
|
close_fds=close_fds,
|
||||||
stdin= st_in,
|
stdin=st_in,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE
|
stderr=subprocess.PIPE
|
||||||
)
|
)
|
||||||
|
@ -1260,10 +1261,48 @@ class AnsibleModule(object):
|
||||||
try:
|
try:
|
||||||
cmd = subprocess.Popen(args, **kwargs)
|
cmd = subprocess.Popen(args, **kwargs)
|
||||||
|
|
||||||
|
# the communication logic here is essentially taken from that
|
||||||
|
# of the _communicate() function in ssh.py
|
||||||
|
|
||||||
|
stdout = ''
|
||||||
|
stderr = ''
|
||||||
|
rpipes = [cmd.stdout, cmd.stderr]
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
if not binary_data:
|
if not binary_data:
|
||||||
data += '\n'
|
data += '\n'
|
||||||
out, err = cmd.communicate(input=data)
|
cmd.stdin.write(data)
|
||||||
|
cmd.stdin.close()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
rfd, wfd, efd = select.select(rpipes, [], rpipes, 1)
|
||||||
|
if cmd.stdout in rfd:
|
||||||
|
dat = os.read(cmd.stdout.fileno(), 9000)
|
||||||
|
stdout += dat
|
||||||
|
if dat == '':
|
||||||
|
rpipes.remove(cmd.stdout)
|
||||||
|
if cmd.stderr in rfd:
|
||||||
|
dat = os.read(cmd.stderr.fileno(), 9000)
|
||||||
|
stderr += dat
|
||||||
|
if dat == '':
|
||||||
|
rpipes.remove(cmd.stderr)
|
||||||
|
# only break out if no pipes are left to read or
|
||||||
|
# the pipes are completely read and
|
||||||
|
# the process is terminated
|
||||||
|
if (not rpipes or not rfd) and cmd.poll() is not None:
|
||||||
|
break
|
||||||
|
# No pipes are left to read but process is not yet terminated
|
||||||
|
# Only then it is safe to wait for the process to be finished
|
||||||
|
# NOTE: Actually cmd.poll() is always None here if rpipes is empty
|
||||||
|
elif not rpipes and cmd.poll() == None:
|
||||||
|
cmd.wait()
|
||||||
|
# The process is terminated. Since no pipes to read from are
|
||||||
|
# left, there is no need to call select() again.
|
||||||
|
break
|
||||||
|
|
||||||
|
cmd.stdout.close()
|
||||||
|
cmd.stderr.close()
|
||||||
|
|
||||||
rc = cmd.returncode
|
rc = cmd.returncode
|
||||||
except (OSError, IOError), e:
|
except (OSError, IOError), e:
|
||||||
self.fail_json(rc=e.errno, msg=str(e), cmd=clean_args)
|
self.fail_json(rc=e.errno, msg=str(e), cmd=clean_args)
|
||||||
|
@ -1271,13 +1310,13 @@ class AnsibleModule(object):
|
||||||
self.fail_json(rc=257, msg=traceback.format_exc(), cmd=clean_args)
|
self.fail_json(rc=257, msg=traceback.format_exc(), cmd=clean_args)
|
||||||
|
|
||||||
if rc != 0 and check_rc:
|
if rc != 0 and check_rc:
|
||||||
msg = err.rstrip()
|
msg = stderr.rstrip()
|
||||||
self.fail_json(cmd=clean_args, rc=rc, stdout=out, stderr=err, msg=msg)
|
self.fail_json(cmd=clean_args, rc=rc, stdout=stdout, stderr=stderr, msg=msg)
|
||||||
|
|
||||||
# reset the pwd
|
# reset the pwd
|
||||||
os.chdir(prev_dir)
|
os.chdir(prev_dir)
|
||||||
|
|
||||||
return (rc, out, err)
|
return (rc, stdout, stderr)
|
||||||
|
|
||||||
def append_to_file(self, filename, str):
|
def append_to_file(self, filename, str):
|
||||||
filename = os.path.expandvars(os.path.expanduser(filename))
|
filename = os.path.expandvars(os.path.expanduser(filename))
|
||||||
|
|
Loading…
Reference in a new issue