Skip to content

Commit

Permalink
Prevent Paramiko deadlock when test sends more than 2MB to stdout
Browse files Browse the repository at this point in the history
The paramiko library has a known problem where checking the exit
status can cause a deadlock if the command has written a lot of
output to the stdout (or stderr) channel:

https://docs.paramiko.org/en/stable/api/channel.html#paramiko.channel.Channel.recv_exit_status

We can work around this, for the stdout case, by reading the
data before checking the exit status.
  • Loading branch information
jburgess777 authored and philpep committed Oct 21, 2024
1 parent fa48a25 commit e1eb9c1
Show file tree
Hide file tree
Showing 2 changed files with 10 additions and 1 deletion.
9 changes: 9 additions & 0 deletions test/test_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -640,3 +640,12 @@ def test_get_hosts():
("a", 10),
("a", 1),
]


@pytest.mark.testinfra_hosts(*HOSTS)
def test_command_deadlock(host):
# Test for deadlock when exceeding Paramiko transport buffer (2MB)
# https://docs.paramiko.org/en/latest/api/channel.html#paramiko.channel.Channel.recv_exit_status
size = 3 * 1024 * 1024
output = host.check_output(f"python3 -c 'print(\"a\" * {size})'")
assert len(output) == size
2 changes: 1 addition & 1 deletion testinfra/backend/paramiko.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,9 @@ def _exec_command(self, command: bytes) -> tuple[int, bytes, bytes]:
if self.get_pty:
chan.get_pty()
chan.exec_command(command)
rc = chan.recv_exit_status()
stdout = b"".join(chan.makefile("rb"))
stderr = b"".join(chan.makefile_stderr("rb"))
rc = chan.recv_exit_status()
return rc, stdout, stderr

def run(self, command: str, *args: str, **kwargs: Any) -> base.CommandResult:
Expand Down

0 comments on commit e1eb9c1

Please sign in to comment.