diff --git a/truenas_api_client/__init__.py b/truenas_api_client/__init__.py index c340586..ccd59b1 100644 --- a/truenas_api_client/__init__.py +++ b/truenas_api_client/__init__.py @@ -58,7 +58,7 @@ from .exc import ReserveFDException, ClientException, ErrnoMixin, ValidationErrors, CallTimeout from .legacy import LegacyClient from .jsonrpc import CollectionUpdateParams, ErrorObj, JobFields, JSONRPCError, JSONRPCMessage, TruenasError -from .utils import MIDDLEWARE_RUN_DIR, ProgressBar, undefined, UndefinedType +from .utils import MIDDLEWARE_RUN_DIR, ProgressBar, undefined, UndefinedType, set_socket_options logger = logging.getLogger(__name__) @@ -218,21 +218,7 @@ def _on_open(self, app): """ # TCP keepalive settings don't apply to local unix sockets if 'ws+unix' not in self.url: - # enable keepalives on the socket - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - - # If the other node panics then the socket will - # remain open and we'll have to wait until the - # TCP timeout value expires (60 seconds default). - # To account for this: - # 1. if the socket is idle for 1 seconds - # 2. send a keepalive packet every 1 second - # 3. for a maximum up to 5 times - # - # after 5 times (5 seconds of no response), the socket will be closed - self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1) - self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 1) - self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5) + set_socket_options(self.socket) # if we're able to connect put socket in blocking mode # until all operations complete or error is raised diff --git a/truenas_api_client/legacy.py b/truenas_api_client/legacy.py index 5a42a8b..3ab056b 100644 --- a/truenas_api_client/legacy.py +++ b/truenas_api_client/legacy.py @@ -22,11 +22,10 @@ from . import ejson as json from .config import CALL_TIMEOUT from .exc import ReserveFDException, ClientException, ValidationErrors, CallTimeout -from .utils import MIDDLEWARE_RUN_DIR, undefined, UndefinedType +from .utils import MIDDLEWARE_RUN_DIR, undefined, UndefinedType, set_socket_options logger = logging.getLogger(__name__) - class WSClient: def __init__(self, url, *, client, reserved_ports=False, verify_ssl=True): self.url = url @@ -104,21 +103,7 @@ def _bind_to_reserved_port(self): def _on_open(self, app): # TCP keepalive settings don't apply to local unix sockets if 'ws+unix' not in self.url: - # enable keepalives on the socket - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - - # If the other node panics then the socket will - # remain open and we'll have to wait until the - # TCP timeout value expires (60 seconds default). - # To account for this: - # 1. if the socket is idle for 1 seconds - # 2. send a keepalive packet every 1 second - # 3. for a maximum up to 5 times - # - # after 5 times (5 seconds of no response), the socket will be closed - self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1) - self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 1) - self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5) + set_socket_options(self.socket) # if we're able to connect put socket in blocking mode # until all operations complete or error is raised diff --git a/truenas_api_client/utils.py b/truenas_api_client/utils.py index 3793bcf..69fdfae 100644 --- a/truenas_api_client/utils.py +++ b/truenas_api_client/utils.py @@ -8,6 +8,7 @@ undefined: A dummy object similar in purpose to `None` that indicates an unset variable. """ +import socket import sys from typing import Any, final, Mapping @@ -131,3 +132,30 @@ def __exit__(self, typ, value, traceback): if self.used_flag: self.draw() self.write_stream.write('\n') + + +def set_socket_options(socobj): + plat = sys.platform + if plat not in ('win32', 'linux', 'freebsd', 'darwin'): + raise RuntimeError('Unsupported platform') + + # enable keepalives on the socket + socobj.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + + # If the other node panics then the socket will + # remain open and we'll have to wait until the + # TCP timeout value expires (60 seconds default). + # To account for this: + # 1. if the socket is idle for 1 seconds + # 2. send a keepalive packet every 1 second + # 3. for a maximum up to 5 times + # + # after 5 times (5 seconds of no response), the socket will be closed + if plat in ('linux', 'freebsd', 'win32'): + socobj.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1) # pytype: disable=module-attr + + else: + socobj.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPALIVE, 1) # pytype: disable=module-attr + + socobj.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 1) + socobj.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5)