Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 32 additions & 7 deletions deluge_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import socket
import ssl
import struct
import time
import warnings
import zlib
import io
Expand Down Expand Up @@ -70,6 +71,20 @@ def __init__(self, host, port, username, password, decode_utf8=False, automatic_

self._create_socket()

def _safe_recv(self, size, retries=5):
"""
Retry-safe recv wrapper to handle Deluge 2.2.0's tendency
to close or stall SSL sockets briefly.
"""
while True:
try:
return self._socket.recv(size)
except (ssl.SSLError, socket.timeout, socket.error):
if retries <= 0:
raise CallTimeoutException()
retries -= 1
time.sleep(0.2)

def _create_socket(self, ssl_version=None, ciphers=None):
# Insecure context without remote certificate verification
# This logic is a bit messy to try and support various configurations
Expand Down Expand Up @@ -132,7 +147,7 @@ def _detect_deluge_version(self):
self._send_call(1, None, 'daemon.info')
self._send_call(2, None, 'daemon.info')
self._send_call(2, 1, 'daemon.info')
result = self._socket.recv(1)
result = self._safe_recv(1)
if result[:1] == b'D':
# This is a protocol deluge 2.0 was using before release
self.deluge_version = 2
Expand Down Expand Up @@ -179,7 +194,7 @@ def _receive_response(self, deluge_version, protocol_version, partial_data=b''):
data = partial_data
while True:
try:
d = self._socket.recv(READ_SIZE)
d = self._safe_recv(READ_SIZE)
except ssl.SSLError:
raise CallTimeoutException()

Expand Down Expand Up @@ -247,13 +262,23 @@ def _receive_response(self, deluge_version, protocol_version, partial_data=b''):
retval = data[0]
return retval

def reconnect(self):
def reconnect(self, retries=5, delay=1):
"""
Reconnect
More tolerant reconnect logic for Deluge 2.2.0.
Handles SSL renegotiation, idle socket closes, and
transient RPC stalls.
"""
self.disconnect()
self._create_socket()
self.connect()
for _ in range(retries):
try:
self.disconnect()
self._create_socket()
self.connect()
return
except (ssl.SSLError, socket.error, ConnectionLostException, CallTimeoutException):
time.sleep(delay)
delay = min(delay * 2, 10)

raise FailedToReconnectException("Could not reconnect after multiple attempts")

def call(self, method, *args, **kwargs):
"""
Expand Down