From 494793617669d995b91608e5ec6b32c772371eea Mon Sep 17 00:00:00 2001 From: Darragh Bailey Date: Tue, 11 Dec 2018 19:26:06 +0000 Subject: [PATCH] Remove sleep and switch to pipe for IPC (#28561) Use a simple multiprocessing pipe to delay exiting the parent process until after the child has been doubly forked. Using a simple IPC to allow the forked process to start avoids the control node waiting unnecessarily for lightly loaded systems. --- .../modules/utilities/logic/async_wrapper.py | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/lib/ansible/modules/utilities/logic/async_wrapper.py b/lib/ansible/modules/utilities/logic/async_wrapper.py index 86ed1656b2b..47fac5989ec 100644 --- a/lib/ansible/modules/utilities/logic/async_wrapper.py +++ b/lib/ansible/modules/utilities/logic/async_wrapper.py @@ -18,6 +18,7 @@ import traceback import signal import time import syslog +import multiprocessing from ansible.module_utils._text import to_text @@ -26,6 +27,9 @@ PY3 = sys.version_info[0] == 3 syslog.openlog('ansible-%s' % os.path.basename(__file__)) syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % " ".join(sys.argv[1:])) +# pipe for communication between forked process and parent +ipc_watcher, ipc_notifier = multiprocessing.Pipe() + def notice(msg): syslog.syslog(syslog.LOG_NOTICE, msg) @@ -129,6 +133,11 @@ def _run_module(wrapped_cmd, jid, job_path): jobfile = open(tmp_job_path, "w") result = {} + # signal grandchild process started and isolated from being terminated + # by the connection being closed sending a signal to the job group + ipc_notifier.send(True) + ipc_notifier.close() + outdata = '' filtered_outdata = '' stderr = '' @@ -140,6 +149,7 @@ def _run_module(wrapped_cmd, jid, job_path): if interpreter: cmd = interpreter + cmd script = subprocess.Popen(cmd, shell=False, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (outdata, stderr) = script.communicate() if PY3: outdata = outdata.decode('utf-8', 'surrogateescape') @@ -241,15 +251,32 @@ if __name__ == '__main__': # to initialize PRIOR to ansible trying to clean up the launch directory (and argsfile) # this probably could be done with some IPC later. Modules should always read # the argsfile at the very first start of their execution anyway + + # close off notifier handle in grandparent, probably unnecessary as + # this process doesn't hang around long enough + ipc_notifier.close() + + # allow waiting up to 2.5 seconds in total should be long enough for worst + # loaded environment in practice. + retries = 25 + while retries > 0: + if ipc_watcher.poll(0.1): + break + else: + retries = retries - 1 + continue + notice("Return async_wrapper task started.") print(json.dumps({"started": 1, "finished": 0, "ansible_job_id": jid, "results_file": job_path, "_ansible_suppress_tmpdir_delete": not preserve_tmp})) sys.stdout.flush() - time.sleep(1) sys.exit(0) else: # The actual wrapper process + # close off the receiving end of the pipe from child process + ipc_watcher.close() + # Daemonize, so we keep on running daemonize_self() @@ -258,6 +285,10 @@ if __name__ == '__main__': sub_pid = os.fork() if sub_pid: + # close off inherited pipe handles + ipc_watcher.close() + ipc_notifier.close() + # the parent stops the process after the time limit remaining = int(time_limit)