Skip to content

Commit 4f23edc

Browse files
authoredAug 25, 2020
Client close no join (#206)
* Correctly handle client disconnection - state can be left in libssh2 on disconnected sessions. Resolves #200 * Updated Changelog
1 parent 47cbb22 commit 4f23edc

File tree

7 files changed

+42
-12
lines changed

7 files changed

+42
-12
lines changed
 

‎Changelog.rst

+9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
Change Log
22
============
33

4+
1.11.2
5+
++++++
6+
7+
Fixes
8+
------
9+
10+
* `ParallelSSHClient.disconnect` would cause new client sessions to fail if `client.join` was not called prior - #200
11+
12+
413
1.11.0
514
++++++
615

‎pssh/clients/native/single.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -136,17 +136,20 @@ def __init__(self, host,
136136
def disconnect(self):
137137
"""Disconnect session, close socket if needed."""
138138
logger.debug("Disconnecting client for host %s", self.host)
139+
self._keepalive_greenlet = None
139140
if self.session is not None:
140141
try:
141142
self._eagain(self.session.disconnect)
142143
except Exception:
143144
pass
144-
if self.sock is not None and not self.sock.closed:
145-
self.sock.close()
146-
logger.debug("Client socket closed for host %s", self.host)
145+
self.session = None
146+
self.sock = None
147147

148148
def __del__(self):
149-
self.disconnect()
149+
try:
150+
self.disconnect()
151+
except Exception:
152+
pass
150153

151154
def __enter__(self):
152155
return self
@@ -328,7 +331,7 @@ def execute(self, cmd, use_pty=False, channel=None):
328331
channel = self.open_session() if channel is None else channel
329332
if use_pty:
330333
self._eagain(channel.pty)
331-
logger.debug("Executing command '%s'" % cmd)
334+
logger.debug("Executing command '%s'", cmd)
332335
self._eagain(channel.execute, cmd)
333336
return channel
334337

‎tests/embedded_server/openssh.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ def __init__(self, listen_ip='127.0.0.1', port=2222):
4141
self.listen_ip = listen_ip
4242
self.port = port
4343
self.server_proc = None
44-
self.sshd_config = SSHD_CONFIG + '_%s' % ''.join(
45-
random.choice(string.ascii_lowercase + string.digits)
46-
for _ in range(8))
44+
self.random_server = ''.join(random.choice(string.ascii_lowercase + string.digits)
45+
for _ in range(8))
46+
self.sshd_config = SSHD_CONFIG + '_%s' % self.random_server
4747
self._fix_masks()
4848
self.make_config()
4949

@@ -60,7 +60,9 @@ def make_config(self):
6060
template = Template(tmpl)
6161
with open(self.sshd_config, 'w') as fh:
6262
fh.write(template.render(parent_dir=os.path.abspath(DIR_NAME),
63-
listen_ip=self.listen_ip))
63+
listen_ip=self.listen_ip,
64+
random_server=self.random_server,
65+
))
6466
fh.write(os.linesep)
6567

6668
def start_server(self):

‎tests/embedded_server/sshd_config.tmpl

+2
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ ListenAddress {{listen_ip}}
77
AcceptEnv LANG LC_*
88
Subsystem sftp internal-sftp
99
AuthorizedKeysFile {{parent_dir}}/authorized_keys
10+
MaxSessions 100
11+
PidFile {{random_server}}.pid

‎tests/test_native_parallel_client.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1511,6 +1511,5 @@ def test_client_disconnect(self):
15111511
return_list=True)
15121512
client.join(output, consume_output=True)
15131513
single_client = list(client._host_clients.values())[0]
1514-
self.assertFalse(single_client.sock.closed)
15151514
del client
1516-
self.assertTrue(single_client.sock.closed)
1515+
self.assertEqual(single_client.session, None)

‎tests/test_native_single_client.py

+15
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,18 @@ def test_connection_timeout(self):
156156
# Should fail within greenlet timeout, otherwise greenlet will
157157
# raise timeout which will fail the test
158158
self.assertRaises(ConnectionErrorException, cmd.get, timeout=2)
159+
160+
def test_multiple_clients_exec_terminates_channels(self):
161+
# See #200 - Multiple clients should not interfere with
162+
# each other. session.disconnect can leave state in libssh2
163+
# and break subsequent sessions even on different socket and
164+
# session
165+
for _ in range(5):
166+
client = SSHClient(self.host, port=self.port,
167+
pkey=self.user_key,
168+
num_retries=1,
169+
allow_agent=False)
170+
channel = client.execute(self.cmd)
171+
output = list(client.read_output(channel))
172+
self.assertListEqual(output, [b'me'])
173+
client.disconnect()

‎tests/test_native_tunnel.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ def test_tunnel_channel_failure(self):
206206
tunnel.cleanup()
207207
spawn(proxy_client.execute, 'echo me')
208208
proxy_client.disconnect()
209-
self.assertTrue(proxy_client.sock.closed)
209+
self.assertEqual(proxy_client.sock, None)
210210
finally:
211211
remote_server.stop()
212212

0 commit comments

Comments
 (0)
Failed to load comments.