Python Paramiko timeout z długim wykonaniem, wymaga pełnego wyjścia
Jest wiele tematów dotykających części tytułu, ale nic, co całkiem nie satysfakcjonuje całej sprawy. Naciskam polecenie na zdalnym serwerze i potrzebuję pełnego wyjścia po długim czasie wykonania, powiedzmy około 5 minut. Korzystając z kanału udało mi się ustawić limit czasu, ale gdy przeczytałem stdout, otrzymałem tylko niewielką część wyjścia. Rozwiązaniem wydawało się czekać na channel.exit_status_ready (). To działało na udanym wywołaniu, ale nieudane wywołanie nigdy nie wyzwoli limitu czasu kanału. Po zapoznaniu się z dokumentami, teoretycznie twierdzę, że limit czasu działa tylko w przypadku operacji odczytu, a czekanie na status wyjścia nie kwalifikuje się. Oto ta próba:
channel = ssh.get_transport().open_session()
channel.settimeout(timeout)
channel.exec_command(cmd) # return on this is not reliable
while True:
try:
if channel.exit_status_ready():
if channel.recv_ready(): # so use recv instead...
output = channel.recv(1048576)
break
if channel.recv_stderr_ready(): # then check error
error = channel.recv_stderr(1048576)
break
except socket.timeout:
print("SSH channel timeout exceeded.")
break
except Exception:
traceback.print_exc()
break
Ładnie, prawda? Chciałbym, żeby to zadziałało.
Moją pierwszą próbą rozwiązania było użycie time.time (), aby uzyskać start, a następnie sprawdź start - time.time ()> timeout. Wydaje się to proste, ale w mojej obecnej wersji wypuszczam start - time.time () ze stałym limitem czasu, który powinien wywołać przerwę ... i zobaczyć różnice, które podwoją i potroją limit czasu bez wystąpienia przerwy. Aby zaoszczędzić miejsce, wspomnę o mojej trzeciej próbie, którą zawarłem z tym. Czytałem tutaj o używaniu select.select, aby poczekać na wyjście, i odnotowałem w dokumentacji, że jest tam także limit czasu. Jak zobaczysz w poniższym kodzie, zmieszałem wszystkie trzy metody - limit czasu kanału, limit czasu time.time i wybrałem limit czasu - a mimo to muszę zabić proces. Oto frankencode:
channel = ssh.get_transport().open_session()
channel.settimeout(timeout)
channel.exec_command(cmd) # return on this is not reliable
print("{0}".format(cmd))
start = time.time()
while True:
try:
rlist, wlist, elist = select([channel], [], [],
float(timeout))
print("{0}, {1}, {2}".format(rlist, wlist, elist))
if rlist is not None and len(rlist) > 0:
if channel.exit_status_ready():
if channel.recv_ready(): # so use recv instead...
output = channel.recv(1048576)
break
elif elist is not None and len(elist) > 0:
if channel.recv_stderr_ready(): # then check error
error = channel.recv_stderr(1048576)
break
print("{0} - {1} = {2}".format(
time.time(), start, time.time() - start))
if time.time() - start > timeout:
break
except socket.timeout:
print("SSH channel timeout exceeded.")
break
except Exception:
traceback.print_exc()
break
Oto typowe dane wyjściowe:
[<paramiko.Channel 3 (open) window=515488 -> <paramiko.Transport at 0x888414cL (cipher aes128-ctr, 128 bits) (active; 1 open channel(s))>>], [], []
1352494558.42 - 1352494554.69 = 3.73274183273
Górny wiersz to [rlist, wlist, elist] od select, dolny wiersz to time.time () - start = (time.time () - start). Rozerwałem ten bieg, licząc iteracje i łamanie na dole próby po zapętleniu 1000 razy. Limit czasu został ustawiony na 3 w przebiegu próbnym. Co dowodzi, że przechodzimy przez próbę, ale oczywiście żaden z trzech sposobów, które powinny ograniczać czas pracy.
Zapraszam do zgrania do kodu, jeśli coś źle zrozumiałem. Chciałbym, żeby to było uber-Pythonic i wciąż się uczę.