# Buggy code
def _ssh_retry(func):
    """
    Decorator to retry ssh/scp/sftp in the case of a connection failure

    Will retry if:
    * an exception is caught
    * ssh returns 255
    Will not retry if
    * remaining_tries is <2
    * retries limit reached
    """

    @wraps(func)
    def wrapped(self, *args, **kwargs):
        remaining_tries = int(C.ANSIBLE_SSH_RETRIES) + 1
        cmd_summary = "%s..." % args[0]
        for attempt in range(remaining_tries):
            cmd = args[0]
            if attempt != 0 and self._play_context.password and isinstance(cmd, list):
                # If this is a retry, the fd/pipe for sshpass is closed, and we need a new one
                self.sshpass_pipe = os.pipe()
                cmd[1] = b"-d" + to_bytes(
                    self.sshpass_pipe[0],
                    nonstring="simplerepr",
                    errors="surrogate_or_strict",
                )

            try:
                try:
                    return_tuple = func(self, *args, **kwargs)
                    display.vvv(return_tuple, host=self.host)
                    # 0 = success
                    # 1-254 = remote command return code
                    # 255 = failure from the ssh command itself
                except AnsibleControlPersistBrokenPipeError as e:
                    # Retry one more time because of the ControlPersist broken pipe (see #16731)
                    display.vvv("RETRYING BECAUSE OF CONTROLPERSIST BROKEN PIPE")
                    return_tuple = func(self, *args, **kwargs)

                if return_tuple[0] != 255:
                    break
                else:
                    raise AnsibleConnectionFailure(
                        "Failed to connect to the host via ssh: %s"
                        % to_native(return_tuple[2])
                    )
            except (AnsibleConnectionFailure, Exception) as e:
                if attempt == remaining_tries - 1:
                    raise
                else:
                    pause = 2**attempt - 1
                    if pause > 30:
                        pause = 30

                    if isinstance(e, AnsibleConnectionFailure):
                        msg = (
                            "ssh_retry: attempt: %d, ssh return code is 255. cmd (%s), pausing for %d seconds"
                            % (attempt, cmd_summary, pause)
                        )
                    else:
                        msg = (
                            "ssh_retry: attempt: %d, caught exception(%s) from cmd (%s), pausing for %d seconds"
                            % (attempt, e, cmd_summary, pause)
                        )

                    display.vv(msg, host=self.host)

                    time.sleep(pause)
                    continue

        return return_tuple

    return wrapped


