Skip to content

Commit 94a592d

Browse files
Fix keepalive connection out of the range issue (patroni#3089) (patroni#3158)
1 parent 74a72e4 commit 94a592d

File tree

2 files changed

+42
-6
lines changed

2 files changed

+42
-6
lines changed

patroni/utils.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,6 +1064,33 @@ def data_directory_is_empty(data_dir: str) -> bool:
10641064
return all(os.name != 'nt' and (n.startswith('.') or n == 'lost+found') for n in os.listdir(data_dir))
10651065

10661066

1067+
def apply_keepalive_limit(option: str, value: int) -> int:
1068+
"""
1069+
Ensures provided *value* for keepalive *option* does not exceed the maximum allowed value for the current platform.
1070+
1071+
:param option: The TCP keepalive option name. Possible values are:
1072+
1073+
* ``TCP_USER_TIMEOUT``;
1074+
* ``TCP_KEEPIDLE``;
1075+
* ``TCP_KEEPINTVL``;
1076+
* ``TCP_KEEPCNT``.
1077+
1078+
:param value: The desired value for the keepalive option.
1079+
1080+
:returns: maybe adjusted value.
1081+
"""
1082+
max_of_options = {
1083+
'linux': {'TCP_USER_TIMEOUT': 2147483647, 'TCP_KEEPIDLE': 32767, 'TCP_KEEPINTVL': 32767, 'TCP_KEEPCNT': 127},
1084+
'darwin': {'TCP_KEEPIDLE': 4294967, 'TCP_KEEPINTVL': 4294967, 'TCP_KEEPCNT': 2147483647},
1085+
}
1086+
platform = 'linux' if sys.platform.startswith('linux') else sys.platform
1087+
max_possible_value = max_of_options.get(platform, {}).get(option)
1088+
if max_possible_value is not None and value > max_possible_value:
1089+
logger.debug('%s changed from %d to %d.', option, value, max_possible_value)
1090+
value = max_possible_value
1091+
return value
1092+
1093+
10671094
def keepalive_intvl(timeout: int, idle: int, cnt: int = 3) -> int:
10681095
"""Calculate the value to be used as ``TCP_KEEPINTVL`` based on *timeout*, *idle*, and *cnt*.
10691096
@@ -1073,7 +1100,8 @@ def keepalive_intvl(timeout: int, idle: int, cnt: int = 3) -> int:
10731100
10741101
:returns: the value to be used as ``TCP_KEEPINTVL``.
10751102
"""
1076-
return max(1, int(float(timeout - idle) / cnt))
1103+
intvl = max(1, int(float(timeout - idle) / cnt))
1104+
return apply_keepalive_limit('TCP_KEEPINTVL', intvl)
10771105

10781106

10791107
def keepalive_socket_options(timeout: int, idle: int, cnt: int = 3) -> Iterator[Tuple[int, int, int]]:
@@ -1105,20 +1133,22 @@ def keepalive_socket_options(timeout: int, idle: int, cnt: int = 3) -> Iterator[
11051133
if not (sys.platform.startswith('linux') or sys.platform.startswith('darwin')):
11061134
return
11071135

1108-
if sys.platform.startswith('linux'):
1109-
yield (socket.SOL_TCP, 18, int(timeout * 1000)) # TCP_USER_TIMEOUT
1110-
1136+
TCP_USER_TIMEOUT = getattr(socket, 'TCP_USER_TIMEOUT', None)
1137+
if TCP_USER_TIMEOUT is not None:
1138+
yield (socket.SOL_TCP, TCP_USER_TIMEOUT, apply_keepalive_limit('TCP_USER_TIMEOUT', int(timeout * 1000)))
11111139
# The socket constants from MacOS netinet/tcp.h are not exported by python's
11121140
# socket module, therefore we are using 0x10, 0x101, 0x102 constants.
11131141
TCP_KEEPIDLE = getattr(socket, 'TCP_KEEPIDLE', 0x10 if sys.platform.startswith('darwin') else None)
11141142
if TCP_KEEPIDLE is not None:
1143+
idle = apply_keepalive_limit('TCP_KEEPIDLE', idle)
11151144
yield (socket.IPPROTO_TCP, TCP_KEEPIDLE, idle)
11161145
TCP_KEEPINTVL = getattr(socket, 'TCP_KEEPINTVL', 0x101 if sys.platform.startswith('darwin') else None)
11171146
if TCP_KEEPINTVL is not None:
11181147
intvl = keepalive_intvl(timeout, idle, cnt)
11191148
yield (socket.IPPROTO_TCP, TCP_KEEPINTVL, intvl)
11201149
TCP_KEEPCNT = getattr(socket, 'TCP_KEEPCNT', 0x102 if sys.platform.startswith('darwin') else None)
11211150
if TCP_KEEPCNT is not None:
1151+
cnt = apply_keepalive_limit('TCP_KEEPCNT', cnt)
11221152
yield (socket.IPPROTO_TCP, TCP_KEEPCNT, cnt)
11231153

11241154

tests/test_utils.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import sys
12
import unittest
23

34
from unittest.mock import Mock, patch
45

56
from patroni.exceptions import PatroniException
6-
from patroni.utils import enable_keepalive, get_major_version, get_postgres_version, \
7-
polling_loop, Retry, RetryFailedError, unquote, validate_directory
7+
from patroni.utils import apply_keepalive_limit, enable_keepalive, get_major_version, \
8+
get_postgres_version, polling_loop, Retry, RetryFailedError, unquote, validate_directory
89

910

1011
class TestUtils(unittest.TestCase):
@@ -43,6 +44,11 @@ def test_enable_keepalive(self):
4344
with patch('sys.platform', platform):
4445
self.assertIsNone(enable_keepalive(Mock(), 10, 5))
4546

47+
def test_apply_keepalive_limit(self):
48+
for platform in ('linux2', 'darwin'):
49+
with patch('sys.platform', platform):
50+
self.assertLess(apply_keepalive_limit('TCP_KEEPIDLE', sys.maxsize), sys.maxsize)
51+
4652
def test_unquote(self):
4753
self.assertEqual(unquote('value'), 'value')
4854
self.assertEqual(unquote('value with spaces'), "value with spaces")

0 commit comments

Comments
 (0)