# Buggy code
def join(self, output, consume_output=False, timeout=None, encoding="utf-8"):
    """Wait until all remote commands in output have finished
    and retrieve exit codes. Does *not* block other commands from
    running in parallel.

    :param output: Output of commands to join on
    :type output: dict as returned by
      :py:func:`pssh.pssh_client.ParallelSSHClient.get_output`
    :param consume_output: Whether or not join should consume output
      buffers. Output buffers will be empty after ``join`` if set
      to ``True``. Must be set to ``True`` to allow host logger to log
      output on call to ``join`` when host logger has been enabled.
    :type consume_output: bool
    :param timeout: Timeout in seconds if remote command is not yet
      finished. Note that use of timeout forces ``consume_output=True``
      otherwise the channel output pending to be consumed always results
      in the channel not being finished.
    :type timeout: int

    :raises: :py:class:`pssh.exceptions.Timeout` on timeout requested and
      reached with commands still running.

    :rtype: ``None``"""
    for host, host_out in output.items():
        if host not in self.host_clients or self.host_clients[host] is None:
            continue
        client = self.host_clients[host]
        channel = host_out.channel
        stdout, stderr = self.reset_output_generators(
            host_out, client=client, channel=channel, timeout=timeout, encoding=encoding
        )
        try:
            client.wait_finished(channel, timeout=timeout)
        except Timeout:
            raise Timeout(
                "Timeout of %s sec(s) reached on host %s with command still running",
                timeout,
                host,
            )
        if timeout:
            # Must consume buffers prior to EOF check
            self._consume_output(stdout, stderr)
            if not channel.eof():
                raise Timeout(
                    "Timeout of %s sec(s) reached on host %s with command "
                    "still running",
                    timeout,
                    host,
                )
        elif consume_output:
            self._consume_output(stdout, stderr)
    self.get_exit_codes(output)


# Traceback (ValueError)
Traceback (most recent call last):
File "./aws-instances", line 172, in <module>
main()
File "./aws-instances", line 73, in main
facts = remote_facts(ips)
File "./aws-instances", line 146, in remote_facts
out_kern = client.run_command('uname -r')
File "/home/user/do-aws-ec2-facts/aws/lib64/python3.6/site-packages/pssh/clients/native/parallel.py", line 223, in run_command
greenlet_timeout=greenlet_timeout)
File "/home/user/do-aws-ec2-facts/aws/lib64/python3.6/site-packages/pssh/clients/base_pssh.py", line 96, in run_command
self.get_output(cmd, output, timeout=greenlet_timeout)
File "/home/user/do-aws-ec2-facts/aws/lib64/python3.6/site-packages/pssh/clients/base_pssh.py", line 141, in get_output
(channel, host, stdout, stderr, stdin) = cmd.get(timeout=timeout)
File "src/gevent/greenlet.py", line 709, in gevent._greenlet.Greenlet.get
File "src/gevent/greenlet.py", line 317, in gevent._greenlet.Greenlet._raise_exception
File "/home/user/do-aws-ec2-facts/aws/lib64/python3.6/site-packages/gevent/_compat.py", line 47, in reraise
raise value.with_traceback(tb)
File "src/gevent/greenlet.py", line 766, in gevent._greenlet.Greenlet.run
File "/home/user/do-aws-ec2-facts/aws/lib64/python3.6/site-packages/pssh/clients/native/parallel.py", line 237, in _run_command
raise ex
File "/home/user/do-aws-ec2-facts/aws/lib64/python3.6/site-packages/pssh/clients/native/parallel.py", line 233, in _run_command
use_pty=use_pty, encoding=encoding, timeout=timeout)
File "/home/user/do-aws-ec2-facts/aws/lib64/python3.6/site-packages/pssh/clients/native/single.py", line 442, in run_command
channel = self.execute(_command, use_pty=use_pty)
File "/home/user/do-aws-ec2-facts/aws/lib64/python3.6/site-packages/pssh/clients/native/single.py", line 324, in execute
channel = self.open_session() if channel is None else channel
File "/home/user/do-aws-ec2-facts/aws/lib64/python3.6/site-packages/pssh/clients/native/single.py", line 297, in open_session
wait_select(self.session)
File "pssh/native/_ssh2.pyx", line 171, in pssh.native._ssh2.wait_select
File "/home/user/do-aws-ec2-facts/aws/lib64/python3.6/site-packages/gevent/select.py", line 145, in select
sel_results = _original_select(rlist, wlist, xlist, 0)
ValueError: filedescriptor out of range in select()

# Fixed code
def join(self, output, consume_output=False, timeout=None, encoding="utf-8"):
    """Wait until all remote commands in output have finished
    and retrieve exit codes. Does *not* block other commands from
    running in parallel.

    :param output: Output of commands to join on
    :type output: dict as returned by
      :py:func:`pssh.pssh_client.ParallelSSHClient.get_output`
    :param consume_output: Whether or not join should consume output
      buffers. Output buffers will be empty after ``join`` if set
      to ``True``. Must be set to ``True`` to allow host logger to log
      output on call to ``join`` when host logger has been enabled.
    :type consume_output: bool
    :param timeout: Timeout in seconds if remote command is not yet
      finished. Note that use of timeout forces ``consume_output=True``
      otherwise the channel output pending to be consumed always results
      in the channel not being finished.
    :type timeout: int
    :param encoding: Encoding to use for output. Must be valid
      `Python codec <https://docs.python.org/library/codecs.html>`_
    :type encoding: str

    :raises: :py:class:`pssh.exceptions.Timeout` on timeout requested and
      reached with commands still running.

    :rtype: ``None``"""
    cmds = []
    if isinstance(output, list):
        for host_i, host_out in enumerate(output):
            host = self.hosts[host_i]
            cmds.append(
                self.pool.spawn(
                    self._join,
                    host_i,
                    host,
                    host_out,
                    consume_output=consume_output,
                    timeout=timeout,
                )
            )
    elif isinstance(output, dict):
        for host_i, (host, host_out) in enumerate(output.items()):
            cmds.append(
                self.pool.spawn(
                    self._join,
                    host_i,
                    host,
                    host_out,
                    consume_output=consume_output,
                    timeout=timeout,
                )
            )
    else:
        raise ValueError("Unexpected output object type")
    # Errors raised by self._join should be propagated.
    # Timeouts are handled by self._join itself.
    joinall(cmds, raise_error=True)
    self.get_exit_codes(output)