perf: cache compute.host_ip + add UDPLink.create timing log

host_ip resolved socket.gethostbyname on every access with no cache.
get_ip_on_same_subnet touches host_ip 2-4 times per link, so opening a
2500-link project issued thousands of blocking DNS calls inline on the
event loop — freezing all concurrent link coroutines each time. This is
the most likely cause of the 12-link/s throughput (1000x below what
Pool(concurrency=100) should deliver) and the burst+pause pattern.

- compute.host_ip: cache the resolution in _host_ip_cache, invalidate
  on host setter change
- UDPLink.create: timing log splitting get_ip / ports / nio so the next
  project-open confirms where time actually goes
This commit is contained in:
YueGuobin committed 2026-08-10 23:20:40 +08:00
1 parent b155980402
commit 96d4d82716
2 files changed
+24 -4

No files matched your search

+13
View File
@@ -17,6 +17,8 @@
import asyncio
import time
import logging
from .controller_error import ControllerError, ControllerNotFoundError
from .link import Link, _UNSET
@@ -32,6 +34,9 @@ _MARKER_CAPABLE_TYPES = frozenset({
})
log = logging.getLogger(__name__)
class UDPLink(Link):
def __init__(self, project, link_id=None):
super().__init__(project, link_id=link_id)
@@ -96,10 +101,12 @@ class UDPLink(Link):
port_number2 = self._nodes[1]["port_number"]
# Get an IP allowing communication between both host
_t0 = time.perf_counter()
try:
(node1_host, node2_host) = await node1.compute.get_ip_on_same_subnet(node2.compute)
except ValueError as e:
raise ControllerError(f"Cannot get an IP address on same subnet: {e}")
_t1 = time.perf_counter()
# Reserve a UDP port on both sides in parallel. Pre-allocated ports
# (used during batch project loading) are popped from memory; otherwise
@@ -114,6 +121,7 @@ class UDPLink(Link):
self._node1_port, self._node2_port = await asyncio.gather(
_allocate_port(node1.compute), _allocate_port(node2.compute)
)
_t2 = time.perf_counter()
node1_filters, node2_filters = self._get_node_filters(node1, node2)
node1_markers, node2_markers = self._get_node_markers(node1, node2)
@@ -171,6 +179,11 @@ class UDPLink(Link):
if cleanup:
await asyncio.gather(*cleanup, return_exceptions=True)
raise errors[0]
_t3 = time.perf_counter()
log.info(
"UDPLink.create timing get_ip=%.3fms ports=%.3fms nio=%.3fms total=%.3fms",
1000 * (_t1 - _t0), 1000 * (_t2 - _t1), 1000 * (_t3 - _t2), 1000 * (_t3 - _t0)
)
self._created = True
# New links automatically inherit every active project-level marker
# definition so the user doesn't have to reconfigure.