Skip to content

Commit 79cfc64

Browse files
sorryhyunclaude
andcommitted
perf: stop blocking startup for 10s on two DNS lookups that always fail
get_cors_origins() resolved the machine's own hostname to discover LAN IPs for the dev CORS allowlist. On macOS the hostname is `*.local` and does not resolve, so gethostbyname and getaddrinfo each burned a full ~5s resolver timeout and returned nothing -- on every backend start and every test run. The LAN IP was in fact always supplied by the default-route probe, which consults the routing table and takes 1ms. Keep the hostname lookup for hosts where it does resolve, but run it on a daemon thread with a 0.5s budget instead of waiting on the resolver. Startup: 10.8s -> 1.2s. Test suite: 13.2s -> 3.6s. CORS origins unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 95c9859 commit 79cfc64

1 file changed

Lines changed: 46 additions & 29 deletions

File tree

backend/core/settings.py

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
All settings are loaded once at application startup.
66
"""
77

8+
import socket
89
import sys
10+
import threading
911
from pathlib import Path
10-
from typing import List, Optional
12+
from typing import List, Optional, Set
1113

1214
from pydantic import AliasChoices, Field, field_validator
1315
from pydantic_settings import BaseSettings
@@ -18,6 +20,42 @@ def _is_frozen() -> bool:
1820
return getattr(sys, "frozen", False)
1921

2022

23+
def _hostname_ips(timeout: float = 0.5) -> Set[str]:
24+
"""Resolve this machine's hostname to LAN IPs, giving up after `timeout`.
25+
26+
A hostname that doesn't resolve (the norm on macOS, where it is `*.local`, and on
27+
any host behind a VPN) costs the resolver ~5s before it fails. Doing that inline
28+
stalled startup, so the lookup runs on a daemon thread we simply stop waiting for.
29+
"""
30+
ips: Set[str] = set()
31+
32+
def _resolve() -> None:
33+
try:
34+
for info in socket.getaddrinfo(socket.gethostname(), None, socket.AF_INET):
35+
ips.add(info[4][0])
36+
except OSError:
37+
pass # Unresolvable hostname: the default-route probe below still finds the LAN IP.
38+
39+
thread = threading.Thread(target=_resolve, daemon=True)
40+
thread.start()
41+
thread.join(timeout)
42+
return set(ips) # Copy: the thread may still be running, and we accept losing late results.
43+
44+
45+
def _default_route_ip() -> Optional[str]:
46+
"""LAN IP of the interface holding the default route.
47+
48+
A UDP `connect` sends no packets — it only consults the routing table — so this is
49+
instant and works offline. This is what actually finds the LAN IP on most machines.
50+
"""
51+
try:
52+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
53+
sock.connect(("8.8.8.8", 80))
54+
return sock.getsockname()[0]
55+
except OSError:
56+
return None
57+
58+
2159
def _get_base_path() -> Path:
2260
"""Get the base path for bundled resources (config files, static files)."""
2361
if _is_frozen():
@@ -294,35 +332,14 @@ def get_cors_origins(self) -> List[str]:
294332
if self.vercel_url:
295333
origins.append(f"https://{self.vercel_url}")
296334

297-
# Add local network IPs for development (including WSL2)
298-
import socket
335+
# Add local network IPs so the dev frontend can be opened from another device
336+
local_ips = _hostname_ips()
337+
default_route_ip = _default_route_ip()
338+
if default_route_ip:
339+
local_ips.add(default_route_ip)
299340

300-
try:
301-
# Get all IPs from all network interfaces
302-
local_ips = set()
303-
hostname = socket.gethostname()
304-
# Try hostname-based lookup
305-
try:
306-
local_ips.add(socket.gethostbyname(hostname))
307-
except Exception:
308-
pass
309-
# Try getting all addresses for the hostname
310-
try:
311-
for info in socket.getaddrinfo(hostname, None, socket.AF_INET):
312-
local_ips.add(info[4][0])
313-
except Exception:
314-
pass
315-
# Try connecting to external to find default route IP (works in WSL2)
316-
try:
317-
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
318-
s.connect(("8.8.8.8", 80))
319-
local_ips.add(s.getsockname()[0])
320-
except Exception:
321-
pass
322-
for local_ip in local_ips:
323-
origins.extend([f"http://{local_ip}:5173", f"http://{local_ip}:5174"])
324-
except Exception:
325-
pass
341+
for local_ip in sorted(local_ips):
342+
origins.extend([f"http://{local_ip}:5173", f"http://{local_ip}:5174"])
326343

327344
return origins
328345

0 commit comments

Comments
 (0)