From e0b80ff9fb4f93c8e53e7ecc9bc3b3c5880673da Mon Sep 17 00:00:00 2001 From: RerankerGuo <121015044+RerankerGuo@users.noreply.github.com> Date: Fri, 7 Aug 2026 20:20:54 +0800 Subject: [PATCH] fix(utils): report free port search exhaustion Raise a clear error when every candidate port is occupied and keep high-range scans within the valid TCP port range. Test: python tests/run.py --test_dir tests/utils --pattern test_port_utils.py --- swift/utils/utils.py | 15 ++++++++---- tests/utils/test_port_utils.py | 42 ++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 tests/utils/test_port_utils.py diff --git a/swift/utils/utils.py b/swift/utils/utils.py index f75a5146c1..64bbbd2d38 100644 --- a/swift/utils/utils.py +++ b/swift/utils/utils.py @@ -295,15 +295,20 @@ def find_node_ip() -> Optional[str]: def find_free_port(start_port: Optional[int] = None, retry: int = 100) -> int: if start_port is None: start_port = 0 - for port in range(start_port, start_port + retry): + if not 0 <= start_port <= 65535: + raise ValueError(f'Invalid start_port: {start_port}') + if retry < 1: + raise ValueError(f'Invalid retry: {retry}') + + stop_port = min(start_port + retry, 65536) + for port in range(start_port, stop_port): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: try: sock.bind(('', port)) - port = sock.getsockname()[1] - break except OSError: - pass - return port + continue + return sock.getsockname()[1] + raise OSError(f'No free port found in range [{start_port}, {stop_port})') def copy_files_by_pattern(source_dir, dest_dir, patterns, exclude_patterns=None): diff --git a/tests/utils/test_port_utils.py b/tests/utils/test_port_utils.py new file mode 100644 index 0000000000..d314614f25 --- /dev/null +++ b/tests/utils/test_port_utils.py @@ -0,0 +1,42 @@ +import unittest +from unittest.mock import call, patch + +from swift.utils import find_free_port + + +class TestFindFreePort(unittest.TestCase): + + @patch('swift.utils.utils.socket.socket') + def test_returns_next_available_port(self, socket_cls): + sock = socket_cls.return_value.__enter__.return_value + sock.bind.side_effect = [OSError('occupied'), None] + sock.getsockname.return_value = ('0.0.0.0', 30001) + + port = find_free_port(30000, retry=2) + + self.assertEqual(port, 30001) + self.assertEqual(sock.bind.call_args_list, [call(('', 30000)), call(('', 30001))]) + + @patch('swift.utils.utils.socket.socket') + def test_raises_when_candidate_range_is_exhausted(self, socket_cls): + sock = socket_cls.return_value.__enter__.return_value + sock.bind.side_effect = OSError('occupied') + + with self.assertRaisesRegex(OSError, r'\[30000, 30003\)'): + find_free_port(30000, retry=3) + + self.assertEqual(sock.bind.call_count, 3) + + @patch('swift.utils.utils.socket.socket') + def test_does_not_scan_past_max_port(self, socket_cls): + sock = socket_cls.return_value.__enter__.return_value + sock.bind.side_effect = OSError('occupied') + + with self.assertRaisesRegex(OSError, r'\[65535, 65536\)'): + find_free_port(65535, retry=2) + + sock.bind.assert_called_once_with(('', 65535)) + + +if __name__ == '__main__': + unittest.main()