Skip to content

Commit c16a75e

Browse files
committed
Ability to set TCP keepalive to detect/closed dead connections
1 parent 3a3f71b commit c16a75e

File tree

1 file changed

+34
-1
lines changed

1 file changed

+34
-1
lines changed

aioftp/client.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import logging
66
import pathlib
77
import re
8+
import socket
89
from functools import partial
910

1011
from . import errors, pathio
@@ -109,7 +110,8 @@ def __init__(self, *, socket_timeout=None,
109110
read_speed_limit=None, write_speed_limit=None,
110111
path_timeout=None, path_io_factory=pathio.PathIO,
111112
encoding="utf-8", ssl=None, parse_list_line_custom=None,
112-
passive_commands=("epsv", "pasv"), **siosocks_asyncio_kwargs):
113+
passive_commands=("epsv", "pasv"), tcp_keepalive=None,
114+
**siosocks_asyncio_kwargs):
113115
self.socket_timeout = socket_timeout
114116
self.throttle = StreamThrottle.from_limits(
115117
read_speed_limit,
@@ -124,11 +126,36 @@ def __init__(self, *, socket_timeout=None,
124126
self._passive_commands = passive_commands
125127
self._open_connection = partial(open_connection, ssl=self.ssl,
126128
**siosocks_asyncio_kwargs)
129+
assert tcp_keepalive is None or isinstance(tcp_keepalive, (list, tuple)) and len(tcp_keepalive) == 3 and all(isinstance(x, int) and x > 0 for x in tcp_keepalive)
130+
self.tcp_keepalive = tcp_keepalive
131+
self._sock = None
132+
133+
def set_tcp_keepalive(self):
134+
"""
135+
Enable TCP keepalive if asked to do so
136+
"""
137+
138+
if self.tcp_keepalive is None:
139+
return
140+
if self._sock is None:
141+
return
142+
143+
self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
144+
if hasattr(socket, "TCP_KEEPIDLE"):
145+
self._sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, self.tcp_keepalive[0])
146+
if hasattr(socket, "TCP_KEEPINTVL"):
147+
self._sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, self.tcp_keepalive[1])
148+
if hasattr(socket, "TCP_KEEPCNT"):
149+
self._sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, self.tcp_keepalive[2])
127150

128151
async def connect(self, host, port=DEFAULT_PORT):
129152
self.server_host = host
130153
self.server_port = port
131154
reader, writer = await self._open_connection(host, port)
155+
156+
self._sock = writer.get_extra_info("socket")
157+
self.set_tcp_keepalive()
158+
132159
self.stream = ThrottleStreamIO(
133160
reader,
134161
writer,
@@ -587,6 +614,12 @@ class Client(BaseClient):
587614
dictionary with fields "modify", "type", "size". For more
588615
information see sources.
589616
:type parse_list_line_custom: callable
617+
618+
:param tcp_keepalive: optional three elements tuple containing integers
619+
to set TCP keepalive on socket (TCP_KEEPIDLE, TCP_KEEPINTVL,
620+
TCP_KEEPCNT) or None to disable this feature
621+
:type tcp_keepalive: :py:class:`list`, :py:class:`tuple` or `None`
622+
590623
:param **siosocks_asyncio_kwargs: siosocks key-word only arguments
591624
"""
592625
async def connect(self, host, port=DEFAULT_PORT):

0 commit comments

Comments
 (0)