Add network timeouts to prevent container hangs on unreachable servers
Without timeouts, smtplib.SMTP() / imaplib.IMAP4_SSL() / paramiko.Transport() hang indefinitely when the remote server is down or firewall-dropped, blocking the entire background thread and eventually freezing the app. - SMTP: 30s connect/operation timeout - IMAP: 30s connect/operation timeout - SFTP (paramiko): 15s socket connect, 15s banner, 30s auth - SMB and FTP already had timeouts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+11
-1
@@ -197,9 +197,19 @@ class _PlainFtpAdapter(_FtpAdapter):
|
|||||||
# SFTP (paramiko)
|
# SFTP (paramiko)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
SFTP_CONNECT_TIMEOUT = 15 # seconds
|
||||||
|
SFTP_BANNER_TIMEOUT = 15
|
||||||
|
SFTP_AUTH_TIMEOUT = 30
|
||||||
|
|
||||||
|
|
||||||
class _SftpAdapter(_FtpAdapter):
|
class _SftpAdapter(_FtpAdapter):
|
||||||
def __init__(self, server: str, port: int, username: str, password: str):
|
def __init__(self, server: str, port: int, username: str, password: str):
|
||||||
self.transport = paramiko.Transport((server, port))
|
# Build socket with timeout to prevent hangs on unreachable hosts
|
||||||
|
import socket as _socket
|
||||||
|
sock = _socket.create_connection((server, port), timeout=SFTP_CONNECT_TIMEOUT)
|
||||||
|
self.transport = paramiko.Transport(sock)
|
||||||
|
self.transport.banner_timeout = SFTP_BANNER_TIMEOUT
|
||||||
|
self.transport.auth_timeout = SFTP_AUTH_TIMEOUT
|
||||||
self.transport.connect(username=username, password=password)
|
self.transport.connect(username=username, password=password)
|
||||||
self.sftp = paramiko.SFTPClient.from_transport(self.transport)
|
self.sftp = paramiko.SFTPClient.from_transport(self.transport)
|
||||||
|
|
||||||
|
|||||||
@@ -40,14 +40,18 @@ def _connect_imap(settings: dict) -> imaplib.IMAP4_SSL | imaplib.IMAP4:
|
|||||||
|
|
||||||
if use_ssl:
|
if use_ssl:
|
||||||
ctx = ssl.create_default_context()
|
ctx = ssl.create_default_context()
|
||||||
conn = imaplib.IMAP4_SSL(server, port, ssl_context=ctx)
|
conn = imaplib.IMAP4_SSL(server, port, ssl_context=ctx, timeout=IMAP_TIMEOUT)
|
||||||
else:
|
else:
|
||||||
conn = imaplib.IMAP4(server, port)
|
conn = imaplib.IMAP4(server, port, timeout=IMAP_TIMEOUT)
|
||||||
|
|
||||||
conn.login(settings["imap_username"], settings["imap_password"])
|
conn.login(settings["imap_username"], settings["imap_password"])
|
||||||
return conn
|
return conn
|
||||||
|
|
||||||
|
|
||||||
|
SMTP_TIMEOUT = 30 # seconds - prevents hangs when SMTP server is unreachable
|
||||||
|
IMAP_TIMEOUT = 30 # seconds
|
||||||
|
|
||||||
|
|
||||||
def _connect_smtp(settings: dict) -> smtplib.SMTP | smtplib.SMTP_SSL:
|
def _connect_smtp(settings: dict) -> smtplib.SMTP | smtplib.SMTP_SSL:
|
||||||
server = settings["smtp_server"]
|
server = settings["smtp_server"]
|
||||||
port = int(settings.get("smtp_port", 587))
|
port = int(settings.get("smtp_port", 587))
|
||||||
@@ -55,9 +59,9 @@ def _connect_smtp(settings: dict) -> smtplib.SMTP | smtplib.SMTP_SSL:
|
|||||||
|
|
||||||
if mode == "ssl":
|
if mode == "ssl":
|
||||||
ctx = ssl.create_default_context()
|
ctx = ssl.create_default_context()
|
||||||
conn = smtplib.SMTP_SSL(server, port, context=ctx)
|
conn = smtplib.SMTP_SSL(server, port, context=ctx, timeout=SMTP_TIMEOUT)
|
||||||
else:
|
else:
|
||||||
conn = smtplib.SMTP(server, port)
|
conn = smtplib.SMTP(server, port, timeout=SMTP_TIMEOUT)
|
||||||
if mode == "starttls":
|
if mode == "starttls":
|
||||||
ctx = ssl.create_default_context()
|
ctx = ssl.create_default_context()
|
||||||
conn.starttls(context=ctx)
|
conn.starttls(context=ctx)
|
||||||
|
|||||||
Reference in New Issue
Block a user