mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
fix(sandbox): stop port allocation at the TCP port limit (#6424)
Co-authored-by: Zhangchenhao-111 <285911435+Zhangchenhao-111@users.noreply.github.com>
This commit is contained in:
parent
a9e5b6a853
commit
db40811ac4
@ -687,6 +687,10 @@ When host Bash is enabled for Local Execution, DeerFlow starts OS detection with
|
||||
|
||||
For Docker development, service startup follows `config.yaml` sandbox mode. In Local/Docker modes, `provisioner` is not started.
|
||||
|
||||
Local AIO sandbox port allocation stops at TCP port 65535. If the remaining
|
||||
ports from the configured starting port are occupied, allocation reports
|
||||
that no port is available instead of attempting an out-of-range bind.
|
||||
|
||||
See the [Sandbox Configuration Guide](backend/docs/CONFIGURATION.md#sandbox) to configure your preferred mode.
|
||||
|
||||
Remote directory listings report traversal failures (for example, unreadable
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
### Port Allocation Bounds
|
||||
|
||||
`network.py::PortAllocator.allocate` caps its exclusive search endpoint at
|
||||
65536. A valid start near 65535 must report exhaustion with `RuntimeError`
|
||||
when all remaining ports are reserved or occupied, without probing invalid
|
||||
TCP ports. Keep the last valid port allocatable and preserve `max_range`.
|
||||
Regression coverage lives in `tests/test_port_allocator_bounds.py`.
|
||||
|
||||
### Goal Objective Validation
|
||||
|
||||
`goal_objective.py` owns the dependency-free normalized 4000-character objective
|
||||
|
||||
@ -63,7 +63,7 @@ class PortAllocator:
|
||||
|
||||
Args:
|
||||
start_port: The port number to start searching from.
|
||||
max_range: Maximum number of ports to search.
|
||||
max_range: Maximum number of ports to search, stopping at port 65535.
|
||||
|
||||
Returns:
|
||||
An available port number.
|
||||
@ -72,12 +72,13 @@ class PortAllocator:
|
||||
RuntimeError: If no available port is found in the specified range.
|
||||
"""
|
||||
with self._lock:
|
||||
for port in range(start_port, start_port + max_range):
|
||||
end_port = min(start_port + max_range, 65536)
|
||||
for port in range(start_port, end_port):
|
||||
if self._is_port_available(port):
|
||||
self._reserved_ports.add(port)
|
||||
return port
|
||||
|
||||
raise RuntimeError(f"No available port found in range {start_port}-{start_port + max_range}")
|
||||
raise RuntimeError(f"No available port found in range {start_port}-{end_port}")
|
||||
|
||||
def release(self, port: int) -> None:
|
||||
"""Release a previously allocated port.
|
||||
|
||||
57
backend/tests/test_port_allocator_bounds.py
Normal file
57
backend/tests/test_port_allocator_bounds.py
Normal file
@ -0,0 +1,57 @@
|
||||
"""Port searches must stop at the largest valid TCP port."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from deerflow.utils.network import PortAllocator
|
||||
|
||||
|
||||
@pytest.mark.parametrize("start_port", [65534, 65535])
|
||||
def test_reserved_tail_reports_exhaustion(start_port):
|
||||
allocator = PortAllocator()
|
||||
allocator._reserved_ports.update(range(start_port, 65536))
|
||||
|
||||
with pytest.raises(RuntimeError, match="No available port"):
|
||||
allocator.allocate(start_port=start_port)
|
||||
|
||||
|
||||
def test_occupied_tail_never_probes_invalid_port():
|
||||
allocator = PortAllocator()
|
||||
|
||||
def bind(address):
|
||||
if address[1] > 65535:
|
||||
raise OverflowError("port must be 0-65535")
|
||||
raise OSError("Address already in use")
|
||||
|
||||
with patch("deerflow.utils.network.socket.socket") as socket_factory:
|
||||
sock = socket_factory.return_value.__enter__.return_value
|
||||
sock.bind.side_effect = bind
|
||||
with pytest.raises(RuntimeError, match="No available port"):
|
||||
allocator.allocate(start_port=65535)
|
||||
sock.bind.assert_called_once_with(("0.0.0.0", 65535))
|
||||
assert not allocator._reserved_ports
|
||||
|
||||
|
||||
def test_last_valid_port_can_be_allocated_and_released():
|
||||
allocator = PortAllocator()
|
||||
allocator._reserved_ports.add(65534)
|
||||
|
||||
with patch("deerflow.utils.network.socket.socket") as socket_factory:
|
||||
sock = socket_factory.return_value.__enter__.return_value
|
||||
with allocator.allocate_context(start_port=65534) as port:
|
||||
assert port == 65535
|
||||
assert port in allocator._reserved_ports
|
||||
sock.bind.assert_called_once_with(("0.0.0.0", 65535))
|
||||
assert port not in allocator._reserved_ports
|
||||
assert allocator._reserved_ports == {65534}
|
||||
|
||||
|
||||
def test_search_still_respects_max_range():
|
||||
allocator = PortAllocator()
|
||||
allocator._reserved_ports.update({8080, 8081})
|
||||
|
||||
with patch.object(allocator, "_is_port_available", return_value=False) as probe:
|
||||
with pytest.raises(RuntimeError, match="No available port"):
|
||||
allocator.allocate(start_port=8080, max_range=2)
|
||||
assert [call.args[0] for call in probe.call_args_list] == [8080, 8081]
|
||||
Loading…
x
Reference in New Issue
Block a user