# Traceback (IOError)
(...)
<192.168.0.22> ESTABLISH SSH CONNECTION FOR USER: XXX
(...)
<192.168.0.22> SSH: ansible.cfg set ssh_args: (-C)(-o)(ControlMaster=auto)(-o)(ControlPersist=60s)
<192.168.0.22> SSH: ANSIBLE_HOST_KEY_CHECKING/host_key_checking disabled: (-o)(StrictHostKeyChecking=no)
<192.168.0.22> SSH: ANSIBLE_REMOTE_PORT/remote_port/ansible_port set: (-o)(Port=22)
<192.168.0.22> SSH: ANSIBLE_REMOTE_USER/remote_user/ansible_user/user/-u set: (-o)(User=XXX)
<192.168.0.22> SSH: ANSIBLE_TIMEOUT/timeout set: (-o)(ConnectTimeout=10)
<192.168.0.22> SSH: PlayContext set ssh_common_args: (-o)(StrictHostKeyChecking=no)
<192.168.0.22> SSH: PlayContext set ssh_extra_args: (-o)(UserKnownHostsFile=/dev/null)
<192.168.0.22> SSH: found only ControlPersist; added ControlPath: (-o)(ControlPath=/home/XXX/.ansible/cp/%h-%p-%r)
<192.168.0.22> SSH: EXEC sshpass -d14 ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o Port=22 -o User=XXX -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ControlPath=/home/XXX/.ansible/cp/%h-%p-%r 192.168.0.22 '/bin/sh -c '"'"'echo ~ &amp;&amp; sleep 0'"'"''
(...)
RETRYING BECAUSE OF CONTROLPERSIST BROKEN PIPE
<192.168.0.22> SSH: EXEC sshpass -d14 ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o Port=22 -o User=XXX -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ControlPath=/home/XXX/.ansible/cp/%h-%p-%r 192.168.0.22 '/bin/sh -c '"'"'echo ~ &amp;&amp; sleep 0'"'"''
(...)
close failed in file object destructor:
IOError: [Errno 9] Bad file descriptor
(...)
The full traceback is:
Traceback (most recent call last):
File "/usr/lib/python2.7/site-packages/ansible/executor/task_executor.py", line 125, in run
res = self._execute()
File "/usr/lib/python2.7/site-packages/ansible/executor/task_executor.py", line 522, in _execute
result = self._handler.run(task_vars=variables)
File "/usr/lib/python2.7/site-packages/ansible/plugins/action/package.py", line 69, in run
result.update(self._execute_module(module_name=module, module_args=new_module_args, task_vars=task_vars, wrap_async=self._task.async))
File "/usr/lib/python2.7/site-packages/ansible/plugins/action/__init__.py", line 637, in _execute_module
tmp = self._make_tmp_path()
File "/usr/lib/python2.7/site-packages/ansible/plugins/action/__init__.py", line 248, in _make_tmp_path
tmpdir =  self._remote_expand_user(C.DEFAULT_REMOTE_TMP, sudoable=False)
File "/usr/lib/python2.7/site-packages/ansible/plugins/action/__init__.py", line 545, in _remote_expand_user
data = self._low_level_execute_command(cmd, sudoable=False)
File "/usr/lib/python2.7/site-packages/ansible/plugins/action/__init__.py", line 883, in _low_level_execute_command
rc, stdout, stderr = self._connection.exec_command(cmd, in_data=in_data, sudoable=sudoable)
File "/usr/lib/python2.7/site-packages/ansible/plugins/connection/ssh.py", line 788, in exec_command
(returncode, stdout, stderr) = self._run(cmd, in_data, sudoable=sudoable)
File "/usr/lib/python2.7/site-packages/ansible/plugins/connection/ssh.py", line 85, in wrapped
return_tuple = func(self, *args, **kwargs)
File "/usr/lib/python2.7/site-packages/ansible/plugins/connection/ssh.py", line 461, in _run
os.write(self.sshpass_pipe[1], to_bytes(self._play_context.password) + b'
')
OSError: [Errno 9] Bad file descriptor
fatal: [192.168.0.22]: FAILED! => {
"failed": true,
"msg": "Unexpected failure during module execution.",
"stdout": ""
}

# Fixed code
def _ssh_retry(func):
    """
    Decorator to retry ssh/scp/sftp in the case of a connection failure

    Will retry if:
    * an exception is caught
    * ssh returns 255
    Will not retry if
    * remaining_tries is <2
    * retries limit reached
    """

    @wraps(func)
    def wrapped(self, *args, **kwargs):
        remaining_tries = int(C.ANSIBLE_SSH_RETRIES) + 1
        cmd_summary = "%s..." % args[0]
        for attempt in range(remaining_tries):
            cmd = args[0]
            if attempt != 0 and self._play_context.password and isinstance(cmd, list):
                # If this is a retry, the fd/pipe for sshpass is closed, and we need a new one
                self.sshpass_pipe = os.pipe()
                cmd[1] = b"-d" + to_bytes(
                    self.sshpass_pipe[0],
                    nonstring="simplerepr",
                    errors="surrogate_or_strict",
                )

            try:
                try:
                    return_tuple = func(self, *args, **kwargs)
                    display.vvv(return_tuple, host=self.host)
                    # 0 = success
                    # 1-254 = remote command return code
                    # 255 = failure from the ssh command itself
                except AnsibleControlPersistBrokenPipeError as e:
                    # Retry one more time because of the ControlPersist broken pipe (see #16731)
                    cmd = args[0]
                    if self._play_context.password and isinstance(cmd, list):
                        # This is a retry, so the fd/pipe for sshpass is closed, and we need a new one
                        self.sshpass_pipe = os.pipe()
                        cmd[1] = b"-d" + to_bytes(
                            self.sshpass_pipe[0],
                            nonstring="simplerepr",
                            errors="surrogate_or_strict",
                        )
                    display.vvv("RETRYING BECAUSE OF CONTROLPERSIST BROKEN PIPE")
                    return_tuple = func(self, *args, **kwargs)

                if return_tuple[0] != 255:
                    break
                else:
                    raise AnsibleConnectionFailure(
                        "Failed to connect to the host via ssh: %s"
                        % to_native(return_tuple[2])
                    )
            except (AnsibleConnectionFailure, Exception) as e:
                if attempt == remaining_tries - 1:
                    raise
                else:
                    pause = 2**attempt - 1
                    if pause > 30:
                        pause = 30

                    if isinstance(e, AnsibleConnectionFailure):
                        msg = (
                            "ssh_retry: attempt: %d, ssh return code is 255. cmd (%s), pausing for %d seconds"
                            % (attempt, cmd_summary, pause)
                        )
                    else:
                        msg = (
                            "ssh_retry: attempt: %d, caught exception(%s) from cmd (%s), pausing for %d seconds"
                            % (attempt, e, cmd_summary, pause)
                        )

                    display.vv(msg, host=self.host)

                    time.sleep(pause)
                    continue

        return return_tuple

    return wrapped